refactor Scene

main
jbb01 6 months ago
parent 8ea894cd3e
commit 36de714f46

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

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

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

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

@ -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<? extends @NotNull Hittable> 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<? extends @NotNull Hittable> objects) {
this(SkyBox.solid(background), objects);
}
@Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
var state = new State();
state.range = range;
public Scene(@NotNull SkyBox background, @NotNull List<? extends @NotNull Hittable> objects) {
var bounded = new ArrayList<Hittable>();
var unbounded = new ArrayList<Hittable>();
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<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());
}
}
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);
}
}

@ -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.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.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.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.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.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.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;

@ -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…
Cancel
Save