Compare commits

...

2 Commits

@ -24,6 +24,7 @@ public class Main {
var renderer = SimpleRenderer.builder() var renderer = SimpleRenderer.builder()
.withSamplesPerPixel(config.samples) .withSamplesPerPixel(config.samples)
.withSpectralSamples(config.spectralSamples)
.withMaxDepth(config.depth) .withMaxDepth(config.depth)
.withIterative(config.iterative) .withIterative(config.iterative)
.withParallel(config.parallel) .withParallel(config.parallel)
@ -45,7 +46,7 @@ public class Main {
PNGImageWriter.sRGB.write(canvas, config.path); PNGImageWriter.sRGB.write(canvas, config.path);
} }
private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int depth) { private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int spectralSamples, int depth) {
public static @NotNull Config parse(@NotNull String @NotNull[] args) { public static @NotNull Config parse(@NotNull String @NotNull[] args) {
IntFunction<Example> example = null; IntFunction<Example> example = null;
Path path = null; Path path = null;
@ -53,6 +54,7 @@ public class Main {
boolean iterative = false; boolean iterative = false;
boolean parallel = false; boolean parallel = false;
int samples = 1000; int samples = 1000;
int spectralSamples = 4;
int depth = 50; int depth = 50;
int height = -1; int height = -1;
@ -81,6 +83,15 @@ public class Main {
throw fail("value " + args[i] + " is not a valid integer"); throw fail("value " + args[i] + " is not a valid integer");
} }
} }
case "--spectral-samples" -> {
if (i + 1 == args.length) throw fail("missing value for parameter --spectral-samples");
try {
spectralSamples = Integer.parseInt(args[++i]);
if (spectralSamples <= 0) throw fail("spectral samples must be positive");
} catch (NumberFormatException ex) {
throw fail("value " + args[i] + " is not a valid integer");
}
}
case "--depth" -> { case "--depth" -> {
if (i + 1 == args.length) throw fail("missing value for parameter --depth"); if (i + 1 == args.length) throw fail("missing value for parameter --depth");
try { try {
@ -112,7 +123,7 @@ public class Main {
if (example == null) example = Examples::getCornellBoxSmoke; if (example == null) example = Examples::getCornellBoxSmoke;
if (path == null) path = Path.of("scene-" + System.currentTimeMillis() + ".png"); if (path == null) path = Path.of("scene-" + System.currentTimeMillis() + ".png");
return new Config(example.apply(height), path, preview, iterative, parallel, samples, depth); return new Config(example.apply(height), path, preview, iterative, parallel, samples, spectralSamples, depth);
} }
private static @NotNull RuntimeException fail(@NotNull String message) { private static @NotNull RuntimeException fail(@NotNull String message) {

@ -1,6 +1,7 @@
package eu.jonahbauer.raytracing.math; package eu.jonahbauer.raytracing.math;
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths; import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
@ -19,4 +20,16 @@ public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull Sample
public @NotNull Vec3 at(double t) { public @NotNull Vec3 at(double t) {
return Vec3.fma(t, direction, origin); return Vec3.fma(t, direction, origin);
} }
public @NotNull Ray with(@NotNull HitResult hit, @NotNull Vec3 direction) {
return new Ray(hit.position(), direction, lambda);
}
public @NotNull Ray with(@NotNull Vec3 origin, @NotNull Vec3 direction) {
return new Ray(origin, direction, lambda);
}
public @NotNull Ray with(@NotNull SampledWavelengths lambda) {
return new Ray(origin, direction, lambda);
}
} }

@ -2,7 +2,6 @@ package eu.jonahbauer.raytracing.render.camera;
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.render.spectrum.SampledWavelengths;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -94,7 +93,7 @@ public final class SimpleCamera implements Camera {
var origin = getRayOrigin(random); var origin = getRayOrigin(random);
var target = getRayTarget(x, y, i, j, n, random); var target = getRayTarget(x, y, i, j, n, random);
return new Ray(origin, target.minus(origin), SampledWavelengths.uniform(random.nextDouble())); return new Ray(origin, target.minus(origin));
} }
/** /**

@ -45,7 +45,7 @@ public record DielectricMaterial(@NotNull RefractiveIndex ri, @NotNull Texture t
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal())); .orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
var attenuation = texture.get(hit); var attenuation = texture.get(hit);
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda()))); return Optional.of(new SpecularScatterResult(attenuation, ray.with(hit, newDirection)));
} }
private double reflectance(double cos, double ri) { private double reflectance(double cos, double ri) {

@ -41,7 +41,7 @@ public final class DirectionalMaterial implements Material {
if (back != null) return back.scatter(ray, hit, random); if (back != null) return back.scatter(ray, hit, random);
} }
// let the ray pass through without obstruction // let the ray pass through without obstruction
return Optional.of(new SpecularScatterResult(Spectra.WHITE, new Ray(ray.at(hit.t()), ray.direction(), ray.lambda()))); return Optional.of(new SpecularScatterResult(Spectra.WHITE, ray.with(hit, ray.direction())));
} }
@Override @Override

@ -28,6 +28,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
newDirection = Vec3.fma(fuzz, Vec3.random(random), newDirection.unit()); newDirection = Vec3.fma(fuzz, Vec3.random(random), newDirection.unit());
} }
var attenuation = texture.get(hit); var attenuation = texture.get(hit);
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda()))); return Optional.of(new SpecularScatterResult(attenuation, ray.with(hit, newDirection)));
} }
} }

@ -7,6 +7,7 @@ import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFun
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum; import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.camera.Camera; import eu.jonahbauer.raytracing.render.camera.Camera;
import eu.jonahbauer.raytracing.render.canvas.Canvas; import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import eu.jonahbauer.raytracing.scene.Scene; import eu.jonahbauer.raytracing.scene.Scene;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -20,7 +21,10 @@ import static eu.jonahbauer.raytracing.Main.DEBUG;
public final class SimpleRenderer implements Renderer { public final class SimpleRenderer implements Renderer {
private final int sqrtSamplesPerPixel; private final int sqrtSamplesPerPixel;
private final int maxDepth; private final int maxDepth;
private final double gamma;
private final int spectralSamples;
private final SampledSpectrum black;
private final SampledSpectrum white;
private final boolean parallel; private final boolean parallel;
private final boolean iterative; private final boolean iterative;
@ -36,7 +40,10 @@ public final class SimpleRenderer implements Renderer {
private SimpleRenderer(@NotNull Builder builder) { private SimpleRenderer(@NotNull Builder builder) {
this.sqrtSamplesPerPixel = (int) Math.sqrt(builder.samplesPerPixel); this.sqrtSamplesPerPixel = (int) Math.sqrt(builder.samplesPerPixel);
this.maxDepth = builder.maxDepth; this.maxDepth = builder.maxDepth;
this.gamma = builder.gamma;
this.spectralSamples = builder.spectralSamples;
this.black = new SampledSpectrum(spectralSamples, 0);
this.white = new SampledSpectrum(spectralSamples, 1);
this.parallel = builder.parallel; this.parallel = builder.parallel;
this.iterative = builder.iterative; this.iterative = builder.iterative;
@ -96,7 +103,8 @@ public final class SimpleRenderer implements Renderer {
int i = 0; int i = 0;
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) { for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
for (int si = 0; si < sqrtSamplesPerPixel; si++) { for (int si = 0; si < sqrtSamplesPerPixel; si++) {
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random); var lambda = SampledWavelengths.uniform(random.nextDouble(), spectralSamples);
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random).with(lambda);
if (DEBUG) { if (DEBUG) {
System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")..."); System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")...");
} }
@ -116,11 +124,11 @@ public final class SimpleRenderer implements Renderer {
} }
private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) { private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
var color = SampledSpectrum.BLACK; var color = black;
var attenuation = SampledSpectrum.WHITE; var attenuation = white;
while (depth-- > 0) { while (depth-- > 0) {
var optional = scene.hit(ray); var optional = scene.hit(ray, random);
if (optional.isEmpty()) { if (optional.isEmpty()) {
var background = scene.getBackgroundColor(ray); var background = scene.getBackgroundColor(ray);
color = SampledSpectrum.fma(attenuation, background, color); color = SampledSpectrum.fma(attenuation, background, color);
@ -136,7 +144,7 @@ public final class SimpleRenderer implements Renderer {
} }
var material = hit.material(); var material = hit.material();
var emitted = material.emitted(hit).sample(ray.lambda()); var emitted = material.emitted(hit).sample(ray.lambda());
if (DEBUG && !SampledSpectrum.BLACK.equals(emitted)) { if (DEBUG && !black.equals(emitted)) {
System.out.println(" Emitted: " + emitted); System.out.println(" Emitted: " + emitted);
} }
@ -213,7 +221,7 @@ public final class SimpleRenderer implements Renderer {
public static class Builder { public static class Builder {
private int samplesPerPixel = 100; private int samplesPerPixel = 100;
private int maxDepth = 10; private int maxDepth = 10;
private double gamma = 2.0; private int spectralSamples = 4;
private boolean parallel = true; private boolean parallel = true;
private boolean iterative = false; private boolean iterative = false;
@ -229,9 +237,9 @@ public final class SimpleRenderer implements Renderer {
return this; return this;
} }
public @NotNull Builder withGamma(double gamma) { public @NotNull Builder withSpectralSamples(int samples) {
if (gamma <= 0 || !Double.isFinite(gamma)) throw new IllegalArgumentException("gamma must be positive"); if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
this.gamma = gamma; this.spectralSamples = samples;
return this; return this;
} }

@ -10,16 +10,6 @@ import java.util.Arrays;
// TODO use Vector API to parallelize operations // TODO use Vector API to parallelize operations
public final class SampledSpectrum implements IVec<SampledSpectrum> { public final class SampledSpectrum implements IVec<SampledSpectrum> {
public static final SampledSpectrum BLACK;
public static final SampledSpectrum WHITE;
static {
BLACK = new SampledSpectrum(new double[SampledWavelengths.SAMPLES]);
var one = new double[SampledWavelengths.SAMPLES];
Arrays.fill(one, 1);
WHITE = new SampledSpectrum(one);
}
private final double @NotNull[] values; private final double @NotNull[] values;
public SampledSpectrum(@NotNull SampledWavelengths lambdas, @NotNull Spectrum spectrum) { public SampledSpectrum(@NotNull SampledWavelengths lambdas, @NotNull Spectrum spectrum) {
@ -30,6 +20,12 @@ public final class SampledSpectrum implements IVec<SampledSpectrum> {
this.values = values; this.values = values;
} }
public SampledSpectrum(int count, double value) {
var values = new double[count];
Arrays.fill(values, value);
this.values = values;
}
private SampledSpectrum(double @NotNull[] values) { private SampledSpectrum(double @NotNull[] values) {
this.values = values; this.values = values;
} }

@ -10,32 +10,31 @@ import java.util.Arrays;
* A set of sampled wavelength that can be tracked together. * A set of sampled wavelength that can be tracked together.
*/ */
public final class SampledWavelengths { public final class SampledWavelengths {
public static final int SAMPLES = 4;
public static final SampledWavelengths EMPTY = new SampledWavelengths(new double[0], new double[0]); public static final SampledWavelengths EMPTY = new SampledWavelengths(new double[0], new double[0]);
private final double @NotNull[] lambdas; private final double @NotNull[] lambdas;
private final double @NotNull[] pdf; private final double @NotNull[] pdf;
public static @NotNull SampledWavelengths uniform(double rng) { public static @NotNull SampledWavelengths uniform(double rng, int count) {
return uniform(rng, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX); return uniform(rng, count, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
} }
public static @NotNull SampledWavelengths uniform(double rng, double min, double max) { public static @NotNull SampledWavelengths uniform(double rng, int count, double min, double max) {
var lambdas = new double[SAMPLES]; var lambdas = new double[count];
// choose first sample at random // choose first sample at random
lambdas[0] = (1 - rng) * min + rng * max; lambdas[0] = (1 - rng) * min + rng * max;
// choose next samples in equal intervals, wrapping if necessary // choose next samples in equal intervals, wrapping if necessary
var delta = (max - min) / SAMPLES; var delta = (max - min) / count;
for (int i = 1; i < SAMPLES; i++) { for (int i = 1; i < count; i++) {
lambdas[i] = lambdas[i - 1] + delta; lambdas[i] = lambdas[i - 1] + delta;
if (lambdas[i] > max) { if (lambdas[i] > max) {
lambdas[i] = min + (lambdas[i] - max); lambdas[i] = min + (lambdas[i] - max);
} }
} }
var pdf = new double[SAMPLES]; var pdf = new double[count];
Arrays.fill(pdf, 1 / (max - min)); Arrays.fill(pdf, 1 / (max - min));
return new SampledWavelengths(lambdas, pdf); return new SampledWavelengths(lambdas, pdf);
} }

@ -9,15 +9,16 @@ import eu.jonahbauer.raytracing.scene.transform.Translate;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Optional; import java.util.Optional;
import java.util.random.RandomGenerator;
public interface Hittable { public interface Hittable {
@NotNull Range FORWARD = new Range(0.001, Double.POSITIVE_INFINITY); @NotNull Range FORWARD = new Range(0.001, Double.POSITIVE_INFINITY);
/** /**
* @see #hit(Ray, Range) * @see #hit(Ray, Range, RandomGenerator)
*/ */
default @NotNull Optional<HitResult> hit(@NotNull Ray ray) { default @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull RandomGenerator random) {
return hit(ray, FORWARD); return hit(ray, FORWARD, random);
} }
/** /**
@ -31,7 +32,7 @@ public interface Hittable {
* @return the result of the hit test, containing (among others) the value {@code t} such that {@code ray.at(t)} is * @return the result of the hit test, containing (among others) the value {@code t} such that {@code ray.at(t)} is
* a point on {@code this} hittable * a point on {@code this} hittable
*/ */
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range); @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random);
/** /**
* {@return the axis-aligned bounding box of this hittable} * {@return the axis-aligned bounding box of this hittable}

@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.random.RandomGenerator;
public final class Scene extends HittableCollection { public final class Scene extends HittableCollection {
private final @NotNull HittableCollection objects; private final @NotNull HittableCollection objects;
@ -42,8 +43,8 @@ public final class Scene extends HittableCollection {
} }
@Override @Override
public void hit(@NotNull Ray ray, @NotNull State state) { public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
objects.hit(ray, state); objects.hit(ray, state, random);
} }
@Override @Override

@ -7,9 +7,11 @@ import eu.jonahbauer.raytracing.render.material.Material;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Hittable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.random.RandomGenerator;
public abstract class Hittable2D implements Hittable { public abstract class Hittable2D implements Hittable {
protected final @NotNull Vec3 origin; protected final @NotNull Vec3 origin;
@ -36,7 +38,7 @@ public abstract class Hittable2D implements Hittable {
} }
@Override @Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) { public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
var denominator = ray.direction().dot(normal); var denominator = ray.direction().dot(normal);
if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel

@ -48,7 +48,7 @@ public final class Box implements Hittable, Target {
} }
@Override @Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) { public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
// based on AABB#hit with additional detection of the side hit // based on AABB#hit with additional detection of the side hit
var origin = ray.origin(); var origin = ray.origin();
var direction = ray.direction(); var direction = ray.direction();
@ -103,13 +103,13 @@ public final class Box implements Hittable, Target {
t = tmin; t = tmin;
side = entry; side = entry;
frontFace = true; frontFace = true;
material = materials[side.ordinal()]; material = materials[entry.ordinal()];
normal = side.normal; normal = side.normal;
} else if (range.surrounds(tmax) && materials[exit.ordinal()] != null) { } else if (range.surrounds(tmax) && materials[exit.ordinal()] != null) {
t = tmax; t = tmax;
side = exit; side = exit;
frontFace = false; frontFace = false;
material = materials[side.ordinal()]; material = materials[exit.ordinal()];
normal = side.normal.neg(); normal = side.normal.neg();
} else { } else {
return Optional.empty(); return Optional.empty();
@ -130,7 +130,7 @@ public final class Box implements Hittable, Target {
@Override @Override
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) { public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
if (contains(origin)) return 1 / (4 * Math.PI); if (contains(origin)) return 1 / (4 * Math.PI);
if (hit(new Ray(origin, direction)).isEmpty()) return 0; if (hit(new Ray(origin, direction), null).isEmpty()) return 0;
var solidAngle = 0d; var solidAngle = 0d;
for (var s : Side.values()) { for (var s : Side.values()) {

@ -10,15 +10,16 @@ import eu.jonahbauer.raytracing.scene.Hittable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Optional; import java.util.Optional;
import java.util.random.RandomGenerator;
public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNull IsotropicMaterial material) implements Hittable { public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNull IsotropicMaterial material) implements Hittable {
@Override @Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) { public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
var hit1 = boundary.hit(ray, Range.UNIVERSE); var hit1 = boundary.hit(ray, Range.UNIVERSE, random);
if (hit1.isEmpty()) return Optional.empty(); if (hit1.isEmpty()) return Optional.empty();
var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY)); var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY), random);
if (hit2.isEmpty()) return Optional.empty(); if (hit2.isEmpty()) return Optional.empty();
var tmin = Math.max(range.min(), hit1.get().t()); var tmin = Math.max(range.min(), hit1.get().t());
@ -28,7 +29,7 @@ public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNul
var length = ray.direction().length(); var length = ray.direction().length();
var distance = length * (tmax - tmin); var distance = length * (tmax - tmin);
var hitDistance = - Math.log(Math.random()) / density; var hitDistance = - Math.log(random.nextDouble()) / density;
if (hitDistance > distance) return Optional.empty(); if (hitDistance > distance) return Optional.empty();
var t = tmin + hitDistance / length; var t = tmin + hitDistance / length;

@ -9,6 +9,7 @@ import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Target; import eu.jonahbauer.raytracing.scene.Target;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -40,7 +41,7 @@ public final class Sphere implements Hittable, Target {
} }
@Override @Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) { public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
var t = hit0(ray, range); var t = hit0(ray, range);
if (Double.isNaN(t)) return Optional.empty(); if (Double.isNaN(t)) return Optional.empty();

@ -2,10 +2,8 @@ package eu.jonahbauer.raytracing.scene.transform;
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.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Target;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
@ -24,7 +22,7 @@ public abstract class Transform implements Hittable {
protected abstract @NotNull HitResult transform(@NotNull HitResult result); protected abstract @NotNull HitResult transform(@NotNull HitResult result);
@Override @Override
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) { public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
return object.hit(transform(ray), range).map(this::transform); return object.hit(transform(ray), range, random).map(this::transform);
} }
} }

@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.random.RandomGenerator;
public final class HittableBinaryTree extends HittableCollection { public final class HittableBinaryTree extends HittableCollection {
private final @Nullable Hittable left; private final @Nullable Hittable left;
@ -46,17 +47,17 @@ public final class HittableBinaryTree extends HittableCollection {
} }
@Override @Override
public void hit(@NotNull Ray ray, @NotNull State state) { public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
if (!bbox.hit(ray, state.getRange())) return; if (!bbox.hit(ray, state.getRange())) return;
if (left instanceof HittableCollection coll) { if (left instanceof HittableCollection coll) {
coll.hit(ray, state); coll.hit(ray, state, random);
} else if (left != null) { } else if (left != null) {
hit(state, ray, left); hit(state, ray, left, random);
} }
if (right instanceof HittableCollection coll) { if (right instanceof HittableCollection coll) {
coll.hit(ray, state); coll.hit(ray, state, random);
} else if (right != null) { } else if (right != null) {
hit(state, ray, right); hit(state, ray, right, random);
} }
} }

@ -8,20 +8,21 @@ import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.random.RandomGenerator;
public abstract class HittableCollection implements Hittable { public abstract class HittableCollection implements Hittable {
@Override @Override
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) { public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
var state = new State(range); var state = new State(range);
hit(ray, state); hit(ray, state, random);
return state.getResult(); return state.getResult();
} }
public abstract void hit(@NotNull Ray ray, @NotNull State state); public abstract void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random);
protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object) { protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object, @NotNull RandomGenerator random) {
var r = object.hit(ray, state.range); var r = object.hit(ray, state.range, random);
if (r.isPresent()) { if (r.isPresent()) {
if (state.range.surrounds(r.get().t())){ if (state.range.surrounds(r.get().t())){
state.result = r.get(); state.result = r.get();

@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.random.RandomGenerator;
public final class HittableList extends HittableCollection { public final class HittableList extends HittableCollection {
private final @NotNull List<Hittable> objects; private final @NotNull List<Hittable> objects;
@ -22,8 +23,8 @@ public final class HittableList extends HittableCollection {
} }
@Override @Override
public void hit(@NotNull Ray ray, @NotNull State state) { public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
objects.forEach(object -> hit(state, ray, object)); objects.forEach(object -> hit(state, ray, object, random));
} }
@Override @Override

Loading…
Cancel
Save