refactor Scene
parent
8ea894cd3e
commit
36de714f46
@ -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<>();
|
||||
|
||||
for (Hittable object : objects) {
|
||||
var bbox = object.getBoundingBox();
|
||||
if (bbox.isPresent()) {
|
||||
octree.add(bbox.get(), object);
|
||||
} else {
|
||||
list.add(object);
|
||||
}
|
||||
}
|
||||
this(Color.BLACK, objects);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||
var state = new State();
|
||||
state.range = range;
|
||||
|
||||
octree.hit(ray, object -> hit(state, ray, object));
|
||||
list.forEach(object -> hit(state, ray, object));
|
||||
|
||||
return Optional.ofNullable(state.result);
|
||||
public Scene(@NotNull Color background, @NotNull List<? extends @NotNull Hittable> objects) {
|
||||
this(SkyBox.solid(background), objects);
|
||||
}
|
||||
|
||||
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;
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
unbounded.add(object);
|
||||
}
|
||||
});
|
||||
|
||||
private static @NotNull Octree<Hittable> newOctree(@NotNull List<? extends Hittable> objects) {
|
||||
Vec3 center = Vec3.ZERO, max = Vec3.MIN, min = Vec3.MAX;
|
||||
this.octree = new HittableOctree(bounded);
|
||||
this.list = new HittableList(unbounded);
|
||||
this.background = background;
|
||||
}
|
||||
|
||||
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 Hittable @NotNull... objects) {
|
||||
this(List.of(objects));
|
||||
}
|
||||
|
||||
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…
Reference in New Issue