diff --git a/src/main/java/eu/jonahbauer/raytracing/Main.java b/src/main/java/eu/jonahbauer/raytracing/Main.java index bdf5901..b673af4 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Main.java +++ b/src/main/java/eu/jonahbauer/raytracing/Main.java @@ -11,6 +11,7 @@ import eu.jonahbauer.raytracing.render.material.*; import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer; import eu.jonahbauer.raytracing.scene.*; import eu.jonahbauer.raytracing.scene.hittable2d.Parallelogram; +import eu.jonahbauer.raytracing.scene.hittable3d.ConstantMedium; import eu.jonahbauer.raytracing.scene.hittable3d.Sphere; import eu.jonahbauer.raytracing.scene.util.Hittables; import org.jetbrains.annotations.NotNull; @@ -22,7 +23,7 @@ import java.util.Random; public class Main { public static void main(String[] args) throws IOException { - var example = getCornellBox(); + var example = getCornellBoxSmoke(); var scene = example.scene(); var camera = example.camera(); @@ -150,6 +151,38 @@ public class Main { ); } + private static @NotNull Example getCornellBoxSmoke() { + var red = new LambertianMaterial(new Color(.65, .05, .05)); + var white = new LambertianMaterial(new Color(.73, .73, .73)); + var green = new LambertianMaterial(new Color(.12, .45, .15)); + var light = new DiffuseLight(new Color(7.0, 7.0, 7.0)); + + return new Example( + new Scene( + new Parallelogram(new Vec3(555, 0, 0), new Vec3(0, 555, 0), new Vec3(0, 0, 555), green), + new Parallelogram(new Vec3(0, 0, 0), new Vec3(0, 555, 0), new Vec3(0, 0, 555), red), + new Parallelogram(new Vec3(113, 554, 127), new Vec3(330, 0, 0), new Vec3(0, 0, 305), light), + new Parallelogram(new Vec3(0, 0, 0), new Vec3(555, 0 ,0), new Vec3(0, 0, 555), white), + new Parallelogram(new Vec3(555, 555, 555), new Vec3(-555, 0 ,0), new Vec3(0, 0, -555), white), + new Parallelogram(new Vec3(0, 0, 555), new Vec3(555, 0 ,0), new Vec3(0, 555, 0), white), + new ConstantMedium( + Hittables.box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)), + 0.01, new IsotropicMaterial(Color.BLACK) + ), + new ConstantMedium( + Hittables.box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white).rotateY(Math.toRadians(-18)).translate(new Vec3(130, 0, 65)), + 0.01, new IsotropicMaterial(Color.WHITE) + ) + ), + SimpleCamera.builder() + .withImage(600, 600) + .withFieldOfView(Math.toRadians(40)) + .withPosition(new Vec3(278, 278, -800)) + .withTarget(new Vec3(278, 278, 0)) + .build() + ); + } + private static @NotNull Scene getSimpleScene() { return new Scene( getSkyBox(), diff --git a/src/main/java/eu/jonahbauer/raytracing/math/Ray.java b/src/main/java/eu/jonahbauer/raytracing/math/Ray.java index 7cadd44..9448ec5 100644 --- a/src/main/java/eu/jonahbauer/raytracing/math/Ray.java +++ b/src/main/java/eu/jonahbauer/raytracing/math/Ray.java @@ -11,7 +11,6 @@ public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) { } public @NotNull Vec3 at(double t) { - if (t < 0) throw new IllegalArgumentException("t must not be negative"); return new Vec3( origin().x() + t * direction.x(), origin().y() + t * direction.y(), diff --git a/src/main/java/eu/jonahbauer/raytracing/render/material/IsotropicMaterial.java b/src/main/java/eu/jonahbauer/raytracing/render/material/IsotropicMaterial.java new file mode 100644 index 0000000..549992c --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/material/IsotropicMaterial.java @@ -0,0 +1,16 @@ +package eu.jonahbauer.raytracing.render.material; + +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.Color; +import eu.jonahbauer.raytracing.scene.HitResult; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public record IsotropicMaterial(@NotNull Color albedo) implements Material{ + @Override + public @NotNull Optional scatter(@NotNull Ray ray, @NotNull HitResult hit) { + return Optional.of(new ScatterResult(new Ray(hit.position(), Vec3.random(true)), albedo())); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java b/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java index dd01287..2d06ff0 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java @@ -14,7 +14,6 @@ public record HitResult( boolean frontFace ) implements Comparable { public HitResult { - if (t < 0 || !Double.isFinite(t)) throw new IllegalArgumentException("t must be non-negative"); Objects.requireNonNull(position, "position"); normal = normal.unit(); } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/ConstantMedium.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/ConstantMedium.java new file mode 100644 index 0000000..675fd0b --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/ConstantMedium.java @@ -0,0 +1,42 @@ +package eu.jonahbauer.raytracing.scene.hittable3d; + +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.render.material.IsotropicMaterial; +import eu.jonahbauer.raytracing.scene.HitResult; +import eu.jonahbauer.raytracing.scene.Hittable; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNull IsotropicMaterial material) implements Hittable { + + @Override + public @NotNull Optional hit(@NotNull Ray ray, @NotNull Range range) { + var hit1 = boundary.hit(ray, Range.UNIVERSE); + if (hit1.isEmpty()) return Optional.empty(); + + var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY)); + if (hit2.isEmpty()) return Optional.empty(); + + var tmin = Math.max(range.min(), hit1.get().t()); + var tmax = Math.min(range.max(), hit2.get().t()); + if (tmin >= tmax) return Optional.empty(); + if (tmin < 0) tmin = 0; + + var length = ray.direction().length(); + var distance = length * (tmax - tmin); + var hitDistance = - Math.log(Math.random()) / density; + if (hitDistance > distance) return Optional.empty(); + + var t = tmin + hitDistance / length; + return Optional.of(new HitResult(t, ray.at(t), Vec3.UNIT_X, material, true)); // arbitrary normal and frontFace + } + + @Override + public @NotNull Optional getBoundingBox() { + return boundary().getBoundingBox(); + } +}