add Range to optimize Shape#hit
This commit is contained in:
parent
5e52db65d4
commit
7757b3d573
19
src/main/java/eu/jonahbauer/raytracing/math/Range.java
Normal file
19
src/main/java/eu/jonahbauer/raytracing/math/Range.java
Normal file
@ -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…
x
Reference in New Issue
Block a user