diff --git a/src/main/java/eu/jonahbauer/raytracing/render/renderer/SimpleRenderer.java b/src/main/java/eu/jonahbauer/raytracing/render/renderer/SimpleRenderer.java index 15a8ecc..b3e8e35 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/renderer/SimpleRenderer.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/renderer/SimpleRenderer.java @@ -130,7 +130,7 @@ public final class SimpleRenderer implements Renderer { var attenuation = Color.WHITE; while (depth-- > 0) { - var optional = scene.hit(ray, new Range(0.001, Double.POSITIVE_INFINITY)); + var optional = scene.hit(ray); if (optional.isEmpty()) { var background = scene.getBackgroundColor(ray); color = Color.add(color, Color.multiply(attenuation, background)); diff --git a/src/main/java/eu/jonahbauer/raytracing/render/texture/Texture.java b/src/main/java/eu/jonahbauer/raytracing/render/texture/Texture.java index e8374f5..fcc9cb0 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/texture/Texture.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/texture/Texture.java @@ -5,12 +5,27 @@ import eu.jonahbauer.raytracing.scene.HitResult; import org.jetbrains.annotations.NotNull; public interface Texture { + /** + * {@return the color of this texture for a hit} + */ default @NotNull Color get(@NotNull HitResult hit) { return get(hit.u(), hit.v(), hit.position()); } + /** + * {@return the color of this texture at the specified position} + * @param u the texture u coordinate + * @param v the texture v coordinate + * @param p the position + */ @NotNull Color get(double u, double v, @NotNull Vec3 p); + /** + * Returns whether {@link #get(double, double, Vec3)} uses the {@code u} and/or {@code v} parameters. + * When a texture indicates that the {@code u} and {@code v} coordinates are not required, the calculation may be + * skipped and {@link Double#NaN} will be passed. + * @return whether {@link #get(double, double, Vec3)} uses the {@code u} and/or {@code v} parameters + */ default boolean isUVRequired() { return true; } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java b/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java index cf21baf..83f4a00 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java @@ -1,11 +1,25 @@ package eu.jonahbauer.raytracing.scene; +import eu.jonahbauer.raytracing.math.Range; +import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.render.material.Material; +import eu.jonahbauer.raytracing.render.texture.Texture; import org.jetbrains.annotations.NotNull; import java.util.Objects; +/** + * The result of a {@linkplain Hittable#hit(Ray, Range) hit}. + * @param t the {@code t} value at which the hit occurs + * @param position the position of the hit + * @param normal the surface normal at the hit position + * @param target the hit target (for debug purposes only) + * @param material the material of the surface + * @param u the texture u coordinate (or {@code Double.NaN} if the {@linkplain Material#texture() material's texture} does {@linkplain Texture#isUVRequired() not depend} on the uv-coordinates) + * @param v the texture v coordinate (or {@code Double.NaN} if the {@linkplain Material#texture() material's texture} does {@linkplain Texture#isUVRequired() not depend} on the uv-coordinates) + * @param isFrontFace whether the front or the back of the surface was it + */ public record HitResult( double t, @NotNull Vec3 position, @NotNull Vec3 normal, @NotNull Hittable target, @NotNull Material material, double u, double v, boolean isFrontFace diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Hittable.java b/src/main/java/eu/jonahbauer/raytracing/scene/Hittable.java index 6f841a1..6df3617 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/Hittable.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/Hittable.java @@ -13,9 +13,22 @@ import java.util.Optional; public interface Hittable { /** - * {@return the value t such that ray.at(t) is the intersection of this shaped closest to - * the ray origin, or Double.NaN if the ray does not intersect this shape} + * @see #hit(Ray, Range) + */ + default @NotNull Optional hit(@NotNull Ray ray) { + return hit(ray, new Range(0.001, Double.POSITIVE_INFINITY)); + } + + /** + * Tests whether the {@code ray} intersects {@code this} hittable. + *

+ * The second parameter {@code range} allows the implementation to skip unnecessary calculations if it can + * determine that a hit (if any) will fall outside the valid range of {@code t}s. The returned hit may still be + * outside the valid range and has to be checked by the caller. * @param ray a ray + * @param range the range of valid {@code t}s + * @return the result of the hit test, containing (among others) the value {@code t} such that {@code ray.at(t)} is + * a point on {@code this} hittable */ @NotNull Optional hit(@NotNull Ray ray, @NotNull Range range); diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java index 90449cc..e4cfbd8 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java @@ -31,7 +31,7 @@ public final class Parallelogram extends Hittable2D implements Target { @Override public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) { - var result = hit(new Ray(origin, direction), new Range(0.001, Double.POSITIVE_INFINITY)); + var result = hit(new Ray(origin, direction)); if (result.isEmpty()) return 0; var a = this.origin; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java index 8df9f0d..5e0dea3 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java @@ -121,7 +121,7 @@ public final class Box implements Hittable, Target { @Override public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) { if (contains(origin)) return 1 / (4 * Math.PI); - if (hit(new Ray(origin, direction), new Range(0.001, Double.POSITIVE_INFINITY)).isEmpty()) return 0; + if (hit(new Ray(origin, direction)).isEmpty()) return 0; var solidAngle = 0d; for (var s : Side.values()) { diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java index 1802dd6..cf03dcd 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java @@ -79,7 +79,7 @@ public final class Sphere implements Hittable, Target { @Override public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) { - if (hit(new Ray(origin, direction), new Range(0.001, Double.POSITIVE_INFINITY)).isEmpty()) return 0; + if (hit(new Ray(origin, direction)).isEmpty()) return 0; var cos = Math.sqrt(1 - radius * radius / (center.minus(origin).squared())); var solidAngle = 2 * Math.PI * (1 - cos);