refactor Scene
This commit is contained in:
parent
8ea894cd3e
commit
36de714f46
@ -1,23 +1,17 @@
|
|||||||
package eu.jonahbauer.raytracing;
|
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.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.render.Color;
|
import eu.jonahbauer.raytracing.render.Color;
|
||||||
import eu.jonahbauer.raytracing.render.ImageFormat;
|
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.camera.SimpleCamera;
|
||||||
import eu.jonahbauer.raytracing.render.canvas.LiveCanvas;
|
|
||||||
import eu.jonahbauer.raytracing.render.canvas.Image;
|
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.render.renderer.SimpleRenderer;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.*;
|
||||||
import eu.jonahbauer.raytracing.scene.flat.Ellipse;
|
import eu.jonahbauer.raytracing.scene.hittable2d.Parallelogram;
|
||||||
import eu.jonahbauer.raytracing.scene.flat.Parallelogram;
|
import eu.jonahbauer.raytracing.scene.hittable3d.Sphere;
|
||||||
import eu.jonahbauer.raytracing.scene.Scene;
|
|
||||||
import eu.jonahbauer.raytracing.scene.Sphere;
|
|
||||||
import eu.jonahbauer.raytracing.scene.flat.Triangle;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -91,18 +85,19 @@ public class Main {
|
|||||||
.withBlurAngle(Math.toRadians(0.6))
|
.withBlurAngle(Math.toRadians(0.6))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return new Example(new Scene(objects), camera);
|
return new Example(new Scene(getSkyBox(), objects), camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NotNull Example getSquares() {
|
private static @NotNull Example getSquares() {
|
||||||
return new Example(
|
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(-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(-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(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, 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)))
|
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()
|
SimpleCamera.builder()
|
||||||
.withImage(400, 400)
|
.withImage(400, 400)
|
||||||
.withFieldOfView(Math.toRadians(80))
|
.withFieldOfView(Math.toRadians(80))
|
||||||
@ -113,13 +108,18 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static @NotNull Scene getSimpleScene() {
|
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, -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(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.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.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))
|
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) {}
|
private record Example(@NotNull Scene scene, @NotNull Camera camera) {}
|
||||||
|
@ -3,8 +3,18 @@ package eu.jonahbauer.raytracing.math;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public record BoundingBox(@NotNull Vec3 min, @NotNull Vec3 max) {
|
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() {
|
public @NotNull Vec3 center() {
|
||||||
return Vec3.average(min, max, 2);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import java.util.Random;
|
|||||||
public record Color(double r, double g, double b) {
|
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 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 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 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 GREEN = new Color(0.0, 1.0, 0.0);
|
||||||
public static final @NotNull Color BLUE = new Color(0.0, 0.0, 1.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 {
|
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(int red, int green, int blue) {
|
public Color(int red, int green, int blue) {
|
||||||
this(red / 255f, green / 255f, blue / 255f);
|
this(red / 255f, green / 255f, blue / 255f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int red() {
|
public int red() {
|
||||||
return (int) (255.99 * r);
|
return toInt(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int green() {
|
public int green() {
|
||||||
return (int) (255.99 * g);
|
return toInt(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int blue() {
|
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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
.map(scatter -> Color.multiply(scatter.attenuation(), getColor0(scene, scatter.ray(), depth - 1)))
|
.map(scatter -> Color.multiply(scatter.attenuation(), getColor0(scene, scatter.ray(), depth - 1)))
|
||||||
.orElse(Color.BLACK);
|
.orElse(Color.BLACK);
|
||||||
} else {
|
} else {
|
||||||
return getSkyboxColor(ray);
|
return scene.getBackgroundColor(ray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,19 +111,6 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
return parallel ? stream.parallel() : stream;
|
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 {
|
public static class Builder {
|
||||||
private int samplesPerPixel = 100;
|
private int samplesPerPixel = 100;
|
||||||
private int maxDepth = 10;
|
private int maxDepth = 10;
|
||||||
|
@ -1,85 +1,64 @@
|
|||||||
package eu.jonahbauer.raytracing.scene;
|
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.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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public final class Scene implements Hittable {
|
public final class Scene extends HittableCollection {
|
||||||
private final @NotNull Octree<@NotNull Hittable> octree;
|
private final @NotNull HittableOctree octree;
|
||||||
private final @NotNull List<@NotNull Hittable> list;
|
private final @NotNull HittableList list;
|
||||||
|
private final @NotNull SkyBox background;
|
||||||
|
|
||||||
public Scene(@NotNull List<? extends @NotNull Hittable> objects) {
|
public Scene(@NotNull List<? extends @NotNull Hittable> objects) {
|
||||||
this.octree = newOctree(objects);
|
this(Color.BLACK, objects);
|
||||||
this.list = new ArrayList<>();
|
}
|
||||||
|
|
||||||
for (Hittable object : objects) {
|
public Scene(@NotNull Color background, @NotNull List<? extends @NotNull Hittable> objects) {
|
||||||
var bbox = object.getBoundingBox();
|
this(SkyBox.solid(background), objects);
|
||||||
if (bbox.isPresent()) {
|
}
|
||||||
octree.add(bbox.get(), object);
|
|
||||||
|
public Scene(@NotNull SkyBox background, @NotNull List<? extends @NotNull Hittable> objects) {
|
||||||
|
var bounded = new ArrayList<Hittable>();
|
||||||
|
var unbounded = new ArrayList<Hittable>();
|
||||||
|
|
||||||
|
objects.forEach(object -> {
|
||||||
|
if (object.getBoundingBox().isPresent()) {
|
||||||
|
bounded.add(object);
|
||||||
} else {
|
} else {
|
||||||
list.add(object);
|
unbounded.add(object);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.octree = new HittableOctree(bounded);
|
||||||
|
this.list = new HittableList(unbounded);
|
||||||
|
this.background = background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Scene(@NotNull Hittable @NotNull... objects) {
|
||||||
|
this(List.of(objects));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Scene(@NotNull Color background, @NotNull Hittable @NotNull... objects) {
|
||||||
|
this(background, List.of(objects));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Scene(@NotNull SkyBox background, @NotNull Hittable @NotNull... objects) {
|
||||||
|
this(background, List.of(objects));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
public void hit(@NotNull Ray ray, @NotNull State state) {
|
||||||
var state = new State();
|
octree.hit(ray, state);
|
||||||
state.range = range;
|
list.hit(ray, state);
|
||||||
|
|
||||||
octree.hit(ray, object -> hit(state, ray, object));
|
|
||||||
list.forEach(object -> hit(state, ray, object));
|
|
||||||
|
|
||||||
return Optional.ofNullable(state.result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object) {
|
public @NotNull Color getBackgroundColor(@NotNull Ray ray) {
|
||||||
var r = object.hit(ray, state.range);
|
return background.getColor(ray);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull Octree<Hittable> newOctree(@NotNull List<? extends Hittable> 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
return new Octree<>(center, dimension);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class State {
|
|
||||||
HitResult result;
|
|
||||||
Range range;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
src/main/java/eu/jonahbauer/raytracing/scene/SkyBox.java
Normal file
25
src/main/java/eu/jonahbauer/raytracing/scene/SkyBox.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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.BoundingBox;
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
@ -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.Range;
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
@ -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.BoundingBox;
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
@ -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.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.render.material.Material;
|
import eu.jonahbauer.raytracing.render.material.Material;
|
@ -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.BoundingBox;
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
@ -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.render.material.Material;
|
||||||
import eu.jonahbauer.raytracing.math.BoundingBox;
|
import eu.jonahbauer.raytracing.math.BoundingBox;
|
||||||
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.math.Vec3;
|
||||||
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
@ -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<HitResult> 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<BoundingBox> getBoundingBox(@NotNull Collection<? extends @NotNull Hittable> 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<HitResult> getResult() {
|
||||||
|
return Optional.ofNullable(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<Hittable> objects;
|
||||||
|
private final @NotNull Optional<BoundingBox> bbox;
|
||||||
|
|
||||||
|
public HittableList(@NotNull List<? extends @NotNull Hittable> 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<BoundingBox> getBoundingBox() {
|
||||||
|
return bbox;
|
||||||
|
}
|
||||||
|
}
|
@ -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<Hittable> objects;
|
||||||
|
private final @NotNull Optional<BoundingBox> bbox;
|
||||||
|
|
||||||
|
public HittableOctree(@NotNull List<? extends @NotNull Hittable> 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<BoundingBox> getBoundingBox() {
|
||||||
|
return bbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull Entry<@NotNull Octree<Hittable>, @NotNull BoundingBox> newOctree(@NotNull List<? extends Hittable> 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<Hittable>(center, dimension);
|
||||||
|
objects.forEach(object -> out.add(object.getBoundingBox().get(), object));
|
||||||
|
return Map.entry(out, new BoundingBox(min, max));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user