Compare commits

..

10 Commits

Author SHA1 Message Date
a90a0db6d5 improve AABB hit test by adding a range check 2024-08-07 16:26:30 +02:00
1b02f8a96d skip unnecessary UV calculations 2024-08-07 15:53:36 +02:00
18c179f8e3 improve performance of boxes 2024-08-07 15:53:36 +02:00
1d48a49987 refactor AABB intersection 2024-08-07 15:53:36 +02:00
9b617a82a8 remove HittableOctree 2024-08-07 15:53:36 +02:00
dfe80011c9 add "final" scene 2024-08-07 15:53:36 +02:00
37539a1906 add minor improvements to perlin noise performance 2024-08-07 15:53:36 +02:00
70f2f38e96 add perlin noise texture 2024-08-07 15:53:36 +02:00
e6447fe684 add image texture 2024-08-07 15:53:36 +02:00
7c0bc68ab2 add texture support 2024-08-07 15:53:36 +02:00
34 changed files with 624 additions and 370 deletions

View File

@ -1,16 +1,20 @@
package eu.jonahbauer.raytracing;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.CheckerTexture;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.camera.SimpleCamera;
import eu.jonahbauer.raytracing.render.material.*;
import eu.jonahbauer.raytracing.render.texture.ImageTexture;
import eu.jonahbauer.raytracing.render.texture.PerlinTexture;
import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Scene;
import eu.jonahbauer.raytracing.scene.SkyBox;
import eu.jonahbauer.raytracing.scene.hittable2d.Parallelogram;
import eu.jonahbauer.raytracing.scene.hittable3d.Box;
import eu.jonahbauer.raytracing.scene.hittable3d.ConstantMedium;
import eu.jonahbauer.raytracing.scene.hittable3d.Sphere;
import eu.jonahbauer.raytracing.scene.util.Hittables;
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@ -31,6 +35,9 @@ public class Examples {
register("CORNELL", Examples::getCornellBox);
register("CORNELL_SMOKE", Examples::getCornellBoxSmoke);
register("DIAGRAMM", Examples::getDiagramm);
register("EARTH", Examples::getEarth);
register("PERLIN", Examples::getPerlin);
register("FINAL", Examples::getFinal);
}
public static @NotNull IntFunction<Example> getByName(@NotNull String name) {
@ -61,7 +68,10 @@ public class Examples {
var rng = new Random(1);
var objects = new ArrayList<Hittable>();
objects.add(new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new Color(0.5, 0.5, 0.5))));
objects.add(new Sphere(
new Vec3(0, -1000, 0), 1000,
new LambertianMaterial(new CheckerTexture(0.32, new Color(.2, .3, .1), new Color(.9, .9, .9)))
));
for (int a = -11; a < 11; a++) {
for (int b = -11; b < 11; b++) {
@ -158,8 +168,8 @@ public class Examples {
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),
Hittables.box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)),
Hittables.box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white).rotateY(Math.toRadians(-18)).translate(new Vec3(130, 0, 65))
new Box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)),
new Box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white).rotateY(Math.toRadians(-18)).translate(new Vec3(130, 0, 65))
),
SimpleCamera.builder()
.withImage(height, height)
@ -186,11 +196,11 @@ public class Examples {
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)),
new 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)),
new 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)
)
),
@ -233,7 +243,7 @@ public class Examples {
for (int i = 0; i < data.size(); i++) {
var partei = data.get(i);
objects.add(Hittables.box(
objects.add(new Box(
new Vec3((i + 1) * spacing + i * size, 0, spacing),
new Vec3((i + 1) * spacing + (i + 1) * size, partei.stimmen() * 15, spacing + size),
new DielectricMaterial(1.5, partei.color())
@ -251,6 +261,111 @@ public class Examples {
);
}
public static @NotNull Example getEarth(int height) {
if (height <= 0) height = 450;
return new Example(
new Scene(
getSkyBox(),
new Sphere(Vec3.ZERO, 2, new LambertianMaterial(new ImageTexture("/earthmap.jpg")))
),
SimpleCamera.builder()
.withImage(height * 16 / 9, height)
.withFieldOfView(Math.toRadians(20))
.withPosition(new Vec3(12, 0, 0))
.withTarget(Vec3.ZERO)
.build()
);
}
public static @NotNull Example getPerlin(int height) {
if (height <= 0) height = 450;
var material = new LambertianMaterial(new PerlinTexture(4));
return new Example(
new Scene(
getSkyBox(),
new Sphere(new Vec3(0, -1000, 0), 1000, material),
new Sphere(new Vec3(0, 2, 0), 2, material)
),
SimpleCamera.builder()
.withImage(height * 16 / 9, height)
.withFieldOfView(Math.toRadians(20))
.withPosition(new Vec3(13, 2, 3))
.withTarget(Vec3.ZERO)
.build()
);
}
public static @NotNull Example getFinal(int height) {
if (height <= 0) height = 400;
var objects = new ArrayList<Hittable>();
var random = new Random(1);
// boxes
var boxes = new ArrayList<Hittable>();
var ground = new LambertianMaterial(new Color(0.48, 0.83, 0.53));
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
var w = 100.0;
var x0 = -1000.0 + i * w;
var z0 = -1000.0 + j * w;
var y0 = 0.0;
var x1 = x0 + w;
var y1 = random.nextInt(1, 101);
var z1 = z0 + w;
boxes.add(new Box(new Vec3(x0, y0, z0), new Vec3(x1, y1, z1), ground));
}
}
objects.add(new HittableBinaryTree(boxes));
// light
objects.add(new Parallelogram(
new Vec3(123, 554, 147), new Vec3(300, 0, 0), new Vec3(0, 0, 265),
new DiffuseLight(new Color(7., 7., 7.))
));
// spheres with different materials
objects.add(new Sphere(new Vec3(400, 400, 200), 50, new LambertianMaterial(new Color(0.7, 0.3, 0.1))));
objects.add(new Sphere(new Vec3(260, 150, 45), 50, new DielectricMaterial(1.5)));
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(new Color(0.8, 0.8, 0.9), 1.0)));
// glass sphere filled with gas
var boundary = new Sphere(new Vec3(360, 150, 145), 70, new DielectricMaterial(1.5));
objects.add(boundary);
objects.add(new ConstantMedium(boundary, 0.2, new IsotropicMaterial(new Color(0.2, 0.4, 0.9))));
// put the world in a glass sphere
objects.add(new ConstantMedium(
new Sphere(new Vec3(0, 0, 0), 5000, new DielectricMaterial(1.5)),
0.0001, new IsotropicMaterial(new Color(1., 1., 1.))
));
// textures spheres
objects.add(new Sphere(new Vec3(400, 200, 400), 100, new LambertianMaterial(new ImageTexture("/earthmap.jpg"))));
objects.add(new Sphere(new Vec3(220, 280, 300), 80, new LambertianMaterial(new PerlinTexture(0.2))));
// box from spheres
var white = new LambertianMaterial(new Color(.73, .73, .73));
var spheres = new ArrayList<Hittable>();
for (int j = 0; j < 1000; j++) {
spheres.add(new Sphere(new Vec3(random.nextDouble(165), random.nextDouble(165), random.nextDouble(165)), 10, white));
}
objects.add(new HittableBinaryTree(spheres).rotateY(Math.toRadians(15)).translate(new Vec3(-100, 270, 395)));
return new Example(
new Scene(objects),
SimpleCamera.builder()
.withImage(height, height)
.withFieldOfView(Math.toRadians(40))
.withPosition(new Vec3(478, 278, -600))
.withTarget(new Vec3(278, 278, 0))
.build()
);
}
private static @NotNull SkyBox getSkyBox() {
return SkyBox.gradient(new Color(0.5, 0.7, 1.0), Color.WHITE);
}

View File

@ -11,7 +11,6 @@ import java.util.Optional;
* An axis-aligned bounding box.
*/
public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) {
public static final AABB UNIVERSE = new AABB(Vec3.MIN, Vec3.MAX);
public static final AABB EMPTY = new AABB(Vec3.ZERO, Vec3.ZERO);
public static final Comparator<AABB> X_AXIS = Comparator.comparing(AABB::min, Comparator.comparingDouble(Vec3::x));
public static final Comparator<AABB> Y_AXIS = Comparator.comparing(AABB::min, Comparator.comparingDouble(Vec3::y));
@ -56,18 +55,10 @@ public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) {
return new AABB(Vec3.min(this.min, box.min), Vec3.max(this.max, box.max));
}
public boolean hit(@NotNull Ray ray) {
return intersect(ray).isPresent();
}
public @NotNull Optional<Range> intersect(@NotNull Ray ray) {
if (this == UNIVERSE) return Optional.of(Range.UNIVERSE);
if (this == EMPTY) return Optional.empty();
int vmask = ray.vmask();
public boolean hit(@NotNull Ray ray, @NotNull Range range) {
var origin = ray.origin();
var invDirection = ray.direction().inv();
var direction = ray.direction();
var invDirection = direction.inv();
// calculate t values for intersection points of ray with planes through min
var tmin = intersect(min(), origin, invDirection);
@ -77,27 +68,24 @@ public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) {
// determine range of t for which the ray is inside this voxel
double tlmax = Double.NEGATIVE_INFINITY; // lower limit maximum
double tumin = Double.POSITIVE_INFINITY; // upper limit minimum
for (int i = 0; i < 3; i++) {
// classify t values as lower or upper limit based on vmask
if ((vmask & (1 << i)) == 0) {
// classify t values as lower or upper limit based on ray direction
if (direction.get(i) >= 0) {
// min is lower limit and max is upper limit
tlmax = Math.max(tlmax, tmin[i]);
tumin = Math.min(tumin, tmax[i]);
if (tmin[i] > tlmax) tlmax = tmin[i];
if (tmax[i] < tumin) tumin = tmax[i];
} else {
// max is lower limit and min is upper limit
tlmax = Math.max(tlmax, tmax[i]);
tumin = Math.min(tumin, tmin[i]);
if (tmax[i] > tlmax) tlmax = tmax[i];
if (tmin[i] < tumin) tumin = tmin[i];
}
}
return tlmax < tumin ? Optional.of(new Range(tlmax, tumin)) : Optional.empty();
return tlmax < tumin && tumin >= range.min() && tlmax <= range.max();
}
public static double @NotNull[] intersect(@NotNull Vec3 corner, @NotNull Ray ray) {
return intersect(corner, ray.origin(), ray.direction().inv());
}
private static double @NotNull[] intersect(@NotNull Vec3 corner, @NotNull Vec3 origin, @NotNull Vec3 invDirection) {
public static double @NotNull[] intersect(@NotNull Vec3 corner, @NotNull Vec3 origin, @NotNull Vec3 invDirection) {
return new double[] {
(corner.x() - origin.x()) * invDirection.x(),
(corner.y() - origin.y()) * invDirection.y(),

View File

@ -146,6 +146,15 @@ public record Vec3(double x, double y, double z) {
return div(length());
}
public double get(int axis) {
return switch (axis) {
case 0 -> x;
case 1 -> y;
case 2 -> z;
default -> throw new IndexOutOfBoundsException(axis);
};
}
public @NotNull Vec3 withX(double x) {
return new Vec3(x, y, z);
}

View File

@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;

View File

@ -1,8 +1,9 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
import java.util.Objects;
public final class Image implements Canvas {
@ -21,6 +22,16 @@ public final class Image implements Canvas {
this.data = new Color[height][width];
}
public Image(@NotNull BufferedImage image) {
this(image.getWidth(), image.getHeight());
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
this.data[y][x] = new Color(image.getRGB(x, y));
}
}
}
@Override
public int getWidth() {
return width;

View File

@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;

View File

@ -2,7 +2,8 @@ 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.render.texture.Color;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@ -10,13 +11,13 @@ import java.util.Objects;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record DielectricMaterial(double refractionIndex, @NotNull Color albedo) implements Material {
public record DielectricMaterial(double refractionIndex, @NotNull Texture texture) implements Material {
public DielectricMaterial(double refractionIndex) {
this(refractionIndex, Color.WHITE);
}
public DielectricMaterial {
Objects.requireNonNull(albedo, "albedo");
Objects.requireNonNull(texture, "texture");
}
@Override
@ -30,7 +31,8 @@ public record DielectricMaterial(double refractionIndex, @NotNull Color albedo)
var newDirection = (reflect ? Optional.<Vec3>empty() : Vec3.refract(ray.direction(), hit.normal(), ri))
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
return Optional.of(new ScatterResult(new Ray(hit.position(), newDirection), albedo));
var attenuation = texture.get(hit);
return Optional.of(new ScatterResult(new Ray(hit.position(), newDirection), attenuation));
}
private double reflectance(double cos) {

View File

@ -1,14 +1,15 @@
package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record DiffuseLight(@NotNull Color emit) implements Material {
public record DiffuseLight(@NotNull Texture texture) implements Material {
@Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
return Optional.empty();
@ -16,6 +17,6 @@ public record DiffuseLight(@NotNull Color emit) implements Material {
@Override
public @NotNull Color emitted(@NotNull HitResult hit) {
return emit;
return texture.get(hit);
}
}

View File

@ -2,16 +2,22 @@ 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.render.texture.Color;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record IsotropicMaterial(@NotNull Color albedo) implements Material{
public record IsotropicMaterial(@NotNull Color albedo) implements Material {
@Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
return Optional.of(new ScatterResult(new Ray(hit.position(), Vec3.random(random, true)), albedo()));
}
@Override
public @NotNull Texture texture() {
return albedo();
}
}

View File

@ -2,7 +2,7 @@ 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.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@ -10,9 +10,9 @@ import java.util.Objects;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record LambertianMaterial(@NotNull Color albedo) implements Material {
public record LambertianMaterial(@NotNull Texture texture) implements Material {
public LambertianMaterial {
Objects.requireNonNull(albedo, "albedo");
Objects.requireNonNull(texture, "texture");
}
@Override
@ -20,7 +20,8 @@ public record LambertianMaterial(@NotNull Color albedo) implements Material {
var newDirection = hit.normal().plus(Vec3.random(random, true));
if (newDirection.isNearZero()) newDirection = hit.normal();
var attenuation = texture.get(hit);
var scattered = new Ray(hit.position(), newDirection);
return Optional.of(new ScatterResult(scattered, albedo));
return Optional.of(new ScatterResult(scattered, attenuation));
}
}

View File

@ -1,7 +1,8 @@
package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@ -11,6 +12,8 @@ import java.util.random.RandomGenerator;
public interface Material {
@NotNull Texture texture();
@NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random);
default @NotNull Color emitted(@NotNull HitResult hit) {

View File

@ -2,7 +2,7 @@ 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.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@ -10,14 +10,14 @@ import java.util.Objects;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record MetallicMaterial(@NotNull Color albedo, double fuzz) implements Material {
public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements Material {
public MetallicMaterial(@NotNull Color albedo) {
this(albedo, 0);
public MetallicMaterial(@NotNull Texture texture) {
this(texture, 0);
}
public MetallicMaterial {
Objects.requireNonNull(albedo, "albedo");
Objects.requireNonNull(texture, "texture");
if (fuzz < 0 || !Double.isFinite(fuzz)) throw new IllegalArgumentException("fuzz must be non-negative");
}
@ -27,6 +27,7 @@ public record MetallicMaterial(@NotNull Color albedo, double fuzz) implements Ma
if (fuzz > 0) {
newDirection = newDirection.unit().plus(Vec3.random(random, true).times(fuzz));
}
return Optional.of(new ScatterResult(new Ray(hit.position(), newDirection), albedo));
var attenuation = texture.get(hit);
return Optional.of(new ScatterResult(new Ray(hit.position(), newDirection), attenuation));
}
}

View File

@ -2,7 +2,7 @@ package eu.jonahbauer.raytracing.render.renderer;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.camera.Camera;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.scene.Scene;

View File

@ -0,0 +1,22 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import org.jetbrains.annotations.NotNull;
public record CheckerTexture(double scale, @NotNull Texture even, @NotNull Texture odd) implements Texture {
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
var x = (int) Math.floor(p.x() / scale);
var y = (int) Math.floor(p.y() / scale);
var z = (int) Math.floor(p.z() / scale);
var even = (x + y + z) % 2 == 0;
return even ? even().get(u, v, p) : odd().get(u, v, p);
}
@Override
public boolean isUVRequired() {
return even.isUVRequired() || odd.isUVRequired();
}
}

View File

@ -1,10 +1,11 @@
package eu.jonahbauer.raytracing.render;
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
public record Color(double r, double g, double b) {
public record Color(double r, double g, double b) implements Texture {
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 RED = new Color(1.0, 0.0, 0.0);
@ -68,7 +69,9 @@ public record Color(double r, double g, double b) {
}
}
public Color {}
public Color(int rgb) {
this((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
}
public Color(int red, int green, int blue) {
this(red / 255f, green / 255f, blue / 255f);
@ -86,7 +89,17 @@ public record Color(double r, double g, double b) {
return toInt(b);
}
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
return this;
}
@Override
public boolean isUVRequired() {
return false;
}
private static int toInt(double value) {
return Math.max(0, Math.min(255, (int) (255.99 * value)));
return Math.clamp((int) (255.99 * value), 0, 255);
}
}

View File

@ -0,0 +1,43 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.canvas.Image;
import org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects;
public record ImageTexture(@NotNull Image image) implements Texture {
public ImageTexture {
Objects.requireNonNull(image, "image");
}
public ImageTexture(@NotNull BufferedImage image) {
this(new Image(image));
}
public ImageTexture(@NotNull String path) {
this(read(path));
}
private static @NotNull BufferedImage read(@NotNull String path) {
try (var in = Objects.requireNonNull(ImageTexture.class.getResourceAsStream(path))) {
return ImageIO.read(in);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
u = Math.clamp(u, 0, 1);
v = 1 - Math.clamp(v, 0, 1);
int x = (int) (u * (image.getWidth() - 1));
int y = (int) (v * (image.getHeight() - 1));
return image.get(x, y);
}
}

View File

@ -0,0 +1,152 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.Random;
import java.util.function.DoubleFunction;
import java.util.random.RandomGenerator;
public final class PerlinTexture implements Texture {
private static final int POINT_COUNT = 256;
private static final @NotNull Random RANDOM = new Random();
private static final @NotNull DoubleFunction<Color> GREYSCALE = t -> new Color(t, t, t);
private final double scale;
private final int turbulence;
private final @NotNull DoubleFunction<Color> color;
private final int mask;
private final Vec3[] randvec;
private final int[] permX;
private final int[] permY;
private final int[] permZ;
public PerlinTexture() {
this(1.0);
}
public PerlinTexture(double scale) {
this(scale, 7);
}
public PerlinTexture(double scale, int turbulence) {
this(scale, turbulence, GREYSCALE);
}
public PerlinTexture(double scale, int turbulence, @NotNull DoubleFunction<Color> color) {
this(scale, turbulence, color, POINT_COUNT, RANDOM);
}
public PerlinTexture(
double scale, int turbulence, @NotNull DoubleFunction<Color> color,
int count, @NotNull RandomGenerator random
) {
if ((count & (count - 1)) != 0) throw new IllegalArgumentException("count must be a power of two");
if (turbulence <= 0) throw new IllegalArgumentException("turbulence must be positive");
this.scale = scale;
this.turbulence = turbulence;
this.color = Objects.requireNonNull(color, "color");
this.mask = count - 1;
this.randvec = new Vec3[count];
for (int i = 0; i < count; i++) {
this.randvec[i] = Vec3.random(random, true);
}
this.permX = generatePerm(count, random);
this.permY = generatePerm(count, random);
this.permZ = generatePerm(count, random);
}
private static int @NotNull[] generatePerm(int count, @NotNull RandomGenerator random) {
int[] p = new int[count];
for (int i = 0; i < count; i++) {
p[i] = i;
}
permutate(p, random);
return p;
}
private static void permutate(int @NotNull[] p, @NotNull RandomGenerator random) {
for (int i = p.length - 1; i > 0; i--) {
int target = random.nextInt(i);
int tmp = p[i];
p[i] = p[target];
p[target] = tmp;
}
}
public double getNoise(@NotNull Vec3 p) {
var x = p.x() * scale;
var y = p.y() * scale;
var z = p.z() * scale;
var u = x - Math.floor(x);
var v = y - Math.floor(y);
var w = z - Math.floor(z);
int i = (int) Math.floor(x);
int j = (int) Math.floor(y);
int k = (int) Math.floor(z);
var c = new Vec3[8];
for (int di = 0; di < 2; di++) {
for (int dj = 0; dj < 2; dj++) {
for (int dk = 0; dk < 2; dk++) {
c[di << 2 | dj << 1 | dk] = randvec[permX[(i + di) & mask] ^ permY[(j + dj) & mask] ^ permZ[(k + dk) & mask]];
}
}
}
return interpolate(c, u, v, w);
}
public double getNoise(@NotNull Vec3 p, int depth) {
var accum = 0.0;
var temp = p;
var weight = 1.0;
for (int i = 0; i < depth; i++) {
accum = Math.fma(weight, getNoise(temp), accum);
weight *= 0.5;
temp = temp.times(2);
}
return accum;
}
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
var noise = getNoise(p, turbulence);
var t = Math.fma(0.5, Math.sin(Math.PI * noise), 0.5);
return color.apply(t);
}
private static double interpolate(Vec3[] c, double u, double v, double w) {
var uu = u * u * Math.fma(-2, u, 3);
var vv = v * v * Math.fma(-2, v, 3);
var ww = w * w * Math.fma(-2, w, 3);
var accum = 0.0;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
var vec = c[i << 2 | j << 1 | k];
var dot = (u - i) * vec.x() + (v - j) * vec.y() + (w - k) * vec.z();
accum += Math.fma(i, uu, (1 - i) * (1 - uu))
* Math.fma(j, vv, (1 - j) * (1 - vv))
* Math.fma(k, ww, (1 - k) * (1 - ww))
* dot;
}
}
}
return accum;
}
@Override
public boolean isUVRequired() {
return false;
}
}

View File

@ -0,0 +1,17 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
public interface Texture {
default @NotNull Color get(@NotNull HitResult hit) {
return get(hit.u(), hit.v(), hit.position());
}
@NotNull Color get(double u, double v, @NotNull Vec3 p);
default boolean isUVRequired() {
return true;
}
}

View File

@ -7,17 +7,18 @@ import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public record HitResult(
double t,
@NotNull Vec3 position,
@NotNull Vec3 normal,
@NotNull Material material,
boolean frontFace
double t, @NotNull Vec3 position, @NotNull Vec3 normal,
@NotNull Material material, double u, double v, boolean frontFace
) implements Comparable<HitResult> {
public HitResult {
Objects.requireNonNull(position, "position");
normal = normal.unit();
}
public @NotNull HitResult withPositionAndNormal(@NotNull Vec3 position, @NotNull Vec3 normal) {
return new HitResult(t, position, normal, material, u, v, frontFace);
}
@Override
public int compareTo(@NotNull HitResult o) {
return Double.compare(t, o.t);

View File

@ -2,7 +2,7 @@ package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.AABB;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
import eu.jonahbauer.raytracing.scene.util.HittableCollection;
import org.jetbrains.annotations.NotNull;

View File

@ -1,7 +1,7 @@
package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface

View File

@ -51,7 +51,10 @@ public abstract class Hittable2D implements Hittable {
if (!isInterior(alpha, beta)) return Optional.empty();
var frontFace = denominator < 0;
return Optional.of(new HitResult(t, position, frontFace ? normal : normal.neg(), material, frontFace));
return Optional.of(new HitResult(
t, position, frontFace ? normal : normal.neg(),
material, alpha, beta, frontFace
));
}
protected abstract boolean isInterior(double alpha, double beta);

View File

@ -0,0 +1,140 @@
package eu.jonahbauer.raytracing.scene.hittable3d;
import eu.jonahbauer.raytracing.math.AABB;
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 record Box(@NotNull AABB box, @NotNull Material material) implements Hittable {
public Box {
Objects.requireNonNull(box, "box");
Objects.requireNonNull(material, "material");
}
public Box(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Material material) {
this(new AABB(a, b), material);
}
@Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
// based on AABB#hit with additional detection of the side hit
var origin = ray.origin();
var direction = ray.direction();
var invDirection = direction.inv();
var tmin = AABB.intersect(box.min(), origin, invDirection);
var tmax = AABB.intersect(box.max(), origin, invDirection);
double tlmax = Double.NEGATIVE_INFINITY;
double tumin = Double.POSITIVE_INFINITY;
Side entry = null;
Side exit = null;
for (int i = 0; i < 3; i++) {
if (direction.get(i) >= 0) {
if (tmin[i] > tlmax) {
tlmax = tmin[i];
entry = Side.NEGATIVE[i];
}
if (tmax[i] < tumin) {
tumin = tmax[i];
exit = Side.POSITIVE[i];
}
} else {
if (tmax[i] > tlmax) {
tlmax = tmax[i];
entry = Side.POSITIVE[i];
}
if (tmin[i] < tumin) {
tumin = tmin[i];
exit = Side.NEGATIVE[i];
}
}
}
if (tlmax < tumin && tumin >= range.min() && tlmax <= range.max()) {
assert entry != null && exit != null;
return hit0(tlmax, tumin, entry, exit, ray, range);
} else {
return Optional.empty();
}
}
private @NotNull Optional<HitResult> hit0(double tmin, double tmax, @NotNull Side entry, @NotNull Side exit, @NotNull Ray ray, @NotNull Range range) {
boolean frontFace;
double t;
if (range.surrounds(tmin)) {
frontFace = true;
t = tmin;
} else if (range.surrounds(tmax)) {
frontFace = false;
t = tmax;
} else {
return Optional.empty();
}
var side = frontFace ? entry : exit;
var normal = frontFace ? side.normal : side.normal.neg();
var position = ray.at(t);
var uv = material().texture().isUVRequired();
var u = uv ? side.getTextureU(box, position) : Double.NaN;
var v = uv ? side.getTextureV(box, position) : Double.NaN;
return Optional.of(new HitResult(t, position, normal, material, u, v, frontFace));
}
@Override
public @NotNull AABB getBoundingBox() {
return box;
}
private enum Side {
NEG_X(Vec3.UNIT_X.neg()),
NEG_Y(Vec3.UNIT_Y.neg()),
NEG_Z(Vec3.UNIT_Z.neg()),
POS_X(Vec3.UNIT_X),
POS_Y(Vec3.UNIT_Y),
POS_Z(Vec3.UNIT_Z),
;
private static final Side[] NEGATIVE = new Side[] {Side.NEG_X, Side.NEG_Y, Side.NEG_Z};
private static final Side[] POSITIVE = new Side[] {Side.POS_X, Side.POS_Y, Side.POS_Z};
private final @NotNull Vec3 normal;
Side(@NotNull Vec3 normal) {
this.normal = Objects.requireNonNull(normal, "normal");
}
/**
* {@return the texture u coordinate for a position on this side of the box}
*/
public double getTextureU(@NotNull AABB box, @NotNull Vec3 pos) {
return switch (this) {
case NEG_X -> (pos.z() - box.min().z()) / (box.max().z() - box.min().z());
case POS_X -> (box.max().z() - pos.z()) / (box.max().z() - box.min().z());
case NEG_Y, POS_Y, POS_Z -> (pos.x() - box.min().x()) / (box.max().x() - box.min().x());
case NEG_Z -> (box.max().x() - pos.x()) / (box.max().x() - box.min().x());
};
}
/**
* {@return the texture v coordinate for a position on this side of the box}
*/
public double getTextureV(@NotNull AABB box, @NotNull Vec3 pos) {
return switch (this) {
case NEG_X, POS_X, NEG_Z, POS_Z -> (pos.y() - box.min().y()) / (box.max().y() - box.min().y());
case NEG_Y -> (pos.z() - box.min().z()) / (box.max().z() - box.min().z());
case POS_Y -> (box.max().z() - pos.z()) / (box.max().z() - box.min().z());
};
}
}
}

View File

@ -32,7 +32,7 @@ public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNul
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
return Optional.of(new HitResult(t, ray.at(t), Vec3.UNIT_X, material, 0, 0, true)); // arbitrary normal and frontFace
}
@Override

View File

@ -49,9 +49,25 @@ public final class Sphere implements Hittable {
if (!range.surrounds(t)) return Optional.empty();
var position = ray.at(t);
var normal = position.minus(center);
var normal = position.minus(center).div(radius);
var frontFace = normal.times(ray.direction()) < 0;
return Optional.of(new HitResult(t, position, frontFace ? normal : normal.times(-1), material, frontFace));
double u;
double v;
if (material.texture().isUVRequired()) {
var theta = Math.acos(-normal.y());
var phi = Math.atan2(-normal.z(), normal.x()) + Math.PI;
u = phi / (2 * Math.PI);
v = theta / Math.PI;
} else {
u = Double.NaN;
v = Double.NaN;
}
return Optional.of(new HitResult(
t, position, frontFace ? normal : normal.neg(),
material, u, v, frontFace
));
}
@Override

View File

@ -79,7 +79,7 @@ public final class RotateY extends Transform {
-sin * normal.x() + cos * normal.z()
);
return new HitResult(result.t(), newPosition, newNormal, result.material(), result.frontFace());
return result.withPositionAndNormal(newPosition, newNormal);
}
@Override

View File

@ -29,13 +29,7 @@ public final class Translate extends Transform {
@Override
protected @NotNull HitResult transform(@NotNull HitResult result) {
return new HitResult(
result.t(),
result.position().plus(offset),
result.normal(),
result.material(),
result.frontFace()
);
return result.withPositionAndNormal(result.position().plus(offset), result.normal());
}
@Override

View File

@ -47,7 +47,7 @@ public final class HittableBinaryTree extends HittableCollection {
@Override
public void hit(@NotNull Ray ray, @NotNull State state) {
if (!bbox.hit(ray)) return;
if (!bbox.hit(ray, state.getRange())) return;
if (left instanceof HittableCollection coll) {
coll.hit(ray, state);
} else if (left != null) {

View File

@ -41,6 +41,10 @@ public abstract class HittableCollection implements Hittable {
this.range = Objects.requireNonNull(range);
}
public @NotNull Range getRange() {
return range;
}
private @NotNull Optional<HitResult> getResult() {
return Optional.ofNullable(result);
}

View File

@ -1,253 +0,0 @@
package eu.jonahbauer.raytracing.scene.util;
import eu.jonahbauer.raytracing.math.AABB;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.Hittable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.IntStream;
public final class HittableOctree extends HittableCollection {
private static final int LIST_SIZE_LIMIT = 16;
private final @Nullable Storage storage;
private final @NotNull AABB bbox;
public HittableOctree(@NotNull List<? extends @NotNull Hittable> objects) {
bbox = AABB.getBoundingBox(objects).orElse(AABB.EMPTY);
storage = newStorage(bbox, objects);
}
private static @NotNull AABB[] getBoundingBoxes(@NotNull AABB aabb, @NotNull Vec3 center) {
return new AABB[] {
new AABB(new Range(aabb.x().min(), center.x()), new Range(aabb.y().min(), center.y()), new Range(aabb.z().min(), center.z())),
new AABB(new Range(center.x(), aabb.x().max()), new Range(aabb.y().min(), center.y()), new Range(aabb.z().min(), center.z())),
new AABB(new Range(aabb.x().min(), center.x()), new Range(center.y(), aabb.y().max()), new Range(aabb.z().min(), center.z())),
new AABB(new Range(center.x(), aabb.x().max()), new Range(center.y(), aabb.y().max()), new Range(aabb.z().min(), center.z())),
new AABB(new Range(aabb.x().min(), center.x()), new Range(aabb.y().min(), center.y()), new Range(center.z(), aabb.z().max())),
new AABB(new Range(center.x(), aabb.x().max()), new Range(aabb.y().min(), center.y()), new Range(center.z(), aabb.z().max())),
new AABB(new Range(aabb.x().min(), center.x()), new Range(center.y(), aabb.y().max()), new Range(center.z(), aabb.z().max())),
new AABB(new Range(center.x(), aabb.x().max()), new Range(center.y(), aabb.y().max()), new Range(center.z(), aabb.z().max())),
};
}
private static @Nullable Storage newStorage(@NotNull AABB aabb, @NotNull List<? extends @NotNull Hittable> objects) {
if (objects.isEmpty()) return null;
if (objects.size() < LIST_SIZE_LIMIT) {
return new ListStorage(aabb, objects);
} else {
var center = aabb.center();
var octants = (List<Hittable>[]) new List<?>[8];
for (int i = 0; i < 8; i++) octants[i] = new ArrayList<>();
var bboxes = getBoundingBoxes(aabb, center);
var list = new ArrayList<Hittable>();
for (var object : objects) {
var bbox = object.getBoundingBox();
var imin = getOctantIndex(center, bbox.min());
var imax = getOctantIndex(center, bbox.max());
if (imin == imax) {
octants[imin].add(object);
} else {
list.add(object);
}
}
return new NodeStorage(aabb, center, list, IntStream.range(0, 8).mapToObj(i -> newStorage(bboxes[i], octants[i])).toArray(Storage[]::new));
}
}
@Override
public void hit(@NotNull Ray ray, @NotNull State state) {
hit(ray, object -> hit(state, ray, object));
}
@Override
public @NotNull AABB getBoundingBox() {
return bbox;
}
/**
* Use HERO algorithms to find all elements that could possibly be hit by the given ray.
* @see <a href="https://doi.org/10.1007/978-3-642-76298-7_3">
* Agate, M., Grimsdale, R.L., Lister, P.F. (1991).
* The HERO Algorithm for Ray-Tracing Octrees.
* In: Grimsdale, R.L., Straßer, W. (eds) Advances in Computer Graphics Hardware IV. Eurographic Seminars. Springer, Berlin, Heidelberg.</a>
*/
private void hit(@NotNull Ray ray, @NotNull Predicate<? super Hittable> action) {
if (storage != null) storage.hit(ray, action);
}
private static int getOctantIndex(@NotNull Vec3 center, @NotNull Vec3 pos) {
return (pos.x() < center.x() ? 0 : 1)
| (pos.y() < center.y() ? 0 : 2)
| (pos.z() < center.z() ? 0 : 4);
}
private abstract static sealed class Storage {
protected final @NotNull AABB bbox;
public Storage(@NotNull AABB bbox) {
this.bbox = Objects.requireNonNull(bbox);
}
protected boolean hit(@NotNull Ray ray, @NotNull Predicate<? super Hittable> action) {
var range = bbox.intersect(ray);
if (range.isEmpty()) return false;
int vmask = ray.vmask();
return hit0(ray, vmask, range.get().min(), range.get().max(), action);
}
protected abstract boolean hit0(@NotNull Ray ray, int vmask, double tmin, double tmax, @NotNull Predicate<? super Hittable> action);
}
private static final class ListStorage extends Storage {
private final @NotNull List<Hittable> list;
public ListStorage(@NotNull AABB bbox, @NotNull List<? extends @NotNull Hittable> entries) {
super(bbox);
this.list = List.copyOf(entries);
}
@Override
protected boolean hit0(@NotNull Ray ray, int vmask, double tmin, double tmax, @NotNull Predicate<? super Hittable> action) {
var hit = false;
for (Hittable hittable : list) {
hit |= action.test(hittable);
}
return hit;
}
}
private static final class NodeStorage extends Storage {
private final @Nullable Storage @NotNull[] octants;
private final @NotNull Vec3 center;
private final int degenerate;
private final @NotNull List<Hittable> list; // track elements spanning multiple octants separately
public NodeStorage(@NotNull AABB bbox, @NotNull Vec3 center, @NotNull List<? extends @NotNull Hittable> list, @Nullable Storage @NotNull[] octants) {
super(bbox);
this.octants = octants;
this.center = center;
this.list = List.copyOf(list);
int count = 0;
int degenerate = 0;
for (int i = 0; i < octants.length; i++) {
if (octants[i] != null) {
count++;
degenerate = i;
}
}
this.degenerate = count == 1 ? degenerate : -1;
}
@Override
protected boolean hit(@NotNull Ray ray, @NotNull Predicate<? super Hittable> action) {
if (degenerate >= 0 && list.isEmpty()) {
return octants[degenerate].hit(ray, action);
} else {
return super.hit(ray, action);
}
}
@Override
protected boolean hit0(@NotNull Ray ray, int vmask, double tmin, double tmax, @NotNull Predicate<? super Hittable> action) {
if (tmax < 0) return false;
// check for hit
var hit = false;
// process entries spanning multiple children
for (Hittable object : list) {
hit |= action.test(object);
}
// t values for intersection points of ray with planes through center
var tmid = AABB.intersect(center, ray);
// masks of planes in the order of intersection, e.g. [2, 1, 4] for a ray intersection y = center.y() then x = center.x() then z = center.z()
var masklist = calculateMasklist(tmid);
// the first child to be hit by the ray assuming a ray with positive x, y and z coordinates
var childmask = (tmid[0] < tmin ? 1 : 0)
| (tmid[1] < tmin ? 2 : 0)
| (tmid[2] < tmin ? 4 : 0);
// the last child to be hit by the ray assuming a ray with positive x, y and z coordinates
var lastmask = (tmid[0] < tmax ? 1 : 0)
| (tmid[1] < tmax ? 2 : 0)
| (tmid[2] < tmax ? 4 : 0);
var childTmin = tmin;
int i = 0;
while (true) {
// use vmask to nullify the assumption of a positive ray made for childmask
var child = octants[childmask ^ vmask];
// calculate t value for exit of child
double childTmax;
if (childmask == lastmask) {
// last child shares tmax
childTmax = tmax;
} else {
// determine next child
while ((masklist[i] & childmask) != 0) {
i++;
}
childmask = childmask | masklist[i];
// tmax of current child is the t value for the intersection with the plane dividing the current and next child
childTmax = tmid[Integer.numberOfTrailingZeros(masklist[i])];
}
// process child
var childHit = child != null && child.hit0(ray, vmask, childTmin, childTmax, action);
hit |= childHit;
// break after last child has been processed or a hit has been found
if (childTmax == tmax || childHit) break;
// tmin of next child is tmax of current child
childTmin = childTmax;
}
return hit;
}
private static final int[][] MASKLISTS = new int[][] {
{1, 2, 4},
{1, 4, 2},
{4, 1, 2},
{2, 1, 4},
{2, 4, 1},
{4, 2, 1}
};
private static int @NotNull [] calculateMasklist(double @NotNull[] tmid) {
if (tmid[0] < tmid[1]) {
if (tmid[1] < tmid[2]) {
return MASKLISTS[0]; // {1, 2, 4}
} else if (tmid[0] < tmid[2]) {
return MASKLISTS[1]; // {1, 4, 2}
} else {
return MASKLISTS[2]; // {4, 1, 2}
}
} else {
if (tmid[0] < tmid[2]) {
return MASKLISTS[3]; // {2, 1, 4}
} else if (tmid[1] < tmid[2]) {
return MASKLISTS[4]; // {2, 4, 1}
} else {
return MASKLISTS[5]; // {4, 2, 1}
}
}
}
}
}

View File

@ -1,35 +0,0 @@
package eu.jonahbauer.raytracing.scene.util;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.material.Material;
import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.hittable2d.Parallelogram;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public final class Hittables {
private Hittables() {
throw new UnsupportedOperationException();
}
public static @NotNull Hittable box(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Material material) {
var sides = new ArrayList<Hittable>();
var min = Vec3.min(a, b);
var max = Vec3.max(a, b);
var dx = new Vec3(max.x() - min.x(), 0, 0);
var dy = new Vec3(0, max.y() - min.y(), 0);
var dz = new Vec3(0, 0, max.z() - min.z());
sides.add(new Parallelogram(new Vec3(min.x(), min.y(), max.z()), dx, dy, material)); // front
sides.add(new Parallelogram(new Vec3(max.x(), min.y(), max.z()), dz.neg(), dy, material)); // right
sides.add(new Parallelogram(new Vec3(max.x(), min.y(), min.z()), dx.neg(), dy, material)); // back
sides.add(new Parallelogram(new Vec3(min.x(), min.y(), min.z()), dz, dy, material)); // left
sides.add(new Parallelogram(new Vec3(min.x(), max.y(), max.z()), dx, dz.neg(), material)); // top
sides.add(new Parallelogram(new Vec3(min.x(), min.y(), min.z()), dx, dz, material)); // bottom
return new HittableList(sides);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.ImageFormat;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

View File

@ -3,7 +3,7 @@ package eu.jonahbauer.raytracing.scene.hittable3d;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.Color;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.material.LambertianMaterial;
import org.junit.jupiter.api.Test;