diff --git a/src/main/java/eu/jonahbauer/raytracing/Examples.java b/src/main/java/eu/jonahbauer/raytracing/Examples.java index 3842977..3abab16 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Examples.java +++ b/src/main/java/eu/jonahbauer/raytracing/Examples.java @@ -35,6 +35,7 @@ public class Examples { register("LIGHT", Examples::getLight); register("CORNELL", Examples::getCornellBox); register("CORNELL_SMOKE", Examples::getCornellBoxSmoke); + register("CORNELL_SPHERE", Examples::getCornellBoxSphere); register("DIAGRAMM", Examples::getDiagramm); register("EARTH", Examples::getEarth); register("PERLIN", Examples::getPerlin); @@ -212,6 +213,38 @@ public class Examples { ); } + public static @NotNull Example getCornellBoxSphere(int height) { + if (height <= 0) height = 600; + + var red = new LambertianMaterial(new Color(.65, .05, .05)); + var white = new LambertianMaterial(new Color(.73, .73, .73)); + var green = new LambertianMaterial(new Color(.12, .45, .15)); + var light = new DiffuseLight(new Color(7.0, 7.0, 7.0)); + var aluminum = new MetallicMaterial(new Color(0.8, 0.85, 0.88)); + var glass = new DielectricMaterial(1.5); + + return new Example( + new Scene( + new Box( + new AABB(new Vec3(0, 0, 0), new Vec3(555, 555, 555)), + white, white, red, green, white, null + ), + new Parallelogram(new Vec3(343, 554, 332), new Vec3(-130, 0, 0), new Vec3(0, 0, -105), light), + new Box( + new AABB(new Vec3(0, 0, 0), new Vec3(165, 330, 165)), + white, white, white, white, white, aluminum + ).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)), + new Sphere(new Vec3(190, 90, 190), 90, glass) + ), + SimpleCamera.builder() + .withImage(height, height) + .withFieldOfView(Math.toRadians(40)) + .withPosition(new Vec3(278, 278, -800)) + .withTarget(new Vec3(278, 278, 0)) + .build() + ); + } + public static @NotNull Example getDiagramm(int height) { if (height <= 0) height = 450; 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 961091f..4be6551 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/renderer/SimpleRenderer.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/renderer/SimpleRenderer.java @@ -3,7 +3,7 @@ package eu.jonahbauer.raytracing.render.renderer; import eu.jonahbauer.raytracing.math.Range; import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.render.material.Material; -import eu.jonahbauer.raytracing.render.renderer.pdf.HittableProbabilityDensityFunction; +import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction; import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction; import eu.jonahbauer.raytracing.render.texture.Color; import eu.jonahbauer.raytracing.render.camera.Camera; @@ -137,9 +137,6 @@ public final class SimpleRenderer implements Renderer { var result = material.scatter(ray, hit, random); color = Color.add(color, Color.multiply(attenuation, emitted)); - if (scatter.isEmpty()) break; - attenuation = Color.multiply(attenuation, scatter.get().attenuation()); - ray = scatter.get().ray(); if (result.isEmpty()) break; switch (result.get()) { @@ -148,8 +145,16 @@ public final class SimpleRenderer implements Renderer { ray = scattered; } case Material.PdfScatterResult(var a, var pdf) -> { - attenuation = Color.multiply(attenuation, a); - ray = new Ray(hit.position(), pdf.generate(random)); + if (scene.getLights() == null) { + attenuation = Color.multiply(attenuation, a); + ray = new Ray(hit.position(), pdf.generate(random)); + } else { + var mixed = new MixtureProbabilityDensityFunction(new TargetingProbabilityDensityFunction(hit.position(), scene.getLights()), pdf); + var direction = mixed.generate(random); + var factor = pdf.value(direction) / mixed.value(direction); + attenuation = Color.multiply(attenuation, Color.multiply(a, factor)); + ray = new Ray(hit.position(), direction); + } } } } diff --git a/src/main/java/eu/jonahbauer/raytracing/render/renderer/pdf/TargetingProbabilityDensityFunction.java b/src/main/java/eu/jonahbauer/raytracing/render/renderer/pdf/TargetingProbabilityDensityFunction.java new file mode 100644 index 0000000..898347b --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/renderer/pdf/TargetingProbabilityDensityFunction.java @@ -0,0 +1,28 @@ +package eu.jonahbauer.raytracing.render.renderer.pdf; + +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.scene.Target; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.random.RandomGenerator; + +/** + * A probability density function targeting a target. + */ +public record TargetingProbabilityDensityFunction(@NotNull Vec3 origin, @NotNull Target target) implements ProbabilityDensityFunction { + public TargetingProbabilityDensityFunction { + Objects.requireNonNull(origin, "origin"); + Objects.requireNonNull(target, "target"); + } + + @Override + public double value(@NotNull Vec3 direction) { + return target.getProbabilityDensity(origin, direction); + } + + @Override + public @NotNull Vec3 generate(@NotNull RandomGenerator random) { + return target.getTargetingDirection(origin, random); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/texture/Color.java b/src/main/java/eu/jonahbauer/raytracing/render/texture/Color.java index 5370224..991badf 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/texture/Color.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/texture/Color.java @@ -26,6 +26,10 @@ public record Color(double r, double g, double b) implements Texture { return new Color(a.r() * b.r(), a.g() * b.g(), a.b() * b.b()); } + public static @NotNull Color multiply(@NotNull Color a, double b) { + return new Color(a.r() * b, a.g() * b, a.b() * b); + } + public static @NotNull Color add(@NotNull Color a, @NotNull Color b) { return new Color(a.r() + b.r(), a.g() + b.g(), a.b() + b.b()); } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Hittable.java b/src/main/java/eu/jonahbauer/raytracing/scene/Hittable.java index dc8cf45..6f841a1 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/Hittable.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/Hittable.java @@ -19,13 +19,16 @@ public interface Hittable { */ @NotNull Optional hit(@NotNull Ray ray, @NotNull Range range); + /** + * {@return the axis-aligned bounding box of this hittable} + */ @NotNull AABB getBoundingBox(); default @NotNull Hittable translate(@NotNull Vec3 offset) { - return new Translate(this, offset); + return Translate.create(this, offset); } default @NotNull Hittable rotateY(double angle) { - return new RotateY(this, angle); + return RotateY.create(this, angle); } } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java b/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java index 79a5101..5517aff 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java @@ -6,6 +6,7 @@ import eu.jonahbauer.raytracing.render.texture.Color; import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree; import eu.jonahbauer.raytracing.scene.util.HittableCollection; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Objects; @@ -14,6 +15,8 @@ public final class Scene extends HittableCollection { private final @NotNull HittableCollection objects; private final @NotNull SkyBox background; + private final @Nullable Target light; + public Scene(@NotNull List objects) { this(Color.BLACK, objects); } @@ -25,6 +28,8 @@ public final class Scene extends HittableCollection { public Scene(@NotNull SkyBox background, @NotNull List objects) { this.objects = new HittableBinaryTree(objects); this.background = Objects.requireNonNull(background); + + this.light = (Target) objects.get(1); } public Scene(@NotNull Hittable @NotNull... objects) { @@ -49,6 +54,10 @@ public final class Scene extends HittableCollection { return objects.getBoundingBox(); } + public @Nullable Target getLights() { + return light; + } + public @NotNull Color getBackgroundColor(@NotNull Ray ray) { return background.getColor(ray); } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Target.java b/src/main/java/eu/jonahbauer/raytracing/scene/Target.java new file mode 100644 index 0000000..e97d01e --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/Target.java @@ -0,0 +1,39 @@ +package eu.jonahbauer.raytracing.scene; + +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.random.RandomGenerator; + +/** + * An interface for objects that can be targeted. A target can construct randomly distributed directions in which + * it will be hit from a given origin. + * @see TargetingProbabilityDensityFunction + */ +public interface Target extends Hittable { + /** + * Returns the probability density for a direction as sampled by {@link #getTargetingDirection(Vec3, RandomGenerator)}. + * @param origin the origin + * @param direction the direction + * @return the probability density for a direction as sampled by {@link #getTargetingDirection(Vec3, RandomGenerator)} + */ + double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction); + + /** + * {@return a vector targeting this hittable from the origin} The vector is chosen randomly. + * @param origin the origin + * @param random a random number generator + */ + @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random); + + @Override + default @NotNull Target translate(@NotNull Vec3 offset) { + return (Target) Hittable.super.translate(offset); + } + + @Override + default @NotNull Target rotateY(double angle) { + return (Target) Hittable.super.rotateY(angle); + } +} 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 93cb166..90449cc 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java @@ -1,11 +1,17 @@ package eu.jonahbauer.raytracing.scene.hittable2d; import eu.jonahbauer.raytracing.math.AABB; +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.scene.Target; +import eu.jonahbauer.raytracing.scene.util.PdfUtil; import org.jetbrains.annotations.NotNull; -public final class Parallelogram extends Hittable2D { +import java.util.random.RandomGenerator; + +public final class Parallelogram extends Hittable2D implements Target { private final @NotNull AABB bbox; public Parallelogram(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) { @@ -22,4 +28,24 @@ public final class Parallelogram extends Hittable2D { public @NotNull AABB getBoundingBox() { return bbox; } + + @Override + public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) { + var result = hit(new Ray(origin, direction), new Range(0.001, Double.POSITIVE_INFINITY)); + if (result.isEmpty()) return 0; + + var a = this.origin; + var b = this.origin.plus(u); + var c = this.origin.plus(v); + var d = b.plus(v); + var angle = PdfUtil.getSolidAngle(origin, a, b, d) + PdfUtil.getSolidAngle(origin, c, b, d); + return 1 / angle; + } + + @Override + public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) { + var alpha = random.nextDouble(); + var beta = random.nextDouble(); + return this.origin.plus(u.times(alpha)).plus(v.times(beta)).minus(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 d1ccbaa..14169af 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java @@ -7,13 +7,16 @@ import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.render.material.Material; import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.Hittable; +import eu.jonahbauer.raytracing.scene.Target; +import eu.jonahbauer.raytracing.scene.util.PdfUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Objects; import java.util.Optional; +import java.util.random.RandomGenerator; -public final class Box implements Hittable { +public final class Box implements Hittable, Target { private final @NotNull AABB box; private final @Nullable Material @NotNull[] materials; @@ -115,6 +118,61 @@ public final class Box implements Hittable { return box; } + @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; + + var solidAngle = 0d; + for (var s : Side.values()) { + if (!s.isExterior(box, origin)) continue; + solidAngle += s.getSolidAngle(box, origin); + } + + return 1 / solidAngle; + } + + @Override + public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) { + if (contains(origin)) return Vec3.random(random, true); + + // determine sides facing the origin and their solid angles + int visible = 0; + + // at most three faces are visible + Side[] sides = new Side[3]; + double[] solidAngle = new double[3]; + + double accumSolidAngle = 0; + for (var s : Side.values()) { + if (!s.isExterior(box, origin)) continue; + + var sa = s.getSolidAngle(box, origin); + accumSolidAngle += sa; + sides[visible] = s; + solidAngle[visible] = accumSolidAngle; + visible++; + } + + // choose a random side facing the origin based on their relative solid angles + var r = random.nextDouble() * solidAngle[visible - 1]; + for (int j = 0; j < visible; j++) { + if (r < solidAngle[j]) { + // choose a random point on that side + var target = sides[j].random(box, random); + return target.minus(origin); + } + } + + throw new AssertionError(); + } + + private boolean contains(@NotNull Vec3 point) { + return box.min().x() < point.x() && point.x() < box.max().x() + && box.min().y() < point.y() && point.y() < box.max().y() + && box.min().z() < point.z() && point.z() < box.max().z(); + } + private enum Side { NEG_X(Vec3.UNIT_X.neg()), NEG_Y(Vec3.UNIT_Y.neg()), @@ -155,5 +213,78 @@ public final class Box implements Hittable { case POS_Y -> (box.max().z() - pos.z()) / (box.max().z() - box.min().z()); }; } + + /** + * {@return whether the given position is outside of the box only considering this side} + */ + public boolean isExterior(@NotNull AABB box, @NotNull Vec3 pos) { + return switch (this) { + case NEG_X -> pos.x() < box.min().x(); + case NEG_Y -> pos.y() < box.min().y(); + case NEG_Z -> pos.z() < box.min().z(); + case POS_X -> pos.x() > box.max().x(); + case POS_Y -> pos.y() > box.max().y(); + case POS_Z -> pos.z() > box.max().z(); + }; + } + + /** + * {@return the point on this side of the box with the given u-v-coordinates} + */ + public @NotNull Vec3 get(@NotNull AABB box, double u, double v) { + return switch (this) { + case NEG_X -> new Vec3( + box.min().x(), + Math.fma(v, box.max().y() - box.min().y(), box.min().y()), + Math.fma(u, box.max().z() - box.min().z(), box.min().z()) + ); + case NEG_Y -> new Vec3( + Math.fma(u, box.max().x() - box.min().x(), box.min().x()), + box.min().y(), + Math.fma(v, box.max().z() - box.min().z(), box.min().z()) + ); + case NEG_Z -> new Vec3( + Math.fma(u, box.min().x() - box.max().x(), box.max().x()), + Math.fma(v, box.max().y() - box.min().y(), box.min().y()), + box.min().z() + ); + case POS_X -> new Vec3( + box.max().x(), + Math.fma(v, box.max().y() - box.min().y(), box.min().y()), + Math.fma(u, box.min().z() - box.max().z(), box.max().z()) + ); + case POS_Y -> new Vec3( + Math.fma(u, box.max().x() - box.min().x(), box.min().x()), + box.max().y(), + Math.fma(v, box.min().z() - box.max().z(), box.max().z()) + ); + case POS_Z -> new Vec3( + Math.fma(u, box.max().x() - box.min().x(), box.min().x()), + Math.fma(v, box.max().y() - box.min().y(), box.min().y()), + box.max().z() + ); + }; + } + + /** + * {@return a random point on this side of the box} + */ + public @NotNull Vec3 random(@NotNull AABB box, @NotNull RandomGenerator random) { + var u = random.nextDouble(); + var v = random.nextDouble(); + return get(box, u, v); + } + + /** + * {@return the solid angle covered by this side of the box when viewed from pos} + */ + public double getSolidAngle(@NotNull AABB box, @NotNull Vec3 pos) { + var a = get(box, 0, 0); + var b = get(box, 0, 1); + var c = get(box, 1, 1); + var d = get(box, 1, 0); + + return PdfUtil.getSolidAngle(pos, a, b, d) + PdfUtil.getSolidAngle(pos, c, b, d); + } } } 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 92bebc6..c1d0ecf 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java @@ -7,12 +7,14 @@ import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.Hittable; +import eu.jonahbauer.raytracing.scene.Target; import org.jetbrains.annotations.NotNull; import java.util.Objects; import java.util.Optional; +import java.util.random.RandomGenerator; -public final class Sphere implements Hittable { +public final class Sphere implements Hittable, Target { private final @NotNull Vec3 center; private final double radius; private final @NotNull Material material; @@ -74,4 +76,25 @@ public final class Sphere implements Hittable { public @NotNull AABB getBoundingBox() { return bbox; } + + @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; + + var cos = Math.sqrt(1 - radius * radius / (center.minus(origin).squared())); + var solidAngle = 2 * Math.PI * (1 - cos); + return 1 / solidAngle; + } + + @Override + public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) { + var direction = center.minus(origin); + + Vec3 target; + do { + target = Vec3.random(random, true); + } while (target.times(direction) >= 0); + + return target.times(radius).plus(center).minus(origin); + } } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/transform/RotateY.java b/src/main/java/eu/jonahbauer/raytracing/scene/transform/RotateY.java index b6f8ffd..0084202 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/transform/RotateY.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/transform/RotateY.java @@ -5,15 +5,26 @@ import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.Hittable; +import eu.jonahbauer.raytracing.scene.Target; import org.jetbrains.annotations.NotNull; -public final class RotateY extends Transform { +import java.util.random.RandomGenerator; + +public sealed class RotateY extends Transform { private final double cos; private final double sin; private final @NotNull AABB bbox; - public RotateY(@NotNull Hittable object, double angle) { + public static @NotNull RotateY create(@NotNull Hittable object, double angle) { + if (object instanceof Target) { + return new RotateYTarget(object, angle); + } else { + return new RotateY(object, angle); + } + } + + private RotateY(@NotNull Hittable object, double angle) { super(object); this.cos = Math.cos(angle); this.sin = Math.sin(angle); @@ -45,45 +56,53 @@ public final class RotateY extends Transform { } @Override - protected @NotNull Ray transform(@NotNull Ray ray) { + protected final @NotNull Ray transform(@NotNull Ray ray) { var origin = ray.origin(); var direction = ray.direction(); - var newOrigin = new Vec3( - cos * origin.x() - sin * origin.z(), - origin.y(), - sin * origin.x() + cos * origin.z() - ); - var newDirection = new Vec3( - cos * direction.x() - sin * direction.z(), - direction.y(), - sin * direction.x() + cos * direction.z() - ); - + var newOrigin = transform(origin); + var newDirection = transform(direction); return new Ray(newOrigin, newDirection); } @Override - protected @NotNull HitResult transform(@NotNull HitResult result) { + protected final @NotNull HitResult transform(@NotNull HitResult result) { var position = result.position(); - var newPosition = new Vec3( - cos * position.x() + sin * position.z(), - position.y(), - - sin * position.x() + cos * position.z() - ); + var newPosition = untransform(position); var normal = result.normal(); - var newNormal = new Vec3( - cos * normal.x() + sin * normal.z(), - normal.y(), - -sin * normal.x() + cos * normal.z() - ); + var newNormal = untransform(normal); return result.withPositionAndNormal(newPosition, newNormal); } + protected final @NotNull Vec3 transform(@NotNull Vec3 vec) { + return new Vec3(cos * vec.x() - sin * vec.z(), vec.y(), sin * vec.x() + cos * vec.z()); + } + + protected final @NotNull Vec3 untransform(@NotNull Vec3 vec) { + return new Vec3(cos * vec.x() + sin * vec.z(), vec.y(), - sin * vec.x() + cos * vec.z()); + } + @Override public @NotNull AABB getBoundingBox() { return bbox; } + + private static final class RotateYTarget extends RotateY implements Target { + + private RotateYTarget(@NotNull Hittable object, double angle) { + super(object, angle); + } + + @Override + public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) { + return ((Target) object).getProbabilityDensity(transform(origin), transform(direction)); + } + + @Override + public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) { + return untransform(((Target) object).getTargetingDirection(transform(origin), random)); + } + } } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/transform/Transform.java b/src/main/java/eu/jonahbauer/raytracing/scene/transform/Transform.java index 92a0cdc..28db126 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/transform/Transform.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/transform/Transform.java @@ -2,12 +2,15 @@ package eu.jonahbauer.raytracing.scene.transform; import eu.jonahbauer.raytracing.math.Range; import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.Hittable; +import eu.jonahbauer.raytracing.scene.Target; import org.jetbrains.annotations.NotNull; import java.util.Objects; import java.util.Optional; +import java.util.random.RandomGenerator; public abstract class Transform implements Hittable { protected final @NotNull Hittable object; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/transform/Translate.java b/src/main/java/eu/jonahbauer/raytracing/scene/transform/Translate.java index edc2921..8eb2cb7 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/transform/Translate.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/transform/Translate.java @@ -5,13 +5,24 @@ import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.Hittable; +import eu.jonahbauer.raytracing.scene.Target; import org.jetbrains.annotations.NotNull; -public final class Translate extends Transform { - private final @NotNull Vec3 offset; +import java.util.random.RandomGenerator; + +public sealed class Translate extends Transform { + protected final @NotNull Vec3 offset; private final @NotNull AABB bbox; - public Translate(@NotNull Hittable object, @NotNull Vec3 offset) { + public static @NotNull Translate create(@NotNull Hittable object, @NotNull Vec3 offset) { + if (object instanceof Target) { + return new TranslateTarget(object, offset); + } else { + return new Translate(object, offset); + } + } + + private Translate(@NotNull Hittable object, @NotNull Vec3 offset) { super(object); this.offset = offset; @@ -23,17 +34,35 @@ public final class Translate extends Transform { } @Override - protected @NotNull Ray transform(@NotNull Ray ray) { + protected final @NotNull Ray transform(@NotNull Ray ray) { return new Ray(ray.origin().minus(offset), ray.direction()); } @Override - protected @NotNull HitResult transform(@NotNull HitResult result) { + protected final @NotNull HitResult transform(@NotNull HitResult result) { return result.withPositionAndNormal(result.position().plus(offset), result.normal()); } @Override - public @NotNull AABB getBoundingBox() { + public final @NotNull AABB getBoundingBox() { return bbox; } + + private static final class TranslateTarget extends Translate implements Target { + private TranslateTarget(@NotNull Hittable object, @NotNull Vec3 offset) { + super(object, offset); + } + + @Override + public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) { + if (!(object instanceof Target target)) throw new UnsupportedOperationException(); + return target.getProbabilityDensity(origin.minus(offset), direction); + } + + @Override + public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) { + if (!(object instanceof Target target)) throw new UnsupportedOperationException(); + return target.getTargetingDirection(origin.minus(offset), random); + } + } } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/util/PdfUtil.java b/src/main/java/eu/jonahbauer/raytracing/scene/util/PdfUtil.java new file mode 100644 index 0000000..efead20 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/util/PdfUtil.java @@ -0,0 +1,18 @@ +package eu.jonahbauer.raytracing.scene.util; + +import eu.jonahbauer.raytracing.math.Vec3; +import org.jetbrains.annotations.NotNull; + +public final class PdfUtil { + private PdfUtil() { + throw new UnsupportedOperationException(); + } + + public static double getSolidAngle(@NotNull Vec3 o, @NotNull Vec3 a, @NotNull Vec3 b, @NotNull Vec3 c) { + var i = a.minus(o).unit(); + var j = b.minus(o).unit(); + var k = c.minus(o).unit(); + + return 2 * Math.atan(Math.abs(i.times(j.cross(k))) / (1 + i.times(j) + j.times(k) + k.times(i))); + } +}