From 590054a046e8682c4e53d1b3b1f349002be4c629 Mon Sep 17 00:00:00 2001 From: jbb01 <32650546+jbb01@users.noreply.github.com> Date: Sat, 3 Aug 2024 01:28:20 +0200 Subject: [PATCH] add sphere --- .../java/eu/jonahbauer/raytracing/Main.java | 3 +- .../jonahbauer/raytracing/render/Color.java | 1 + .../jonahbauer/raytracing/render/Scene.java | 23 ++++++++- .../eu/jonahbauer/raytracing/shape/Shape.java | 14 +++++ .../jonahbauer/raytracing/shape/Sphere.java | 51 +++++++++++++++++++ .../raytracing/shape/SphereTest.java | 25 +++++++++ 6 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 src/main/java/eu/jonahbauer/raytracing/shape/Shape.java create mode 100644 src/main/java/eu/jonahbauer/raytracing/shape/Sphere.java create mode 100644 src/test/java/eu/jonahbauer/raytracing/shape/SphereTest.java diff --git a/src/main/java/eu/jonahbauer/raytracing/Main.java b/src/main/java/eu/jonahbauer/raytracing/Main.java index 02d1e1b..7efef97 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Main.java +++ b/src/main/java/eu/jonahbauer/raytracing/Main.java @@ -3,13 +3,14 @@ package eu.jonahbauer.raytracing; import eu.jonahbauer.raytracing.render.Camera; import eu.jonahbauer.raytracing.render.ImageIO; import eu.jonahbauer.raytracing.render.Scene; +import eu.jonahbauer.raytracing.shape.Sphere; import java.io.IOException; import java.nio.file.Path; public class Main { public static void main(String[] args) throws IOException { - var scene = new Scene(); + var scene = new Scene(new Sphere(0, 0, 1, 0.5)); var camera = new Camera(256, 2, 16 / 9d); var image = scene.render(camera); diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Color.java b/src/main/java/eu/jonahbauer/raytracing/render/Color.java index 98c17da..dee19d1 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Color.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Color.java @@ -6,6 +6,7 @@ public record Color(double r, double g, double b) { public static final @NotNull Color BLACK = new Color(0.0, 0.0, 0.0); public static final @NotNull Color WHITE = new Color(1.0, 1.0, 1.0); public static final @NotNull Color SKY = new Color(0.5, 0.7, 1.0); + public static final @NotNull Color RED = new Color(1.0, 0.0, 0.0); public static @NotNull Color lerp(@NotNull Color a, @NotNull Color b, double t) { if (t < 0) return a; diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Scene.java b/src/main/java/eu/jonahbauer/raytracing/render/Scene.java index 1f0ea1e..02f4dba 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Scene.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Scene.java @@ -1,9 +1,20 @@ package eu.jonahbauer.raytracing.render; import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.shape.Shape; import org.jetbrains.annotations.NotNull; -public record Scene() { +import java.util.List; + +public record Scene(@NotNull List<@NotNull Shape> shapes) { + + public Scene { + shapes = List.copyOf(shapes); + } + + public Scene(@NotNull Shape @NotNull ... shapes) { + this(List.of(shapes)); + } public @NotNull Image render(@NotNull Camera camera) { var image = new Image(camera.width(), camera.height()); @@ -13,7 +24,15 @@ public record Scene() { var y = pixel.y(); var ray = pixel.ray(); - image.set(x, y, getSkyboxColor(ray)); + var result = shapes.stream() + .mapToDouble(shape -> shape.hit(ray)) + .filter(Double::isFinite) + .min(); + if (result.isPresent()) { + image.set(x, y, Color.RED); + } else { + image.set(x, y, getSkyboxColor(ray)); + } }); return image; diff --git a/src/main/java/eu/jonahbauer/raytracing/shape/Shape.java b/src/main/java/eu/jonahbauer/raytracing/shape/Shape.java new file mode 100644 index 0000000..ef3fe77 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/shape/Shape.java @@ -0,0 +1,14 @@ +package eu.jonahbauer.raytracing.shape; + +import eu.jonahbauer.raytracing.math.Ray; +import org.jetbrains.annotations.NotNull; + +public sealed interface Shape permits Sphere { + + /** + * {@return the value t such that ray.at(t) is the intersection of this shaped closest to + * the ray origin, or Double.NaN if the ray does not intersect this shape} + * @param ray a ray + */ + double hit(@NotNull Ray ray); +} diff --git a/src/main/java/eu/jonahbauer/raytracing/shape/Sphere.java b/src/main/java/eu/jonahbauer/raytracing/shape/Sphere.java new file mode 100644 index 0000000..020de3f --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/shape/Sphere.java @@ -0,0 +1,51 @@ +package eu.jonahbauer.raytracing.shape; + +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.math.Vec3; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public record Sphere(@NotNull Vec3 center, double radius) implements Shape { + public static final @NotNull Sphere UNIT = new Sphere(Vec3.ZERO, 1.0); + + public Sphere { + Objects.requireNonNull(center, "center"); + if (radius <= 0 || !Double.isFinite(radius)) throw new IllegalArgumentException("radius must be positive"); + } + + public Sphere(double x, double y, double z, double r) { + this(new Vec3(x, y, z), r); + } + + @Override + public double hit(@NotNull Ray ray) { + var oc = ray.origin().minus(center()); + + var a = ray.direction().squared(); + var b = 2 * ray.direction().times(oc); + var c = oc.squared() - radius * radius; + + var discriminant = b * b - 4 * a * c; + if (discriminant < 0) return Double.NaN; + + var sd = Math.sqrt(discriminant); + + double t = (- b - sd) / (2 * a); + if (t < 0) t = (-b + sd) / (2 * a); + if (t < 0) t = Double.NaN; + return t; + } + + public @NotNull Sphere withCenter(@NotNull Vec3 center) { + return new Sphere(center, radius); + } + + public @NotNull Sphere withCenter(double x, double y, double z) { + return withCenter(new Vec3(x, y, z)); + } + + public @NotNull Sphere withRadius(double radius) { + return new Sphere(center, radius); + } +} diff --git a/src/test/java/eu/jonahbauer/raytracing/shape/SphereTest.java b/src/test/java/eu/jonahbauer/raytracing/shape/SphereTest.java new file mode 100644 index 0000000..5bec826 --- /dev/null +++ b/src/test/java/eu/jonahbauer/raytracing/shape/SphereTest.java @@ -0,0 +1,25 @@ +package eu.jonahbauer.raytracing.shape; + +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.math.Vec3; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SphereTest { + + @Test + void hit() { + var center = new Vec3(1, 2, 3); + var radius = 5; + var sphere = new Sphere(center, radius); + + var origin = new Vec3(6, 7, 8); + var direction = new Vec3(-1, -1, -1); + var ray = new Ray(origin, direction); + + var t = sphere.hit(ray); + assertFalse(Double.isNaN(t)); + assertEquals(center.plus(new Vec3(1, 1, 1).unit().times(radius)), ray.at(t)); + } +} \ No newline at end of file