diff --git a/src/main/java/eu/jonahbauer/raytracing/Main.java b/src/main/java/eu/jonahbauer/raytracing/Main.java index cf5981f..a13b6dd 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Main.java +++ b/src/main/java/eu/jonahbauer/raytracing/Main.java @@ -1,23 +1,17 @@ package eu.jonahbauer.raytracing; -import eu.jonahbauer.raytracing.render.camera.Camera; -import eu.jonahbauer.raytracing.render.material.DielectricMaterial; -import eu.jonahbauer.raytracing.render.material.LambertianMaterial; -import eu.jonahbauer.raytracing.render.material.Material; -import eu.jonahbauer.raytracing.render.material.MetallicMaterial; import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.render.Color; import eu.jonahbauer.raytracing.render.ImageFormat; +import eu.jonahbauer.raytracing.render.camera.Camera; import eu.jonahbauer.raytracing.render.camera.SimpleCamera; -import eu.jonahbauer.raytracing.render.canvas.LiveCanvas; import eu.jonahbauer.raytracing.render.canvas.Image; +import eu.jonahbauer.raytracing.render.canvas.LiveCanvas; +import eu.jonahbauer.raytracing.render.material.*; import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer; -import eu.jonahbauer.raytracing.scene.Hittable; -import eu.jonahbauer.raytracing.scene.flat.Ellipse; -import eu.jonahbauer.raytracing.scene.flat.Parallelogram; -import eu.jonahbauer.raytracing.scene.Scene; -import eu.jonahbauer.raytracing.scene.Sphere; -import eu.jonahbauer.raytracing.scene.flat.Triangle; +import eu.jonahbauer.raytracing.scene.*; +import eu.jonahbauer.raytracing.scene.hittable2d.Parallelogram; +import eu.jonahbauer.raytracing.scene.hittable3d.Sphere; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -91,18 +85,19 @@ public class Main { .withBlurAngle(Math.toRadians(0.6)) .build(); - return new Example(new Scene(objects), camera); + return new Example(new Scene(getSkyBox(), objects), camera); } private static @NotNull Example getSquares() { return new Example( - new Scene(List.of( + new Scene( + getSkyBox(), new Parallelogram(new Vec3(-3, -2, 5), new Vec3(0, 0, -4), new Vec3(0, 4, 0), new LambertianMaterial(new Color(1.0, 0.2, 0.2))), new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(new Color(0.2, 1.0, 0.2))), new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(new Color(0.2, 0.2, 1.0))), new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(new Color(1.0, 0.5, 0.0))), new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(new Color(0.2, 0.8, 0.8))) - )), + ), SimpleCamera.builder() .withImage(400, 400) .withFieldOfView(Math.toRadians(80)) @@ -113,13 +108,18 @@ public class Main { } private static @NotNull Scene getSimpleScene() { - return new Scene(List.of( + return new Scene( + getSkyBox(), new Sphere(new Vec3(0, -100.5, -1.0), 100.0, new LambertianMaterial(new Color(0.8, 0.8, 0.0))), new Sphere(new Vec3(0, 0, -1.2), 0.5, new LambertianMaterial(new Color(0.1, 0.2, 0.5))), new Sphere(new Vec3(-1.0, 0, -1.2), 0.5, new DielectricMaterial(1.5)), new Sphere(new Vec3(-1.0, 0, -1.2), 0.4, new DielectricMaterial(1 / 1.5)), new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(new Color(0.8, 0.6, 0.2), 0.0)) - )); + ); + } + + private static @NotNull SkyBox getSkyBox() { + return SkyBox.gradient(new Color(0.5, 0.7, 1.0), Color.WHITE); } private record Example(@NotNull Scene scene, @NotNull Camera camera) {} diff --git a/src/main/java/eu/jonahbauer/raytracing/math/BoundingBox.java b/src/main/java/eu/jonahbauer/raytracing/math/BoundingBox.java index e6f5ec0..81a53d4 100644 --- a/src/main/java/eu/jonahbauer/raytracing/math/BoundingBox.java +++ b/src/main/java/eu/jonahbauer/raytracing/math/BoundingBox.java @@ -3,8 +3,18 @@ package eu.jonahbauer.raytracing.math; import org.jetbrains.annotations.NotNull; public record BoundingBox(@NotNull Vec3 min, @NotNull Vec3 max) { + public BoundingBox { + var a = min; + var b = max; + min = Vec3.min(a, b); + max = Vec3.max(a, b); + } public @NotNull Vec3 center() { return Vec3.average(min, max, 2); } + + public @NotNull BoundingBox expand(@NotNull BoundingBox box) { + return new BoundingBox(Vec3.min(this.min, box.min), Vec3.max(this.max, box.max)); + } } diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Color.java b/src/main/java/eu/jonahbauer/raytracing/render/Color.java index a1e9042..af9311a 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Color.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Color.java @@ -7,7 +7,6 @@ import java.util.Random; public record Color(double r, double g, double b) { public static final @NotNull Color BLACK = new Color(0.0, 0.0, 0.0); public static final @NotNull Color WHITE = new Color(1.0, 1.0, 1.0); - public static final @NotNull Color SKY = new Color(0.5, 0.7, 1.0); public static final @NotNull Color RED = new Color(1.0, 0.0, 0.0); public static final @NotNull Color GREEN = new Color(0.0, 1.0, 0.0); public static final @NotNull Color BLUE = new Color(0.0, 0.0, 1.0); @@ -65,25 +64,25 @@ public record Color(double r, double g, double b) { } } - public Color { - if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) { - throw new IllegalArgumentException("r, g and b must be in the range 0 to 1"); - } - } + public Color {} public Color(int red, int green, int blue) { this(red / 255f, green / 255f, blue / 255f); } public int red() { - return (int) (255.99 * r); + return toInt(r); } public int green() { - return (int) (255.99 * g); + return toInt(g); } public int blue() { - return (int) (255.99 * b); + return toInt(b); + } + + private static int toInt(double value) { + return Math.max(0, Math.min(255, (int) (255.99 * value))); } } 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 737b11f..97ba91d 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/renderer/SimpleRenderer.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/renderer/SimpleRenderer.java @@ -96,7 +96,7 @@ public final class SimpleRenderer implements Renderer { .map(scatter -> Color.multiply(scatter.attenuation(), getColor0(scene, scatter.ray(), depth - 1))) .orElse(Color.BLACK); } else { - return getSkyboxColor(ray); + return scene.getBackgroundColor(ray); } } @@ -111,19 +111,6 @@ public final class SimpleRenderer implements Renderer { return parallel ? stream.parallel() : stream; } - /** - * {@return the color of the skybox for a given ray} The skybox color is a linear gradient based on the altitude of - * the ray above the horizon with {@link Color#SKY} at the top and {@link Color#WHITE} at the bottom. - */ - private static @NotNull Color getSkyboxColor(@NotNull Ray ray) { - // altitude from -pi/2 to pi/2 - var alt = Math.copySign( - Math.acos(ray.direction().withY(0).unit().times(ray.direction().unit())), - ray.direction().y() - ); - return Color.lerp(Color.WHITE, Color.SKY, alt / Math.PI + 0.5); - } - public static class Builder { private int samplesPerPixel = 100; private int maxDepth = 10; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java b/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java index bfba547..dcac35b 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java @@ -1,85 +1,64 @@ package eu.jonahbauer.raytracing.scene; -import eu.jonahbauer.raytracing.math.Octree; -import eu.jonahbauer.raytracing.math.Range; import eu.jonahbauer.raytracing.math.Ray; -import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.Color; +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.Arrays; import java.util.List; -import java.util.Optional; -public final class Scene implements Hittable { - private final @NotNull Octree<@NotNull Hittable> octree; - private final @NotNull List<@NotNull Hittable> list; +public final class Scene extends HittableCollection { + private final @NotNull HittableOctree octree; + private final @NotNull HittableList list; + private final @NotNull SkyBox background; public Scene(@NotNull List objects) { - this.octree = newOctree(objects); - this.list = new ArrayList<>(); + this(Color.BLACK, objects); + } - for (Hittable object : objects) { - var bbox = object.getBoundingBox(); - if (bbox.isPresent()) { - octree.add(bbox.get(), object); - } else { - list.add(object); - } - } + public Scene(@NotNull Color background, @NotNull List objects) { + this(SkyBox.solid(background), objects); } - @Override - public @NotNull Optional hit(@NotNull Ray ray, @NotNull Range range) { - var state = new State(); - state.range = range; + public Scene(@NotNull SkyBox background, @NotNull List objects) { + var bounded = new ArrayList(); + var unbounded = new ArrayList(); - octree.hit(ray, object -> hit(state, ray, object)); - list.forEach(object -> hit(state, ray, object)); + objects.forEach(object -> { + if (object.getBoundingBox().isPresent()) { + bounded.add(object); + } else { + unbounded.add(object); + } + }); - return Optional.ofNullable(state.result); + this.octree = new HittableOctree(bounded); + this.list = new HittableList(unbounded); + this.background = background; } - private boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object) { - var r = object.hit(ray, state.range); - if (r.isPresent()) { - if (state.range.surrounds(r.get().t())){ - state.result = r.get(); - state.range = new Range(state.range.min(), state.result.t()); - } - return true; - } else { - return false; - } + public Scene(@NotNull Hittable @NotNull... objects) { + this(List.of(objects)); } - private static @NotNull Octree newOctree(@NotNull List objects) { - Vec3 center = Vec3.ZERO, max = Vec3.MIN, min = Vec3.MAX; - - int i = 1; - for (Hittable object : objects) { - var bbox = object.getBoundingBox(); - if (bbox.isPresent()) { - center = Vec3.average(center, bbox.get().center(), i++); - max = Vec3.max(max, bbox.get().max()); - min = Vec3.min(min, bbox.get().min()); - } - } + public Scene(@NotNull Color background, @NotNull Hittable @NotNull... objects) { + this(background, List.of(objects)); + } - var dimension = Arrays.stream(new double[] { - Math.abs(max.x() - center.x()), - Math.abs(max.y() - center.y()), - Math.abs(max.z() - center.z()), - Math.abs(min.x() - center.x()), - Math.abs(min.y() - center.y()), - Math.abs(min.z() - center.z()) - }).max().orElse(10); + public Scene(@NotNull SkyBox background, @NotNull Hittable @NotNull... objects) { + this(background, List.of(objects)); + } - return new Octree<>(center, dimension); + @Override + public void hit(@NotNull Ray ray, @NotNull State state) { + octree.hit(ray, state); + list.hit(ray, state); } - private static class State { - HitResult result; - Range range; + public @NotNull Color getBackgroundColor(@NotNull Ray ray) { + return background.getColor(ray); } } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/SkyBox.java b/src/main/java/eu/jonahbauer/raytracing/scene/SkyBox.java new file mode 100644 index 0000000..c08dc4a --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/SkyBox.java @@ -0,0 +1,25 @@ +package eu.jonahbauer.raytracing.scene; + +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.render.Color; +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface SkyBox { + @NotNull Color getColor(@NotNull Ray ray); + + static @NotNull SkyBox gradient(@NotNull Color top, @NotNull Color bottom) { + return ray -> { + // altitude from -pi/2 to pi/2 + var alt = Math.copySign( + Math.acos(ray.direction().withY(0).unit().times(ray.direction().unit())), + ray.direction().y() + ); + return Color.lerp(bottom, top, alt / Math.PI + 0.5); + }; + } + + static @NotNull SkyBox solid(@NotNull Color color) { + return _ -> color; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Ellipse.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Ellipse.java similarity index 93% rename from src/main/java/eu/jonahbauer/raytracing/scene/flat/Ellipse.java rename to src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Ellipse.java index 7af392b..cb2ef6c 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Ellipse.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Ellipse.java @@ -1,4 +1,4 @@ -package eu.jonahbauer.raytracing.scene.flat; +package eu.jonahbauer.raytracing.scene.hittable2d; import eu.jonahbauer.raytracing.math.BoundingBox; import eu.jonahbauer.raytracing.math.Vec3; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Hittable2D.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Hittable2D.java similarity index 97% rename from src/main/java/eu/jonahbauer/raytracing/scene/flat/Hittable2D.java rename to src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Hittable2D.java index 5563b95..16d6197 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Hittable2D.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Hittable2D.java @@ -1,4 +1,4 @@ -package eu.jonahbauer.raytracing.scene.flat; +package eu.jonahbauer.raytracing.scene.hittable2d; import eu.jonahbauer.raytracing.math.Range; import eu.jonahbauer.raytracing.math.Ray; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Parallelogram.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java similarity index 94% rename from src/main/java/eu/jonahbauer/raytracing/scene/flat/Parallelogram.java rename to src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java index ba3e18e..cf84e89 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Parallelogram.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Parallelogram.java @@ -1,4 +1,4 @@ -package eu.jonahbauer.raytracing.scene.flat; +package eu.jonahbauer.raytracing.scene.hittable2d; import eu.jonahbauer.raytracing.math.BoundingBox; import eu.jonahbauer.raytracing.math.Vec3; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Plane.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Plane.java similarity index 89% rename from src/main/java/eu/jonahbauer/raytracing/scene/flat/Plane.java rename to src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Plane.java index 05ead58..5a19b87 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Plane.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Plane.java @@ -1,4 +1,4 @@ -package eu.jonahbauer.raytracing.scene.flat; +package eu.jonahbauer.raytracing.scene.hittable2d; import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.render.material.Material; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Triangle.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Triangle.java similarity index 93% rename from src/main/java/eu/jonahbauer/raytracing/scene/flat/Triangle.java rename to src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Triangle.java index 2b57aad..67f28b7 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Triangle.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable2d/Triangle.java @@ -1,4 +1,4 @@ -package eu.jonahbauer.raytracing.scene.flat; +package eu.jonahbauer.raytracing.scene.hittable2d; import eu.jonahbauer.raytracing.math.BoundingBox; import eu.jonahbauer.raytracing.math.Vec3; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Sphere.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java similarity index 93% rename from src/main/java/eu/jonahbauer/raytracing/scene/Sphere.java rename to src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java index ec69a19..dcaa90b 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/Sphere.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Sphere.java @@ -1,10 +1,12 @@ -package eu.jonahbauer.raytracing.scene; +package eu.jonahbauer.raytracing.scene.hittable3d; import eu.jonahbauer.raytracing.render.material.Material; import eu.jonahbauer.raytracing.math.BoundingBox; 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 org.jetbrains.annotations.NotNull; import java.util.Objects; diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableCollection.java b/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableCollection.java new file mode 100644 index 0000000..26cb952 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableCollection.java @@ -0,0 +1,65 @@ +package eu.jonahbauer.raytracing.scene.util; + +import eu.jonahbauer.raytracing.math.BoundingBox; +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 org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +public abstract class HittableCollection implements Hittable { + + @Override + public final @NotNull Optional hit(@NotNull Ray ray, @NotNull Range range) { + var state = new State(range); + hit(ray, state); + return state.getResult(); + } + + public abstract void hit(@NotNull Ray ray, @NotNull State state); + + protected static @NotNull Optional getBoundingBox(@NotNull Collection objects) { + var bbox = new BoundingBox(Vec3.ZERO, Vec3.ZERO); + for (var object : objects) { + var b = object.getBoundingBox(); + if (b.isPresent()) { + bbox = bbox.expand(b.get()); + } else { + bbox = null; + break; + } + } + return Optional.ofNullable(bbox); + } + + protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object) { + var r = object.hit(ray, state.range); + if (r.isPresent()) { + if (state.range.surrounds(r.get().t())){ + state.result = r.get(); + state.range = new Range(state.range.min(), state.result.t()); + } + return true; + } else { + return false; + } + } + + public static class State { + private @NotNull Range range; + private HitResult result; + + private State(@NotNull Range range) { + this.range = Objects.requireNonNull(range); + } + + private @NotNull Optional getResult() { + return Optional.ofNullable(result); + } + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableList.java b/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableList.java new file mode 100644 index 0000000..af7f789 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableList.java @@ -0,0 +1,33 @@ +package eu.jonahbauer.raytracing.scene.util; + +import eu.jonahbauer.raytracing.math.BoundingBox; +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.scene.Hittable; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +public final class HittableList extends HittableCollection { + private final @NotNull List objects; + private final @NotNull Optional bbox; + + public HittableList(@NotNull List objects) { + this.objects = List.copyOf(objects); + this.bbox = getBoundingBox(this.objects); + } + + public HittableList(@NotNull Hittable @NotNull... objects) { + this(List.of(objects)); + } + + @Override + public void hit(@NotNull Ray ray, @NotNull State state) { + objects.forEach(object -> hit(state, ray, object)); + } + + @Override + public @NotNull Optional getBoundingBox() { + return bbox; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableOctree.java b/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableOctree.java new file mode 100644 index 0000000..9bb8b79 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/util/HittableOctree.java @@ -0,0 +1,61 @@ +package eu.jonahbauer.raytracing.scene.util; + +import eu.jonahbauer.raytracing.math.*; +import eu.jonahbauer.raytracing.scene.Hittable; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +public final class HittableOctree extends HittableCollection { + private final @NotNull Octree objects; + private final @NotNull Optional bbox; + + public HittableOctree(@NotNull List objects) { + var result = newOctree(objects); + this.objects = result.getKey(); + this.bbox = Optional.of(result.getValue()); + } + + public HittableOctree(@NotNull Hittable @NotNull... objects) { + this(List.of(objects)); + } + + @Override + public void hit(@NotNull Ray ray, @NotNull State state) { + objects.hit(ray, object -> hit(state, ray, object)); + } + + @Override + public @NotNull Optional getBoundingBox() { + return bbox; + } + + private static @NotNull Entry<@NotNull Octree, @NotNull BoundingBox> newOctree(@NotNull List objects) { + Vec3 center = Vec3.ZERO, max = Vec3.MIN, min = Vec3.MAX; + + int i = 1; + for (var object : objects) { + var bbox = object.getBoundingBox().orElseThrow(); + center = Vec3.average(center, bbox.center(), i++); + max = Vec3.max(max, bbox.max()); + min = Vec3.min(min, bbox.min()); + } + + var dimension = Arrays.stream(new double[] { + Math.abs(max.x() - center.x()), + Math.abs(max.y() - center.y()), + Math.abs(max.z() - center.z()), + Math.abs(min.x() - center.x()), + Math.abs(min.y() - center.y()), + Math.abs(min.z() - center.z()) + }).max().orElse(10); + + var out = new Octree(center, dimension); + objects.forEach(object -> out.add(object.getBoundingBox().get(), object)); + return Map.entry(out, new BoundingBox(min, max)); + } +}