Compare commits

..

11 commits

18 changed files with 1906 additions and 107 deletions

View file

@ -2,13 +2,13 @@
<library name="google.guava" type="repository"> <library name="google.guava" type="repository">
<properties maven-id="com.google.guava:guava:RELEASE" /> <properties maven-id="com.google.guava:guava:RELEASE" />
<CLASSES> <CLASSES>
<root url="jar://$PROJECT_DIR$/lib/guava-31.1-jre.jar!/" /> <root url="jar://$PROJECT_DIR$/lib/guava-32.1.2-jre.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/failureaccess-1.0.1.jar!/" /> <root url="jar://$PROJECT_DIR$/lib/failureaccess-1.0.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar!/" /> <root url="jar://$PROJECT_DIR$/lib/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/jsr305-3.0.2.jar!/" /> <root url="jar://$PROJECT_DIR$/lib/jsr305-3.0.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/checker-qual-3.12.0.jar!/" /> <root url="jar://$PROJECT_DIR$/lib/checker-qual-3.33.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/error_prone_annotations-2.11.0.jar!/" /> <root url="jar://$PROJECT_DIR$/lib/error_prone_annotations-2.18.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/j2objc-annotations-1.3.jar!/" /> <root url="jar://$PROJECT_DIR$/lib/j2objc-annotations-2.8.jar!/" />
</CLASSES> </CLASSES>
<JAVADOC /> <JAVADOC />
<SOURCES /> <SOURCES />

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="liberica-19" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

View file

@ -104,6 +104,23 @@ public class Color {
return oklabPolar(v.x(), v.y(), v.z()); return oklabPolar(v.x(), v.y(), v.z());
} }
static double clipDoubleXyz(double x) {
if (x < 0.0)
return 0.0;
return x;
}
public static Color xyz(double x, double y, double z) {
double r = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z;
double g = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z;
double b = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z;
r = clipDoubleXyz(r);
g = clipDoubleXyz(g);
b = clipDoubleXyz(b);
return Color.rgb(r,g,b);
}
public static Color code(int code) { public static Color code(int code) {
return rgb( return rgb(

View file

@ -0,0 +1,14 @@
package xyz.marsavic.gfxlab;
import javafx.util.Pair;
import java.util.Arrays;
import java.util.Comparator;
@FunctionalInterface
public interface Spectrum {
public double at(double wavelength);
public final Spectrum WHITE = w -> 1.0;
public final Spectrum BLACK = w -> 0.0;
}

View file

@ -0,0 +1,85 @@
package xyz.marsavic.gfxlab;
import javafx.util.Pair;
import java.util.Arrays;
import java.util.Comparator;
public class SplineSpectrum implements Spectrum {
Pair<Double, Double>[] samples;
double[] m;
public SplineSpectrum(Pair<Double, Double>[] samples) {
if (samples == null) {
throw new NullPointerException();
}
this.samples = samples;
int n = samples.length;
double[] d = new double[n - 1];
m = new double[n];
for (int i = 1; i < samples.length; i++) {
double h = samples[i].getKey() - samples[i - 1].getKey();
if (h <= 0.0)
throw new IllegalArgumentException("Samples must have strictly increasing x coordinates.");
d[i - 1] = (samples[i].getValue() - samples[i - 1].getValue());
}
m[0] = d[0];
for (int i = 1; i < n - 1; i++) {
if (d[i] == 0.0) {
m[i] = 0.0;
m[i + 1] = 0.0;
} else {
double a = m[i] / d[i];
double b = m[i + 1] / d[i];
double h = a*a+b*b;
if (h > 9.0) {
double t = 3.0 / h;
m[i] = t * a * d[i];
m[i + 1] = t * b * d[i];
}
}
}
}
@Override
public double at(double wavelength) {
final int n = samples.length;
if (wavelength <= samples[0].getKey()) {
return samples[0].getValue();
}
if (wavelength >= samples[n-1].getKey()) {
return samples[n-1].getValue();
}
int i = Arrays.binarySearch(samples,
new Pair<Double, Double>(Double.valueOf(wavelength), null),
new DoublePairKeyComparator());
if (i >= 0) {
return samples[i].getValue();
}
i = -(i+2); // Invert negative result and get index of previous element
double h = samples[i+1].getKey() - samples[i].getKey();
double t = (wavelength - samples[i].getKey()) / h;
return (samples[i].getValue() * (1 + 2*t) + h*m[i]*t) * (1 - t) * (1 - t)
+ (samples[i+1].getValue() * (3 - 2*t) + h * m[i+1] * (t - 1)) * t * t;
}
}
class DoublePairKeyComparator implements Comparator<Pair<Double, Double>> {
@Override
public int compare(Pair<Double, Double> o1, Pair<Double, Double> o2) {
double k1 = o1.getKey(), k2 = o2.getKey();
if (k1 > k2)
return 1;
else if (k1 < k2) {
return -1;
} else {
return 0;
}
}
}

View file

@ -1,20 +1,20 @@
package xyz.marsavic.gfxlab.graphics3d; package xyz.marsavic.gfxlab.graphics3d;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Spectrum;
import xyz.marsavic.gfxlab.Vec3; import xyz.marsavic.gfxlab.Vec3;
/** Point light. */ /** Point light. */
public record Light( public record Light(
Vec3 p, Vec3 p,
Color c Spectrum s
) { ) {
public static Light pc(Vec3 p, Color c) { public static Light ps(Vec3 p, Spectrum s) {
return new Light(p, c); return new Light(p, s);
} }
public static Light p(Vec3 p) { public static Light p(Vec3 p) {
return pc(p, Color.WHITE); return ps(p, wavelength -> 1.0);
} }
} }

View file

@ -1,30 +1,46 @@
package xyz.marsavic.gfxlab.graphics3d; package xyz.marsavic.gfxlab.graphics3d;
import javafx.util.Pair;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Spectrum;
import xyz.marsavic.gfxlab.SplineSpectrum;
public record Material( public record Material(
Color diffuse, Spectrum diffuse,
Color specular, Spectrum specular,
Spectrum emissive,
double shininess, double shininess,
Color reflective, Spectrum reflective,
Color refractive, Spectrum refractive,
double refractiveIndex Spectrum refractiveIndex
) { ) {
public Material diffuse (Color diffuse ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material diffuse (Spectrum diffuse ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material specular (Color specular ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material specular (Spectrum specular ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material shininess (double shininess ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material emissive (Spectrum emissive ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material reflective (Color reflective ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material shininess (double shininess ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material refractive (Color refractive ) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material reflective (Spectrum reflective ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material refractiveIndex(double refractiveIndex) { return new Material(diffuse, specular, shininess, reflective, refractive, refractiveIndex); } public Material refractive (Spectrum refractive ) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
public Material refractiveIndex(Spectrum refractiveIndex) { return new Material(diffuse, specular, emissive, shininess, reflective, refractive, refractiveIndex); }
// Since refractive index is a function from wavelength to a real number, it can be viewed as a spectrum
public static final Material BLACK = new Material(Color.BLACK, Color.BLACK, 32, Color.BLACK, Color.BLACK, 1.5); public static final Material BLACK = new Material(w -> 0, w -> 0, w -> 0, 32, w -> 0, w -> 0, w -> 1.5);
public static Material matte (Color c) { return BLACK.diffuse(c); } public static Material matte (Spectrum s) { return BLACK.diffuse(s); }
public static Material matte (double k) { return matte(Color.gray(k)); } public static Material matte (double k) { return matte(w -> k); }
public static Material matte ( ) { return matte(Color.WHITE); } public static Material matte ( ) { return matte(w -> 1.0); } // TODO: potentially have to replace with D65
public static final Material MATTE = matte(); public static final Material MATTE = matte();
public static final Material MIRROR = BLACK.reflective(Color.WHITE); public static final Material MIRROR = BLACK.reflective(new SplineSpectrum(new Pair[]{
public static final Material GLASS = BLACK.refractive(Color.WHITE).refractiveIndex(1.5); new Pair<Double, Double>(248.0, 0.926),
new Pair<Double, Double>(400.0, 0.920),
new Pair<Double, Double>(532.0, 0.916),
new Pair<Double, Double>(633.0, 0.907),
new Pair<Double, Double>(800.0, 0.868)
}));
public static final Material GLASS = BLACK.refractive(w -> 1.0)
.refractiveIndex(w -> 1.6 + (w-400)/(800-400) * (1.55 - 1.6)); /* Made to roughly resemble refractive index
of BaK4 crown glass*/
public static Material light (Spectrum s) { return BLACK.emissive(s); }
} }

View file

@ -1,6 +1,7 @@
package xyz.marsavic.gfxlab.graphics3d; package xyz.marsavic.gfxlab.graphics3d;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Spectrum;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -12,16 +13,13 @@ public interface Scene {
Collection<Light> lights(); Collection<Light> lights();
default Color colorBackground() { Spectrum backgroundSpectrum = wavelength -> 0;
return Color.BLACK;
}
class Base implements Scene { class Base implements Scene {
protected Solid solid; protected Solid solid;
protected final List<Light> lights = new ArrayList<>(); protected final List<Light> lights = new ArrayList<>();
protected final Color colorBackground = Color.BLACK;
@Override @Override
public Solid solid() { public Solid solid() {
@ -33,10 +31,7 @@ public interface Scene {
return lights; return lights;
} }
@Override public final Spectrum backgroundSpectrum = wavelength -> 0;
public Color colorBackground() {
return colorBackground;
}
} }

View file

@ -1,31 +1,59 @@
package xyz.marsavic.gfxlab.graphics3d.raytracers; package xyz.marsavic.gfxlab.graphics3d.raytracers;
import xyz.marsavic.geometry.Vec;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Vec3; import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.*; import xyz.marsavic.gfxlab.graphics3d.*;
import xyz.marsavic.random.RNG;
import xyz.marsavic.utils.Numeric;
public class RayTracerSimple extends RayTracer { public class RayTracerSimple extends RayTracer {
private static final int spectrumSamples = 20;
private static final double minWavelength = 380;
private static final double maxWavelength = 780;
private static final double EPSILON = 1e-9; private static final double EPSILON = 1e-9;
private static final int sampleNumber = 4;
private static final double stopProbability = 1.0;
public RayTracerSimple(Scene scene, Camera camera) { public RayTracerSimple(Scene scene, Camera camera) {
super(scene, camera); super(scene, camera);
} }
public static RNG rng = new RNG();
@Override @Override
protected Color sample(Ray ray) { protected Color sample(Ray ray) {
return sample(ray, 64); Color result = Color.BLACK;
for (int i = 0; i < sampleNumber; i++) {
double x = 0.0, y = 0.0, z = 0.0;
for (int j = 0; j < spectrumSamples; j++) {
double wavelength = minWavelength + (((double) j + rng.nextDouble()) / spectrumSamples) * (maxWavelength - minWavelength);
double intensity = sample(ray, wavelength, 7);
x += intensity * Xyz.x[(int) wavelength];
y += intensity * Xyz.y[(int) wavelength];
z += intensity * Xyz.z[(int) wavelength];
} }
protected Color sample(Ray ray, int depthRemaining) { result = result.add(Color.xyz(x, y, z));
if (depthRemaining == 0) { }
return Color.BLACK; return result.div(sampleNumber);
}
protected double sample(Ray ray, double wavelength, int depthRemaining) {
double returnFactor = 1.0;
if (depthRemaining <= 0) {
if (rng.nextDouble(1.0) <= stopProbability) {
return 0.0;
} else {
returnFactor = 1/(1-stopProbability);
}
} }
Hit hit = scene.solid().firstHit(ray, EPSILON); Hit hit = scene.solid().firstHit(ray, EPSILON);
if (hit == null) { if (hit == null) {
return scene.colorBackground(); return scene.backgroundSpectrum.at(wavelength);
} }
Vec3 p = ray.at(hit.t()); // The hit point Vec3 p = ray.at(hit.t()); // The hit point
@ -35,50 +63,38 @@ public class RayTracerSimple extends RayTracer {
Vec3 r = GeometryUtils.reflectedN(n_, i); // Reflected ray (i reflected over n) Vec3 r = GeometryUtils.reflectedN(n_, i); // Reflected ray (i reflected over n)
Vec3 r_ = r.div(lI); // Reflected ray (i reflected over n) Vec3 r_ = r.div(lI); // Reflected ray (i reflected over n)
Color lightDiffuse = Color.BLACK; // The sum of diffuse contributions from all the lights
Color lightSpecular = Color.BLACK; // The sum of specular contributions from all the lights
Material material = hit.material(); Material material = hit.material();
for (Light light : scene.lights()) { double lightDiffuse = 0.0; // The diffuse contribution of the generated path
Vec3 l = light.p().sub(p); // Vector from p to the light; double lightSpecular = 0.0; // The specular contribution of the generated path
double lightReflected = 0.0; // The reflective contribution of the generated path
double lightRefracted = 0.0; // The refractive contribution of the generated path
double lightEmissive = material.emissive().at(wavelength); // The contribution of the light surface itself
Ray rayToLight = Ray.pd(p, l); double result = lightEmissive;
if (scene.solid().hitBetween(rayToLight, EPSILON, 1)) continue;
double lLSqr = l.lengthSquared(); // Distance from p to the light squared double diffuse = material.diffuse().at(wavelength);
double lL = Math.sqrt(lLSqr); // Distance from p to the light if (diffuse != 0.0) {
double cosLN = n_.dot(l) / lL; // Cosine of the angle between l and n_ double r1 = rng.nextDouble(1.0); // Angle of projected random vector in the plain normal to n_
double r2 = rng.nextDouble(0.25); // Angle of vector compared to n_
if (cosLN > 0) { // If the light is above the surface Vec3 u_ = GeometryUtils.normal(n_).normalized_(); // One of the normalized vectors normal to n
Color irradiance = light.c().mul(cosLN / lLSqr); Vec3 v_ = n_.cross(u_); // Doesn't need to be normalized because n_ and u_ are normalized and normal
// The irradiance represents how much light is received by a unit area of the surface. It is Vec3 o = u_.mul(Numeric.sinT(r1)).add(v_.mul(Numeric.cosT(r1)))
// proportional to the cosine of the incoming angle and inversely proportional to the distance squared .mul(Numeric.cosT(r2)).add(n_.mul(Numeric.sinT(r2))); // Outgoing sample vector
// (inverse-square law). lightDiffuse = diffuse * sample(Ray.pd(p, o), wavelength, depthRemaining - 1);
lightDiffuse = lightDiffuse.add(irradiance);
double cosLR = l.dot(r_);
if (cosLR > 0) { // If the angle between l and r is acute
cosLR /= lL;
lightSpecular = lightSpecular.add(irradiance.mul(Math.pow(cosLR, material.shininess())));
}
}
} }
Color result = Color.BLACK; result += lightDiffuse;
result = result.add(material.diffuse ().mul(lightDiffuse )); double reflective = material.reflective().at(wavelength);
result = result.add(material.specular().mul(lightSpecular)); if (reflective != 0.0) {
lightReflected = sample(Ray.pd(p, r), wavelength, depthRemaining - 1);
if (material.reflective().notZero()) { result += reflective * lightReflected;
// When material has reflective properties we recursively find the color visible along the ray (p, r).
Color lightReflected = sample(Ray.pd(p, r), depthRemaining - 1);
result = result.add(material.reflective().mul(lightReflected));
} }
double refractive = material.refractive().at(wavelength);
if (material.refractive().notZero()) { if (refractive != 0.0) {
Vec3 b; // refracted light vector Vec3 b; // refracted light vector
double rInd = 1/material.refractiveIndex(); double rInd = 1/material.refractiveIndex().at(wavelength);
double iCosN = i.dot(n_); double iCosN = i.dot(n_);
if (iCosN < 0) { if (iCosN < 0) {
@ -98,11 +114,10 @@ public class RayTracerSimple extends RayTracer {
} }
b = bRejection.add(bProjection); b = bRejection.add(bProjection);
} }
Color lightRefracted = sample(Ray.pd(p, b), depthRemaining - 1); lightRefracted = sample(Ray.pd(p, b), wavelength, depthRemaining - 1);
result = result.add(material.refractive().mul(lightRefracted)); result += refractive * lightRefracted;
} }
return result; return returnFactor * result;
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,7 @@
/*
package xyz.marsavic.gfxlab.graphics3d.scene; package xyz.marsavic.gfxlab.graphics3d.scene;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Vec3; import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.Light; import xyz.marsavic.gfxlab.graphics3d.Light;
@ -56,3 +58,4 @@ public class DiscoRoom extends Scene.Base {
} }
} }
*/

View file

@ -1,3 +1,4 @@
/*
package xyz.marsavic.gfxlab.graphics3d.scene; package xyz.marsavic.gfxlab.graphics3d.scene;
import xyz.marsavic.geometry.Vector; import xyz.marsavic.geometry.Vector;
@ -50,3 +51,4 @@ public class Mirrors extends Scene.Base {
} }
} }
*/

View file

@ -1,5 +1,7 @@
/*
package xyz.marsavic.gfxlab.graphics3d.scene; package xyz.marsavic.gfxlab.graphics3d.scene;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Vec3; import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.Light; import xyz.marsavic.gfxlab.graphics3d.Light;
@ -48,3 +50,4 @@ public class RefractionTest extends Scene.Base {
} }
} }
*/

View file

@ -1,4 +1,4 @@
package xyz.marsavic.gfxlab.graphics3d.scene; /* package xyz.marsavic.gfxlab.graphics3d.scene;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Vec3; import xyz.marsavic.gfxlab.Vec3;
@ -32,3 +32,4 @@ public class SceneTest1 extends Scene.Base{
} }
} }
*/

View file

@ -0,0 +1,75 @@
package xyz.marsavic.gfxlab.graphics3d.scene;
import xyz.marsavic.functions.interfaces.F1;
import xyz.marsavic.geometry.Vector;
import xyz.marsavic.gfxlab.SplineSpectrum;
import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.*;
import xyz.marsavic.gfxlab.graphics3d.solids.Ball;
import xyz.marsavic.gfxlab.graphics3d.solids.Group;
import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace;
import xyz.marsavic.gfxlab.graphics3d.solids.Parallelepiped;
import xyz.marsavic.gfxlab.graphics3d.textures.Grid;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
public class SpectrumTest extends Scene.Base {
public SpectrumTest() {
var materialUVWalls = (F1<Material, Vector>) (uv -> Material.matte(1.0));
var materialBlocks = (F1<Material, Vector>) (uv -> Material.matte(0.0));
var materialUVWallsL = Grid.standard(w -> 1.0);
var materialUVWallsB = Grid.standard(w -> 1.0);
var materialCoverBlock = Grid.standard(w -> 0.0);
var materialUVWallsR = Grid.standard(w -> 1.0);
var materialGlass = (F1<Material, Vector>) (uv -> Material.GLASS
.refractiveIndex(w -> 5.6 + (w-400)/(800-400) * (1.55 - 5.6)));
var materialMirror = (F1<Material, Vector>) (uv -> Material.MIRROR);
var materialLight = (F1<Material, Vector>) (uv -> Material.light(w -> 1.0));
Collection<Solid> solids = new ArrayList<>();
Collections.addAll(solids,
HalfSpace.pn(Vec3.xyz(-1, 0, 0), Vec3.xyz( 1, 0, 0), materialUVWallsL),
HalfSpace.pn(Vec3.xyz( 1, 0, 0), Vec3.xyz(-1, 0, 0), materialUVWallsR),
HalfSpace.pn(Vec3.xyz( 0, -1, 0), Vec3.xyz( 0, 1, 0), materialUVWalls),
// HalfSpace.pn(Vec3.xyz( 0, 1, 0), Vec3.xyz( 0, -1, 0), materialUVWalls),
HalfSpace.pn(Vec3.xyz( 0, 0, 1), Vec3.xyz( 0, 0, -1), materialUVWallsB),
HalfSpace.pn(Vec3.xyz( 0, 0, -6), Vec3.xyz( 0, 0, 1), materialUVWallsB),
Ball.cr(Vec3.xyz(0, 6, 0), 0.8, materialLight),
Parallelepiped.pabc(Vec3.xyz(-0.25, 0, 0.25),
Vec3.xyz(0.6, 0.8, 0),
Vec3.xyz(-0.2, -0.6, 0),
Vec3.xyz(0, 0, -0.5),
materialGlass),
Parallelepiped.pabc(Vec3.xyz(-1, 1, 1),
Vec3.xyz(0.75, 0, 0),
Vec3.xyz(0, 0.4, 0),
Vec3.xyz(0, 0, -6),
materialCoverBlock),
Parallelepiped.pabc(Vec3.xyz(0.25, 1, 1),
Vec3.xyz(2, 0, 0),
Vec3.xyz(0, 0.4, 0),
Vec3.xyz(0, 0, -6),
materialCoverBlock),
Parallelepiped.pabc(Vec3.xyz(-0.25, 1, 1),
Vec3.xyz(0.5, 0, 0),
Vec3.xyz(0, 0.4, 0),
Vec3.xyz(0, 0, -0.75),
materialCoverBlock),
Parallelepiped.pabc(Vec3.xyz(-0.25, 1, -0.25),
Vec3.xyz(0.5, 0, 0),
Vec3.xyz(0, 0.4, 0),
Vec3.xyz(0, 0, -6),
materialCoverBlock)
);
solid = Group.of(solids);
}
}

View file

@ -0,0 +1,108 @@
package xyz.marsavic.gfxlab.graphics3d.solids;
import xyz.marsavic.functions.interfaces.F1;
import xyz.marsavic.geometry.Vector;
import xyz.marsavic.gfxlab.Vec3;
import xyz.marsavic.gfxlab.graphics3d.Hit;
import xyz.marsavic.gfxlab.graphics3d.Material;
import xyz.marsavic.gfxlab.graphics3d.Ray;
import xyz.marsavic.gfxlab.graphics3d.Solid;
public class Parallelepiped implements Solid {
private final Vec3 p; // starting vertex
private final Vec3[][][] vertices;
HalfSpace [] sides;
private final Vec3 a, b, c;
private Parallelepiped(Vec3 point, Vec3 aEdge, Vec3 bEdge, Vec3 cEdge, F1<Material, Vector> mapMaterial) {
this.p = point;
this.a = aEdge;
this.b = bEdge;
this.c = cEdge;
vertices = new Vec3[][][]
{{{p, p.add(c)}, {p.add(b), p.add(b).add(c)}},
{{p.add(a), p.add(a).add(c)}, {p.add(a).add(b), p.add(a).add(b).add(c)}}};
sides = new HalfSpace[6];
sides[0] = HalfSpace.pef(p, b, a, mapMaterial);
sides[1] = HalfSpace.pef(p.add(c), a, b, mapMaterial);
sides[2] = HalfSpace.pef(p, a, c, mapMaterial);
sides[3] = HalfSpace.pef(p.add(b), c, a, mapMaterial);
sides[4] = HalfSpace.pef(p, c, b, mapMaterial);
sides[5] = HalfSpace.pef(p.add(a), b, c, mapMaterial);
}
public static Parallelepiped pabc(Vec3 p, Vec3 a, Vec3 b, Vec3 c, F1<Material, Vector> mapMaterial) {
return new Parallelepiped(p, a, b, c, mapMaterial);
}
public Vec3 p() {
return p;
}
public Vec3 a() {
return a;
}
public Vec3 b() {
return b;
}
public Vec3 c() {
return c;
}
public Vec3[][][] vertices() {
return vertices.clone();
}
private static boolean pointOnParallelogram(Vec3 p, Vec3 e, Vec3 f) {
// we solve p as a linear combination of a*e+b*f, then check if this combination is in
// 0<=a<=1 and 0<=b<=1
double D = e.lengthSquared() * f.lengthSquared() - e.dot(f)*e.dot(f);
if (D == 0)
return false;
// System determinant
double Da = p.dot(e)*f.lengthSquared() - p.dot(f)*e.dot(f);
// a's determinant
double Db = e.lengthSquared()*p.dot(f) - e.dot(f)*p.dot(e);
double a = Da/D;
// a's determinant
double b = Db/D;
// b's determinant
return a >= 0 && a <= 1 && b >= 0 && b <= 1;
}
@Override
public Hit firstHit(Ray ray, double afterTime) {
Hit[] planeHits = new Hit[6];
for (int i = 0; i < 6; i++) {
planeHits[i] = sides[i].firstHit(ray, afterTime);
if (planeHits[i] == null) {
continue;
}
Vec3 rayPlaneIntersect = ray.at(planeHits[i].t());
if (!pointOnParallelogram(rayPlaneIntersect.sub(sides[i].p()), sides[i].e(), sides[i].f())) {
planeHits[i] = null;
}
}
double minT = Hit.t(null); // Positive infinity
int minI = 0;
for (int i = 0; i < 6; i++) {
if (Hit.t(planeHits[i]) < minT) {
minT = Hit.t(planeHits[i]);
minI = i;
}
}
return planeHits[minI];
}
}

View file

@ -4,6 +4,7 @@ package xyz.marsavic.gfxlab.graphics3d.textures;
import xyz.marsavic.functions.interfaces.F1; import xyz.marsavic.functions.interfaces.F1;
import xyz.marsavic.geometry.Vector; import xyz.marsavic.geometry.Vector;
import xyz.marsavic.gfxlab.Color; import xyz.marsavic.gfxlab.Color;
import xyz.marsavic.gfxlab.Spectrum;
import xyz.marsavic.gfxlab.graphics3d.Material; import xyz.marsavic.gfxlab.graphics3d.Material;
@ -33,22 +34,22 @@ public class Grid implements F1<Material, Vector> {
} }
public static Grid standard(Color color) { public static Grid standard(Spectrum spectrum) {
return new Grid( return new Grid(
Vector.xy(0.25, 0.25), Vector.xy(0.25, 0.25),
Vector.xy(0.01, 0.01), Vector.xy(0.01, 0.01),
Material.matte(color), Material.matte(spectrum),
Material.matte(color.mul(0.75)) Material.matte(w -> spectrum.at(w) * 0.75)
); );
} }
public static Grid standardUnit(Color color) { public static Grid standardUnit(Spectrum spectrum) {
return new Grid( return new Grid(
Vector.UNIT_DIAGONAL, Vector.UNIT_DIAGONAL,
Vector.xy(1.0/64), Vector.xy(1.0/64),
Material.matte(color), Material.matte(spectrum),
Material.matte(color.mul(0.75)) Material.matte(w -> spectrum.at(w) * 0.75)
); );
} }
} }

View file

@ -8,7 +8,7 @@ import xyz.marsavic.gfxlab.graphics3d.Affine;
import xyz.marsavic.gfxlab.graphics3d.cameras.Perspective; import xyz.marsavic.gfxlab.graphics3d.cameras.Perspective;
import xyz.marsavic.gfxlab.graphics3d.cameras.TransformedCamera; import xyz.marsavic.gfxlab.graphics3d.cameras.TransformedCamera;
import xyz.marsavic.gfxlab.graphics3d.raytracers.RayTracerSimple; import xyz.marsavic.gfxlab.graphics3d.raytracers.RayTracerSimple;
import xyz.marsavic.gfxlab.graphics3d.scene.RefractionTest; import xyz.marsavic.gfxlab.graphics3d.scene.SpectrumTest;
import xyz.marsavic.gfxlab.gui.UtilsGL; import xyz.marsavic.gfxlab.gui.UtilsGL;
import xyz.marsavic.gfxlab.tonemapping.ColorTransform; import xyz.marsavic.gfxlab.tonemapping.ColorTransform;
import xyz.marsavic.gfxlab.tonemapping.ToneMapping; import xyz.marsavic.gfxlab.tonemapping.ToneMapping;
@ -35,14 +35,13 @@ public class GfxLab {
e(RayTracerSimple::new, e(RayTracerSimple::new,
// e(RefractionTest::new), // e(RefractionTest::new),
// e(DiscoRoom::new, val(16), val(16), val(0x3361EB272FEA4C62L)), // e(DiscoRoom::new, val(16), val(16), val(0x3361EB272FEA4C62L)),
e(RefractionTest::new), e(SpectrumTest::new),
// e(Mirrors::new, val(3)), // e(Mirrors::new, val(3)),
e(TransformedCamera::new, e(TransformedCamera::new,
e(Perspective::new, val(1.0/3)), e(Perspective::new, val(1.0/3)),
// e(Orthographic::new), // e(Orthographic::new),
e(Affine.IDENTITY e(Affine.IDENTITY
.then(Affine.translation(Vec3.xyz(0, 0, -4))) .then(Affine.translation(Vec3.xyz(0, 0, -4)))
// .then(Affine.rotationAboutY(0.03))
) )
) )
), ),