diff --git a/src/main/java/eu/jonahbauer/raytracing/math/AABB.java b/src/main/java/eu/jonahbauer/raytracing/math/AABB.java index 4beb8ea..44796fc 100644 --- a/src/main/java/eu/jonahbauer/raytracing/math/AABB.java +++ b/src/main/java/eu/jonahbauer/raytracing/math/AABB.java @@ -3,6 +3,7 @@ package eu.jonahbauer.raytracing.math; import eu.jonahbauer.raytracing.scene.Hittable; import org.jetbrains.annotations.NotNull; +import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -12,6 +13,9 @@ import java.util.Optional; public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) { public static final AABB UNIVERSE = new AABB(Vec3.MIN, Vec3.MAX); public static final AABB EMPTY = new AABB(Vec3.ZERO, Vec3.ZERO); + public static final Comparator X_AXIS = Comparator.comparing(AABB::min, Comparator.comparingDouble(Vec3::x)); + public static final Comparator Y_AXIS = Comparator.comparing(AABB::min, Comparator.comparingDouble(Vec3::y)); + public static final Comparator Z_AXIS = Comparator.comparing(AABB::min, Comparator.comparingDouble(Vec3::z)); public AABB { var a = min; @@ -58,13 +62,17 @@ public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) { public @NotNull Optional intersect(@NotNull Ray ray) { if (this == UNIVERSE) return Optional.of(Range.UNIVERSE); + if (this == EMPTY) return Optional.empty(); int vmask = ray.vmask(); + var origin = ray.origin(); + var invDirection = ray.direction().inv(); + // calculate t values for intersection points of ray with planes through min - var tmin = intersect(min(), ray); + var tmin = intersect(min(), origin, invDirection); // calculate t values for intersection points of ray with planes through max - var tmax = intersect(max(), ray); + var tmax = intersect(max(), origin, invDirection); // determine range of t for which the ray is inside this voxel double tlmax = Double.NEGATIVE_INFINITY; // lower limit maximum @@ -86,10 +94,14 @@ public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) { } public static double @NotNull[] intersect(@NotNull Vec3 corner, @NotNull Ray ray) { + return intersect(corner, ray.origin(), ray.direction().inv()); + } + + private static double @NotNull[] intersect(@NotNull Vec3 corner, @NotNull Vec3 origin, @NotNull Vec3 invDirection) { return new double[] { - (corner.x() - ray.origin().x()) / ray.direction().x(), - (corner.y() - ray.origin().y()) / ray.direction().y(), - (corner.z() - ray.origin().z()) / ray.direction().z(), + (corner.x() - origin.x()) * invDirection.x(), + (corner.y() - origin.y()) * invDirection.y(), + (corner.z() - origin.z()) * invDirection.z(), }; } } diff --git a/src/main/java/eu/jonahbauer/raytracing/math/Range.java b/src/main/java/eu/jonahbauer/raytracing/math/Range.java index 2e48ae9..72248ef 100644 --- a/src/main/java/eu/jonahbauer/raytracing/math/Range.java +++ b/src/main/java/eu/jonahbauer/raytracing/math/Range.java @@ -16,4 +16,8 @@ public record Range(double min, double max) { public boolean surrounds(double value) { return min < value && value < max; } + + public double size() { + return max - min; + } } diff --git a/src/main/java/eu/jonahbauer/raytracing/math/Vec3.java b/src/main/java/eu/jonahbauer/raytracing/math/Vec3.java index 35bc699..56da0b0 100644 --- a/src/main/java/eu/jonahbauer/raytracing/math/Vec3.java +++ b/src/main/java/eu/jonahbauer/raytracing/math/Vec3.java @@ -106,6 +106,10 @@ public record Vec3(double x, double y, double z) { return new Vec3(-x, -y, -z); } + public @NotNull Vec3 inv() { + return new Vec3(1 / x, 1 / y, 1 / z); + } + public @NotNull Vec3 cross(@NotNull Vec3 b) { return new Vec3( this.y() * b.z() - b.y() * this.z(), diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java b/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java index c37c47e..a987a7a 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java @@ -3,17 +3,15 @@ package eu.jonahbauer.raytracing.scene; import eu.jonahbauer.raytracing.math.AABB; import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.render.Color; +import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree; import eu.jonahbauer.raytracing.scene.util.HittableCollection; -import eu.jonahbauer.raytracing.scene.util.HittableList; -import eu.jonahbauer.raytracing.scene.util.HittableOctree; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.List; +import java.util.Objects; public final class Scene extends HittableCollection { - private final @NotNull HittableOctree octree; - private final @NotNull HittableList list; + private final @NotNull HittableCollection objects; private final @NotNull SkyBox background; public Scene(@NotNull List objects) { @@ -25,20 +23,8 @@ public final class Scene extends HittableCollection { } public Scene(@NotNull SkyBox background, @NotNull List objects) { - var bounded = new ArrayList(); - var unbounded = new ArrayList(); - - objects.forEach(object -> { - if (object.getBoundingBox().isPresent()) { - bounded.add(object); - } else { - unbounded.add(object); - } - }); - - this.octree = new HittableOctree(bounded); - this.list = new HittableList(unbounded); - this.background = background; + this.objects = new HittableBinaryTree(objects); + this.background = Objects.requireNonNull(background); } public Scene(@NotNull Hittable @NotNull... objects) { @@ -55,8 +41,7 @@ public final class Scene extends HittableCollection { @Override public void hit(@NotNull Ray ray, @NotNull State state) { - octree.hit(ray, state); - list.hit(ray, state); + objects.hit(ray, state); } @Override diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableBinaryTree.java b/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableBinaryTree.java new file mode 100644 index 0000000..2eacad0 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableBinaryTree.java @@ -0,0 +1,67 @@ +package eu.jonahbauer.raytracing.scene.util; + +import eu.jonahbauer.raytracing.math.AABB; +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.scene.Hittable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Comparator; +import java.util.List; + +public final class HittableBinaryTree extends HittableCollection { + private final @Nullable Hittable left; + private final @Nullable Hittable right; + private final @NotNull AABB bbox; + + public HittableBinaryTree(@NotNull List objects) { + bbox = AABB.getBoundingBox(objects).orElse(AABB.EMPTY); + if (objects.isEmpty()) { + left = null; + right = null; + } else if (objects.size() == 1) { + left = objects.getFirst(); + right = null; + } else if (objects.size() == 2) { + left = objects.getFirst(); + right = objects.getLast(); + } else { + var x = bbox.x().size(); + var y = bbox.y().size(); + var z = bbox.z().size(); + Comparator comparator; + if (x > y && x > z) { + comparator = AABB.X_AXIS; + } else if (y > z) { + comparator = AABB.Y_AXIS; + } else { + comparator = AABB.Z_AXIS; + } + var sorted = objects.stream().sorted(Comparator.comparing(Hittable::getBoundingBox, comparator)).toList(); + var size = sorted.size(); + + left = new HittableBinaryTree(sorted.subList(0, size / 2)); + right = new HittableBinaryTree(sorted.subList(size / 2, size)); + } + } + + @Override + public void hit(@NotNull Ray ray, @NotNull State state) { + if (!bbox.hit(ray)) return; + if (left instanceof HittableCollection coll) { + coll.hit(ray, state); + } else if (left != null) { + hit(state, ray, left); + } + if (right instanceof HittableCollection coll) { + coll.hit(ray, state); + } else if (right != null) { + hit(state, ray, right); + } + } + + @Override + public @NotNull AABB getBoundingBox() { + return bbox; + } +}