|
|
@ -21,6 +21,7 @@ public final class Camera {
|
|
|
|
private final int samplesPerPixel;
|
|
|
|
private final int samplesPerPixel;
|
|
|
|
private final int maxDepth;
|
|
|
|
private final int maxDepth;
|
|
|
|
private final double gamma;
|
|
|
|
private final double gamma;
|
|
|
|
|
|
|
|
private final double blurRadius;
|
|
|
|
|
|
|
|
|
|
|
|
// internal properties
|
|
|
|
// internal properties
|
|
|
|
private final @NotNull Vec3 u;
|
|
|
|
private final @NotNull Vec3 u;
|
|
|
@ -42,7 +43,7 @@ public final class Camera {
|
|
|
|
this.width = builder.imageWidth;
|
|
|
|
this.width = builder.imageWidth;
|
|
|
|
this.height = builder.imageHeight;
|
|
|
|
this.height = builder.imageHeight;
|
|
|
|
|
|
|
|
|
|
|
|
var viewportHeight = 2 * Math.tan(0.5 * builder.fov);
|
|
|
|
var viewportHeight = 2 * Math.tan(0.5 * builder.fov) * builder.focusDistance;
|
|
|
|
var viewportWidth = viewportHeight * ((double) width / height);
|
|
|
|
var viewportWidth = viewportHeight * ((double) width / height);
|
|
|
|
|
|
|
|
|
|
|
|
this.origin = builder.position;
|
|
|
|
this.origin = builder.position;
|
|
|
@ -51,6 +52,7 @@ public final class Camera {
|
|
|
|
this.samplesPerPixel = builder.samplePerPixel;
|
|
|
|
this.samplesPerPixel = builder.samplePerPixel;
|
|
|
|
this.maxDepth = builder.maxDepth;
|
|
|
|
this.maxDepth = builder.maxDepth;
|
|
|
|
this.gamma = builder.gamma;
|
|
|
|
this.gamma = builder.gamma;
|
|
|
|
|
|
|
|
this.blurRadius = Math.tan(0.5 * builder.blurAngle) * builder.focusDistance;
|
|
|
|
|
|
|
|
|
|
|
|
// project direction the horizontal plane
|
|
|
|
// project direction the horizontal plane
|
|
|
|
var dXZ = direction.withY(0).unit();
|
|
|
|
var dXZ = direction.withY(0).unit();
|
|
|
@ -63,7 +65,7 @@ public final class Camera {
|
|
|
|
this.pixelU = u.times(viewportWidth / width);
|
|
|
|
this.pixelU = u.times(viewportWidth / width);
|
|
|
|
this.pixelV = v.times(viewportHeight / height);
|
|
|
|
this.pixelV = v.times(viewportHeight / height);
|
|
|
|
|
|
|
|
|
|
|
|
this.pixel00 = origin.plus(direction)
|
|
|
|
this.pixel00 = origin.plus(direction.times(builder.focusDistance))
|
|
|
|
.minus(u.times(0.5 * viewportWidth)).minus(v.times(0.5 * viewportHeight))
|
|
|
|
.minus(u.times(0.5 * viewportWidth)).minus(v.times(0.5 * viewportHeight))
|
|
|
|
.plus(pixelU.div(2)).plus(pixelV.div(2));
|
|
|
|
.plus(pixelU.div(2)).plus(pixelV.div(2));
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -97,6 +99,17 @@ public final class Camera {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private @NotNull Ray getRay(int x, int y) {
|
|
|
|
private @NotNull Ray getRay(int x, int y) {
|
|
|
|
|
|
|
|
var origin = this.origin;
|
|
|
|
|
|
|
|
if (blurRadius > 0) {
|
|
|
|
|
|
|
|
double bu, bv;
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
bu = 2 * Math.random() - 1;
|
|
|
|
|
|
|
|
bv = 2 * Math.random() - 1;
|
|
|
|
|
|
|
|
} while (bu * bu + bv * bv >= 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
origin = origin.plus(u.times(blurRadius * bu)).plus(v.times(blurRadius * bv));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Ray(origin, getPixel(x, y).minus(origin));
|
|
|
|
return new Ray(origin, getPixel(x, y).minus(origin));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -147,6 +160,8 @@ public final class Camera {
|
|
|
|
private double rotation = 0.0;
|
|
|
|
private double rotation = 0.0;
|
|
|
|
|
|
|
|
|
|
|
|
private double fov = 0.5 * Math.PI;
|
|
|
|
private double fov = 0.5 * Math.PI;
|
|
|
|
|
|
|
|
private double focusDistance = 10;
|
|
|
|
|
|
|
|
private double blurAngle = 0.0;
|
|
|
|
|
|
|
|
|
|
|
|
private int samplePerPixel = 100;
|
|
|
|
private int samplePerPixel = 100;
|
|
|
|
private int maxDepth = 10;
|
|
|
|
private int maxDepth = 10;
|
|
|
@ -190,6 +205,18 @@ public final class Camera {
|
|
|
|
return this;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public @NotNull Builder withFocusDistance(double focusDistance) {
|
|
|
|
|
|
|
|
if (focusDistance <= 0 || !Double.isFinite(focusDistance)) throw new IllegalArgumentException("focus distance must be positive");
|
|
|
|
|
|
|
|
this.focusDistance = focusDistance;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public @NotNull Builder withBlurAngle(double angle) {
|
|
|
|
|
|
|
|
if (angle < 0 || angle >= Math.PI || !Double.isFinite(angle)) throw new IllegalArgumentException("blur-angle must be in the range [0, π)");
|
|
|
|
|
|
|
|
this.blurAngle = angle;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public @NotNull Builder withSamplesPerPixel(int samples) {
|
|
|
|
public @NotNull Builder withSamplesPerPixel(int samples) {
|
|
|
|
if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
|
|
|
|
if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
|
|
|
|
this.samplePerPixel = samples;
|
|
|
|
this.samplePerPixel = samples;
|
|
|
|