add targeting probability density function

main
jbb01 6 months ago
parent 6b47f44ad2
commit c4ee560dc9

@ -35,6 +35,7 @@ public class Examples {
register("LIGHT", Examples::getLight); register("LIGHT", Examples::getLight);
register("CORNELL", Examples::getCornellBox); register("CORNELL", Examples::getCornellBox);
register("CORNELL_SMOKE", Examples::getCornellBoxSmoke); register("CORNELL_SMOKE", Examples::getCornellBoxSmoke);
register("CORNELL_SPHERE", Examples::getCornellBoxSphere);
register("DIAGRAMM", Examples::getDiagramm); register("DIAGRAMM", Examples::getDiagramm);
register("EARTH", Examples::getEarth); register("EARTH", Examples::getEarth);
register("PERLIN", Examples::getPerlin); 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) { public static @NotNull Example getDiagramm(int height) {
if (height <= 0) height = 450; if (height <= 0) height = 450;

@ -3,7 +3,7 @@ package eu.jonahbauer.raytracing.render.renderer;
import eu.jonahbauer.raytracing.math.Range; import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.material.Material; 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.renderer.pdf.MixtureProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.texture.Color; import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.camera.Camera; import eu.jonahbauer.raytracing.render.camera.Camera;
@ -137,9 +137,6 @@ public final class SimpleRenderer implements Renderer {
var result = material.scatter(ray, hit, random); var result = material.scatter(ray, hit, random);
color = Color.add(color, Color.multiply(attenuation, emitted)); 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; if (result.isEmpty()) break;
switch (result.get()) { switch (result.get()) {
@ -148,8 +145,16 @@ public final class SimpleRenderer implements Renderer {
ray = scattered; ray = scattered;
} }
case Material.PdfScatterResult(var a, var pdf) -> { case Material.PdfScatterResult(var a, var pdf) -> {
if (scene.getLights() == null) {
attenuation = Color.multiply(attenuation, a); attenuation = Color.multiply(attenuation, a);
ray = new Ray(hit.position(), pdf.generate(random)); 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);
}
} }
} }
} }

@ -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);
}
}

@ -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()); 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) { 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()); return new Color(a.r() + b.r(), a.g() + b.g(), a.b() + b.b());
} }

@ -19,13 +19,16 @@ public interface Hittable {
*/ */
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range); @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range);
/**
* {@return the axis-aligned bounding box of this hittable}
*/
@NotNull AABB getBoundingBox(); @NotNull AABB getBoundingBox();
default @NotNull Hittable translate(@NotNull Vec3 offset) { default @NotNull Hittable translate(@NotNull Vec3 offset) {
return new Translate(this, offset); return Translate.create(this, offset);
} }
default @NotNull Hittable rotateY(double angle) { default @NotNull Hittable rotateY(double angle) {
return new RotateY(this, angle); return RotateY.create(this, angle);
} }
} }

@ -6,6 +6,7 @@ import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree; import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
import eu.jonahbauer.raytracing.scene.util.HittableCollection; import eu.jonahbauer.raytracing.scene.util.HittableCollection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -14,6 +15,8 @@ public final class Scene extends HittableCollection {
private final @NotNull HittableCollection objects; private final @NotNull HittableCollection objects;
private final @NotNull SkyBox background; private final @NotNull SkyBox background;
private final @Nullable Target light;
public Scene(@NotNull List<? extends @NotNull Hittable> objects) { public Scene(@NotNull List<? extends @NotNull Hittable> objects) {
this(Color.BLACK, objects); this(Color.BLACK, objects);
} }
@ -25,6 +28,8 @@ public final class Scene extends HittableCollection {
public Scene(@NotNull SkyBox background, @NotNull List<? extends @NotNull Hittable> objects) { public Scene(@NotNull SkyBox background, @NotNull List<? extends @NotNull Hittable> objects) {
this.objects = new HittableBinaryTree(objects); this.objects = new HittableBinaryTree(objects);
this.background = Objects.requireNonNull(background); this.background = Objects.requireNonNull(background);
this.light = (Target) objects.get(1);
} }
public Scene(@NotNull Hittable @NotNull... objects) { public Scene(@NotNull Hittable @NotNull... objects) {
@ -49,6 +54,10 @@ public final class Scene extends HittableCollection {
return objects.getBoundingBox(); return objects.getBoundingBox();
} }
public @Nullable Target getLights() {
return light;
}
public @NotNull Color getBackgroundColor(@NotNull Ray ray) { public @NotNull Color getBackgroundColor(@NotNull Ray ray) {
return background.getColor(ray); return background.getColor(ray);
} }

@ -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 <code>origin</code>} 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);
}
}

@ -1,11 +1,17 @@
package eu.jonahbauer.raytracing.scene.hittable2d; package eu.jonahbauer.raytracing.scene.hittable2d;
import eu.jonahbauer.raytracing.math.AABB; 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.math.Vec3;
import eu.jonahbauer.raytracing.render.material.Material; 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; 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; private final @NotNull AABB bbox;
public Parallelogram(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) { 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() { public @NotNull AABB getBoundingBox() {
return bbox; 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);
}
} }

@ -7,13 +7,16 @@ import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.material.Material; import eu.jonahbauer.raytracing.render.material.Material;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable; 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; 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 @NotNull AABB box;
private final @Nullable Material @NotNull[] materials; private final @Nullable Material @NotNull[] materials;
@ -115,6 +118,61 @@ public final class Box implements Hittable {
return box; 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 { private enum Side {
NEG_X(Vec3.UNIT_X.neg()), NEG_X(Vec3.UNIT_X.neg()),
NEG_Y(Vec3.UNIT_Y.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()); 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 <code>this</code> 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 <code>this</code> side of the <code>box</code> with the given <code>u</code>-<code>v</code>-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 <code>this</code> side of the <code>box</code>}
*/
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 <code>this</code> side of the <code>box</code> when viewed from <code>pos</code>}
*/
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);
}
} }
} }

@ -7,12 +7,14 @@ import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Target;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; 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 @NotNull Vec3 center;
private final double radius; private final double radius;
private final @NotNull Material material; private final @NotNull Material material;
@ -74,4 +76,25 @@ public final class Sphere implements Hittable {
public @NotNull AABB getBoundingBox() { public @NotNull AABB getBoundingBox() {
return bbox; 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);
}
} }

@ -5,15 +5,26 @@ import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Target;
import org.jetbrains.annotations.NotNull; 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 cos;
private final double sin; private final double sin;
private final @NotNull AABB bbox; 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); super(object);
this.cos = Math.cos(angle); this.cos = Math.cos(angle);
this.sin = Math.sin(angle); this.sin = Math.sin(angle);
@ -45,45 +56,53 @@ public final class RotateY extends Transform {
} }
@Override @Override
protected @NotNull Ray transform(@NotNull Ray ray) { protected final @NotNull Ray transform(@NotNull Ray ray) {
var origin = ray.origin(); var origin = ray.origin();
var direction = ray.direction(); var direction = ray.direction();
var newOrigin = new Vec3( var newOrigin = transform(origin);
cos * origin.x() - sin * origin.z(), var newDirection = transform(direction);
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()
);
return new Ray(newOrigin, newDirection); return new Ray(newOrigin, newDirection);
} }
@Override @Override
protected @NotNull HitResult transform(@NotNull HitResult result) { protected final @NotNull HitResult transform(@NotNull HitResult result) {
var position = result.position(); var position = result.position();
var newPosition = new Vec3( var newPosition = untransform(position);
cos * position.x() + sin * position.z(),
position.y(),
- sin * position.x() + cos * position.z()
);
var normal = result.normal(); var normal = result.normal();
var newNormal = new Vec3( var newNormal = untransform(normal);
cos * normal.x() + sin * normal.z(),
normal.y(),
-sin * normal.x() + cos * normal.z()
);
return result.withPositionAndNormal(newPosition, newNormal); 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 @Override
public @NotNull AABB getBoundingBox() { public @NotNull AABB getBoundingBox() {
return bbox; 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));
}
}
} }

@ -2,12 +2,15 @@ package eu.jonahbauer.raytracing.scene.transform;
import eu.jonahbauer.raytracing.math.Range; import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Target;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.random.RandomGenerator;
public abstract class Transform implements Hittable { public abstract class Transform implements Hittable {
protected final @NotNull Hittable object; protected final @NotNull Hittable object;

@ -5,13 +5,24 @@ import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Target;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public final class Translate extends Transform { import java.util.random.RandomGenerator;
private final @NotNull Vec3 offset;
public sealed class Translate extends Transform {
protected final @NotNull Vec3 offset;
private final @NotNull AABB bbox; 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); super(object);
this.offset = offset; this.offset = offset;
@ -23,17 +34,35 @@ public final class Translate extends Transform {
} }
@Override @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()); return new Ray(ray.origin().minus(offset), ray.direction());
} }
@Override @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()); return result.withPositionAndNormal(result.position().plus(offset), result.normal());
} }
@Override @Override
public @NotNull AABB getBoundingBox() { public final @NotNull AABB getBoundingBox() {
return bbox; 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);
}
}
} }

@ -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)));
}
}
Loading…
Cancel
Save