add Range to optimize Shape#hit

main
jbb01 6 months ago
parent 5e52db65d4
commit 7757b3d573

@ -0,0 +1,19 @@
package eu.jonahbauer.raytracing.math;
public record Range(double min, double max) {
public static final Range EMPTY = new Range(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
public static final Range UNIVERSE = new Range(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
public static final Range NON_NEGATIVE = new Range(0, Double.POSITIVE_INFINITY);
public Range {
if (Double.isNaN(min) || Double.isNaN(max)) throw new IllegalArgumentException("min and max must not be NaN");
}
public boolean contains(double value) {
return min <= value && value <= max;
}
public boolean surrounds(double value) {
return min < value && value < max;
}
}

@ -1,10 +1,12 @@
package eu.jonahbauer.raytracing.render;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.shape.HitResult;
import eu.jonahbauer.raytracing.shape.Shape;
import org.jetbrains.annotations.NotNull;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
@ -26,18 +28,10 @@ public record Scene(@NotNull List<@NotNull Shape> shapes) {
var y = pixel.y();
var ray = pixel.ray();
var result = shapes.stream()
.map(shape -> shape.hit(ray))
.flatMap(Optional::stream)
.min(Comparator.naturalOrder());
var result = hit(ray);
if (result.isPresent()) {
var normal = result.get().normal();
image.set(x, y, new Color(
0.5 * (normal.x() + 1),
0.5 * (normal.y() + 1),
0.5 * (normal.z() + 1)
));
image.set(x, y, getNormalColor(normal));
} else {
image.set(x, y, getSkyboxColor(ray));
}
@ -46,6 +40,27 @@ public record Scene(@NotNull List<@NotNull Shape> shapes) {
return image;
}
private @NotNull Optional<HitResult> hit(@NotNull Ray ray) {
var range = new Range(0, Double.POSITIVE_INFINITY);
var result = (HitResult) null;
for (var shape : shapes) {
var r = shape.hit(ray, range);
if (r.isPresent() && (result == null || r.get().t() < result.t())) {
result = r.get();
range = new Range(0, result.t());
}
}
return Optional.ofNullable(result);
}
private @NotNull Color getNormalColor(@NotNull Vec3 normal) {
return new Color(
0.5 * (normal.x() + 1),
0.5 * (normal.y() + 1),
0.5 * (normal.z() + 1)
);
}
private @NotNull Color getSkyboxColor(@NotNull Ray ray) {
// altitude from -pi/2 to pi/2
var alt = Math.copySign(

@ -1,5 +1,6 @@
package eu.jonahbauer.raytracing.shape;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import org.jetbrains.annotations.NotNull;
@ -12,5 +13,5 @@ public sealed interface Shape permits Sphere {
* the ray origin, or <code>Double.NaN</code> if the ray does not intersect this shape}
* @param ray a ray
*/
@NotNull Optional<HitResult> hit(@NotNull Ray ray);
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range);
}

@ -1,5 +1,6 @@
package eu.jonahbauer.raytracing.shape;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import org.jetbrains.annotations.NotNull;
@ -20,7 +21,7 @@ public record Sphere(@NotNull Vec3 center, double radius) implements Shape {
}
@Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray) {
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
var oc = ray.origin().minus(center());
var a = ray.direction().squared();
@ -33,8 +34,8 @@ public record Sphere(@NotNull Vec3 center, double radius) implements Shape {
var sd = Math.sqrt(discriminant);
double t = (- h - sd) / a;
if (t < 0) t = (- h + sd) / a;
if (t < 0) return Optional.empty();
if (!range.surrounds(t)) t = (- h + sd) / a;
if (!range.surrounds(t)) return Optional.empty();
return Optional.of(new HitResult(t, ray.at(t).minus(center)));
}

@ -1,5 +1,6 @@
package eu.jonahbauer.raytracing.shape;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import org.junit.jupiter.api.Test;
@ -18,7 +19,7 @@ class SphereTest {
var direction = new Vec3(-1, -1, -1);
var ray = new Ray(origin, direction);
var result = sphere.hit(ray);
var result = sphere.hit(ray, Range.NON_NEGATIVE);
assertFalse(result.isEmpty());
assertEquals(center.plus(new Vec3(1, 1, 1).unit().times(radius)), ray.at(result.get().t()));
}

Loading…
Cancel
Save