Compare commits
1 Commits
feature/sp
...
main
Author | SHA1 | Date |
---|---|---|
jbb01 | 5c92606f99 | 5 months ago |
Binary file not shown.
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 3.1 MiB |
@ -1,33 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.math;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A vector-like object that implements the standard mathematical operations
|
|
||||||
* @param <T> the type
|
|
||||||
*/
|
|
||||||
public interface IVec<T extends IVec<T>> {
|
|
||||||
@Contract(pure = true)
|
|
||||||
double get(int i);
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
@NotNull T plus(@NotNull T other);
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
@NotNull T minus(@NotNull T other);
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
@NotNull T times(@NotNull T other);
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
@NotNull T times(double d);
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
default @NotNull T div(double d) {
|
|
||||||
return times(1 / d);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
double @NotNull[] toArray();
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.math;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A vector-like object with three components.
|
|
||||||
* @param <T> the type
|
|
||||||
*/
|
|
||||||
public interface IVec3<T extends Record & IVec3<T>> extends IVec<T> {
|
|
||||||
@Contract(pure = true)
|
|
||||||
default double component1() {
|
|
||||||
return toVec3().x();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
default double component2() {
|
|
||||||
return toVec3().y();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
default double component3() {
|
|
||||||
return toVec3().z();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Contract(pure = true)
|
|
||||||
default double get(int i) {
|
|
||||||
return switch (i) {
|
|
||||||
case 0 -> component1();
|
|
||||||
case 1 -> component2();
|
|
||||||
case 2 -> component3();
|
|
||||||
default -> throw new IndexOutOfBoundsException(i);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
default @NotNull Vec3 toVec3() {
|
|
||||||
return new Vec3(component1(), component2(), component3());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Contract(pure = true)
|
|
||||||
default double @NotNull [] toArray() {
|
|
||||||
return new double[] {component1(), component2(), component3()};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,255 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.math;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record Matrix3(
|
|
||||||
double a11, double a12, double a13,
|
|
||||||
double a21, double a22, double a23,
|
|
||||||
double a31, double a32, double a33
|
|
||||||
) {
|
|
||||||
public static @NotNull Matrix3 fromRows(@NotNull Vec3 @NotNull[] rows) {
|
|
||||||
if (rows.length != 3) throw new IllegalArgumentException();
|
|
||||||
return fromRows(rows[0], rows[1], rows[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull Matrix3 fromRows(@NotNull Vec3 row0, @NotNull Vec3 row1, @NotNull Vec3 row2) {
|
|
||||||
return new Matrix3(
|
|
||||||
row0.x(), row0.y(), row0.z(),
|
|
||||||
row1.x(), row1.y(), row1.z(),
|
|
||||||
row2.x(), row2.y(), row2.z()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull Matrix3 fromColumns(@NotNull Vec3 @NotNull[] cols) {
|
|
||||||
if (cols.length != 3) throw new IllegalArgumentException();
|
|
||||||
return fromColumns(cols[0], cols[1], cols[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull Matrix3 fromColumns(@NotNull Vec3 col0, @NotNull Vec3 col1, @NotNull Vec3 col2) {
|
|
||||||
return new Matrix3(
|
|
||||||
col0.x(), col1.x(), col2.x(),
|
|
||||||
col0.y(), col1.y(), col2.y(),
|
|
||||||
col0.z(), col1.z(), col2.z()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull Matrix3 fromArray(double @NotNull[] @NotNull[] array) {
|
|
||||||
return new Matrix3(
|
|
||||||
array[0][0], array[0][1], array[0][2],
|
|
||||||
array[1][0], array[1][1], array[1][2],
|
|
||||||
array[2][0], array[2][1], array[2][2]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Matrix3() {
|
|
||||||
this(1, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Matrix3(double a11, double a22, double a33) {
|
|
||||||
this(a11, 0, 0, 0, a22, 0, 0, 0, a33);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Matrix3 times(@NotNull Matrix3 other) {
|
|
||||||
return new Matrix3(
|
|
||||||
a11 * other.a11 + a12 * other.a21 + a13 * other.a31,
|
|
||||||
a11 * other.a12 + a12 * other.a22 + a13 * other.a32,
|
|
||||||
a11 * other.a13 + a12 * other.a23 + a13 * other.a33,
|
|
||||||
a21 * other.a11 + a22 * other.a21 + a23 * other.a31,
|
|
||||||
a21 * other.a12 + a22 * other.a22 + a23 * other.a32,
|
|
||||||
a21 * other.a13 + a22 * other.a23 + a23 * other.a33,
|
|
||||||
a31 * other.a11 + a32 * other.a21 + a33 * other.a31,
|
|
||||||
a31 * other.a12 + a32 * other.a22 + a33 * other.a32,
|
|
||||||
a31 * other.a13 + a32 * other.a23 + a33 * other.a33
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Matrix3 times(double other) {
|
|
||||||
return new Matrix3(
|
|
||||||
a11 * other, a12 * other, a13 * other,
|
|
||||||
a21 * other, a22 * other, a23 * other,
|
|
||||||
a31 * other, a32 * other, a33 * other
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 times(@NotNull Vec3 other) {
|
|
||||||
return new Vec3(
|
|
||||||
a11 * other.x() + a12 * other.y() + a13 * other.z(),
|
|
||||||
a21 * other.x() + a22 * other.y() + a23 * other.z(),
|
|
||||||
a31 * other.x() + a32 * other.y() + a33 * other.z()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Matrix3 plus(@NotNull Matrix3 other) {
|
|
||||||
return new Matrix3(
|
|
||||||
a11 + other.a11, a12 + other.a12, a13 + other.a13,
|
|
||||||
a21 + other.a21, a22 + other.a22, a23 + other.a23,
|
|
||||||
a31 + other.a31, a32 + other.a32, a33 + other.a33
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double det() {
|
|
||||||
return a11 * a22 * a33 + a12 * a23 * a31 + a13 * a21 * a32
|
|
||||||
- a13 * a22 * a31 - a23 * a32 * a11 - a33 * a12 * a21;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Matrix3 invert() {
|
|
||||||
var det = det();
|
|
||||||
if (det == 0) throw new IllegalStateException();
|
|
||||||
var t = 1 / det;
|
|
||||||
return new Matrix3(
|
|
||||||
t * (Math.fma( a22, a33, -a23 * a32)),
|
|
||||||
t * (Math.fma(-a12, a33, a13 * a32)),
|
|
||||||
t * (Math.fma( a12, a23, -a13 * a22)),
|
|
||||||
t * (Math.fma(-a21, a33, a23 * a31)),
|
|
||||||
t * (Math.fma( a11, a33, -a13 * a31)),
|
|
||||||
t * (Math.fma(-a11, a23, a13 * a21)),
|
|
||||||
t * (Math.fma( a21, a32, -a22 * a31)),
|
|
||||||
t * (Math.fma(-a11, a32, a12 * a31)),
|
|
||||||
t * (Math.fma( a11, a22, -a12 * a21))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 column(int i) {
|
|
||||||
return switch (i) {
|
|
||||||
case 0 -> new Vec3(a11, a21, a31);
|
|
||||||
case 1 -> new Vec3(a12, a22, a32);
|
|
||||||
case 2 -> new Vec3(a13, a23, a33);
|
|
||||||
default -> throw new IndexOutOfBoundsException(i);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 @NotNull[] columns() {
|
|
||||||
return new Vec3[] {
|
|
||||||
new Vec3(a11, a21, a31),
|
|
||||||
new Vec3(a12, a22, a32),
|
|
||||||
new Vec3(a13, a23, a33)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 row(int i) {
|
|
||||||
return switch (i) {
|
|
||||||
case 0 -> new Vec3(a11, a12, a13);
|
|
||||||
case 1 -> new Vec3(a21, a22, a23);
|
|
||||||
case 2 -> new Vec3(a31, a32, a33);
|
|
||||||
default -> throw new IndexOutOfBoundsException(i);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 @NotNull[] rows() {
|
|
||||||
return new Vec3[] {
|
|
||||||
new Vec3(a11, a12, a13),
|
|
||||||
new Vec3(a21, a22, a23),
|
|
||||||
new Vec3(a31, a32, a33)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public double @NotNull[] @NotNull[] toArray() {
|
|
||||||
return new double[][] {
|
|
||||||
{a11, a12, a13},
|
|
||||||
{a21, a22, a23},
|
|
||||||
{a31, a32, a33}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public double get(int i, int j) {
|
|
||||||
Objects.checkIndex(i, 3);
|
|
||||||
Objects.checkIndex(j, 3);
|
|
||||||
var idx = 3 * i + j;
|
|
||||||
return switch (idx) {
|
|
||||||
case 0 -> a11;
|
|
||||||
case 1 -> a12;
|
|
||||||
case 2 -> a13;
|
|
||||||
case 3 -> a21;
|
|
||||||
case 4 -> a22;
|
|
||||||
case 5 -> a23;
|
|
||||||
case 6 -> a31;
|
|
||||||
case 7 -> a32;
|
|
||||||
case 8 -> a33;
|
|
||||||
default -> throw new AssertionError();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs lower-upper decomposition with partial pivoting (LUP decomposition) on {@code this} matrix.
|
|
||||||
* @param tolerance a small tolerance number to detect failure when the matrix is near degenerate
|
|
||||||
* @see <a href="https://en.wikipedia.org/w/index.php?title=LU_decomposition&oldid=1213102558#C_code_example">LU decomposition — Wikipedia, The Free Encyclopedia</a>
|
|
||||||
*/
|
|
||||||
public @NotNull LUPDecomposition decompose(double tolerance) {
|
|
||||||
// unit permutation matrix
|
|
||||||
var perm = new int[] {0, 1, 2, 3};
|
|
||||||
var A = toArray();
|
|
||||||
var N = 3;
|
|
||||||
|
|
||||||
for (int i = 0; i < N; i++) {
|
|
||||||
double maxA = 0.0;
|
|
||||||
int imax = i;
|
|
||||||
|
|
||||||
for (int k = i; k < N; k++) {
|
|
||||||
double absA = Math.abs(A[k][i]);
|
|
||||||
if (absA > maxA) {
|
|
||||||
maxA = absA;
|
|
||||||
imax = k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxA < tolerance) throw new IllegalArgumentException("matrix is degenerate");
|
|
||||||
|
|
||||||
if (imax != i) {
|
|
||||||
// pivoting P
|
|
||||||
int j = perm[i];
|
|
||||||
perm[i] = perm[imax];
|
|
||||||
perm[imax] = j;
|
|
||||||
|
|
||||||
// pivoting rows of A
|
|
||||||
var ptr = A[i];
|
|
||||||
A[i] = A[imax];
|
|
||||||
A[imax] = ptr;
|
|
||||||
|
|
||||||
// counting pivots starting from N (for determinant)
|
|
||||||
perm[3]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = i + 1; j < N; j++) {
|
|
||||||
A[j][i] /= A[i][i];
|
|
||||||
|
|
||||||
for (int k = i + 1; k < N; k++) {
|
|
||||||
A[j][k] -= A[j][i] * A[i][k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LUPDecomposition(fromArray(A), perm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record LUPDecomposition(@NotNull Matrix3 matrix, int @NotNull[] permutation) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Solves the equation {@code Ax = b} where {@code A} is the matrix that {@code this} decomposition was derived
|
|
||||||
* from.
|
|
||||||
* @param b the right hand side vector
|
|
||||||
* @return the solution vector
|
|
||||||
*/
|
|
||||||
public @NotNull Vec3 solve(@NotNull Vec3 b) {
|
|
||||||
var N = 3;
|
|
||||||
var x = new double[N];
|
|
||||||
for (int i = 0; i < N; i++) {
|
|
||||||
x[i] = b.get(permutation[i]);
|
|
||||||
|
|
||||||
for (int k = 0; k < i; k++) {
|
|
||||||
x[i] -= matrix.get(i, k) * x[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = N - 1; i >= 0; i--) {
|
|
||||||
for (int k = i + 1; k < N; k++) {
|
|
||||||
x[i] -= matrix.get(i, k) * x[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
x[i] /= matrix.get(i, i);
|
|
||||||
}
|
|
||||||
return new Vec3(x[0], x[1], x[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +1,16 @@
|
|||||||
package eu.jonahbauer.raytracing.math;
|
package eu.jonahbauer.raytracing.math;
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class Ray {
|
public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||||
private final @NotNull Vec3 origin;
|
public Ray {
|
||||||
private final @NotNull Vec3 direction;
|
Objects.requireNonNull(origin, "origin");
|
||||||
private final @NotNull SampledWavelengths lambda;
|
Objects.requireNonNull(direction, "direction");
|
||||||
|
|
||||||
private final @NotNull Vec3 inv;
|
|
||||||
private final @NotNull Vec3 negInvOrigin;
|
|
||||||
|
|
||||||
public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
|
||||||
this(origin, direction, SampledWavelengths.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda) {
|
|
||||||
this.origin = Objects.requireNonNull(origin, "origin");
|
|
||||||
this.direction = Objects.requireNonNull(direction, "direction");
|
|
||||||
this.lambda = Objects.requireNonNull(lambda, "lambda");
|
|
||||||
|
|
||||||
this.inv = direction.inv();
|
|
||||||
this.negInvOrigin = inv.neg().times(origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda, @NotNull Vec3 inv, @NotNull Vec3 negInvOrigin) {
|
|
||||||
this.origin = origin;
|
|
||||||
this.direction = direction;
|
|
||||||
this.lambda = lambda;
|
|
||||||
this.inv = inv;
|
|
||||||
this.negInvOrigin = negInvOrigin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull Vec3 at(double t) {
|
public @NotNull Vec3 at(double t) {
|
||||||
return Vec3.fma(t, direction, origin);
|
return Vec3.fma(t, direction, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull Ray with(@NotNull HitResult hit, @NotNull Vec3 direction) {
|
|
||||||
return new Ray(hit.position(), direction, lambda);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Ray with(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
|
||||||
return new Ray(origin, direction, lambda);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Ray with(@NotNull SampledWavelengths lambda) {
|
|
||||||
return new Ray(origin, direction, lambda, inv, negInvOrigin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 origin() {
|
|
||||||
return origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 direction() {
|
|
||||||
return direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull SampledWavelengths lambda() {
|
|
||||||
return lambda;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 getInvDirection() {
|
|
||||||
return inv;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 getNegInvOrigin() {
|
|
||||||
return negInvOrigin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj == this) return true;
|
|
||||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
|
||||||
var that = (Ray) obj;
|
|
||||||
return Objects.equals(this.origin, that.origin) &&
|
|
||||||
Objects.equals(this.direction, that.direction) &&
|
|
||||||
Objects.equals(this.lambda, that.lambda);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(origin, direction, lambda);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "Ray[" +
|
|
||||||
"origin=" + origin + ", " +
|
|
||||||
"direction=" + direction + ", " +
|
|
||||||
"lambda=" + lambda + ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
package eu.jonahbauer.raytracing.render;
|
||||||
|
|
||||||
|
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||||
|
import eu.jonahbauer.raytracing.render.canvas.Image;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.CheckedOutputStream;
|
||||||
|
import java.util.zip.DeflaterOutputStream;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
|
public enum ImageFormat {
|
||||||
|
PPM {
|
||||||
|
@Override
|
||||||
|
public void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException {
|
||||||
|
try (var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.US_ASCII))) {
|
||||||
|
writer.write("P3\n");
|
||||||
|
writer.write(String.valueOf(image.getWidth()));
|
||||||
|
writer.write(" ");
|
||||||
|
writer.write(String.valueOf(image.getHeight()));
|
||||||
|
writer.write("\n255\n");
|
||||||
|
|
||||||
|
var it = image.pixels().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
var color = it.next();
|
||||||
|
writer.write(String.valueOf(color.red()));
|
||||||
|
writer.write(" ");
|
||||||
|
writer.write(String.valueOf(color.green()));
|
||||||
|
writer.write(" ");
|
||||||
|
writer.write(String.valueOf(color.blue()));
|
||||||
|
writer.write("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PNG {
|
||||||
|
private static final byte[] MAGIC = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
||||||
|
private static final int IHDR_LENGTH = 13;
|
||||||
|
private static final int IHDR_TYPE = 0x49484452;
|
||||||
|
private static final int IDAT_TYPE = 0x49444154;
|
||||||
|
private static final int IEND_TYPE = 0x49454E44;
|
||||||
|
private static final int IEND_CRC = 0xAE426082;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException {
|
||||||
|
try (var data = new NoCloseDataOutputStream(out); var _ = data.closeable()) {
|
||||||
|
data.write(MAGIC);
|
||||||
|
|
||||||
|
writeIHDR(image, data);
|
||||||
|
writeIDAT(image, data);
|
||||||
|
writeIEND(image, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeIHDR(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
|
||||||
|
data.writeInt(IHDR_LENGTH);
|
||||||
|
try (
|
||||||
|
var crc = new CheckedOutputStream(data, new CRC32());
|
||||||
|
var ihdr = new DataOutputStream(crc)
|
||||||
|
) {
|
||||||
|
ihdr.writeInt(IHDR_TYPE);
|
||||||
|
ihdr.writeInt(image.getWidth()); // image width
|
||||||
|
ihdr.writeInt(image.getHeight()); // image height
|
||||||
|
ihdr.writeByte(8); // bit depth
|
||||||
|
ihdr.writeByte(2); // color type
|
||||||
|
ihdr.writeByte(0); // compression method
|
||||||
|
ihdr.writeByte(0); // filter method
|
||||||
|
ihdr.writeByte(0); // interlace method
|
||||||
|
ihdr.flush();
|
||||||
|
data.writeInt((int) crc.getChecksum().getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeIDAT(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
|
||||||
|
try (
|
||||||
|
var baos = new ByteArrayOutputStream();
|
||||||
|
var crc = new CheckedOutputStream(baos, new CRC32());
|
||||||
|
var idat = new DataOutputStream(crc)
|
||||||
|
) {
|
||||||
|
idat.writeInt(IDAT_TYPE);
|
||||||
|
|
||||||
|
try (var deflate = new DataOutputStream(new DeflaterOutputStream(idat))) {
|
||||||
|
var pixels = image.pixels().iterator();
|
||||||
|
for (int i = 0; pixels.hasNext(); i = (i + 1) % image.getWidth()) {
|
||||||
|
if (i == 0) deflate.writeByte(0); // filter type
|
||||||
|
var pixel = pixels.next();
|
||||||
|
deflate.writeByte(pixel.red());
|
||||||
|
deflate.writeByte(pixel.green());
|
||||||
|
deflate.writeByte(pixel.blue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = baos.toByteArray();
|
||||||
|
data.writeInt(bytes.length - 4); // don't include type in length
|
||||||
|
data.write(bytes);
|
||||||
|
data.writeInt((int) crc.getChecksum().getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeIEND(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
|
||||||
|
data.writeInt(0);
|
||||||
|
data.writeInt(IEND_TYPE);
|
||||||
|
data.writeInt(IEND_CRC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NoCloseDataOutputStream extends DataOutputStream {
|
||||||
|
public NoCloseDataOutputStream(OutputStream out) {
|
||||||
|
super(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public Closeable closeable() {
|
||||||
|
return super::close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
;
|
||||||
|
|
||||||
|
public void write(@NotNull Canvas image, @NotNull Path path) throws IOException {
|
||||||
|
try (var out = Files.newOutputStream(path)) {
|
||||||
|
write(image, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException;
|
||||||
|
}
|
@ -1,37 +1,22 @@
|
|||||||
package eu.jonahbauer.raytracing.render.canvas;
|
package eu.jonahbauer.raytracing.render.canvas;
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public interface Canvas {
|
public interface Canvas {
|
||||||
/**
|
|
||||||
* {@return the width of this canvas}
|
|
||||||
*/
|
|
||||||
int getWidth();
|
int getWidth();
|
||||||
|
|
||||||
/**
|
|
||||||
* {@return the height of this canvas}
|
|
||||||
*/
|
|
||||||
int getHeight();
|
int getHeight();
|
||||||
|
|
||||||
/**
|
void set(int x, int y, @NotNull Color color);
|
||||||
* Adds a sample to this canvas
|
@NotNull Color get(int x, int y);
|
||||||
* @param x the pixel x coordinate
|
|
||||||
* @param y the pixel y coordinate
|
|
||||||
* @param n the index of the sample
|
|
||||||
* @param spectrum the sampled spectrum
|
|
||||||
* @param lambda the sampled wavelengths
|
|
||||||
*/
|
|
||||||
void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda);
|
|
||||||
|
|
||||||
/**
|
default @NotNull Stream<Color> pixels() {
|
||||||
* {@return the color at a given pixel}
|
return IntStream.range(0, getHeight())
|
||||||
* @param x the pixel x coordinate
|
.mapToObj(y -> IntStream.range(0, getWidth()).mapToObj(x -> get(x, y)))
|
||||||
* @param y the pixel y coordinate
|
.flatMap(Function.identity());
|
||||||
* @param cs the color space of the output
|
}
|
||||||
*/
|
|
||||||
@NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs);
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
package eu.jonahbauer.raytracing.render.canvas;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
|
||||||
|
private final Color[][] data;
|
||||||
|
|
||||||
|
public Image(int width, int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
|
||||||
|
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
||||||
|
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Color get(int x, int y) {
|
||||||
|
Objects.checkIndex(x, width);
|
||||||
|
Objects.checkIndex(y, height);
|
||||||
|
return Objects.requireNonNullElse(this.data[y][x], Color.BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x, int y, @NotNull Color color) {
|
||||||
|
Objects.checkIndex(x, width);
|
||||||
|
Objects.checkIndex(y, height);
|
||||||
|
this.data[y][x] = Objects.requireNonNull(color);
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.canvas;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class RGBCanvas implements Canvas {
|
|
||||||
private final int width;
|
|
||||||
private final int height;
|
|
||||||
|
|
||||||
private final @NotNull ColorSpace cs;
|
|
||||||
private final @NotNull ColorRGB[][] data;
|
|
||||||
|
|
||||||
public RGBCanvas(int width, int height, @NotNull ColorSpace cs) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.cs = Objects.requireNonNull(cs);
|
|
||||||
|
|
||||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
|
||||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
|
||||||
|
|
||||||
this.data = new ColorRGB[height][width];
|
|
||||||
}
|
|
||||||
|
|
||||||
public RGBCanvas(@NotNull BufferedImage image, @NotNull ColorSpace cs) {
|
|
||||||
this(image.getWidth(), image.getHeight(), cs);
|
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
this.data[y][x] = new ColorRGB(image.getRGB(x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWidth() {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHeight() {
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
|
|
||||||
assert x < width;
|
|
||||||
assert y < height;
|
|
||||||
|
|
||||||
var rgb = spectrum.toRGB(lambda, cs);
|
|
||||||
data[y][x] = ColorRGB.average(data[y][x], rgb, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs) {
|
|
||||||
if (cs == this.cs) return get(x, y);
|
|
||||||
return cs.toRGB(this.cs.toXYZ(get(x, y)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull ColorRGB get(int x, int y) {
|
|
||||||
assert x < width;
|
|
||||||
assert y < height;
|
|
||||||
return Objects.requireNonNullElse(data[y][x], ColorRGB.BLACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(int x, int y, @NotNull ColorRGB color) {
|
|
||||||
assert x < width;
|
|
||||||
assert y < height;
|
|
||||||
data[y][x] = Objects.requireNonNull(color);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.canvas;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class XYZCanvas implements Canvas {
|
|
||||||
private final int width;
|
|
||||||
private final int height;
|
|
||||||
|
|
||||||
private final @NotNull ColorXYZ[][] data;
|
|
||||||
|
|
||||||
public XYZCanvas(int width, int height) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
|
|
||||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
|
||||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
|
||||||
|
|
||||||
this.data = new ColorXYZ[height][width];
|
|
||||||
}
|
|
||||||
|
|
||||||
public XYZCanvas(@NotNull BufferedImage image, @NotNull ColorSpace cs) {
|
|
||||||
this(image.getWidth(), image.getHeight());
|
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
data[y][x] = cs.toXYZ(new ColorRGB(image.getRGB(x, y)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWidth() {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHeight() {
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
|
|
||||||
assert x < width;
|
|
||||||
assert y < height;
|
|
||||||
|
|
||||||
var xyz = spectrum.toXYZ(lambda);
|
|
||||||
data[y][x] = ColorXYZ.average(get(x, y), xyz, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull ColorXYZ get(int x, int y) {
|
|
||||||
assert x < width;
|
|
||||||
assert y < height;
|
|
||||||
return Objects.requireNonNullElse(data[y][x], ColorXYZ.BLACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs) {
|
|
||||||
return cs.toRGB(get(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(int x, int y, @NotNull ColorXYZ color) {
|
|
||||||
assert x < width;
|
|
||||||
assert y < height;
|
|
||||||
data[y][x] = Objects.requireNonNull(color);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A pair of chromaticity coordinates in the xyY color space
|
|
||||||
* @param x the x coordinate
|
|
||||||
* @param y the y coordinate
|
|
||||||
*/
|
|
||||||
public record Chromaticity(double x, double y) {
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.IVec3;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB> {
|
|
||||||
public static final @NotNull ColorRGB BLACK = new ColorRGB(0.0, 0.0, 0.0);
|
|
||||||
public static final @NotNull ColorRGB WHITE = new ColorRGB(1.0, 1.0, 1.0);
|
|
||||||
|
|
||||||
public static @NotNull ColorRGB random(@NotNull Random random) {
|
|
||||||
return new ColorRGB(random.nextDouble(), random.nextDouble(), random.nextDouble());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull ColorRGB random(@NotNull Random random, double min, double max) {
|
|
||||||
var span = max - min;
|
|
||||||
return new ColorRGB(
|
|
||||||
Math.fma(random.nextDouble(), span, min),
|
|
||||||
Math.fma(random.nextDouble(), span, min),
|
|
||||||
Math.fma(random.nextDouble(), span, min)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorRGB(int rgb) {
|
|
||||||
this(((rgb >> 16) & 0xFF) / 255d, ((rgb >> 8) & 0xFF) / 255d, (rgb & 0xFF) / 255d);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorRGB {
|
|
||||||
assert Double.isFinite(r) : "r must be finite";
|
|
||||||
assert Double.isFinite(g) : "g must be finite";
|
|
||||||
assert Double.isFinite(b) : "b must be finite";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Math
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static @NotNull ColorRGB average(@NotNull ColorRGB current, @NotNull ColorRGB next, int index) {
|
|
||||||
return lerp(current, next, 1d / index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull ColorRGB lerp(@NotNull ColorRGB a, @NotNull ColorRGB b, double t) {
|
|
||||||
if (t < 0) return a;
|
|
||||||
if (t > 1) return b;
|
|
||||||
return new ColorRGB(
|
|
||||||
Math.fma(t, b.r - a.r, a.r),
|
|
||||||
Math.fma(t, b.g - a.g, a.g),
|
|
||||||
Math.fma(t, b.b - a.b, a.b)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull ColorRGB fma(@NotNull ColorRGB a, @NotNull ColorRGB b, @NotNull ColorRGB c) {
|
|
||||||
return new ColorRGB(
|
|
||||||
Math.fma(a.r, b.r, c.r),
|
|
||||||
Math.fma(a.g, b.g, c.g),
|
|
||||||
Math.fma(a.b, b.b, c.b)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorRGB plus(@NotNull ColorRGB other) {
|
|
||||||
return new ColorRGB(r + other.r, g + other.g, b + other.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorRGB minus(@NotNull ColorRGB other) {
|
|
||||||
return new ColorRGB(r - other.r, g - other.g, b - other.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorRGB times(double d) {
|
|
||||||
return new ColorRGB(r * d, g * d, b * d);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorRGB times(@NotNull ColorRGB other) {
|
|
||||||
return new ColorRGB(r * other.r, g * other.g, b * other.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Accessors
|
|
||||||
*/
|
|
||||||
|
|
||||||
public int red() {
|
|
||||||
return toInt(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int green() {
|
|
||||||
return toInt(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int blue() {
|
|
||||||
return toInt(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int toInt(double value) {
|
|
||||||
return Math.clamp((int) (255.99 * value), 0, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double component1() {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double component2() {
|
|
||||||
return g;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double component3() {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.Matrix3;
|
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.*;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An RGB color space.
|
|
||||||
*/
|
|
||||||
public final class ColorSpace {
|
|
||||||
private final @NotNull Chromaticity r;
|
|
||||||
private final @NotNull Chromaticity g;
|
|
||||||
private final @NotNull Chromaticity b;
|
|
||||||
private final @NotNull Chromaticity w;
|
|
||||||
private final @NotNull DenselySampledSpectrum illuminant;
|
|
||||||
|
|
||||||
private final @NotNull ColorXYZ R;
|
|
||||||
private final @NotNull ColorXYZ G;
|
|
||||||
private final @NotNull ColorXYZ B;
|
|
||||||
private final @NotNull ColorXYZ W;
|
|
||||||
|
|
||||||
private final @NotNull Matrix3 XYZfromRGB;
|
|
||||||
private final @NotNull Matrix3 RGBfromXYZ;
|
|
||||||
private final @NotNull SpectrumTable RGBtoSpectrumTable;
|
|
||||||
private final @NotNull TransferFunction transferFunction;
|
|
||||||
|
|
||||||
public ColorSpace(
|
|
||||||
@NotNull Chromaticity r, @NotNull Chromaticity g, @NotNull Chromaticity b,
|
|
||||||
@NotNull Spectrum illuminant, @NotNull SpectrumTable table, @NotNull TransferFunction transferFunction
|
|
||||||
) {
|
|
||||||
this.r = Objects.requireNonNull(r, "r");
|
|
||||||
this.g = Objects.requireNonNull(g, "g");
|
|
||||||
this.b = Objects.requireNonNull(b, "b");
|
|
||||||
this.illuminant = new DenselySampledSpectrum(illuminant);
|
|
||||||
this.RGBtoSpectrumTable = table; // no null-check
|
|
||||||
this.transferFunction = transferFunction; // no null-check
|
|
||||||
|
|
||||||
this.W = illuminant.toXYZ();
|
|
||||||
this.w = W.xy();
|
|
||||||
|
|
||||||
this.R = new ColorXYZ(r);
|
|
||||||
this.G = new ColorXYZ(g);
|
|
||||||
this.B = new ColorXYZ(b);
|
|
||||||
var rgb = new Matrix3(
|
|
||||||
R.x(), G.x(), B.x(),
|
|
||||||
R.y(), G.y(), B.y(),
|
|
||||||
R.z(), G.z(), B.z()
|
|
||||||
);
|
|
||||||
var C = rgb.invert().times(W.toVec3());
|
|
||||||
|
|
||||||
this.XYZfromRGB = rgb.times(new Matrix3(C.x(), C.y(), C.z()));
|
|
||||||
this.RGBfromXYZ = XYZfromRGB.invert();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Conversions
|
|
||||||
*/
|
|
||||||
|
|
||||||
public @NotNull ColorRGB toRGB(@NotNull ColorXYZ xyz) {
|
|
||||||
var out = RGBfromXYZ.times(xyz.toVec3());
|
|
||||||
return new ColorRGB(out.x(), out.y(), out.z());
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull ColorXYZ toXYZ(@NotNull ColorRGB rgb) {
|
|
||||||
var out = XYZfromRGB.times(rgb.toVec3());
|
|
||||||
return new ColorXYZ(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 toCIELab(@NotNull ColorRGB rgb) {
|
|
||||||
return toCIELab(toXYZ(rgb));
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Vec3 toCIELab(@NotNull ColorXYZ xyz) {
|
|
||||||
return new Vec3(
|
|
||||||
116 * cieLabCbrt(xyz.y() / W.y()) - 16,
|
|
||||||
500 * (cieLabCbrt(xyz.x() / W.x()) - cieLabCbrt(xyz.y() / W.y())),
|
|
||||||
200 * (cieLabCbrt(xyz.y() / W.y()) - cieLabCbrt(xyz.z() / W.z()))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double cieLabCbrt(double x) {
|
|
||||||
var delta = 6.0 / 29.0;
|
|
||||||
if (x > delta * delta * delta) {
|
|
||||||
return Math.cbrt(x);
|
|
||||||
} else {
|
|
||||||
return x / (delta * delta * 3.0) + (4.0 / 29.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull ColorRGB encode(@NotNull ColorRGB rgb) {
|
|
||||||
return transferFunction.encode(rgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull ColorRGB decode(@NotNull ColorRGB rgb) {
|
|
||||||
return transferFunction.decode(rgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Spectrum
|
|
||||||
*/
|
|
||||||
|
|
||||||
public @NotNull SigmoidPolynomial toPolynomial(@NotNull ColorRGB rgb) {
|
|
||||||
return RGBtoSpectrumTable.get(new ColorRGB(
|
|
||||||
Math.max(0, rgb.r()),
|
|
||||||
Math.max(0, rgb.g()),
|
|
||||||
Math.max(0, rgb.b())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Spectrum toSpectrum(@NotNull ColorRGB rgb, @NotNull Spectrum.Type type) {
|
|
||||||
return switch (type) {
|
|
||||||
case ALBEDO -> new RGBAlbedoSpectrum(this, rgb);
|
|
||||||
case ILLUMINANT -> new RGBIlluminantSpectrum(this, rgb);
|
|
||||||
case UNBOUNDED -> new RGBUnboundedSpectrum(this, rgb);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Spectrum albedo(double r, double g, double b) {
|
|
||||||
return albedo(new ColorRGB(r, g, b));
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Spectrum albedo(@NotNull ColorRGB rgb) {
|
|
||||||
return toSpectrum(rgb, Spectrum.Type.ALBEDO);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Spectrum illuminant(double intensity) {
|
|
||||||
return illuminant.scale(intensity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Spectrum illuminant(double r, double g, double b) {
|
|
||||||
return illuminant(new ColorRGB(r, g, b));
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Spectrum illuminant(@NotNull ColorRGB rgb) {
|
|
||||||
return toSpectrum(rgb, Spectrum.Type.ILLUMINANT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Accessors
|
|
||||||
*/
|
|
||||||
|
|
||||||
public @NotNull Chromaticity r() {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Chromaticity g() {
|
|
||||||
return g;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Chromaticity b() {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Chromaticity w() {
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull DenselySampledSpectrum illuminant() {
|
|
||||||
return illuminant;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class ColorSpaces {
|
|
||||||
// Rec. ITU-R BT.709.3
|
|
||||||
public static final @NotNull ColorSpace sRGB = new ColorSpace(
|
|
||||||
new Chromaticity(0.6400, 0.3300),
|
|
||||||
new Chromaticity(0.3000, 0.6000),
|
|
||||||
new Chromaticity(0.1500, 0.0600),
|
|
||||||
Spectra.D65, read("sRGB_spectrum.bin"), TransferFunctions.sRGB
|
|
||||||
);
|
|
||||||
// P3-D65 (display)
|
|
||||||
public static final @NotNull ColorSpace DCI_P3 = new ColorSpace(
|
|
||||||
new Chromaticity(0.680, 0.320),
|
|
||||||
new Chromaticity(0.265, 0.690),
|
|
||||||
new Chromaticity(0.150, 0.060),
|
|
||||||
Spectra.D65, read("DCI_P3_spectrum.bin"), TransferFunctions.sRGB
|
|
||||||
);
|
|
||||||
// ITU-R Rec BT.2020
|
|
||||||
public static final @NotNull ColorSpace Rec2020 = new ColorSpace(
|
|
||||||
new Chromaticity(0.708, 0.292),
|
|
||||||
new Chromaticity(0.170, 0.797),
|
|
||||||
new Chromaticity(0.131, 0.046),
|
|
||||||
Spectra.D65, read("Rec2020_spectrum.bin"), null
|
|
||||||
);
|
|
||||||
|
|
||||||
private static @NotNull SpectrumTable read(@NotNull String name) {
|
|
||||||
try (var in = ColorSpaces.class.getResourceAsStream("/eu/jonahbauer/raytracing/colorspace/" + name)) {
|
|
||||||
return SpectrumTable.read(Objects.requireNonNull(in));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ColorSpaces() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.IVec3;
|
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A CIE XYZ color
|
|
||||||
*/
|
|
||||||
public record ColorXYZ(double x, double y, double z) implements IVec3<ColorXYZ> {
|
|
||||||
public static final double CIE_Y_INTEGRAL = 106.85689500000002;
|
|
||||||
|
|
||||||
public static final @NotNull ColorXYZ BLACK = new ColorXYZ(0, 0, 0);
|
|
||||||
public static final @NotNull ColorXYZ WHITE = new ColorXYZ(0, 1, 0);
|
|
||||||
|
|
||||||
public ColorXYZ(@NotNull Chromaticity chromaticity) {
|
|
||||||
this(chromaticity, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorXYZ(@NotNull Chromaticity chromaticity, double Y) {
|
|
||||||
this(
|
|
||||||
chromaticity.y() == 0 ? 0 : Y * chromaticity.x() / chromaticity.y(),
|
|
||||||
chromaticity.y() == 0 ? 0 : Y,
|
|
||||||
chromaticity.y() == 0 ? 0 : Y * (1 - chromaticity.x() - chromaticity.y()) / chromaticity.y()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorXYZ(@NotNull Vec3 vec) {
|
|
||||||
this(vec.x(), vec.y(), vec.z());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorXYZ {
|
|
||||||
assert Double.isFinite(x) : "x must be finite";
|
|
||||||
assert Double.isFinite(y) : "y must be finite";
|
|
||||||
assert Double.isFinite(z) : "z must be finite";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Math
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static @NotNull ColorXYZ average(@NotNull ColorXYZ a, @NotNull ColorXYZ b, int index) {
|
|
||||||
return lerp(a, b, 1d / index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull ColorXYZ lerp(@NotNull ColorXYZ a, @NotNull ColorXYZ b, double t) {
|
|
||||||
if (t < 0) return a;
|
|
||||||
if (t > 1) return b;
|
|
||||||
return new ColorXYZ(
|
|
||||||
Math.fma(t, b.x - a.x, a.x),
|
|
||||||
Math.fma(t, b.y - a.y, a.y),
|
|
||||||
Math.fma(t, b.z - a.z, a.z)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull ColorXYZ fma(@NotNull ColorXYZ a, @NotNull ColorXYZ b, @NotNull ColorXYZ c) {
|
|
||||||
return new ColorXYZ(
|
|
||||||
Math.fma(a.x, b.x, c.x),
|
|
||||||
Math.fma(a.y, b.y, c.y),
|
|
||||||
Math.fma(a.z, b.z, c.z)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorXYZ plus(@NotNull ColorXYZ other) {
|
|
||||||
return new ColorXYZ(x + other.x, y + other.y, z + other.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorXYZ minus(@NotNull ColorXYZ other) {
|
|
||||||
return new ColorXYZ(x - other.x, y - other.y, z - other.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorXYZ times(@NotNull ColorXYZ other) {
|
|
||||||
return new ColorXYZ(x * other.x, y * other.y, z * other.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorXYZ times(double d) {
|
|
||||||
return new ColorXYZ(x * d, y * d, z * d);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Accessors
|
|
||||||
*/
|
|
||||||
|
|
||||||
public double average() {
|
|
||||||
return (x + y + z) / 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Chromaticity xy() {
|
|
||||||
var factor = 1 / (x + y + z);
|
|
||||||
return new Chromaticity(factor * x, factor * y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double component1() {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double component2() {
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double component3() {
|
|
||||||
return z;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function of the form {@code s(p(x))} where {@code p} is a polynomial of second degree and {@code s} is the sigmoid
|
|
||||||
* function <code>s(x) = 0.5 + x / (2 * sqrt(1 + x^2))</code>.
|
|
||||||
* <p>
|
|
||||||
* A function of this form is used to generate a {@link Spectrum} from an RGB value.
|
|
||||||
*
|
|
||||||
* @param c0 the coefficient of the quadratic term
|
|
||||||
* @param c1 the coefficient of the linear term
|
|
||||||
* @param c2 the coefficient of the constant term
|
|
||||||
*/
|
|
||||||
public record SigmoidPolynomial(double c0, double c1, double c2) {
|
|
||||||
|
|
||||||
public double get(double x) {
|
|
||||||
var p = Math.fma(Math.fma(c0, x, c1), x, c2);
|
|
||||||
if (!Double.isFinite(p)) return p > 0 ? 1 : 0;
|
|
||||||
return Math.fma(.5 * p, 1 / Math.sqrt(Math.fma(p, p, 1)), .5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double max() {
|
|
||||||
// evaluate at the edges
|
|
||||||
var result = Math.max(get(Spectrum.LAMBDA_MIN), get(Spectrum.LAMBDA_MAX));
|
|
||||||
var lambda = -c1 / (2 * c0);
|
|
||||||
if (lambda >= 360 && lambda <= 830) {
|
|
||||||
// evaluate at the vertex
|
|
||||||
return Math.max(result, get(lambda));
|
|
||||||
} else {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A table of sigmoid polynomials used to convert between RGB values and spectra.
|
|
||||||
* <p>
|
|
||||||
* The rgb values are renormalized to xyz coordinates with {@code z} being the largest of the rgb components, and
|
|
||||||
* {@code x} and {@code y} being the other two rgb components divided by {@code z}. By this construction, {@code x},
|
|
||||||
* {@code y} and {@code z} are all in the range [0, 1] which allows for better use of the samples in a fixed grid.
|
|
||||||
* The {@code z} coordinate is additionally remapped using {@link #zNodes} to improve sampling at both ends of the scale.
|
|
||||||
* <p>
|
|
||||||
* The coefficients of the sigmoid functions are stored in a flattened five-dimensional array with indices as described
|
|
||||||
* in {@link #coefficients}.
|
|
||||||
*/
|
|
||||||
public final class SpectrumTable {
|
|
||||||
private final int resolution;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the remapped {@code z} values
|
|
||||||
*/
|
|
||||||
private final double[] zNodes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the stored coefficients as a flattened five-dimensional array with the following indices
|
|
||||||
* <ol>
|
|
||||||
* <li>the component index of the biggest rgb component</li>
|
|
||||||
* <li>the {@code z} coordinate</li>
|
|
||||||
* <li>the {@code y} coordinate</li>
|
|
||||||
* <li>the {@code x} coordinate</li>
|
|
||||||
* <li>the coefficient index</li>
|
|
||||||
* </ol>
|
|
||||||
*/
|
|
||||||
private final double[] coefficients;
|
|
||||||
|
|
||||||
public static void write(@NotNull SpectrumTable table, @NotNull OutputStream out) throws IOException {
|
|
||||||
var dos = new DataOutputStream(out);
|
|
||||||
dos.writeInt(table.resolution);
|
|
||||||
for (double z : table.zNodes) {
|
|
||||||
dos.writeDouble(z);
|
|
||||||
}
|
|
||||||
for (double c : table.coefficients) {
|
|
||||||
dos.writeDouble(c);
|
|
||||||
}
|
|
||||||
dos.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull SpectrumTable read(@NotNull InputStream in) throws IOException {
|
|
||||||
var dis = new DataInputStream(in);
|
|
||||||
var resolution = dis.readInt();
|
|
||||||
var nodes = new double[resolution];
|
|
||||||
for (int i = 0; i < resolution; i++) {
|
|
||||||
nodes[i] = dis.readDouble();
|
|
||||||
}
|
|
||||||
var table = new double[3 * resolution * resolution * resolution * 3];
|
|
||||||
for (int i = 0; i < table.length; i++) {
|
|
||||||
table[i] = dis.readDouble();
|
|
||||||
}
|
|
||||||
return new SpectrumTable(resolution, nodes, table);
|
|
||||||
}
|
|
||||||
|
|
||||||
SpectrumTable(int resolution, double @NotNull[] zNodes, double[] coefficients) {
|
|
||||||
this.resolution = resolution;
|
|
||||||
this.zNodes = zNodes;
|
|
||||||
this.coefficients = coefficients;
|
|
||||||
|
|
||||||
// check input array lengths
|
|
||||||
if (zNodes.length != resolution) {
|
|
||||||
throw new IllegalArgumentException("length of zNodes must be equal to the RESOLUTION");
|
|
||||||
}
|
|
||||||
if (coefficients.length != 3 * resolution * resolution * resolution * 3) {
|
|
||||||
throw new IllegalArgumentException("coefficients length must be 3 * RESOLUTION * RESOLUTION * RESOLUTION * 3");
|
|
||||||
}
|
|
||||||
// check ascending zNodes
|
|
||||||
for (int i = 1; i < resolution; i++) {
|
|
||||||
if (zNodes[i - 1] >= zNodes[i]) {
|
|
||||||
throw new IllegalArgumentException("zNodes must be in increasing order");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (zNodes[0] != 0.0 || zNodes[zNodes.length - 1] != 1.0) {
|
|
||||||
throw new IllegalArgumentException("zNodes must start with 0.0 and end with 1.0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull SigmoidPolynomial get(@NotNull ColorRGB color) {
|
|
||||||
// handle uniform rgb values
|
|
||||||
if (color.r() == color.g() && color.g() == color.b()) {
|
|
||||||
return new SigmoidPolynomial(0, 0, (color.r() - .5) / Math.sqrt(color.r() * (1 - color.r())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// find maximum component and compute remapped component values
|
|
||||||
var max = color.r() > color.g()
|
|
||||||
? (color.r() > color.b() ? 0 : 2)
|
|
||||||
: (color.g() > color.b() ? 1 : 2);
|
|
||||||
|
|
||||||
var z = color.get(max);
|
|
||||||
var x = color.get((max + 1) % 3) * (resolution - 1) / z;
|
|
||||||
var y = color.get((max + 2) % 3) * (resolution - 1) / z;
|
|
||||||
|
|
||||||
// compute integer indices and offsets for coefficient interpolation
|
|
||||||
int xi = Math.min((int) x, resolution - 2);
|
|
||||||
int yi = Math.min((int) y, resolution - 2);
|
|
||||||
int zi = Arrays.binarySearch(zNodes, z);
|
|
||||||
if (zi < 0) {
|
|
||||||
zi = -zi - 2;
|
|
||||||
} else if (zi > 0) {
|
|
||||||
zi = zi - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dx = x - xi;
|
|
||||||
var dy = y -yi;
|
|
||||||
var dz = (z - zNodes[zi]) / (zNodes[zi + 1] - zNodes[zi]);
|
|
||||||
|
|
||||||
// trilinearly interpolate sigmoid polynomial coefficients
|
|
||||||
var c = new double[3];
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
c[i] = lerp(dz,
|
|
||||||
lerp(dy,
|
|
||||||
lerp(dx, get(max, zi + 0, yi + 0, xi + 0, i), get(max, zi + 0, yi + 0, xi + 1, i)),
|
|
||||||
lerp(dx, get(max, zi + 0, yi + 1, xi + 0, i), get(max, zi + 0, yi + 1, xi + 1, i))
|
|
||||||
),
|
|
||||||
lerp(dy,
|
|
||||||
lerp(dx, get(max, zi + 1, yi + 0, xi + 0, i), get(max, zi + 1, yi + 0, xi + 1, i)),
|
|
||||||
lerp(dx, get(max, zi + 1, yi + 1, xi + 0, i), get(max, zi + 1, yi + 1, xi + 1, i))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SigmoidPolynomial(c[0], c[1], c[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double get(int l, int z, int y, int x, int i) {
|
|
||||||
return coefficients[(((l * resolution + z) * resolution + y) * resolution + x) * 3 + i];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double lerp(double t, double a, double b) {
|
|
||||||
return Math.fma(t, b, Math.fma(-t, a, a));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,204 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.Matrix3;
|
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a lookup table for RGB to spectrum conversion.
|
|
||||||
* <p>
|
|
||||||
* The spectrum for each RGB value is a {@link SigmoidPolynomial} with coefficients such that the round trip error
|
|
||||||
* from converting the RGB value to a spectrum and back is minimized.
|
|
||||||
* <p>
|
|
||||||
* <img src="doc-files/rgb2spectrum.png">
|
|
||||||
*/
|
|
||||||
public final class SpectrumTableGenerator {
|
|
||||||
private static final double EPSILON = 1e-4;
|
|
||||||
private static final int ITERATIONS = 15;
|
|
||||||
|
|
||||||
private final int resolution = 64;
|
|
||||||
private final @NotNull ColorSpace cs;
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
var generator = new SpectrumTableGenerator(ColorSpaces.DCI_P3);
|
|
||||||
var table = generator.generate();
|
|
||||||
|
|
||||||
try (var out = Files.newOutputStream(Path.of("DCI_P3_spectrum.bin"))) {
|
|
||||||
SpectrumTable.write(table, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpectrumTableGenerator(@NotNull ColorSpace cs) {
|
|
||||||
this.cs = Objects.requireNonNull(cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull SpectrumTable generate() {
|
|
||||||
var scale = new double[resolution];
|
|
||||||
for (int i = 0; i < scale.length; i++) {
|
|
||||||
var t = (double) i / (resolution - 1);
|
|
||||||
scale[i] = smoothstep(smoothstep(t));
|
|
||||||
}
|
|
||||||
|
|
||||||
var table = new double[3 * resolution * resolution * resolution * 3];
|
|
||||||
|
|
||||||
for (int l0 = 0; l0 < 3; l0++) {
|
|
||||||
var l = l0;
|
|
||||||
IntStream.range(0, resolution).parallel().forEach(i -> {
|
|
||||||
System.out.println("l = " + l + ", i = " + i);
|
|
||||||
var x = (double) i / (resolution - 1);
|
|
||||||
for (int j = 0; j < resolution; j++) {
|
|
||||||
var y = (double) j / (resolution - 1);
|
|
||||||
|
|
||||||
var start = resolution / 5;
|
|
||||||
|
|
||||||
var c = new double[3];
|
|
||||||
for (int k = start; k < resolution; k++) {
|
|
||||||
var z = scale[k];
|
|
||||||
var idx = ((((l * resolution + k) * resolution) + j) * resolution + i) * 3;
|
|
||||||
var color = getColor(l, x, y, z);
|
|
||||||
generate(color, c, table, idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
Arrays.fill(c, 0);
|
|
||||||
for (int k = start; k >= 0; --k) {
|
|
||||||
var z = scale[k];
|
|
||||||
var idx = ((((l * resolution + k) * resolution) + j) * resolution + i) * 3;
|
|
||||||
var color = getColor(l, x, y, z);
|
|
||||||
generate(color, c, table, idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return new SpectrumTable(resolution, scale, table);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generate(@NotNull ColorRGB rgb, double @NotNull[] c, double @NotNull[] out, int offset) {
|
|
||||||
gaussNewton(rgb, c, ITERATIONS);
|
|
||||||
double c0 = 360.0, c1 = 1.0 / (830.0 - 360.0);
|
|
||||||
double A = c[0], B = c[1], C = c[2];
|
|
||||||
out[offset] = A * c1 * c1;
|
|
||||||
out[offset + 1] = B * c1 - 2 * A * c0 * c1 * c1;
|
|
||||||
out[offset + 2] = C - B * c0 * c1 + A * c0 * c0 * c1 * c1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use Gauss-Newton algorithm to calculate coefficients {@code c} of a {@link SigmoidPolynomial} such that the round
|
|
||||||
* trip error from converting the {@code rgb} value to a spectrum and back is minimized.
|
|
||||||
* @param rgb the input color
|
|
||||||
* @param c the coefficients, used as initial values and output
|
|
||||||
* @param it the number of iterations
|
|
||||||
*/
|
|
||||||
private void gaussNewton(@NotNull ColorRGB rgb, double @NotNull[] c, int it) {
|
|
||||||
var bestQuality = Double.POSITIVE_INFINITY;
|
|
||||||
var bestCoefficients = new double[3];
|
|
||||||
|
|
||||||
for (int i = 0; i < it; ++i) {
|
|
||||||
var polynomial = new SigmoidPolynomial(c[0], c[1], c[2]);
|
|
||||||
var residual = getResidual(rgb, polynomial);
|
|
||||||
var jacobian = getJacobian(rgb, polynomial);
|
|
||||||
|
|
||||||
var delta = jacobian.decompose(1e-15).solve(residual);
|
|
||||||
for (int j = 0; j < 3; ++j) {
|
|
||||||
c[j] -= delta.get(j);
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch runaway
|
|
||||||
double max = Math.max(Math.max(c[0], c[1]), c[2]);
|
|
||||||
if (max > 200) {
|
|
||||||
for (int j = 0; j < 3; ++j) {
|
|
||||||
c[j] *= 200 / max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var quality = residual.squared();
|
|
||||||
if (quality <= 1e-6) {
|
|
||||||
return;
|
|
||||||
} else if (quality < bestQuality) {
|
|
||||||
bestQuality = quality;
|
|
||||||
System.arraycopy(c, 0, bestCoefficients, 0, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.arraycopy(bestCoefficients, 0, c, 0, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the Jacobian matrix of the {@code polynomial}.
|
|
||||||
*/
|
|
||||||
private @NotNull Matrix3 getJacobian(@NotNull ColorRGB rgb, @NotNull SigmoidPolynomial polynomial) {
|
|
||||||
var jac = new double[3][3];
|
|
||||||
|
|
||||||
// central finite difference coefficients for first derivative with sixth-order accuracy
|
|
||||||
var factors = new double[] { -1d/60, 3d/20, -3d/4, 0, 3d/4, -3d/20, 1d/60 };
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
var derivative = Vec3.ZERO;
|
|
||||||
for (int d = - factors.length / 2, j = 0; j < factors.length; d++, j++) {
|
|
||||||
if (factors[j] == 0) continue;
|
|
||||||
var tmp = switch (i) {
|
|
||||||
case 0 -> new SigmoidPolynomial(polynomial.c0() + d * EPSILON, polynomial.c1(), polynomial.c2());
|
|
||||||
case 1 -> new SigmoidPolynomial(polynomial.c0(), polynomial.c1() + d * EPSILON, polynomial.c2());
|
|
||||||
case 2 -> new SigmoidPolynomial(polynomial.c0(), polynomial.c1(), polynomial.c2() + d * EPSILON);
|
|
||||||
default -> throw new AssertionError();
|
|
||||||
};
|
|
||||||
var r = getResidual(rgb, tmp);
|
|
||||||
derivative = Vec3.fma(factors[j], r, derivative);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = 0; j < 3; j++) {
|
|
||||||
jac[j][i] = derivative.get(j) / EPSILON;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Matrix3(
|
|
||||||
jac[0][0], jac[0][1], jac[0][2],
|
|
||||||
jac[1][0], jac[1][1], jac[1][2],
|
|
||||||
jac[2][0], jac[2][1], jac[2][2]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the difference between the RGB color and the result of converting the RGB color to a spectrum using
|
|
||||||
* the given coefficients, illuminating it with the color space's standard illuminant, and converting it back to an
|
|
||||||
* RBG color. The output is a vector in CIE Lab color space.
|
|
||||||
*/
|
|
||||||
private @NotNull Vec3 getResidual(@NotNull ColorRGB rgb, @NotNull SigmoidPolynomial polynomial) {
|
|
||||||
var out = new SigmoidPolynomialSpectrum(polynomial, cs).toXYZ();
|
|
||||||
return cs.toCIELab(rgb).minus(cs.toCIELab(out));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static double smoothstep(double x) {
|
|
||||||
return x * x * (3.0 - 2.0 * x);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull ColorRGB getColor(int l, double x, double y, double z) {
|
|
||||||
var rgb = new double[3];
|
|
||||||
rgb[l] = z;
|
|
||||||
rgb[(l + 1) % 3] = x * z;
|
|
||||||
rgb[(l + 2) % 3] = y * z;
|
|
||||||
return new ColorRGB(rgb[0], rgb[1], rgb[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private record SigmoidPolynomialSpectrum(@NotNull SigmoidPolynomial polynomial, @NotNull ColorSpace cs) implements Spectrum {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return polynomial.max();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
var l = (lambda - Spectrum.LAMBDA_MIN) / (Spectrum.LAMBDA_MAX - Spectrum.LAMBDA_MIN);
|
|
||||||
return polynomial.get(l) * cs.illuminant().get(lambda);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public interface TransferFunction {
|
|
||||||
@NotNull ColorRGB decode(@NotNull ColorRGB rgb);
|
|
||||||
@NotNull ColorRGB encode(@NotNull ColorRGB rgb);
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.color;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public final class TransferFunctions {
|
|
||||||
public static final @NotNull TransferFunction sRGB = new ComponentTransferFunction() {
|
|
||||||
@Override
|
|
||||||
protected double encode(double value) {
|
|
||||||
if (value <= 0.0031308) return 12.92 * value;
|
|
||||||
return 1.055 * Math.pow(value, 1. / 2.4) - 0.055;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected double decode(double value) {
|
|
||||||
if (value <= 0.04045) return value / 12.92;
|
|
||||||
return Math.pow((value + 0.055) / 1.055, 2.4d);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final @NotNull TransferFunction LINEAR = new TransferFunction() {
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorRGB encode(@NotNull ColorRGB rgb) {
|
|
||||||
return rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ColorRGB decode(@NotNull ColorRGB rgb) {
|
|
||||||
return rgb;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private TransferFunctions() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract static class ComponentTransferFunction implements TransferFunction {
|
|
||||||
@Override
|
|
||||||
public final @NotNull ColorRGB decode(@NotNull ColorRGB rgb) {
|
|
||||||
return new ColorRGB(decode(rgb.r()), decode(rgb.g()), decode(rgb.b()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final @NotNull ColorRGB encode(@NotNull ColorRGB rgb) {
|
|
||||||
return new ColorRGB(encode(rgb.r()), encode(rgb.g()), encode(rgb.b()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract double encode(double value);
|
|
||||||
protected abstract double decode(double value);
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.6 KiB |
@ -1,19 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.image;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public interface ImageWriter {
|
|
||||||
default void write(@NotNull Canvas image, @NotNull Path path) throws IOException {
|
|
||||||
try (var out = Files.newOutputStream(path)) {
|
|
||||||
write(image, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void write(@NotNull Canvas canvas, @NotNull OutputStream out) throws IOException;
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.image;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.zip.CRC32;
|
|
||||||
import java.util.zip.CheckedOutputStream;
|
|
||||||
import java.util.zip.DeflaterOutputStream;
|
|
||||||
|
|
||||||
public class PNGImageWriter implements ImageWriter {
|
|
||||||
public static final @NotNull PNGImageWriter sRGB = new PNGImageWriter(ColorSpaces.sRGB);
|
|
||||||
|
|
||||||
private static final byte[] MAGIC = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
|
||||||
private static final int IHDR_LENGTH = 13;
|
|
||||||
private static final int IHDR_TYPE = 0x49484452;
|
|
||||||
private static final int IDAT_TYPE = 0x49444154;
|
|
||||||
private static final int IEND_TYPE = 0x49454E44;
|
|
||||||
private static final int IEND_CRC = 0xAE426082;
|
|
||||||
|
|
||||||
private final @NotNull ColorSpace cs;
|
|
||||||
|
|
||||||
public PNGImageWriter(@NotNull ColorSpace cs) {
|
|
||||||
this.cs = Objects.requireNonNull(cs, "cs");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException {
|
|
||||||
try (var data = new NoCloseDataOutputStream(out); var _ = data.closeable()) {
|
|
||||||
data.write(MAGIC);
|
|
||||||
|
|
||||||
writeIHDR(image, data);
|
|
||||||
writeIDAT(image, data);
|
|
||||||
writeIEND(image, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeIHDR(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
|
|
||||||
data.writeInt(IHDR_LENGTH);
|
|
||||||
try (
|
|
||||||
var crc = new CheckedOutputStream(data, new CRC32());
|
|
||||||
var ihdr = new DataOutputStream(crc)
|
|
||||||
) {
|
|
||||||
ihdr.writeInt(IHDR_TYPE);
|
|
||||||
ihdr.writeInt(image.getWidth()); // image width
|
|
||||||
ihdr.writeInt(image.getHeight()); // image height
|
|
||||||
ihdr.writeByte(8); // bit depth
|
|
||||||
ihdr.writeByte(2); // color type
|
|
||||||
ihdr.writeByte(0); // compression method
|
|
||||||
ihdr.writeByte(0); // filter method
|
|
||||||
ihdr.writeByte(0); // interlace method
|
|
||||||
ihdr.flush();
|
|
||||||
data.writeInt((int) crc.getChecksum().getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeIDAT(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
|
|
||||||
try (
|
|
||||||
var baos = new ByteArrayOutputStream();
|
|
||||||
var crc = new CheckedOutputStream(baos, new CRC32());
|
|
||||||
var idat = new DataOutputStream(crc)
|
|
||||||
) {
|
|
||||||
idat.writeInt(IDAT_TYPE);
|
|
||||||
|
|
||||||
try (var deflate = new DataOutputStream(new DeflaterOutputStream(idat))) {
|
|
||||||
for (int y = 0; y < image.getHeight(); y++) {
|
|
||||||
deflate.writeByte(0); // filter type
|
|
||||||
for (int x = 0; x < image.getWidth(); x++) {
|
|
||||||
var pixel = cs.encode(image.getRGB(x, y, cs));
|
|
||||||
deflate.writeByte(pixel.red());
|
|
||||||
deflate.writeByte(pixel.green());
|
|
||||||
deflate.writeByte(pixel.blue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes = baos.toByteArray();
|
|
||||||
data.writeInt(bytes.length - 4); // don't include type in length
|
|
||||||
data.write(bytes);
|
|
||||||
data.writeInt((int) crc.getChecksum().getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeIEND(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
|
|
||||||
data.writeInt(0);
|
|
||||||
data.writeInt(IEND_TYPE);
|
|
||||||
data.writeInt(IEND_CRC);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NoCloseDataOutputStream extends DataOutputStream {
|
|
||||||
public NoCloseDataOutputStream(OutputStream out) {
|
|
||||||
super(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
public Closeable closeable() {
|
|
||||||
return super::close;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.image;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class PPMImageWriter implements ImageWriter {
|
|
||||||
public static final PPMImageWriter sRGB = new PPMImageWriter(ColorSpaces.sRGB);
|
|
||||||
|
|
||||||
private final @NotNull ColorSpace cs;
|
|
||||||
|
|
||||||
public PPMImageWriter(@NotNull ColorSpace cs) {
|
|
||||||
this.cs = Objects.requireNonNull(cs, "cs");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException {
|
|
||||||
try (var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.US_ASCII))) {
|
|
||||||
writer.write("P3\n");
|
|
||||||
writer.write(String.valueOf(image.getWidth()));
|
|
||||||
writer.write(" ");
|
|
||||||
writer.write(String.valueOf(image.getHeight()));
|
|
||||||
writer.write("\n255\n");
|
|
||||||
|
|
||||||
for (int y = 0; y < image.getHeight(); y++) {
|
|
||||||
for (int x = 0; x < image.getWidth(); x++) {
|
|
||||||
var color = cs.encode(image.getRGB(x, y, cs));
|
|
||||||
writer.write(String.valueOf(color.red()));
|
|
||||||
writer.write(" ");
|
|
||||||
writer.write(String.valueOf(color.green()));
|
|
||||||
writer.write(" ");
|
|
||||||
writer.write(String.valueOf(color.blue()));
|
|
||||||
writer.write("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +1,22 @@
|
|||||||
package eu.jonahbauer.raytracing.render.material;
|
package eu.jonahbauer.raytracing.render.material;
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.random.RandomGenerator;
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public record DiffuseLight(@NotNull Texture texture) implements Material {
|
public record DiffuseLight(@NotNull Texture texture) implements Material {
|
||||||
public DiffuseLight {
|
|
||||||
Objects.requireNonNull(texture, "texture");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Spectrum emitted(@NotNull HitResult hit) {
|
public @NotNull Color emitted(@NotNull HitResult hit) {
|
||||||
return texture.get(hit);
|
return texture.get(hit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.material;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
|
|
||||||
import eu.jonahbauer.raytracing.render.material.DielectricMaterial.SellmeierRefractiveIndex;
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
|
|
||||||
import eu.jonahbauer.raytracing.render.texture.CheckerTexture;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public final class Materials {
|
|
||||||
public static final @NotNull Material GLASS = new DielectricMaterial(new SellmeierRefractiveIndex(
|
|
||||||
1.0361212, 0.231792344, 1.01046945,
|
|
||||||
6.00069867E-3, 2.00179144E-2, 1.03560653E2
|
|
||||||
));
|
|
||||||
|
|
||||||
public static final @NotNull Material MIRROR = new MetallicMaterial(ColorSpaces.sRGB.albedo(0.7, 0.7, 0.7));
|
|
||||||
|
|
||||||
public static final @NotNull Material DEBUG = new DirectionalMaterial(
|
|
||||||
new LambertianMaterial(new CheckerTexture(50.0, ColorSpaces.sRGB.albedo(1.0, 0.0, 1.0), Spectra.BLACK)),
|
|
||||||
new LambertianMaterial(new CheckerTexture(50.0, ColorSpaces.sRGB.albedo(1.0, 1.0, 1.0), Spectra.BLACK))
|
|
||||||
);
|
|
||||||
|
|
||||||
private Materials() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
public final class BlackbodySpectrum implements Spectrum {
|
|
||||||
/**
|
|
||||||
* the speed of light in m/s
|
|
||||||
*/
|
|
||||||
private static final double c = 299792458d;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the planck constant in m^2*kg/s
|
|
||||||
*/
|
|
||||||
private static final double h = 6.62607015E-34;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the boltzmann constant in m^2*kg/s^2/K
|
|
||||||
*/
|
|
||||||
private static final double k = 1.380649E-23;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* wien's displacement constant in m*K
|
|
||||||
*/
|
|
||||||
private static final double b = 2.897771995E-3;
|
|
||||||
|
|
||||||
private final double T;
|
|
||||||
private final double factor;
|
|
||||||
|
|
||||||
public BlackbodySpectrum(double T) {
|
|
||||||
if (T < 0) throw new IllegalArgumentException("T must be non-negative");
|
|
||||||
this.T = T;
|
|
||||||
this.factor = 1 / get(b / T);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
lambda *= 1E-9;
|
|
||||||
var l2 = lambda * lambda;
|
|
||||||
var x = h * c / (lambda * k * T);
|
|
||||||
return 2 * h * c * c / (l2 * l2 * lambda) / (Math.exp(x) - 1) * factor;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A constant spectrum.
|
|
||||||
* @param c the constant value
|
|
||||||
*/
|
|
||||||
public record ConstantSpectrum(double c) implements Spectrum {
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A spectrum sampled in one nanometer intervals.
|
|
||||||
*/
|
|
||||||
public final class DenselySampledSpectrum implements Spectrum {
|
|
||||||
private final double[] samples;
|
|
||||||
private final int min;
|
|
||||||
|
|
||||||
private final double max;
|
|
||||||
|
|
||||||
public DenselySampledSpectrum(@NotNull Spectrum spectrum) {
|
|
||||||
this(spectrum, LAMBDA_MIN, LAMBDA_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DenselySampledSpectrum(@NotNull Spectrum spectrum, int min, int max) {
|
|
||||||
if (max - min + 1 <= 0) throw new IllegalArgumentException("samples must not be empty");
|
|
||||||
this.samples = new double[max - min + 1];
|
|
||||||
var maxValue = 0d;
|
|
||||||
for (int lambda = min, i = 0; lambda <= max; lambda++, i++) {
|
|
||||||
var sample = spectrum.get(lambda);
|
|
||||||
if (sample > maxValue) maxValue = sample;
|
|
||||||
this.samples[i] = sample;
|
|
||||||
}
|
|
||||||
this.min = min;
|
|
||||||
this.max = maxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DenselySampledSpectrum(double @NotNull[] samples, int lambdaMin) {
|
|
||||||
if (samples.length == 0) throw new IllegalArgumentException("samples must not be empty");
|
|
||||||
this.samples = Arrays.copyOf(samples, samples.length);
|
|
||||||
this.min = lambdaMin;
|
|
||||||
this.max = Arrays.stream(this.samples).max().orElseThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
int offset = (int) Math.round(lambda) - min;
|
|
||||||
if (offset < 0 || offset >= samples.length) return 0;
|
|
||||||
return samples[offset];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public final class PiecewiseLinearSpectrum implements Spectrum {
|
|
||||||
private final double[] lambdas;
|
|
||||||
private final double[] values;
|
|
||||||
private final double max;
|
|
||||||
|
|
||||||
public PiecewiseLinearSpectrum(double[] lambdas, double[] values) {
|
|
||||||
if (lambdas.length != values.length) {
|
|
||||||
throw new IllegalArgumentException("lambdas and values must have the same length");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lambdas = Arrays.copyOf(lambdas, lambdas.length);
|
|
||||||
this.values = Arrays.copyOf(values, values.length);
|
|
||||||
|
|
||||||
var max = 0d;
|
|
||||||
for (int i = 1; i < this.lambdas.length; i++) {
|
|
||||||
if (this.lambdas[i] <= this.lambdas[i - 1]) {
|
|
||||||
throw new IllegalArgumentException("lambdas must be in increasing order");
|
|
||||||
}
|
|
||||||
if (this.values[i] < 0) {
|
|
||||||
throw new IllegalArgumentException("values must be non-negative");
|
|
||||||
} else if (this.values[i] > max) {
|
|
||||||
max = this.values[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.max = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
if (lambdas.length == 0 || lambda < lambdas[0] || lambda > lambdas[lambdas.length - 1]) return 0;
|
|
||||||
if (lambda == lambdas[lambdas.length - 1]) return values[values.length - 1];
|
|
||||||
|
|
||||||
var i = Arrays.binarySearch(lambdas, lambda);
|
|
||||||
if (i < 0) i = -i - 1;
|
|
||||||
|
|
||||||
var t = (lambda - lambdas[i]) / (lambdas[i + 1] - lambdas[i]);
|
|
||||||
return (1 - t) * values[i] + t * values[i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public final class RGBAlbedoSpectrum implements Spectrum {
|
|
||||||
private final @NotNull SigmoidPolynomial polynomial;
|
|
||||||
|
|
||||||
public RGBAlbedoSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
|
||||||
if (rgb.r() < 0 || rgb.r() > 1 || rgb.g() < 0 || rgb.g() > 1 || rgb.b() < 0 || rgb.b() > 1) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
this.polynomial = cs.toPolynomial(rgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return polynomial.max();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
return polynomial.get(lambda);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A spectrum based on an RGB color used as an illuminant. The spectrum is adjusted to account for the color space's
|
|
||||||
* standard illuminant.
|
|
||||||
*/
|
|
||||||
public final class RGBIlluminantSpectrum implements Spectrum {
|
|
||||||
private final double scale;
|
|
||||||
private final @NotNull SigmoidPolynomial polynomial;
|
|
||||||
private final @NotNull Spectrum illuminant;
|
|
||||||
|
|
||||||
public RGBIlluminantSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
|
||||||
if (rgb.r() < 0 || rgb.g() < 0 || rgb.b() < 0) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
|
|
||||||
this.scale = 2 * max;
|
|
||||||
this.polynomial = cs.toPolynomial(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
|
|
||||||
this.illuminant = cs.illuminant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return scale * polynomial.max() * illuminant.max();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
return scale * polynomial.get(lambda) * illuminant.get(lambda);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public final class RGBUnboundedSpectrum implements Spectrum {
|
|
||||||
private final double scale;
|
|
||||||
private final @NotNull SigmoidPolynomial polynomial;
|
|
||||||
|
|
||||||
public RGBUnboundedSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
|
||||||
if (rgb.r() < 0 || rgb.g() < 0 || rgb.b() < 0) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
|
|
||||||
this.scale = 2 * max;
|
|
||||||
this.polynomial = cs.toPolynomial(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return scale * polynomial.max();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
return scale * polynomial.get(lambda);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.IVec;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
// TODO use Vector API to parallelize operations
|
|
||||||
public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
|
||||||
private final double @NotNull[] values;
|
|
||||||
|
|
||||||
public SampledSpectrum(@NotNull SampledWavelengths lambdas, @NotNull Spectrum spectrum) {
|
|
||||||
var values = new double[lambdas.size()];
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
values[i] = spectrum.get(lambdas.get(i));
|
|
||||||
}
|
|
||||||
this.values = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SampledSpectrum(int count, double value) {
|
|
||||||
var values = new double[count];
|
|
||||||
Arrays.fill(values, value);
|
|
||||||
this.values = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SampledSpectrum(double @NotNull[] values) {
|
|
||||||
this.values = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Math
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static @NotNull SampledSpectrum fma(@NotNull SampledSpectrum a, @NotNull SampledSpectrum b, @NotNull SampledSpectrum c) {
|
|
||||||
var out = new double[a.values.length];
|
|
||||||
for (int i = 0; i < a.values.length; i++) {
|
|
||||||
out[i] = Math.fma(a.values[i], b.values[i], c.values[i]);
|
|
||||||
}
|
|
||||||
return new SampledSpectrum(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull SampledSpectrum lerp(@NotNull SampledSpectrum a, @NotNull SampledSpectrum b, double t) {
|
|
||||||
if (t < 0) return a;
|
|
||||||
if (t > 1) return b;
|
|
||||||
var out = new double[a.values.length];
|
|
||||||
for (int i = 0; i < a.values.length; i++) {
|
|
||||||
out[i] = Math.fma(t, b.values[i] - a.values[i], a.values[i]);
|
|
||||||
}
|
|
||||||
return new SampledSpectrum(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull SampledSpectrum plus(@NotNull SampledSpectrum other) {
|
|
||||||
var out = new double[other.values.length];
|
|
||||||
for (int i = 0; i < other.values.length; i++) {
|
|
||||||
out[i] = values[i] + other.values[i];
|
|
||||||
}
|
|
||||||
return new SampledSpectrum(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull SampledSpectrum minus(@NotNull SampledSpectrum other) {
|
|
||||||
var out = new double[other.values.length];
|
|
||||||
for (int i = 0; i < other.values.length; i++) {
|
|
||||||
out[i] = values[i] - other.values[i];
|
|
||||||
}
|
|
||||||
return new SampledSpectrum(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull SampledSpectrum times(@NotNull SampledSpectrum other) {
|
|
||||||
var out = new double[other.values.length];
|
|
||||||
for (int i = 0; i < other.values.length; i++) {
|
|
||||||
out[i] = values[i] * other.values[i];
|
|
||||||
}
|
|
||||||
return new SampledSpectrum(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull SampledSpectrum times(double d) {
|
|
||||||
var out = new double[values.length];
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
out[i] = values[i] * d;
|
|
||||||
}
|
|
||||||
return new SampledSpectrum(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double @NotNull [] toArray() {
|
|
||||||
return Arrays.copyOf(values, values.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Accessors
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(int index) {
|
|
||||||
return values[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int size() {
|
|
||||||
return values.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Object
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return obj instanceof SampledSpectrum o && Arrays.equals(values, o.values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Arrays.hashCode(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "SampledSpectrum[values=" + Arrays.toString(values) + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Conversions
|
|
||||||
*/
|
|
||||||
|
|
||||||
public @NotNull ColorXYZ toXYZ(@NotNull SampledWavelengths lambdas) {
|
|
||||||
return lambdas.toXYZ(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull ColorRGB toRGB(@NotNull SampledWavelengths lambdas, @NotNull ColorSpace cs) {
|
|
||||||
return cs.toRGB(toXYZ(lambdas));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A set of sampled wavelength that can be tracked together.
|
|
||||||
*/
|
|
||||||
public final class SampledWavelengths {
|
|
||||||
public static final SampledWavelengths EMPTY = new SampledWavelengths(new double[0], new double[0]);
|
|
||||||
|
|
||||||
private final double @NotNull[] lambdas;
|
|
||||||
private final double @NotNull[] pdf;
|
|
||||||
|
|
||||||
public static @NotNull SampledWavelengths uniform(double rng, int count) {
|
|
||||||
return uniform(rng, count, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull SampledWavelengths uniform(double rng, int count, double min, double max) {
|
|
||||||
var lambdas = new double[count];
|
|
||||||
|
|
||||||
// choose first sample at random
|
|
||||||
lambdas[0] = (1 - rng) * min + rng * max;
|
|
||||||
|
|
||||||
// choose next samples in equal intervals, wrapping if necessary
|
|
||||||
var delta = (max - min) / count;
|
|
||||||
for (int i = 1; i < count; i++) {
|
|
||||||
lambdas[i] = lambdas[i - 1] + delta;
|
|
||||||
if (lambdas[i] > max) {
|
|
||||||
lambdas[i] = min + (lambdas[i] - max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pdf = new double[count];
|
|
||||||
Arrays.fill(pdf, 1 / (max - min));
|
|
||||||
return new SampledWavelengths(lambdas, pdf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SampledWavelengths(double @NotNull[] lambdas, double @NotNull[] pdf) {
|
|
||||||
this.lambdas = lambdas;
|
|
||||||
this.pdf = pdf;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
public double get(int index) {
|
|
||||||
return lambdas[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract(pure = true)
|
|
||||||
public int size() {
|
|
||||||
return lambdas.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminates the secondary wavelengths. This method should be called after a wavelength-dependent operation like
|
|
||||||
* refraction that makes it incorrect to track multiple wavelengths together.
|
|
||||||
*/
|
|
||||||
@Contract(mutates = "this")
|
|
||||||
public double collapse() {
|
|
||||||
if (pdf.length >= 2 || pdf[1] != 0) {
|
|
||||||
Arrays.fill(pdf, 1, pdf.length, 0d);
|
|
||||||
pdf[0] /= pdf.length;
|
|
||||||
}
|
|
||||||
return lambdas[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Object
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return obj instanceof SampledWavelengths o && Arrays.equals(lambdas, o.lambdas) && Arrays.equals(pdf, o.pdf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return 31 * Arrays.hashCode(lambdas) + Arrays.hashCode(pdf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "SampledWavelengths[lambdas=" + Arrays.toString(lambdas) + ", pdf=" + Arrays.toString(pdf) + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Conversions
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
ColorXYZ toXYZ(@NotNull SampledSpectrum spectrum) {
|
|
||||||
var x = Spectra.X.sample(this);
|
|
||||||
var y = Spectra.Y.sample(this);
|
|
||||||
var z = Spectra.Z.sample(this);
|
|
||||||
|
|
||||||
return new ColorXYZ(
|
|
||||||
toXYZ0(spectrum, x) / ColorXYZ.CIE_Y_INTEGRAL,
|
|
||||||
toXYZ0(spectrum, y) / ColorXYZ.CIE_Y_INTEGRAL,
|
|
||||||
toXYZ0(spectrum, z) / ColorXYZ.CIE_Y_INTEGRAL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double toXYZ0(@NotNull SampledSpectrum spectrum, @NotNull SampledSpectrum cie) {
|
|
||||||
var avg = 0d;
|
|
||||||
for (int i = 0; i < spectrum.size(); i++) {
|
|
||||||
var pdf = this.pdf[i];
|
|
||||||
double value;
|
|
||||||
if (pdf == 0) {
|
|
||||||
value = 0;
|
|
||||||
} else {
|
|
||||||
value = spectrum.get(i) * cie.get(i) / pdf;
|
|
||||||
}
|
|
||||||
avg = Math.fma(1d / (i + 1), value - avg, avg);
|
|
||||||
}
|
|
||||||
return avg;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public record ScaledSpectrum(@NotNull Spectrum spectrum, double scale) implements Spectrum {
|
|
||||||
@Override
|
|
||||||
public double max() {
|
|
||||||
return spectrum.max() * scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get(double lambda) {
|
|
||||||
return spectrum.get(lambda) * scale;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,410 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public final class Spectra {
|
|
||||||
private static final String PATH_PREFIX = "/eu/jonahbauer/raytracing/spectrum/";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the CIE XYZ color matching curve for X
|
|
||||||
*/
|
|
||||||
public static final Spectrum X = new DenselySampledSpectrum(new PiecewiseLinearSpectrum(CIE_XYZ.CIE_lambda, CIE_XYZ.CIE_X));
|
|
||||||
/**
|
|
||||||
* the CIE XYZ color matching curve for Y
|
|
||||||
*/
|
|
||||||
public static final Spectrum Y = new DenselySampledSpectrum(new PiecewiseLinearSpectrum(CIE_XYZ.CIE_lambda, CIE_XYZ.CIE_Y));;
|
|
||||||
/**
|
|
||||||
* the CIE XYZ color matching curve for Z
|
|
||||||
*/
|
|
||||||
public static final Spectrum Z = new DenselySampledSpectrum(new PiecewiseLinearSpectrum(CIE_XYZ.CIE_lambda, CIE_XYZ.CIE_Z));;
|
|
||||||
/**
|
|
||||||
* the CIE standard illuminant D50
|
|
||||||
* @see <a href="https://doi.org/10.25039/CIE.DS.hjfjmt59">CIE 2022, CIE standard illuminant D65, International Commission on Illumination (CIE), Vienna, Austria, DOI: 10.25039/CIE.DS.hjfjmt59</a>
|
|
||||||
*/
|
|
||||||
public static final Spectrum D50 = read("CIE_std_illum_D50.csv", true);
|
|
||||||
/**
|
|
||||||
* the CIE standard illuminant D65
|
|
||||||
* @see <a href="https://doi.org/10.25039/CIE.DS.etgmuqt5">CIE 2022, Relative spectral power distributions of CIE standard illuminants A, D65 and D50 (wavelengths in standard air) (data table), International Commission on Illumination (CIE), Vienna, Austria, DOI:10.25039/CIE.DS.etgmuqt5</a>
|
|
||||||
*/
|
|
||||||
public static final Spectrum D65 = read("CIE_std_illum_D65.csv", true);
|
|
||||||
|
|
||||||
public static final Spectrum BLACK = new ConstantSpectrum(0);
|
|
||||||
public static final Spectrum WHITE = new ConstantSpectrum(1);
|
|
||||||
|
|
||||||
private static @NotNull Spectrum read(@NotNull String path, boolean normalize) {
|
|
||||||
var lambda = new ArrayList<Double>();
|
|
||||||
var values = new ArrayList<Double>();
|
|
||||||
|
|
||||||
try (
|
|
||||||
var is = Spectra.class.getResourceAsStream(PATH_PREFIX + path);
|
|
||||||
var in = new BufferedReader(new InputStreamReader(is, StandardCharsets.US_ASCII))
|
|
||||||
) {
|
|
||||||
String line;
|
|
||||||
while ((line = in.readLine()) != null) {
|
|
||||||
var parts = line.split(",");
|
|
||||||
lambda.add(Double.parseDouble(parts[0]));
|
|
||||||
values.add(Double.parseDouble(parts[1]));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pls = new PiecewiseLinearSpectrum(
|
|
||||||
lambda.stream().mapToDouble(Double::doubleValue).toArray(),
|
|
||||||
values.stream().mapToDouble(Double::doubleValue).toArray()
|
|
||||||
);
|
|
||||||
if (normalize) {
|
|
||||||
return pls.scale(ColorXYZ.CIE_Y_INTEGRAL / Util.innerProduct(pls, Spectra.Y));
|
|
||||||
} else {
|
|
||||||
return pls;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Spectra() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see <a href="https://doi.org/10.25039/CIE.DS.xvudnb9b">CIE 2018, CIE 1931 colour-matching functions , 2 degree observer (data table),
|
|
||||||
* International Commission on Illumination (CIE), Vienna, Austria,
|
|
||||||
* DOI:10.25039/CIE.DS.xvudnb9b</a>
|
|
||||||
*/
|
|
||||||
private static final class CIE_XYZ {
|
|
||||||
private static final double[] CIE_lambda = {
|
|
||||||
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376,
|
|
||||||
377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393,
|
|
||||||
394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410,
|
|
||||||
411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427,
|
|
||||||
428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444,
|
|
||||||
445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461,
|
|
||||||
462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478,
|
|
||||||
479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495,
|
|
||||||
496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512,
|
|
||||||
513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529,
|
|
||||||
530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546,
|
|
||||||
547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563,
|
|
||||||
564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580,
|
|
||||||
581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597,
|
|
||||||
598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614,
|
|
||||||
615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631,
|
|
||||||
632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648,
|
|
||||||
649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665,
|
|
||||||
666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682,
|
|
||||||
683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699,
|
|
||||||
700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716,
|
|
||||||
717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733,
|
|
||||||
734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750,
|
|
||||||
751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767,
|
|
||||||
768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784,
|
|
||||||
785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801,
|
|
||||||
802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818,
|
|
||||||
819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private static final double[] CIE_X = {
|
|
||||||
// CIE X function values
|
|
||||||
0.0001299000, 0.0001458470, 0.0001638021, 0.0001840037, 0.0002066902,
|
|
||||||
0.0002321000, 0.0002607280, 0.0002930750, 0.0003293880, 0.0003699140,
|
|
||||||
0.0004149000, 0.0004641587, 0.0005189860, 0.0005818540, 0.0006552347,
|
|
||||||
0.0007416000, 0.0008450296, 0.0009645268, 0.001094949, 0.001231154,
|
|
||||||
0.001368000, 0.001502050, 0.001642328, 0.001802382, 0.001995757,
|
|
||||||
0.002236000, 0.002535385, 0.002892603, 0.003300829, 0.003753236,
|
|
||||||
0.004243000, 0.004762389, 0.005330048, 0.005978712, 0.006741117,
|
|
||||||
0.007650000, 0.008751373, 0.01002888, 0.01142170, 0.01286901,
|
|
||||||
0.01431000, 0.01570443, 0.01714744, 0.01878122, 0.02074801,
|
|
||||||
0.02319000, 0.02620736, 0.02978248, 0.03388092, 0.03846824,
|
|
||||||
0.04351000, 0.04899560, 0.05502260, 0.06171880, 0.06921200,
|
|
||||||
0.07763000, 0.08695811, 0.09717672, 0.1084063, 0.1207672,
|
|
||||||
0.1343800, 0.1493582, 0.1653957, 0.1819831, 0.1986110,
|
|
||||||
0.2147700, 0.2301868, 0.2448797, 0.2587773, 0.2718079,
|
|
||||||
0.2839000, 0.2949438, 0.3048965, 0.3137873, 0.3216454,
|
|
||||||
0.3285000, 0.3343513, 0.3392101, 0.3431213, 0.3461296,
|
|
||||||
0.3482800, 0.3495999, 0.3501474, 0.3500130, 0.3492870,
|
|
||||||
0.3480600, 0.3463733, 0.3442624, 0.3418088, 0.3390941,
|
|
||||||
0.3362000, 0.3331977, 0.3300411, 0.3266357, 0.3228868,
|
|
||||||
0.3187000, 0.3140251, 0.3088840, 0.3032904, 0.2972579,
|
|
||||||
0.2908000, 0.2839701, 0.2767214, 0.2689178, 0.2604227,
|
|
||||||
0.2511000, 0.2408475, 0.2298512, 0.2184072, 0.2068115,
|
|
||||||
0.1953600, 0.1842136, 0.1733273, 0.1626881, 0.1522833,
|
|
||||||
0.1421000, 0.1321786, 0.1225696, 0.1132752, 0.1042979,
|
|
||||||
0.09564000, 0.08729955, 0.07930804, 0.07171776, 0.06458099,
|
|
||||||
0.05795001, 0.05186211, 0.04628152, 0.04115088, 0.03641283,
|
|
||||||
0.03201000, 0.02791720, 0.02414440, 0.02068700, 0.01754040,
|
|
||||||
0.01470000, 0.01216179, 0.009919960, 0.007967240, 0.006296346,
|
|
||||||
0.004900000, 0.003777173, 0.002945320, 0.002424880, 0.002236293,
|
|
||||||
0.002400000, 0.002925520, 0.003836560, 0.005174840, 0.006982080,
|
|
||||||
0.009300000, 0.01214949, 0.01553588, 0.01947752, 0.02399277,
|
|
||||||
0.02910000, 0.03481485, 0.04112016, 0.04798504, 0.05537861,
|
|
||||||
0.06327000, 0.07163501, 0.08046224, 0.08973996, 0.09945645,
|
|
||||||
0.1096000, 0.1201674, 0.1311145, 0.1423679, 0.1538542,
|
|
||||||
0.1655000, 0.1772571, 0.1891400, 0.2011694, 0.2133658,
|
|
||||||
0.2257499, 0.2383209, 0.2510668, 0.2639922, 0.2771017,
|
|
||||||
0.2904000, 0.3038912, 0.3175726, 0.3314384, 0.3454828,
|
|
||||||
0.3597000, 0.3740839, 0.3886396, 0.4033784, 0.4183115,
|
|
||||||
0.4334499, 0.4487953, 0.4643360, 0.4800640, 0.4959713,
|
|
||||||
0.5120501, 0.5282959, 0.5446916, 0.5612094, 0.5778215,
|
|
||||||
0.5945000, 0.6112209, 0.6279758, 0.6447602, 0.6615697,
|
|
||||||
0.6784000, 0.6952392, 0.7120586, 0.7288284, 0.7455188,
|
|
||||||
0.7621000, 0.7785432, 0.7948256, 0.8109264, 0.8268248,
|
|
||||||
0.8425000, 0.8579325, 0.8730816, 0.8878944, 0.9023181,
|
|
||||||
0.9163000, 0.9297995, 0.9427984, 0.9552776, 0.9672179,
|
|
||||||
0.9786000, 0.9893856, 0.9995488, 1.0090892, 1.0180064,
|
|
||||||
1.0263000, 1.0339827, 1.0409860, 1.0471880, 1.0524667,
|
|
||||||
1.0567000, 1.0597944, 1.0617992, 1.0628068, 1.0629096,
|
|
||||||
1.0622000, 1.0607352, 1.0584436, 1.0552244, 1.0509768,
|
|
||||||
1.0456000, 1.0390369, 1.0313608, 1.0226662, 1.0130477,
|
|
||||||
1.0026000, 0.9913675, 0.9793314, 0.9664916, 0.9528479,
|
|
||||||
0.9384000, 0.9231940, 0.9072440, 0.8905020, 0.8729200,
|
|
||||||
0.8544499, 0.8350840, 0.8149460, 0.7941860, 0.7729540,
|
|
||||||
0.7514000, 0.7295836, 0.7075888, 0.6856022, 0.6638104,
|
|
||||||
0.6424000, 0.6215149, 0.6011138, 0.5811052, 0.5613977,
|
|
||||||
0.5419000, 0.5225995, 0.5035464, 0.4847436, 0.4661939,
|
|
||||||
0.4479000, 0.4298613, 0.4120980, 0.3946440, 0.3775333,
|
|
||||||
0.3608000, 0.3444563, 0.3285168, 0.3130192, 0.2980011,
|
|
||||||
0.2835000, 0.2695448, 0.2561184, 0.2431896, 0.2307272,
|
|
||||||
0.2187000, 0.2070971, 0.1959232, 0.1851708, 0.1748323,
|
|
||||||
0.1649000, 0.1553667, 0.1462300, 0.1374900, 0.1291467,
|
|
||||||
0.1212000, 0.1136397, 0.1064650, 0.09969044, 0.09333061,
|
|
||||||
0.08740000, 0.08190096, 0.07680428, 0.07207712, 0.06768664,
|
|
||||||
0.06360000, 0.05980685, 0.05628216, 0.05297104, 0.04981861,
|
|
||||||
0.04677000, 0.04378405, 0.04087536, 0.03807264, 0.03540461,
|
|
||||||
0.03290000, 0.03056419, 0.02838056, 0.02634484, 0.02445275,
|
|
||||||
0.02270000, 0.02108429, 0.01959988, 0.01823732, 0.01698717,
|
|
||||||
0.01584000, 0.01479064, 0.01383132, 0.01294868, 0.01212920,
|
|
||||||
0.01135916, 0.01062935, 0.009938846, 0.009288422, 0.008678854,
|
|
||||||
0.008110916, 0.007582388, 0.007088746, 0.006627313, 0.006195408,
|
|
||||||
0.005790346, 0.005409826, 0.005052583, 0.004717512, 0.004403507,
|
|
||||||
0.004109457, 0.003833913, 0.003575748, 0.003334342, 0.003109075,
|
|
||||||
0.002899327, 0.002704348, 0.002523020, 0.002354168, 0.002196616,
|
|
||||||
0.002049190, 0.001910960, 0.001781438, 0.001660110, 0.001546459,
|
|
||||||
0.001439971, 0.001340042, 0.001246275, 0.001158471, 0.001076430,
|
|
||||||
0.0009999493, 0.0009287358, 0.0008624332, 0.0008007503, 0.0007433960,
|
|
||||||
0.0006900786, 0.0006405156, 0.0005945021, 0.0005518646, 0.0005124290,
|
|
||||||
0.0004760213, 0.0004424536, 0.0004115117, 0.0003829814, 0.0003566491,
|
|
||||||
0.0003323011, 0.0003097586, 0.0002888871, 0.0002695394, 0.0002515682,
|
|
||||||
0.0002348261, 0.0002191710, 0.0002045258, 0.0001908405, 0.0001780654,
|
|
||||||
0.0001661505, 0.0001550236, 0.0001446219, 0.0001349098, 0.0001258520,
|
|
||||||
0.0001174130, 0.0001095515, 0.0001022245, 0.00009539445, 0.00008902390,
|
|
||||||
0.00008307527, 0.00007751269, 0.00007231304, 0.00006745778, 0.00006292844,
|
|
||||||
0.00005870652, 0.00005477028, 0.00005109918, 0.00004767654, 0.00004448567,
|
|
||||||
0.00004150994, 0.00003873324, 0.00003614203, 0.00003372352, 0.00003146487,
|
|
||||||
0.00002935326, 0.00002737573, 0.00002552433, 0.00002379376, 0.00002217870,
|
|
||||||
0.00002067383, 0.00001927226, 0.00001796640, 0.00001674991, 0.00001561648,
|
|
||||||
0.00001455977, 0.00001357387, 0.00001265436, 0.00001179723, 0.00001099844,
|
|
||||||
0.00001025398, 0.000009559646, 0.000008912044, 0.000008308358, 0.000007745769,
|
|
||||||
0.000007221456, 0.000006732475, 0.000006276423, 0.000005851304, 0.000005455118,
|
|
||||||
0.000005085868, 0.000004741466, 0.000004420236, 0.000004120783, 0.000003841716,
|
|
||||||
0.000003581652, 0.000003339127, 0.000003112949, 0.000002902121, 0.000002705645,
|
|
||||||
0.000002522525, 0.000002351726, 0.000002192415, 0.000002043902, 0.000001905497,
|
|
||||||
0.000001776509, 0.000001656215, 0.000001544022, 0.000001439440, 0.000001341977,
|
|
||||||
0.000001251141
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final double[] CIE_Y = {
|
|
||||||
// CIE Y function values
|
|
||||||
0.000003917000, 0.000004393581, 0.000004929604, 0.000005532136, 0.000006208245,
|
|
||||||
0.000006965000, 0.000007813219, 0.000008767336, 0.000009839844, 0.00001104323,
|
|
||||||
0.00001239000, 0.00001388641, 0.00001555728, 0.00001744296, 0.00001958375,
|
|
||||||
0.00002202000, 0.00002483965, 0.00002804126, 0.00003153104, 0.00003521521,
|
|
||||||
0.00003900000, 0.00004282640, 0.00004691460, 0.00005158960, 0.00005717640,
|
|
||||||
0.00006400000, 0.00007234421, 0.00008221224, 0.00009350816, 0.0001061361,
|
|
||||||
0.0001200000, 0.0001349840, 0.0001514920, 0.0001702080, 0.0001918160,
|
|
||||||
0.0002170000, 0.0002469067, 0.0002812400, 0.0003185200, 0.0003572667,
|
|
||||||
0.0003960000, 0.0004337147, 0.0004730240, 0.0005178760, 0.0005722187,
|
|
||||||
0.0006400000, 0.0007245600, 0.0008255000, 0.0009411600, 0.001069880,
|
|
||||||
0.001210000, 0.001362091, 0.001530752, 0.001720368, 0.001935323,
|
|
||||||
0.002180000, 0.002454800, 0.002764000, 0.003117800, 0.003526400,
|
|
||||||
0.004000000, 0.004546240, 0.005159320, 0.005829280, 0.006546160,
|
|
||||||
0.007300000, 0.008086507, 0.008908720, 0.009767680, 0.01066443,
|
|
||||||
0.01160000, 0.01257317, 0.01358272, 0.01462968, 0.01571509,
|
|
||||||
0.01684000, 0.01800736, 0.01921448, 0.02045392, 0.02171824,
|
|
||||||
0.02300000, 0.02429461, 0.02561024, 0.02695857, 0.02835125,
|
|
||||||
0.02980000, 0.03131083, 0.03288368, 0.03452112, 0.03622571,
|
|
||||||
0.03800000, 0.03984667, 0.04176800, 0.04376600, 0.04584267,
|
|
||||||
0.04800000, 0.05024368, 0.05257304, 0.05498056, 0.05745872,
|
|
||||||
0.06000000, 0.06260197, 0.06527752, 0.06804208, 0.07091109,
|
|
||||||
0.07390000, 0.07701600, 0.08026640, 0.08366680, 0.08723280,
|
|
||||||
0.09098000, 0.09491755, 0.09904584, 0.1033674, 0.1078846,
|
|
||||||
0.1126000, 0.1175320, 0.1226744, 0.1279928, 0.1334528,
|
|
||||||
0.1390200, 0.1446764, 0.1504693, 0.1564619, 0.1627177,
|
|
||||||
0.1693000, 0.1762431, 0.1835581, 0.1912735, 0.1994180,
|
|
||||||
0.2080200, 0.2171199, 0.2267345, 0.2368571, 0.2474812,
|
|
||||||
0.2586000, 0.2701849, 0.2822939, 0.2950505, 0.3085780,
|
|
||||||
0.3230000, 0.3384021, 0.3546858, 0.3716986, 0.3892875,
|
|
||||||
0.4073000, 0.4256299, 0.4443096, 0.4633944, 0.4829395,
|
|
||||||
0.5030000, 0.5235693, 0.5445120, 0.5656900, 0.5869653,
|
|
||||||
0.6082000, 0.6293456, 0.6503068, 0.6708752, 0.6908424,
|
|
||||||
0.7100000, 0.7281852, 0.7454636, 0.7619694, 0.7778368,
|
|
||||||
0.7932000, 0.8081104, 0.8224962, 0.8363068, 0.8494916,
|
|
||||||
0.8620000, 0.8738108, 0.8849624, 0.8954936, 0.9054432,
|
|
||||||
0.9148501, 0.9237348, 0.9320924, 0.9399226, 0.9472252,
|
|
||||||
0.9540000, 0.9602561, 0.9660074, 0.9712606, 0.9760225,
|
|
||||||
0.9803000, 0.9840924, 0.9874812, 0.9903128, 0.9928116,
|
|
||||||
0.9949501, 0.9967108, 0.9980983, 0.9991120, 0.9997482,
|
|
||||||
1.0000000, 0.9998567, 0.9993046, 0.9983255, 0.9968987,
|
|
||||||
0.9950000, 0.9926005, 0.9897426, 0.9864444, 0.9827241,
|
|
||||||
0.9786000, 0.9740837, 0.9691712, 0.9638568, 0.9581349,
|
|
||||||
0.9520000, 0.9454504, 0.9384992, 0.9311628, 0.9234576,
|
|
||||||
0.9154000, 0.9070064, 0.8982772, 0.8892048, 0.8797816,
|
|
||||||
0.8700000, 0.8598613, 0.8493920, 0.8386220, 0.8275813,
|
|
||||||
0.8163000, 0.8047947, 0.7930820, 0.7811920, 0.7691547,
|
|
||||||
0.7570000, 0.7447541, 0.7324224, 0.7200036, 0.7074965,
|
|
||||||
0.6949000, 0.6822192, 0.6694716, 0.6566744, 0.6438448,
|
|
||||||
0.6310000, 0.6181555, 0.6053144, 0.5924756, 0.5796379,
|
|
||||||
0.5668000, 0.5539611, 0.5411372, 0.5283528, 0.5156323,
|
|
||||||
0.5030000, 0.4904688, 0.4780304, 0.4656776, 0.4534032,
|
|
||||||
0.4412000, 0.4290800, 0.4170360, 0.4050320, 0.3930320,
|
|
||||||
0.3810000, 0.3689184, 0.3568272, 0.3447768, 0.3328176,
|
|
||||||
0.3210000, 0.3093381, 0.2978504, 0.2865936, 0.2756245,
|
|
||||||
0.2650000, 0.2547632, 0.2448896, 0.2353344, 0.2260528,
|
|
||||||
0.2170000, 0.2081616, 0.1995488, 0.1911552, 0.1829744,
|
|
||||||
0.1750000, 0.1672235, 0.1596464, 0.1522776, 0.1451259,
|
|
||||||
0.1382000, 0.1315003, 0.1250248, 0.1187792, 0.1127691,
|
|
||||||
0.1070000, 0.1014762, 0.09618864, 0.09112296, 0.08626485,
|
|
||||||
0.08160000, 0.07712064, 0.07282552, 0.06871008, 0.06476976,
|
|
||||||
0.06100000, 0.05739621, 0.05395504, 0.05067376, 0.04754965,
|
|
||||||
0.04458000, 0.04175872, 0.03908496, 0.03656384, 0.03420048,
|
|
||||||
0.03200000, 0.02996261, 0.02807664, 0.02632936, 0.02470805,
|
|
||||||
0.02320000, 0.02180077, 0.02050112, 0.01928108, 0.01812069,
|
|
||||||
0.01700000, 0.01590379, 0.01483718, 0.01381068, 0.01283478,
|
|
||||||
0.01192000, 0.01106831, 0.01027339, 0.009533311, 0.008846157,
|
|
||||||
0.008210000, 0.007623781, 0.007085424, 0.006591476, 0.006138485,
|
|
||||||
0.005723000, 0.005343059, 0.004995796, 0.004676404, 0.004380075,
|
|
||||||
0.004102000, 0.003838453, 0.003589099, 0.003354219, 0.003134093,
|
|
||||||
0.002929000, 0.002738139, 0.002559876, 0.002393244, 0.002237275,
|
|
||||||
0.002091000, 0.001953587, 0.001824580, 0.001703580, 0.001590187,
|
|
||||||
0.001484000, 0.001384496, 0.001291268, 0.001204092, 0.001122744,
|
|
||||||
0.001047000, 0.0009765896, 0.0009111088, 0.0008501332, 0.0007932384,
|
|
||||||
0.0007400000, 0.0006900827, 0.0006433100, 0.0005994960, 0.0005584547,
|
|
||||||
0.0005200000, 0.0004839136, 0.0004500528, 0.0004183452, 0.0003887184,
|
|
||||||
0.0003611000, 0.0003353835, 0.0003114404, 0.0002891656, 0.0002684539,
|
|
||||||
0.0002492000, 0.0002313019, 0.0002146856, 0.0001992884, 0.0001850475,
|
|
||||||
0.0001719000, 0.0001597781, 0.0001486044, 0.0001383016, 0.0001287925,
|
|
||||||
0.0001200000, 0.0001118595, 0.0001043224, 0.00009733560, 0.00009084587,
|
|
||||||
0.00008480000, 0.00007914667, 0.00007385800, 0.00006891600, 0.00006430267,
|
|
||||||
0.00006000000, 0.00005598187, 0.00005222560, 0.00004871840, 0.00004544747,
|
|
||||||
0.00004240000, 0.00003956104, 0.00003691512, 0.00003444868, 0.00003214816,
|
|
||||||
0.00003000000, 0.00002799125, 0.00002611356, 0.00002436024, 0.00002272461,
|
|
||||||
0.00002120000, 0.00001977855, 0.00001845285, 0.00001721687, 0.00001606459,
|
|
||||||
0.00001499000, 0.00001398728, 0.00001305155, 0.00001217818, 0.00001136254,
|
|
||||||
0.00001060000, 0.000009885877, 0.000009217304, 0.000008592362, 0.000008009133,
|
|
||||||
0.000007465700, 0.000006959567, 0.000006487995, 0.000006048699, 0.000005639396,
|
|
||||||
0.000005257800, 0.000004901771, 0.000004569720, 0.000004260194, 0.000003971739,
|
|
||||||
0.000003702900, 0.000003452163, 0.000003218302, 0.000003000300, 0.000002797139,
|
|
||||||
0.000002607800, 0.000002431220, 0.000002266531, 0.000002113013, 0.000001969943,
|
|
||||||
0.000001836600, 0.000001712230, 0.000001596228, 0.000001488090, 0.000001387314,
|
|
||||||
0.000001293400, 0.000001205820, 0.000001124143, 0.000001048009, 0.0000009770578,
|
|
||||||
0.0000009109300, 0.0000008492513, 0.0000007917212, 0.0000007380904, 0.0000006881098,
|
|
||||||
0.0000006415300, 0.0000005980895, 0.0000005575746, 0.0000005198080, 0.0000004846123,
|
|
||||||
0.0000004518100
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final double[] CIE_Z = {
|
|
||||||
// CIE Z function values
|
|
||||||
0.0006061000, 0.0006808792, 0.0007651456, 0.0008600124, 0.0009665928,
|
|
||||||
0.001086000, 0.001220586, 0.001372729, 0.001543579, 0.001734286,
|
|
||||||
0.001946000, 0.002177777, 0.002435809, 0.002731953, 0.003078064,
|
|
||||||
0.003486000, 0.003975227, 0.004540880, 0.005158320, 0.005802907,
|
|
||||||
0.006450001, 0.007083216, 0.007745488, 0.008501152, 0.009414544,
|
|
||||||
0.01054999, 0.01196580, 0.01365587, 0.01558805, 0.01773015,
|
|
||||||
0.02005001, 0.02251136, 0.02520288, 0.02827972, 0.03189704,
|
|
||||||
0.03621000, 0.04143771, 0.04750372, 0.05411988, 0.06099803,
|
|
||||||
0.06785001, 0.07448632, 0.08136156, 0.08915364, 0.09854048,
|
|
||||||
0.1102000, 0.1246133, 0.1417017, 0.1613035, 0.1832568,
|
|
||||||
0.2074000, 0.2336921, 0.2626114, 0.2947746, 0.3307985,
|
|
||||||
0.3713000, 0.4162091, 0.4654642, 0.5196948, 0.5795303,
|
|
||||||
0.6456000, 0.7184838, 0.7967133, 0.8778459, 0.9594390,
|
|
||||||
1.0390501, 1.1153673, 1.1884971, 1.2581233, 1.3239296,
|
|
||||||
1.3856000, 1.4426352, 1.4948035, 1.5421903, 1.5848807,
|
|
||||||
1.6229600, 1.6564048, 1.6852959, 1.7098745, 1.7303821,
|
|
||||||
1.7470600, 1.7600446, 1.7696233, 1.7762637, 1.7804334,
|
|
||||||
1.7826000, 1.7829682, 1.7816998, 1.7791982, 1.7758671,
|
|
||||||
1.7721100, 1.7682589, 1.7640390, 1.7589438, 1.7524663,
|
|
||||||
1.7441000, 1.7335595, 1.7208581, 1.7059369, 1.6887372,
|
|
||||||
1.6692000, 1.6475287, 1.6234127, 1.5960223, 1.5645280,
|
|
||||||
1.5281000, 1.4861114, 1.4395215, 1.3898799, 1.3387362,
|
|
||||||
1.2876400, 1.2374223, 1.1878243, 1.1387611, 1.0901480,
|
|
||||||
1.0419000, 0.9941976, 0.9473473, 0.9014531, 0.8566193,
|
|
||||||
0.8129501, 0.7705173, 0.7294448, 0.6899136, 0.6521049,
|
|
||||||
0.6162000, 0.5823286, 0.5504162, 0.5203376, 0.4919673,
|
|
||||||
0.4651800, 0.4399246, 0.4161836, 0.3938822, 0.3729459,
|
|
||||||
0.3533000, 0.3348578, 0.3175521, 0.3013375, 0.2861686,
|
|
||||||
0.2720000, 0.2588171, 0.2464838, 0.2347718, 0.2234533,
|
|
||||||
0.2123000, 0.2011692, 0.1901196, 0.1792254, 0.1685608,
|
|
||||||
0.1582000, 0.1481383, 0.1383758, 0.1289942, 0.1200751,
|
|
||||||
0.1117000, 0.1039048, 0.09666748, 0.08998272, 0.08384531,
|
|
||||||
0.07824999, 0.07320899, 0.06867816, 0.06456784, 0.06078835,
|
|
||||||
0.05725001, 0.05390435, 0.05074664, 0.04775276, 0.04489859,
|
|
||||||
0.04216000, 0.03950728, 0.03693564, 0.03445836, 0.03208872,
|
|
||||||
0.02984000, 0.02771181, 0.02569444, 0.02378716, 0.02198925,
|
|
||||||
0.02030000, 0.01871805, 0.01724036, 0.01586364, 0.01458461,
|
|
||||||
0.01340000, 0.01230723, 0.01130188, 0.01037792, 0.009529306,
|
|
||||||
0.008749999, 0.008035200, 0.007381600, 0.006785400, 0.006242800,
|
|
||||||
0.005749999, 0.005303600, 0.004899800, 0.004534200, 0.004202400,
|
|
||||||
0.003900000, 0.003623200, 0.003370600, 0.003141400, 0.002934800,
|
|
||||||
0.002749999, 0.002585200, 0.002438600, 0.002309400, 0.002196800,
|
|
||||||
0.002100000, 0.002017733, 0.001948200, 0.001889800, 0.001840933,
|
|
||||||
0.001800000, 0.001766267, 0.001737800, 0.001711200, 0.001683067,
|
|
||||||
0.001650001, 0.001610133, 0.001564400, 0.001513600, 0.001458533,
|
|
||||||
0.001400000, 0.001336667, 0.001270000, 0.001205000, 0.001146667,
|
|
||||||
0.001100000, 0.001068800, 0.001049400, 0.001035600, 0.001021200,
|
|
||||||
0.001000000, 0.0009686400, 0.0009299200, 0.0008868800, 0.0008425600,
|
|
||||||
0.0008000000, 0.0007609600, 0.0007236800, 0.0006859200, 0.0006454400,
|
|
||||||
0.0006000000, 0.0005478667, 0.0004916000, 0.0004354000, 0.0003834667,
|
|
||||||
0.0003400000, 0.0003072533, 0.0002831600, 0.0002654400, 0.0002518133,
|
|
||||||
0.0002400000, 0.0002295467, 0.0002206400, 0.0002119600, 0.0002021867,
|
|
||||||
0.0001900000, 0.0001742133, 0.0001556400, 0.0001359600, 0.0001168533,
|
|
||||||
0.0001000000, 0.00008613333, 0.00007460000, 0.00006500000, 0.00005693333,
|
|
||||||
0.00004999999, 0.00004416000, 0.00003948000, 0.00003572000, 0.00003264000,
|
|
||||||
0.00003000000, 0.00002765333, 0.00002556000, 0.00002364000, 0.00002181333,
|
|
||||||
0.00002000000, 0.00001813333, 0.00001620000, 0.00001420000, 0.00001213333,
|
|
||||||
0.00001000000, 0.000007733333, 0.000005400000, 0.000003200000, 0.000001333333,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
|
|
||||||
0.000000000000
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
|
||||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
|
||||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
|
||||||
import eu.jonahbauer.raytracing.scene.SkyBox;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public interface Spectrum extends Texture, SkyBox {
|
|
||||||
int LAMBDA_MIN = 360;
|
|
||||||
int LAMBDA_MAX = 830;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@return the maximum value of <code>this</code> spectrum over the range of wavelengths}
|
|
||||||
*/
|
|
||||||
double max();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@return the value of <code>this</code> spectrum at a given wavelength}
|
|
||||||
* @param lambda the wavelength in nanometers
|
|
||||||
*/
|
|
||||||
double get(double lambda);
|
|
||||||
|
|
||||||
default @NotNull Spectrum scale(double scale) {
|
|
||||||
return new ScaledSpectrum(this, scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
default @NotNull SampledSpectrum sample(@NotNull SampledWavelengths lambdas) {
|
|
||||||
return new SampledSpectrum(lambdas, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
default @NotNull ColorXYZ toXYZ() {
|
|
||||||
return new ColorXYZ(
|
|
||||||
Util.innerProduct(Spectra.X, this) / ColorXYZ.CIE_Y_INTEGRAL,
|
|
||||||
Util.innerProduct(Spectra.Y, this) / ColorXYZ.CIE_Y_INTEGRAL,
|
|
||||||
Util.innerProduct(Spectra.Z, this) / ColorXYZ.CIE_Y_INTEGRAL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
default @NotNull ColorRGB toRGB(@NotNull ColorSpace cs) {
|
|
||||||
return cs.toRGB(toXYZ());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Texture
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default @NotNull Spectrum get(@NotNull HitResult hit) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default boolean isUVRequired() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* SkyBox
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default @NotNull SampledSpectrum getColor(@NotNull Ray ray) {
|
|
||||||
return this.sample(ray.lambda());
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Type {
|
|
||||||
ALBEDO, ILLUMINANT, UNBOUNDED
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package eu.jonahbauer.raytracing.render.spectrum;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
final class Util {
|
|
||||||
private Util() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double innerProduct(@NotNull Spectrum f, @NotNull Spectrum g) {
|
|
||||||
var integral = 0.0;
|
|
||||||
for (var lambda = Spectrum.LAMBDA_MIN; lambda <= Spectrum.LAMBDA_MAX; lambda++) {
|
|
||||||
integral += f.get(lambda) * g.get(lambda);
|
|
||||||
}
|
|
||||||
return integral;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,130 @@
|
|||||||
|
package eu.jonahbauer.raytracing.render.texture;
|
||||||
|
|
||||||
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
|
import eu.jonahbauer.raytracing.scene.SkyBox;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||||
|
|
||||||
|
public record Color(double r, double g, double b) implements Texture, SkyBox {
|
||||||
|
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);
|
||||||
|
public static final @NotNull Color GREEN = new Color(0.0, 1.0, 0.0);
|
||||||
|
public static final @NotNull Color BLUE = new Color(0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
public static @NotNull Color lerp(@NotNull Color a, @NotNull Color b, double t) {
|
||||||
|
if (t < 0) return a;
|
||||||
|
if (t > 1) return b;
|
||||||
|
return new Color(
|
||||||
|
(1 - t) * a.r + t * b.r,
|
||||||
|
(1 - t) * a.g + t * b.g,
|
||||||
|
(1 - t) * a.b + t * b.b
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Color multiply(@NotNull Color a, @NotNull Color b) {
|
||||||
|
return new Color(a.r() * b.r(), a.g() * b.g(), a.b() * b.b());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Color multiply(@NotNull Color a, double b) {
|
||||||
|
return new Color(a.r() * b, a.g() * b, a.b() * b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Color add(@NotNull Color a, @NotNull Color b) {
|
||||||
|
return new Color(a.r() + b.r(), a.g() + b.g(), a.b() + b.b());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Color random(@NotNull Random random) {
|
||||||
|
return new Color(random.nextDouble(), random.nextDouble(), random.nextDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Color random(@NotNull Random random, double min, double max) {
|
||||||
|
var span = max - min;
|
||||||
|
return new Color(
|
||||||
|
Math.fma(random.nextDouble(), span, min),
|
||||||
|
Math.fma(random.nextDouble(), span, min),
|
||||||
|
Math.fma(random.nextDouble(), span, min)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Color average(@NotNull Color current, @NotNull Color next, int index) {
|
||||||
|
var factor = 1d / index;
|
||||||
|
return new Color(
|
||||||
|
Math.fma(factor, next.r() - current.r(), current.r()),
|
||||||
|
Math.fma(factor, next.g() - current.g(), current.g()),
|
||||||
|
Math.fma(factor, next.b() - current.b(), current.b())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Color gamma(@NotNull Color color, double gamma) {
|
||||||
|
if (gamma == 1.0) {
|
||||||
|
return color;
|
||||||
|
} else if (gamma == 2.0) {
|
||||||
|
return new Color(
|
||||||
|
Math.sqrt(color.r()),
|
||||||
|
Math.sqrt(color.g()),
|
||||||
|
Math.sqrt(color.b())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new Color(
|
||||||
|
Math.pow(color.r(), 1 / gamma),
|
||||||
|
Math.pow(color.g(), 1 / gamma),
|
||||||
|
Math.pow(color.b(), 1 / gamma)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color {
|
||||||
|
if (DEBUG) {
|
||||||
|
if (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b)) {
|
||||||
|
throw new IllegalArgumentException("r, g and b must be finite");
|
||||||
|
}
|
||||||
|
if (r < 0 || g < 0 || b < 0) {
|
||||||
|
throw new IllegalArgumentException("r, g and b must be non-negative");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int red() {
|
||||||
|
return toInt(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int green() {
|
||||||
|
return toInt(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int blue() {
|
||||||
|
return toInt(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Color getColor(@NotNull Ray ray) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUVRequired() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toInt(double value) {
|
||||||
|
return Math.clamp((int) (255.99 * value), 0, 255);
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,21 @@
|
|||||||
package eu.jonahbauer.raytracing.scene;
|
package eu.jonahbauer.raytracing.scene;
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface SkyBox {
|
public interface SkyBox {
|
||||||
@NotNull SampledSpectrum getColor(@NotNull Ray ray);
|
@NotNull Color getColor(@NotNull Ray ray);
|
||||||
|
|
||||||
static @NotNull SkyBox gradient(@NotNull Spectrum top, @NotNull Spectrum bottom) {
|
static @NotNull SkyBox gradient(@NotNull Color top, @NotNull Color bottom) {
|
||||||
return ray -> {
|
return ray -> {
|
||||||
// altitude from -pi/2 to pi/2
|
// altitude from -pi/2 to pi/2
|
||||||
var alt = Math.copySign(
|
var alt = Math.copySign(
|
||||||
Math.acos(ray.direction().withY(0).unit().dot(ray.direction().unit())),
|
Math.acos(ray.direction().withY(0).unit().times(ray.direction().unit())),
|
||||||
ray.direction().y()
|
ray.direction().y()
|
||||||
);
|
);
|
||||||
|
return Color.lerp(bottom, top, alt / Math.PI + 0.5);
|
||||||
return SampledSpectrum.lerp(
|
|
||||||
top.sample(ray.lambda()),
|
|
||||||
bottom.sample(ray.lambda()),
|
|
||||||
alt / Math.PI + 0.5
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,531 +0,0 @@
|
|||||||
300,0.01922
|
|
||||||
301,0.222348
|
|
||||||
302,0.425476
|
|
||||||
303,0.628604
|
|
||||||
304,0.831732
|
|
||||||
305,1.03486
|
|
||||||
306,1.23799
|
|
||||||
307,1.44112
|
|
||||||
308,1.64424
|
|
||||||
309,1.84737
|
|
||||||
310,2.0505
|
|
||||||
311,2.62329
|
|
||||||
312,3.19608
|
|
||||||
313,3.76887
|
|
||||||
314,4.34166
|
|
||||||
315,4.91445
|
|
||||||
316,5.48724
|
|
||||||
317,6.06003
|
|
||||||
318,6.63282
|
|
||||||
319,7.20561
|
|
||||||
320,7.7784
|
|
||||||
321,8.47531
|
|
||||||
322,9.17222
|
|
||||||
323,9.86913
|
|
||||||
324,10.566
|
|
||||||
325,11.263
|
|
||||||
326,11.9599
|
|
||||||
327,12.6568
|
|
||||||
328,13.3537
|
|
||||||
329,14.0506
|
|
||||||
330,14.7475
|
|
||||||
331,15.0676
|
|
||||||
332,15.3876
|
|
||||||
333,15.7076
|
|
||||||
334,16.0277
|
|
||||||
335,16.3478
|
|
||||||
336,16.6678
|
|
||||||
337,16.9878
|
|
||||||
338,17.3079
|
|
||||||
339,17.628
|
|
||||||
340,17.948
|
|
||||||
341,18.2542
|
|
||||||
342,18.5603
|
|
||||||
343,18.8665
|
|
||||||
344,19.1727
|
|
||||||
345,19.4788
|
|
||||||
346,19.785
|
|
||||||
347,20.0912
|
|
||||||
348,20.3974
|
|
||||||
349,20.7035
|
|
||||||
350,21.0097
|
|
||||||
351,21.3029
|
|
||||||
352,21.5961
|
|
||||||
353,21.8894
|
|
||||||
354,22.1826
|
|
||||||
355,22.4758
|
|
||||||
356,22.769
|
|
||||||
357,23.0622
|
|
||||||
358,23.3555
|
|
||||||
359,23.6487
|
|
||||||
360,23.9419
|
|
||||||
361,24.2438
|
|
||||||
362,24.5457
|
|
||||||
363,24.8475
|
|
||||||
364,25.1494
|
|
||||||
365,25.4513
|
|
||||||
366,25.7532
|
|
||||||
367,26.0551
|
|
||||||
368,26.3569
|
|
||||||
369,26.6588
|
|
||||||
370,26.9607
|
|
||||||
371,26.7134
|
|
||||||
372,26.4661
|
|
||||||
373,26.2187
|
|
||||||
374,25.9714
|
|
||||||
375,25.7241
|
|
||||||
376,25.4768
|
|
||||||
377,25.2295
|
|
||||||
378,24.9821
|
|
||||||
379,24.7348
|
|
||||||
380,24.4875
|
|
||||||
381,25.0258
|
|
||||||
382,25.5641
|
|
||||||
383,26.1024
|
|
||||||
384,26.6407
|
|
||||||
385,27.179
|
|
||||||
386,27.7174
|
|
||||||
387,28.2557
|
|
||||||
388,28.794
|
|
||||||
389,29.3323
|
|
||||||
390,29.8706
|
|
||||||
391,31.8144
|
|
||||||
392,33.7581
|
|
||||||
393,35.7018
|
|
||||||
394,37.6456
|
|
||||||
395,39.5894
|
|
||||||
396,41.5331
|
|
||||||
397,43.4768
|
|
||||||
398,45.4206
|
|
||||||
399,47.3644
|
|
||||||
400,49.3081
|
|
||||||
401,50.0286
|
|
||||||
402,50.749
|
|
||||||
403,51.4695
|
|
||||||
404,52.19
|
|
||||||
405,52.9104
|
|
||||||
406,53.6309
|
|
||||||
407,54.3514
|
|
||||||
408,55.0719
|
|
||||||
409,55.7923
|
|
||||||
410,56.5128
|
|
||||||
411,56.8649
|
|
||||||
412,57.217
|
|
||||||
413,57.5691
|
|
||||||
414,57.9212
|
|
||||||
415,58.2733
|
|
||||||
416,58.6254
|
|
||||||
417,58.9775
|
|
||||||
418,59.3296
|
|
||||||
419,59.6817
|
|
||||||
420,60.0338
|
|
||||||
421,59.8122
|
|
||||||
422,59.5905
|
|
||||||
423,59.3689
|
|
||||||
424,59.1473
|
|
||||||
425,58.9256
|
|
||||||
426,58.704
|
|
||||||
427,58.4824
|
|
||||||
428,58.2608
|
|
||||||
429,58.0391
|
|
||||||
430,57.8175
|
|
||||||
431,59.5182
|
|
||||||
432,61.219
|
|
||||||
433,62.9197
|
|
||||||
434,64.6205
|
|
||||||
435,66.3212
|
|
||||||
436,68.0219
|
|
||||||
437,69.7227
|
|
||||||
438,71.4234
|
|
||||||
439,73.1242
|
|
||||||
440,74.8249
|
|
||||||
441,76.0671
|
|
||||||
442,77.3094
|
|
||||||
443,78.5516
|
|
||||||
444,79.7938
|
|
||||||
445,81.036
|
|
||||||
446,82.2783
|
|
||||||
447,83.5205
|
|
||||||
448,84.7627
|
|
||||||
449,86.005
|
|
||||||
450,87.2472
|
|
||||||
451,87.5837
|
|
||||||
452,87.9202
|
|
||||||
453,88.2567
|
|
||||||
454,88.5932
|
|
||||||
455,88.9297
|
|
||||||
456,89.2662
|
|
||||||
457,89.6027
|
|
||||||
458,89.9392
|
|
||||||
459,90.2757
|
|
||||||
460,90.6122
|
|
||||||
461,90.6878
|
|
||||||
462,90.7634
|
|
||||||
463,90.839
|
|
||||||
464,90.9146
|
|
||||||
465,90.9902
|
|
||||||
466,91.0657
|
|
||||||
467,91.1413
|
|
||||||
468,91.2169
|
|
||||||
469,91.2925
|
|
||||||
470,91.3681
|
|
||||||
471,91.7421
|
|
||||||
472,92.1162
|
|
||||||
473,92.4902
|
|
||||||
474,92.8643
|
|
||||||
475,93.2383
|
|
||||||
476,93.6123
|
|
||||||
477,93.9864
|
|
||||||
478,94.3604
|
|
||||||
479,94.7345
|
|
||||||
480,95.1085
|
|
||||||
481,94.7939
|
|
||||||
482,94.4793
|
|
||||||
483,94.1648
|
|
||||||
484,93.8502
|
|
||||||
485,93.5356
|
|
||||||
486,93.221
|
|
||||||
487,92.9064
|
|
||||||
488,92.5919
|
|
||||||
489,92.2773
|
|
||||||
490,91.9627
|
|
||||||
491,92.3388
|
|
||||||
492,92.7149
|
|
||||||
493,93.091
|
|
||||||
494,93.4671
|
|
||||||
495,93.8432
|
|
||||||
496,94.2193
|
|
||||||
497,94.5954
|
|
||||||
498,94.9715
|
|
||||||
499,95.3476
|
|
||||||
500,95.7237
|
|
||||||
501,95.8127
|
|
||||||
502,95.9016
|
|
||||||
503,95.9906
|
|
||||||
504,96.0795
|
|
||||||
505,96.1685
|
|
||||||
506,96.2575
|
|
||||||
507,96.3464
|
|
||||||
508,96.4354
|
|
||||||
509,96.5243
|
|
||||||
510,96.6133
|
|
||||||
511,96.6649
|
|
||||||
512,96.7164
|
|
||||||
513,96.768
|
|
||||||
514,96.8196
|
|
||||||
515,96.8712
|
|
||||||
516,96.9227
|
|
||||||
517,96.9743
|
|
||||||
518,97.0259
|
|
||||||
519,97.0774
|
|
||||||
520,97.129
|
|
||||||
521,97.626
|
|
||||||
522,98.123
|
|
||||||
523,98.62
|
|
||||||
524,99.117
|
|
||||||
525,99.614
|
|
||||||
526,100.111
|
|
||||||
527,100.608
|
|
||||||
528,101.105
|
|
||||||
529,101.602
|
|
||||||
530,102.099
|
|
||||||
531,101.965
|
|
||||||
532,101.83
|
|
||||||
533,101.696
|
|
||||||
534,101.561
|
|
||||||
535,101.427
|
|
||||||
536,101.292
|
|
||||||
537,101.158
|
|
||||||
538,101.024
|
|
||||||
539,100.889
|
|
||||||
540,100.755
|
|
||||||
541,100.911
|
|
||||||
542,101.067
|
|
||||||
543,101.223
|
|
||||||
544,101.38
|
|
||||||
545,101.536
|
|
||||||
546,101.692
|
|
||||||
547,101.848
|
|
||||||
548,102.005
|
|
||||||
549,102.161
|
|
||||||
550,102.317
|
|
||||||
551,102.085
|
|
||||||
552,101.854
|
|
||||||
553,101.622
|
|
||||||
554,101.39
|
|
||||||
555,101.158
|
|
||||||
556,100.927
|
|
||||||
557,100.695
|
|
||||||
558,100.463
|
|
||||||
559,100.232
|
|
||||||
560,100
|
|
||||||
561,99.7735
|
|
||||||
562,99.547
|
|
||||||
563,99.3205
|
|
||||||
564,99.094
|
|
||||||
565,98.8675
|
|
||||||
566,98.641
|
|
||||||
567,98.4145
|
|
||||||
568,98.188
|
|
||||||
569,97.9615
|
|
||||||
570,97.735
|
|
||||||
571,97.8533
|
|
||||||
572,97.9716
|
|
||||||
573,98.0899
|
|
||||||
574,98.2082
|
|
||||||
575,98.3265
|
|
||||||
576,98.4448
|
|
||||||
577,98.5631
|
|
||||||
578,98.6814
|
|
||||||
579,98.7997
|
|
||||||
580,98.918
|
|
||||||
581,98.3761
|
|
||||||
582,97.8342
|
|
||||||
583,97.2922
|
|
||||||
584,96.7503
|
|
||||||
585,96.2084
|
|
||||||
586,95.6665
|
|
||||||
587,95.1246
|
|
||||||
588,94.5826
|
|
||||||
589,94.0407
|
|
||||||
590,93.4988
|
|
||||||
591,93.9177
|
|
||||||
592,94.3366
|
|
||||||
593,94.7555
|
|
||||||
594,95.1744
|
|
||||||
595,95.5933
|
|
||||||
596,96.0122
|
|
||||||
597,96.4311
|
|
||||||
598,96.85
|
|
||||||
599,97.2689
|
|
||||||
600,97.6878
|
|
||||||
601,97.8459
|
|
||||||
602,98.0041
|
|
||||||
603,98.1622
|
|
||||||
604,98.3203
|
|
||||||
605,98.4784
|
|
||||||
606,98.6366
|
|
||||||
607,98.7947
|
|
||||||
608,98.9528
|
|
||||||
609,99.111
|
|
||||||
610,99.2691
|
|
||||||
611,99.2463
|
|
||||||
612,99.2236
|
|
||||||
613,99.2008
|
|
||||||
614,99.1781
|
|
||||||
615,99.1553
|
|
||||||
616,99.1325
|
|
||||||
617,99.1098
|
|
||||||
618,99.087
|
|
||||||
619,99.0643
|
|
||||||
620,99.0415
|
|
||||||
621,98.7095
|
|
||||||
622,98.3776
|
|
||||||
623,98.0456
|
|
||||||
624,97.7136
|
|
||||||
625,97.3816
|
|
||||||
626,97.0497
|
|
||||||
627,96.7177
|
|
||||||
628,96.3857
|
|
||||||
629,96.0538
|
|
||||||
630,95.7218
|
|
||||||
631,96.0353
|
|
||||||
632,96.3489
|
|
||||||
633,96.6624
|
|
||||||
634,96.976
|
|
||||||
635,97.2895
|
|
||||||
636,97.603
|
|
||||||
637,97.9166
|
|
||||||
638,98.2301
|
|
||||||
639,98.5437
|
|
||||||
640,98.8572
|
|
||||||
641,98.5382
|
|
||||||
642,98.2192
|
|
||||||
643,97.9002
|
|
||||||
644,97.5812
|
|
||||||
645,97.2622
|
|
||||||
646,96.9432
|
|
||||||
647,96.6242
|
|
||||||
648,96.3052
|
|
||||||
649,95.9862
|
|
||||||
650,95.6672
|
|
||||||
651,95.9195
|
|
||||||
652,96.1717
|
|
||||||
653,96.424
|
|
||||||
654,96.6762
|
|
||||||
655,96.9285
|
|
||||||
656,97.1808
|
|
||||||
657,97.433
|
|
||||||
658,97.6853
|
|
||||||
659,97.9375
|
|
||||||
660,98.1898
|
|
||||||
661,98.6712
|
|
||||||
662,99.1525
|
|
||||||
663,99.6339
|
|
||||||
664,100.115
|
|
||||||
665,100.597
|
|
||||||
666,101.078
|
|
||||||
667,101.559
|
|
||||||
668,102.041
|
|
||||||
669,102.522
|
|
||||||
670,103.003
|
|
||||||
671,102.616
|
|
||||||
672,102.229
|
|
||||||
673,101.842
|
|
||||||
674,101.455
|
|
||||||
675,101.068
|
|
||||||
676,100.681
|
|
||||||
677,100.294
|
|
||||||
678,99.9071
|
|
||||||
679,99.52
|
|
||||||
680,99.133
|
|
||||||
681,97.9578
|
|
||||||
682,96.7826
|
|
||||||
683,95.6074
|
|
||||||
684,94.4322
|
|
||||||
685,93.257
|
|
||||||
686,92.0817
|
|
||||||
687,90.9065
|
|
||||||
688,89.7313
|
|
||||||
689,88.5561
|
|
||||||
690,87.3809
|
|
||||||
691,87.8032
|
|
||||||
692,88.2254
|
|
||||||
693,88.6477
|
|
||||||
694,89.0699
|
|
||||||
695,89.4922
|
|
||||||
696,89.9145
|
|
||||||
697,90.3367
|
|
||||||
698,90.759
|
|
||||||
699,91.1812
|
|
||||||
700,91.6035
|
|
||||||
701,91.732
|
|
||||||
702,91.8605
|
|
||||||
703,91.989
|
|
||||||
704,92.1175
|
|
||||||
705,92.246
|
|
||||||
706,92.3746
|
|
||||||
707,92.5031
|
|
||||||
708,92.6316
|
|
||||||
709,92.7601
|
|
||||||
710,92.8886
|
|
||||||
711,91.2852
|
|
||||||
712,89.6818
|
|
||||||
713,88.0783
|
|
||||||
714,86.4749
|
|
||||||
715,84.8715
|
|
||||||
716,83.2681
|
|
||||||
717,81.6647
|
|
||||||
718,80.0612
|
|
||||||
719,78.4578
|
|
||||||
720,76.8544
|
|
||||||
721,77.8201
|
|
||||||
722,78.7858
|
|
||||||
723,79.7514
|
|
||||||
724,80.7171
|
|
||||||
725,81.6828
|
|
||||||
726,82.6485
|
|
||||||
727,83.6142
|
|
||||||
728,84.5798
|
|
||||||
729,85.5455
|
|
||||||
730,86.5112
|
|
||||||
731,87.1181
|
|
||||||
732,87.7249
|
|
||||||
733,88.3318
|
|
||||||
734,88.9386
|
|
||||||
735,89.5455
|
|
||||||
736,90.1524
|
|
||||||
737,90.7592
|
|
||||||
738,91.3661
|
|
||||||
739,91.9729
|
|
||||||
740,92.5798
|
|
||||||
741,91.1448
|
|
||||||
742,89.7098
|
|
||||||
743,88.2748
|
|
||||||
744,86.8398
|
|
||||||
745,85.4048
|
|
||||||
746,83.9699
|
|
||||||
747,82.5349
|
|
||||||
748,81.0999
|
|
||||||
749,79.6649
|
|
||||||
750,78.2299
|
|
||||||
751,76.1761
|
|
||||||
752,74.1223
|
|
||||||
753,72.0685
|
|
||||||
754,70.0147
|
|
||||||
755,67.9608
|
|
||||||
756,65.907
|
|
||||||
757,63.8532
|
|
||||||
758,61.7994
|
|
||||||
759,59.7456
|
|
||||||
760,57.6918
|
|
||||||
761,60.2149
|
|
||||||
762,62.738
|
|
||||||
763,65.2612
|
|
||||||
764,67.7843
|
|
||||||
765,70.3074
|
|
||||||
766,72.8305
|
|
||||||
767,75.3536
|
|
||||||
768,77.8768
|
|
||||||
769,80.3999
|
|
||||||
770,82.923
|
|
||||||
771,82.4581
|
|
||||||
772,81.9932
|
|
||||||
773,81.5283
|
|
||||||
774,81.0634
|
|
||||||
775,80.5985
|
|
||||||
776,80.1336
|
|
||||||
777,79.6687
|
|
||||||
778,79.2038
|
|
||||||
779,78.7389
|
|
||||||
780,78.274
|
|
||||||
781,78.402
|
|
||||||
782,78.5301
|
|
||||||
783,78.6581
|
|
||||||
784,78.7862
|
|
||||||
785,78.9142
|
|
||||||
786,79.0422
|
|
||||||
787,79.1703
|
|
||||||
788,79.2983
|
|
||||||
789,79.4264
|
|
||||||
790,79.5544
|
|
||||||
791,78.9391
|
|
||||||
792,78.3238
|
|
||||||
793,77.7085
|
|
||||||
794,77.0932
|
|
||||||
795,76.478
|
|
||||||
796,75.8627
|
|
||||||
797,75.2474
|
|
||||||
798,74.6321
|
|
||||||
799,74.0168
|
|
||||||
800,73.4015
|
|
||||||
801,72.4534
|
|
||||||
802,71.5052
|
|
||||||
803,70.5571
|
|
||||||
804,69.609
|
|
||||||
805,68.6608
|
|
||||||
806,67.7127
|
|
||||||
807,66.7646
|
|
||||||
808,65.8165
|
|
||||||
809,64.8683
|
|
||||||
810,63.9202
|
|
||||||
811,64.6059
|
|
||||||
812,65.2916
|
|
||||||
813,65.9772
|
|
||||||
814,66.6629
|
|
||||||
815,67.3486
|
|
||||||
816,68.0343
|
|
||||||
817,68.72
|
|
||||||
818,69.4056
|
|
||||||
819,70.0913
|
|
||||||
820,70.777
|
|
||||||
821,71.1435
|
|
||||||
822,71.5099
|
|
||||||
823,71.8764
|
|
||||||
824,72.2429
|
|
||||||
825,72.6094
|
|
||||||
826,72.9758
|
|
||||||
827,73.3423
|
|
||||||
828,73.7088
|
|
||||||
829,74.0752
|
|
||||||
830,74.4417
|
|
|
@ -1,531 +0,0 @@
|
|||||||
300,0.0341
|
|
||||||
301,0.36014
|
|
||||||
302,0.68618
|
|
||||||
303,1.01222
|
|
||||||
304,1.33826
|
|
||||||
305,1.6643
|
|
||||||
306,1.99034
|
|
||||||
307,2.31638
|
|
||||||
308,2.64242
|
|
||||||
309,2.96846
|
|
||||||
310,3.2945
|
|
||||||
311,4.98865
|
|
||||||
312,6.6828
|
|
||||||
313,8.37695
|
|
||||||
314,10.0711
|
|
||||||
315,11.7652
|
|
||||||
316,13.4594
|
|
||||||
317,15.1535
|
|
||||||
318,16.8477
|
|
||||||
319,18.5418
|
|
||||||
320,20.236
|
|
||||||
321,21.9177
|
|
||||||
322,23.5995
|
|
||||||
323,25.2812
|
|
||||||
324,26.963
|
|
||||||
325,28.6447
|
|
||||||
326,30.3265
|
|
||||||
327,32.0082
|
|
||||||
328,33.69
|
|
||||||
329,35.3717
|
|
||||||
330,37.0535
|
|
||||||
331,37.343
|
|
||||||
332,37.6326
|
|
||||||
333,37.9221
|
|
||||||
334,38.2116
|
|
||||||
335,38.5011
|
|
||||||
336,38.7907
|
|
||||||
337,39.0802
|
|
||||||
338,39.3697
|
|
||||||
339,39.6593
|
|
||||||
340,39.9488
|
|
||||||
341,40.4451
|
|
||||||
342,40.9414
|
|
||||||
343,41.4377
|
|
||||||
344,41.934
|
|
||||||
345,42.4302
|
|
||||||
346,42.9265
|
|
||||||
347,43.4228
|
|
||||||
348,43.9191
|
|
||||||
349,44.4154
|
|
||||||
350,44.9117
|
|
||||||
351,45.0844
|
|
||||||
352,45.257
|
|
||||||
353,45.4297
|
|
||||||
354,45.6023
|
|
||||||
355,45.775
|
|
||||||
356,45.9477
|
|
||||||
357,46.1203
|
|
||||||
358,46.293
|
|
||||||
359,46.4656
|
|
||||||
360,46.6383
|
|
||||||
361,47.1834
|
|
||||||
362,47.7285
|
|
||||||
363,48.2735
|
|
||||||
364,48.8186
|
|
||||||
365,49.3637
|
|
||||||
366,49.9088
|
|
||||||
367,50.4539
|
|
||||||
368,50.9989
|
|
||||||
369,51.544
|
|
||||||
370,52.0891
|
|
||||||
371,51.8777
|
|
||||||
372,51.6664
|
|
||||||
373,51.455
|
|
||||||
374,51.2437
|
|
||||||
375,51.0323
|
|
||||||
376,50.8209
|
|
||||||
377,50.6096
|
|
||||||
378,50.3982
|
|
||||||
379,50.1869
|
|
||||||
380,49.9755
|
|
||||||
381,50.4428
|
|
||||||
382,50.91
|
|
||||||
383,51.3773
|
|
||||||
384,51.8446
|
|
||||||
385,52.3118
|
|
||||||
386,52.7791
|
|
||||||
387,53.2464
|
|
||||||
388,53.7137
|
|
||||||
389,54.1809
|
|
||||||
390,54.6482
|
|
||||||
391,57.4589
|
|
||||||
392,60.2695
|
|
||||||
393,63.0802
|
|
||||||
394,65.8909
|
|
||||||
395,68.7015
|
|
||||||
396,71.5122
|
|
||||||
397,74.3229
|
|
||||||
398,77.1336
|
|
||||||
399,79.9442
|
|
||||||
400,82.7549
|
|
||||||
401,83.628
|
|
||||||
402,84.5011
|
|
||||||
403,85.3742
|
|
||||||
404,86.2473
|
|
||||||
405,87.1204
|
|
||||||
406,87.9936
|
|
||||||
407,88.8667
|
|
||||||
408,89.7398
|
|
||||||
409,90.6129
|
|
||||||
410,91.486
|
|
||||||
411,91.6806
|
|
||||||
412,91.8752
|
|
||||||
413,92.0697
|
|
||||||
414,92.2643
|
|
||||||
415,92.4589
|
|
||||||
416,92.6535
|
|
||||||
417,92.8481
|
|
||||||
418,93.0426
|
|
||||||
419,93.2372
|
|
||||||
420,93.4318
|
|
||||||
421,92.7568
|
|
||||||
422,92.0819
|
|
||||||
423,91.4069
|
|
||||||
424,90.732
|
|
||||||
425,90.057
|
|
||||||
426,89.3821
|
|
||||||
427,88.7071
|
|
||||||
428,88.0322
|
|
||||||
429,87.3572
|
|
||||||
430,86.6823
|
|
||||||
431,88.5006
|
|
||||||
432,90.3188
|
|
||||||
433,92.1371
|
|
||||||
434,93.9554
|
|
||||||
435,95.7736
|
|
||||||
436,97.5919
|
|
||||||
437,99.4102
|
|
||||||
438,101.228
|
|
||||||
439,103.047
|
|
||||||
440,104.865
|
|
||||||
441,106.079
|
|
||||||
442,107.294
|
|
||||||
443,108.508
|
|
||||||
444,109.722
|
|
||||||
445,110.936
|
|
||||||
446,112.151
|
|
||||||
447,113.365
|
|
||||||
448,114.579
|
|
||||||
449,115.794
|
|
||||||
450,117.008
|
|
||||||
451,117.088
|
|
||||||
452,117.169
|
|
||||||
453,117.249
|
|
||||||
454,117.33
|
|
||||||
455,117.41
|
|
||||||
456,117.49
|
|
||||||
457,117.571
|
|
||||||
458,117.651
|
|
||||||
459,117.732
|
|
||||||
460,117.812
|
|
||||||
461,117.517
|
|
||||||
462,117.222
|
|
||||||
463,116.927
|
|
||||||
464,116.632
|
|
||||||
465,116.336
|
|
||||||
466,116.041
|
|
||||||
467,115.746
|
|
||||||
468,115.451
|
|
||||||
469,115.156
|
|
||||||
470,114.861
|
|
||||||
471,114.967
|
|
||||||
472,115.073
|
|
||||||
473,115.18
|
|
||||||
474,115.286
|
|
||||||
475,115.392
|
|
||||||
476,115.498
|
|
||||||
477,115.604
|
|
||||||
478,115.711
|
|
||||||
479,115.817
|
|
||||||
480,115.923
|
|
||||||
481,115.212
|
|
||||||
482,114.501
|
|
||||||
483,113.789
|
|
||||||
484,113.078
|
|
||||||
485,112.367
|
|
||||||
486,111.656
|
|
||||||
487,110.945
|
|
||||||
488,110.233
|
|
||||||
489,109.522
|
|
||||||
490,108.811
|
|
||||||
491,108.865
|
|
||||||
492,108.92
|
|
||||||
493,108.974
|
|
||||||
494,109.028
|
|
||||||
495,109.082
|
|
||||||
496,109.137
|
|
||||||
497,109.191
|
|
||||||
498,109.245
|
|
||||||
499,109.3
|
|
||||||
500,109.354
|
|
||||||
501,109.199
|
|
||||||
502,109.044
|
|
||||||
503,108.888
|
|
||||||
504,108.733
|
|
||||||
505,108.578
|
|
||||||
506,108.423
|
|
||||||
507,108.268
|
|
||||||
508,108.112
|
|
||||||
509,107.957
|
|
||||||
510,107.802
|
|
||||||
511,107.501
|
|
||||||
512,107.2
|
|
||||||
513,106.898
|
|
||||||
514,106.597
|
|
||||||
515,106.296
|
|
||||||
516,105.995
|
|
||||||
517,105.694
|
|
||||||
518,105.392
|
|
||||||
519,105.091
|
|
||||||
520,104.79
|
|
||||||
521,105.08
|
|
||||||
522,105.37
|
|
||||||
523,105.66
|
|
||||||
524,105.95
|
|
||||||
525,106.239
|
|
||||||
526,106.529
|
|
||||||
527,106.819
|
|
||||||
528,107.109
|
|
||||||
529,107.399
|
|
||||||
530,107.689
|
|
||||||
531,107.361
|
|
||||||
532,107.032
|
|
||||||
533,106.704
|
|
||||||
534,106.375
|
|
||||||
535,106.047
|
|
||||||
536,105.719
|
|
||||||
537,105.39
|
|
||||||
538,105.062
|
|
||||||
539,104.733
|
|
||||||
540,104.405
|
|
||||||
541,104.369
|
|
||||||
542,104.333
|
|
||||||
543,104.297
|
|
||||||
544,104.261
|
|
||||||
545,104.225
|
|
||||||
546,104.19
|
|
||||||
547,104.154
|
|
||||||
548,104.118
|
|
||||||
549,104.082
|
|
||||||
550,104.046
|
|
||||||
551,103.641
|
|
||||||
552,103.237
|
|
||||||
553,102.832
|
|
||||||
554,102.428
|
|
||||||
555,102.023
|
|
||||||
556,101.618
|
|
||||||
557,101.214
|
|
||||||
558,100.809
|
|
||||||
559,100.405
|
|
||||||
560,100
|
|
||||||
561,99.6334
|
|
||||||
562,99.2668
|
|
||||||
563,98.9003
|
|
||||||
564,98.5337
|
|
||||||
565,98.1671
|
|
||||||
566,97.8005
|
|
||||||
567,97.4339
|
|
||||||
568,97.0674
|
|
||||||
569,96.7008
|
|
||||||
570,96.3342
|
|
||||||
571,96.2796
|
|
||||||
572,96.225
|
|
||||||
573,96.1703
|
|
||||||
574,96.1157
|
|
||||||
575,96.0611
|
|
||||||
576,96.0065
|
|
||||||
577,95.9519
|
|
||||||
578,95.8972
|
|
||||||
579,95.8426
|
|
||||||
580,95.788
|
|
||||||
581,95.0778
|
|
||||||
582,94.3675
|
|
||||||
583,93.6573
|
|
||||||
584,92.947
|
|
||||||
585,92.2368
|
|
||||||
586,91.5266
|
|
||||||
587,90.8163
|
|
||||||
588,90.1061
|
|
||||||
589,89.3958
|
|
||||||
590,88.6856
|
|
||||||
591,88.8177
|
|
||||||
592,88.9497
|
|
||||||
593,89.0818
|
|
||||||
594,89.2138
|
|
||||||
595,89.3459
|
|
||||||
596,89.478
|
|
||||||
597,89.61
|
|
||||||
598,89.7421
|
|
||||||
599,89.8741
|
|
||||||
600,90.0062
|
|
||||||
601,89.9655
|
|
||||||
602,89.9248
|
|
||||||
603,89.8841
|
|
||||||
604,89.8434
|
|
||||||
605,89.8026
|
|
||||||
606,89.7619
|
|
||||||
607,89.7212
|
|
||||||
608,89.6805
|
|
||||||
609,89.6398
|
|
||||||
610,89.5991
|
|
||||||
611,89.4091
|
|
||||||
612,89.219
|
|
||||||
613,89.029
|
|
||||||
614,88.8389
|
|
||||||
615,88.6489
|
|
||||||
616,88.4589
|
|
||||||
617,88.2688
|
|
||||||
618,88.0788
|
|
||||||
619,87.8887
|
|
||||||
620,87.6987
|
|
||||||
621,87.2577
|
|
||||||
622,86.8167
|
|
||||||
623,86.3757
|
|
||||||
624,85.9347
|
|
||||||
625,85.4936
|
|
||||||
626,85.0526
|
|
||||||
627,84.6116
|
|
||||||
628,84.1706
|
|
||||||
629,83.7296
|
|
||||||
630,83.2886
|
|
||||||
631,83.3297
|
|
||||||
632,83.3707
|
|
||||||
633,83.4118
|
|
||||||
634,83.4528
|
|
||||||
635,83.4939
|
|
||||||
636,83.535
|
|
||||||
637,83.576
|
|
||||||
638,83.6171
|
|
||||||
639,83.6581
|
|
||||||
640,83.6992
|
|
||||||
641,83.332
|
|
||||||
642,82.9647
|
|
||||||
643,82.5975
|
|
||||||
644,82.2302
|
|
||||||
645,81.863
|
|
||||||
646,81.4958
|
|
||||||
647,81.1285
|
|
||||||
648,80.7613
|
|
||||||
649,80.394
|
|
||||||
650,80.0268
|
|
||||||
651,80.0456
|
|
||||||
652,80.0644
|
|
||||||
653,80.0831
|
|
||||||
654,80.1019
|
|
||||||
655,80.1207
|
|
||||||
656,80.1395
|
|
||||||
657,80.1583
|
|
||||||
658,80.177
|
|
||||||
659,80.1958
|
|
||||||
660,80.2146
|
|
||||||
661,80.4209
|
|
||||||
662,80.6272
|
|
||||||
663,80.8336
|
|
||||||
664,81.0399
|
|
||||||
665,81.2462
|
|
||||||
666,81.4525
|
|
||||||
667,81.6588
|
|
||||||
668,81.8652
|
|
||||||
669,82.0715
|
|
||||||
670,82.2778
|
|
||||||
671,81.8784
|
|
||||||
672,81.4791
|
|
||||||
673,81.0797
|
|
||||||
674,80.6804
|
|
||||||
675,80.281
|
|
||||||
676,79.8816
|
|
||||||
677,79.4823
|
|
||||||
678,79.0829
|
|
||||||
679,78.6836
|
|
||||||
680,78.2842
|
|
||||||
681,77.4279
|
|
||||||
682,76.5716
|
|
||||||
683,75.7153
|
|
||||||
684,74.859
|
|
||||||
685,74.0027
|
|
||||||
686,73.1465
|
|
||||||
687,72.2902
|
|
||||||
688,71.4339
|
|
||||||
689,70.5776
|
|
||||||
690,69.7213
|
|
||||||
691,69.9101
|
|
||||||
692,70.0989
|
|
||||||
693,70.2876
|
|
||||||
694,70.4764
|
|
||||||
695,70.6652
|
|
||||||
696,70.854
|
|
||||||
697,71.0428
|
|
||||||
698,71.2315
|
|
||||||
699,71.4203
|
|
||||||
700,71.6091
|
|
||||||
701,71.8831
|
|
||||||
702,72.1571
|
|
||||||
703,72.4311
|
|
||||||
704,72.7051
|
|
||||||
705,72.979
|
|
||||||
706,73.253
|
|
||||||
707,73.527
|
|
||||||
708,73.801
|
|
||||||
709,74.075
|
|
||||||
710,74.349
|
|
||||||
711,73.0745
|
|
||||||
712,71.8
|
|
||||||
713,70.5255
|
|
||||||
714,69.251
|
|
||||||
715,67.9765
|
|
||||||
716,66.702
|
|
||||||
717,65.4275
|
|
||||||
718,64.153
|
|
||||||
719,62.8785
|
|
||||||
720,61.604
|
|
||||||
721,62.4322
|
|
||||||
722,63.2603
|
|
||||||
723,64.0885
|
|
||||||
724,64.9166
|
|
||||||
725,65.7448
|
|
||||||
726,66.573
|
|
||||||
727,67.4011
|
|
||||||
728,68.2293
|
|
||||||
729,69.0574
|
|
||||||
730,69.8856
|
|
||||||
731,70.4057
|
|
||||||
732,70.9259
|
|
||||||
733,71.446
|
|
||||||
734,71.9662
|
|
||||||
735,72.4863
|
|
||||||
736,73.0064
|
|
||||||
737,73.5266
|
|
||||||
738,74.0467
|
|
||||||
739,74.5669
|
|
||||||
740,75.087
|
|
||||||
741,73.9376
|
|
||||||
742,72.7881
|
|
||||||
743,71.6387
|
|
||||||
744,70.4893
|
|
||||||
745,69.3398
|
|
||||||
746,68.1904
|
|
||||||
747,67.041
|
|
||||||
748,65.8916
|
|
||||||
749,64.7421
|
|
||||||
750,63.5927
|
|
||||||
751,61.8752
|
|
||||||
752,60.1578
|
|
||||||
753,58.4403
|
|
||||||
754,56.7229
|
|
||||||
755,55.0054
|
|
||||||
756,53.288
|
|
||||||
757,51.5705
|
|
||||||
758,49.8531
|
|
||||||
759,48.1356
|
|
||||||
760,46.4182
|
|
||||||
761,48.4569
|
|
||||||
762,50.4956
|
|
||||||
763,52.5344
|
|
||||||
764,54.5731
|
|
||||||
765,56.6118
|
|
||||||
766,58.6505
|
|
||||||
767,60.6892
|
|
||||||
768,62.728
|
|
||||||
769,64.7667
|
|
||||||
770,66.8054
|
|
||||||
771,66.4631
|
|
||||||
772,66.1209
|
|
||||||
773,65.7786
|
|
||||||
774,65.4364
|
|
||||||
775,65.0941
|
|
||||||
776,64.7518
|
|
||||||
777,64.4096
|
|
||||||
778,64.0673
|
|
||||||
779,63.7251
|
|
||||||
780,63.3828
|
|
||||||
781,63.4749
|
|
||||||
782,63.567
|
|
||||||
783,63.6592
|
|
||||||
784,63.7513
|
|
||||||
785,63.8434
|
|
||||||
786,63.9355
|
|
||||||
787,64.0276
|
|
||||||
788,64.1198
|
|
||||||
789,64.2119
|
|
||||||
790,64.304
|
|
||||||
791,63.8188
|
|
||||||
792,63.3336
|
|
||||||
793,62.8484
|
|
||||||
794,62.3632
|
|
||||||
795,61.8779
|
|
||||||
796,61.3927
|
|
||||||
797,60.9075
|
|
||||||
798,60.4223
|
|
||||||
799,59.9371
|
|
||||||
800,59.4519
|
|
||||||
801,58.7026
|
|
||||||
802,57.9533
|
|
||||||
803,57.204
|
|
||||||
804,56.4547
|
|
||||||
805,55.7054
|
|
||||||
806,54.9562
|
|
||||||
807,54.2069
|
|
||||||
808,53.4576
|
|
||||||
809,52.7083
|
|
||||||
810,51.959
|
|
||||||
811,52.5072
|
|
||||||
812,53.0553
|
|
||||||
813,53.6035
|
|
||||||
814,54.1516
|
|
||||||
815,54.6998
|
|
||||||
816,55.248
|
|
||||||
817,55.7961
|
|
||||||
818,56.3443
|
|
||||||
819,56.8924
|
|
||||||
820,57.4406
|
|
||||||
821,57.7278
|
|
||||||
822,58.015
|
|
||||||
823,58.3022
|
|
||||||
824,58.5894
|
|
||||||
825,58.8765
|
|
||||||
826,59.1637
|
|
||||||
827,59.4509
|
|
||||||
828,59.7381
|
|
||||||
829,60.0253
|
|
||||||
830,60.3125
|
|
|
Loading…
Reference in New Issue