diff --git a/src/main/java/eu/jonahbauer/raytracing/Main.java b/src/main/java/eu/jonahbauer/raytracing/Main.java index 5ee8361..cf5981f 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Main.java +++ b/src/main/java/eu/jonahbauer/raytracing/Main.java @@ -1,5 +1,6 @@ package eu.jonahbauer.raytracing; +import eu.jonahbauer.raytracing.render.camera.Camera; import eu.jonahbauer.raytracing.render.material.DielectricMaterial; import eu.jonahbauer.raytracing.render.material.LambertianMaterial; import eu.jonahbauer.raytracing.render.material.Material; @@ -12,8 +13,11 @@ import eu.jonahbauer.raytracing.render.canvas.LiveCanvas; import eu.jonahbauer.raytracing.render.canvas.Image; import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer; import eu.jonahbauer.raytracing.scene.Hittable; +import eu.jonahbauer.raytracing.scene.flat.Ellipse; +import eu.jonahbauer.raytracing.scene.flat.Parallelogram; import eu.jonahbauer.raytracing.scene.Scene; import eu.jonahbauer.raytracing.scene.Sphere; +import eu.jonahbauer.raytracing.scene.flat.Triangle; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -24,16 +28,9 @@ import java.util.Random; public class Main { public static void main(String[] args) throws IOException { - var scene = getScene(); - - var camera = SimpleCamera.builder() - .withImage(1200, 675) - .withPosition(new Vec3(13, 2, 3)) - .withTarget(new Vec3(0, 0, 0)) - .withFieldOfView(Math.toRadians(20)) - .withFocusDistance(10.0) - .withBlurAngle(Math.toRadians(0.6)) - .build(); + var example = getSquares(); + var scene = example.scene(); + var camera = example.camera(); var renderer = SimpleRenderer.builder() .withSamplesPerPixel(500) @@ -51,7 +48,7 @@ public class Main { ImageFormat.PNG.write(image, Path.of("scene-" + System.currentTimeMillis() + ".png")); } - private static @NotNull Scene getScene() { + private static @NotNull Example getSpheres() { var rng = new Random(1); var objects = new ArrayList(); objects.add(new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new Color(0.5, 0.5, 0.5)))); @@ -85,7 +82,34 @@ public class Main { objects.add(new Sphere(new Vec3(-4, 1, 0), 1.0, new LambertianMaterial(new Color(0.4, 0.2, 0.1)))); objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(new Color(0.7, 0.6, 0.5)))); - return new Scene(objects); + var camera = SimpleCamera.builder() + .withImage(1200, 675) + .withPosition(new Vec3(13, 2, 3)) + .withTarget(new Vec3(0, 0, 0)) + .withFieldOfView(Math.toRadians(20)) + .withFocusDistance(10.0) + .withBlurAngle(Math.toRadians(0.6)) + .build(); + + return new Example(new Scene(objects), camera); + } + + private static @NotNull Example getSquares() { + return new Example( + new Scene(List.of( + new Parallelogram(new Vec3(-3, -2, 5), new Vec3(0, 0, -4), new Vec3(0, 4, 0), new LambertianMaterial(new Color(1.0, 0.2, 0.2))), + new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(new Color(0.2, 1.0, 0.2))), + new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(new Color(0.2, 0.2, 1.0))), + new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(new Color(1.0, 0.5, 0.0))), + new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(new Color(0.2, 0.8, 0.8))) + )), + SimpleCamera.builder() + .withImage(400, 400) + .withFieldOfView(Math.toRadians(80)) + .withPosition(new Vec3(0, 0, 9)) + .withTarget(new Vec3(0, 0, 0)) + .build() + ); } private static @NotNull Scene getSimpleScene() { @@ -97,4 +121,6 @@ public class Main { new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(new Color(0.8, 0.6, 0.2), 0.0)) )); } + + private record Example(@NotNull Scene scene, @NotNull Camera camera) {} } \ No newline at end of file diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Ellipse.java b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Ellipse.java new file mode 100644 index 0000000..7af392b --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Ellipse.java @@ -0,0 +1,26 @@ +package eu.jonahbauer.raytracing.scene.flat; + +import eu.jonahbauer.raytracing.math.BoundingBox; +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.material.Material; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public final class Ellipse extends Hittable2D { + public Ellipse(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) { + super(origin, u, v, material); + } + + @Override + protected boolean isInterior(double alpha, double beta) { + return alpha * alpha + beta * beta < 1; + } + + @Override + public @NotNull Optional getBoundingBox() { + var a = origin.minus(u).minus(v); + var b = origin.plus(u).plus(v); + return Optional.of(new BoundingBox(Vec3.min(a, b), Vec3.max(a, b))); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Hittable2D.java b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Hittable2D.java new file mode 100644 index 0000000..5563b95 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Hittable2D.java @@ -0,0 +1,64 @@ +package eu.jonahbauer.raytracing.scene.flat; + +import eu.jonahbauer.raytracing.math.Range; +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.material.Material; +import eu.jonahbauer.raytracing.scene.HitResult; +import eu.jonahbauer.raytracing.scene.Hittable; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Optional; + +public abstract class Hittable2D implements Hittable { + protected final @NotNull Vec3 origin; + protected final @NotNull Vec3 u; + protected final @NotNull Vec3 v; + private final @NotNull Material material; + + // internal + private final @NotNull Vec3 normal; + private final double d; + private final @NotNull Vec3 w; + + protected Hittable2D(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) { + this.origin = Objects.requireNonNull(origin); + this.u = Objects.requireNonNull(u); + this.v = Objects.requireNonNull(v); + this.material = Objects.requireNonNull(material); + + var n = u.cross(v); + if (n.squared() < 1e-8) throw new IllegalArgumentException(); + this.normal = n.unit(); + this.d = origin.times(normal); + this.w = n.div(n.squared()); + } + + @Override + public @NotNull Optional hit(@NotNull Ray ray, @NotNull Range range) { + var denominator = ray.direction().times(normal); + if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel + + var t = (d - ray.origin().times(normal)) / denominator; + if (!range.surrounds(t)) return Optional.empty(); + + var position = ray.at(t); + var p = position.minus(origin); + + if (!isInterior(p)) return Optional.empty(); + + var frontFace = denominator < 0; + return Optional.of(new HitResult(t, position, frontFace ? normal : normal.neg(), material, frontFace)); + } + + protected boolean isInterior(@NotNull Vec3 p) { + var alpha = w.times(p.cross(v)); + var beta = w.times(u.cross(p)); + return isInterior(alpha, beta); + } + + protected boolean isInterior(double alpha, double beta) { + return false; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Parallelogram.java b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Parallelogram.java new file mode 100644 index 0000000..ba3e18e --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Parallelogram.java @@ -0,0 +1,27 @@ +package eu.jonahbauer.raytracing.scene.flat; + +import eu.jonahbauer.raytracing.math.BoundingBox; +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.material.Material; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public final class Parallelogram extends Hittable2D { + + public Parallelogram(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) { + super(origin, u, v, material); + } + + @Override + protected boolean isInterior(double alpha, double beta) { + return 0 <= alpha && alpha < 1 && 0 <= beta && beta < 1; + } + + @Override + public @NotNull Optional getBoundingBox() { + var a = origin; + var b = origin.plus(u).plus(v); + return Optional.of(new BoundingBox(Vec3.min(a, b), Vec3.max(a, b))); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Plane.java b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Plane.java new file mode 100644 index 0000000..05ead58 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Plane.java @@ -0,0 +1,16 @@ +package eu.jonahbauer.raytracing.scene.flat; + +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.material.Material; +import org.jetbrains.annotations.NotNull; + +public final class Plane extends Hittable2D { + public Plane(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) { + super(origin, u, v, material); + } + + @Override + protected boolean isInterior(@NotNull Vec3 p) { + return true; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/flat/Triangle.java b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Triangle.java new file mode 100644 index 0000000..2b57aad --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/flat/Triangle.java @@ -0,0 +1,27 @@ +package eu.jonahbauer.raytracing.scene.flat; + +import eu.jonahbauer.raytracing.math.BoundingBox; +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.material.Material; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public final class Triangle extends Hittable2D { + + public Triangle(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) { + super(origin, u, v, material); + } + + @Override + protected boolean isInterior(double alpha, double beta) { + return 0 <= alpha && 0 <= beta && alpha + beta <= 1; + } + + @Override + public @NotNull Optional getBoundingBox() { + var a = origin; + var b = origin.plus(u).plus(v); + return Optional.of(new BoundingBox(Vec3.min(a, b), Vec3.max(a, b))); + } +}