commit 72970d9f5e219f7d819f47aa97cdb56e62b45a8a Author: Marko Savić <4282607+marsavic@users.noreply.github.com> Date: Tue Nov 15 02:21:15 2022 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21b4487 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Project exclude paths +/out/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..984eb6d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..0f64527 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/.idea/libraries/caffeine_3_1_1.xml b/.idea/libraries/caffeine_3_1_1.xml new file mode 100644 index 0000000..2e95ca2 --- /dev/null +++ b/.idea/libraries/caffeine_3_1_1.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/checker_qual_3_22_0.xml b/.idea/libraries/checker_qual_3_22_0.xml new file mode 100644 index 0000000..546028c --- /dev/null +++ b/.idea/libraries/checker_qual_3_22_0.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/error_prone_annotations_2_14_0.xml b/.idea/libraries/error_prone_annotations_2_14_0.xml new file mode 100644 index 0000000..69d7634 --- /dev/null +++ b/.idea/libraries/error_prone_annotations_2_14_0.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/google_guava.xml b/.idea/libraries/google_guava.xml new file mode 100644 index 0000000..1d726c4 --- /dev/null +++ b/.idea/libraries/google_guava.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/kordamp_ikonli_core.xml b/.idea/libraries/kordamp_ikonli_core.xml new file mode 100644 index 0000000..ecde2b6 --- /dev/null +++ b/.idea/libraries/kordamp_ikonli_core.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/kordamp_ikonli_javafx.xml b/.idea/libraries/kordamp_ikonli_javafx.xml new file mode 100644 index 0000000..921e6e7 --- /dev/null +++ b/.idea/libraries/kordamp_ikonli_javafx.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/kordamp_ikonli_materialdesign2_pack.xml b/.idea/libraries/kordamp_ikonli_materialdesign2_pack.xml new file mode 100644 index 0000000..ed36204 --- /dev/null +++ b/.idea/libraries/kordamp_ikonli_materialdesign2_pack.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/mars_bits_2022_11_15.xml b/.idea/libraries/mars_bits_2022_11_15.xml new file mode 100644 index 0000000..a813648 --- /dev/null +++ b/.idea/libraries/mars_bits_2022_11_15.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/object_instruments_2022_11_15.xml b/.idea/libraries/object_instruments_2022_11_15.xml new file mode 100644 index 0000000..97e40d7 --- /dev/null +++ b/.idea/libraries/object_instruments_2022_11_15.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a4ad09f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..06e2772 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GfxLab.iml b/GfxLab.iml new file mode 100644 index 0000000..f87716c --- /dev/null +++ b/GfxLab.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b5c72f --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# GfxLab + +## Setup + +- Uključite JAR fajlove iz lib foldera u projekat (ako se to ne desi automatski). + - JavaFX biblioteke (base, controls, graphics, swing) dolaze u verzijama za različite platforme (win, linux, mac, mac-aarch64). Ubacite samo one koje odgovaraju vašoj platformi, a izbacite ostale. + +- Pokrećete klasu `gui.App`. + +- Nameštate šta želite da prikažete u klasi `playground.GfxLab`. + +- Sve što budemo razvijali u toku nastave biće u paketu `graphics3d`. diff --git a/lib/caffeine-3.1.1.jar b/lib/caffeine-3.1.1.jar new file mode 100644 index 0000000..88f8cca Binary files /dev/null and b/lib/caffeine-3.1.1.jar differ diff --git a/lib/checker-qual-3.12.0.jar b/lib/checker-qual-3.12.0.jar new file mode 100644 index 0000000..e9eed80 Binary files /dev/null and b/lib/checker-qual-3.12.0.jar differ diff --git a/lib/checker-qual-3.22.0.jar b/lib/checker-qual-3.22.0.jar new file mode 100644 index 0000000..1c5c12e Binary files /dev/null and b/lib/checker-qual-3.22.0.jar differ diff --git a/lib/error_prone_annotations-2.11.0.jar b/lib/error_prone_annotations-2.11.0.jar new file mode 100644 index 0000000..bec7656 Binary files /dev/null and b/lib/error_prone_annotations-2.11.0.jar differ diff --git a/lib/error_prone_annotations-2.14.0.jar b/lib/error_prone_annotations-2.14.0.jar new file mode 100644 index 0000000..7d65a92 Binary files /dev/null and b/lib/error_prone_annotations-2.14.0.jar differ diff --git a/lib/failureaccess-1.0.1.jar b/lib/failureaccess-1.0.1.jar new file mode 100644 index 0000000..9b56dc7 Binary files /dev/null and b/lib/failureaccess-1.0.1.jar differ diff --git a/lib/guava-31.1-jre.jar b/lib/guava-31.1-jre.jar new file mode 100644 index 0000000..1681922 Binary files /dev/null and b/lib/guava-31.1-jre.jar differ diff --git a/lib/ikonli-core-12.3.1-javadoc.jar b/lib/ikonli-core-12.3.1-javadoc.jar new file mode 100644 index 0000000..e60315a Binary files /dev/null and b/lib/ikonli-core-12.3.1-javadoc.jar differ diff --git a/lib/ikonli-core-12.3.1-javadoc1.jar b/lib/ikonli-core-12.3.1-javadoc1.jar new file mode 100644 index 0000000..e60315a Binary files /dev/null and b/lib/ikonli-core-12.3.1-javadoc1.jar differ diff --git a/lib/ikonli-core-12.3.1-sources.jar b/lib/ikonli-core-12.3.1-sources.jar new file mode 100644 index 0000000..ab6586f Binary files /dev/null and b/lib/ikonli-core-12.3.1-sources.jar differ diff --git a/lib/ikonli-core-12.3.1-sources1.jar b/lib/ikonli-core-12.3.1-sources1.jar new file mode 100644 index 0000000..ab6586f Binary files /dev/null and b/lib/ikonli-core-12.3.1-sources1.jar differ diff --git a/lib/ikonli-core-12.3.1.jar b/lib/ikonli-core-12.3.1.jar new file mode 100644 index 0000000..47baf5e Binary files /dev/null and b/lib/ikonli-core-12.3.1.jar differ diff --git a/lib/ikonli-core-12.3.11.jar b/lib/ikonli-core-12.3.11.jar new file mode 100644 index 0000000..47baf5e Binary files /dev/null and b/lib/ikonli-core-12.3.11.jar differ diff --git a/lib/ikonli-core-12.3.12.jar b/lib/ikonli-core-12.3.12.jar new file mode 100644 index 0000000..47baf5e Binary files /dev/null and b/lib/ikonli-core-12.3.12.jar differ diff --git a/lib/ikonli-javafx-12.3.1.jar b/lib/ikonli-javafx-12.3.1.jar new file mode 100644 index 0000000..bbf9044 Binary files /dev/null and b/lib/ikonli-javafx-12.3.1.jar differ diff --git a/lib/ikonli-materialdesign2-pack-12.3.1.jar b/lib/ikonli-materialdesign2-pack-12.3.1.jar new file mode 100644 index 0000000..03dea6d Binary files /dev/null and b/lib/ikonli-materialdesign2-pack-12.3.1.jar differ diff --git a/lib/j2objc-annotations-1.3.jar b/lib/j2objc-annotations-1.3.jar new file mode 100644 index 0000000..a429c72 Binary files /dev/null and b/lib/j2objc-annotations-1.3.jar differ diff --git a/lib/jsr305-3.0.2.jar b/lib/jsr305-3.0.2.jar new file mode 100644 index 0000000..59222d9 Binary files /dev/null and b/lib/jsr305-3.0.2.jar differ diff --git a/lib/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar b/lib/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar new file mode 100644 index 0000000..45832c0 Binary files /dev/null and b/lib/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar differ diff --git a/lib/mars-bits-2022-11-15.jar b/lib/mars-bits-2022-11-15.jar new file mode 100644 index 0000000..4845be8 Binary files /dev/null and b/lib/mars-bits-2022-11-15.jar differ diff --git a/lib/object-instruments-2022-11-15.jar b/lib/object-instruments-2022-11-15.jar new file mode 100644 index 0000000..31ad92b Binary files /dev/null and b/lib/object-instruments-2022-11-15.jar differ diff --git a/resources/xyz/marsavic/gfxlab/resources/icons/mars 016.png b/resources/xyz/marsavic/gfxlab/resources/icons/mars 016.png new file mode 100644 index 0000000..5e2116d Binary files /dev/null and b/resources/xyz/marsavic/gfxlab/resources/icons/mars 016.png differ diff --git a/resources/xyz/marsavic/gfxlab/resources/icons/mars 024.png b/resources/xyz/marsavic/gfxlab/resources/icons/mars 024.png new file mode 100644 index 0000000..1cbc7e3 Binary files /dev/null and b/resources/xyz/marsavic/gfxlab/resources/icons/mars 024.png differ diff --git a/resources/xyz/marsavic/gfxlab/resources/icons/mars 032.png b/resources/xyz/marsavic/gfxlab/resources/icons/mars 032.png new file mode 100644 index 0000000..17770e1 Binary files /dev/null and b/resources/xyz/marsavic/gfxlab/resources/icons/mars 032.png differ diff --git a/resources/xyz/marsavic/gfxlab/resources/icons/mars 048.png b/resources/xyz/marsavic/gfxlab/resources/icons/mars 048.png new file mode 100644 index 0000000..9b68b88 Binary files /dev/null and b/resources/xyz/marsavic/gfxlab/resources/icons/mars 048.png differ diff --git a/resources/xyz/marsavic/gfxlab/resources/icons/mars 064.png b/resources/xyz/marsavic/gfxlab/resources/icons/mars 064.png new file mode 100644 index 0000000..1ba7f68 Binary files /dev/null and b/resources/xyz/marsavic/gfxlab/resources/icons/mars 064.png differ diff --git a/resources/xyz/marsavic/gfxlab/resources/icons/mars 128.png b/resources/xyz/marsavic/gfxlab/resources/icons/mars 128.png new file mode 100644 index 0000000..d3f5bc8 Binary files /dev/null and b/resources/xyz/marsavic/gfxlab/resources/icons/mars 128.png differ diff --git a/resources/xyz/marsavic/gfxlab/resources/icons/mars 256.png b/resources/xyz/marsavic/gfxlab/resources/icons/mars 256.png new file mode 100644 index 0000000..15b1ac2 Binary files /dev/null and b/resources/xyz/marsavic/gfxlab/resources/icons/mars 256.png differ diff --git a/src/module-info.java b/src/module-info.java new file mode 100644 index 0000000..a1bc89c --- /dev/null +++ b/src/module-info.java @@ -0,0 +1,18 @@ +open module xyz.marsavic.GfxLab { + + requires xyz.marsavic.MarsBits.main; + requires xyz.marsavic.objectinstruments; + + requires javafx.controls; + requires javafx.graphics; + requires javafx.swing; + + requires java.management; + + requires org.kordamp.ikonli.core; + requires org.kordamp.ikonli.materialdesign2; + requires org.kordamp.ikonli.javafx; + + requires com.github.benmanes.caffeine; + +} \ No newline at end of file diff --git a/src/xyz/marsavic/gfxlab/Color.java b/src/xyz/marsavic/gfxlab/Color.java new file mode 100644 index 0000000..06b4f82 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/Color.java @@ -0,0 +1,322 @@ +package xyz.marsavic.gfxlab; + +import xyz.marsavic.utils.Numeric; + +import java.util.function.DoubleUnaryOperator; + +/** + * Colors in linear sRGB color space. + */ +public class Color { + public static final Color BLACK = rgb(0, 0, 0); + public static final Color WHITE = rgb(1, 1, 1); + public static final Color DEBUG = rgb(1, 0, 0.5); + + + final double r, g, b; + + + + private Color(double r, double g, double b) { + this.r = r; + this.g = g; + this.b = b; + } + + + public static Color rgb(double r, double g, double b) { + return new Color(r, g, b); + } + + + public static Color rgb(Vec3 v) { + return rgb(v.x(), v.y(), v.z()); + } + + + public static Color gray(double k) { + return rgb(k, k, k); + } + + + public static Color hsb(double h, double s, double b) { + h = Numeric.mod(h); + int base = (int) (h * 6.0); + double f = h * 6.0 - base; + double p = b * (1.0 - s); + double q = b * (1.0 - s * f); + double t = b * (1.0 - s * (1.0 - f)); + return switch (base) { + case 0 -> Color.rgb(b, t, p); + case 1 -> Color.rgb(q, b, p); + case 2 -> Color.rgb(p, b, t); + case 3 -> Color.rgb(p, q, b); + case 4 -> Color.rgb(t, p, b); + case 5 -> Color.rgb(b, p, q); + default -> null; + }; + } + + + public static Color hsb(Vec3 v) { + return hsb(v.x(), v.y(), v.z()); + } + + + public static Color oklab(double l, double a, double b) { + double l_ = l + 0.3963377774f * a + 0.2158037573f * b; + double m_ = l - 0.1055613458f * a - 0.0638541728f * b; + double s_ = l - 0.0894841775f * a - 1.2914855480f * b; + + double cl = l_ * l_ * l_; + double cm = m_ * m_ * m_; + double cs = s_ * s_ * s_; + + double cr = 4.0767245293f * cl - 3.3072168827f * cm + 0.2307590544f * cs; + double cg = -1.2681437731f * cl + 2.6093323231f * cm - 0.3411344290f * cs; + double cb = -0.0041119885f * cl - 0.7034763098f * cm + 1.7068625689f * cs; + + return + ( + cr < 0 || cr > 1 || + cg < 0 || cg > 1 || + cb < 0 || cb > 1 + ) ? Color.BLACK : + Color.rgb(cr, cg, cb); + } + + + public static Color oklab(Vec3 v) { + return oklab(v.x(), v.y(), v.z()); + } + + + public static Color oklabPolar(double h, double c, double l) { + return oklab( + l, + c * Numeric.cosT(h), + c * Numeric.sinT(h) + ); + } + + + public static Color oklabPolar(Vec3 v) { + return oklabPolar(v.x(), v.y(), v.z()); + } + + + public static Color code(int code) { + return rgb( + byteToValue((code >> 16) & 0xFF), + byteToValue((code >> 8) & 0xFF), + byteToValue((code ) & 0xFF) + ); + } + + + + public Color add(Color o) { + return rgb(r + o.r, g + o.g, b + o.b); + } + + + public Color sub(Color o) { + return rgb(r - o.r, g - o.g, b - o.b); + } + + + public Color mul(double c) { + return rgb(r * c, g * c, b * c); + } + + + public Color mul(Color o) { + return rgb(r * o.r, g * o.g, b * o.b); + } + + + public Color div(double c) { + return rgb(r / c, g / c, b / c); + } + + + public Color pow(double c) { + return rgb( + Math.pow(r, c), + Math.pow(g, c), + Math.pow(b, c) + ); + } + + + public Color f(DoubleUnaryOperator f) { + return rgb( + f.applyAsDouble(r), + f.applyAsDouble(g), + f.applyAsDouble(b) + ); + } + + + public double luminance() { + return + 0.212655 * r + + 0.715158 * g + + 0.072187 * b; + } + + + /** + * For colors with components in [0, 1]. + */ + public double perceivedLightness() { + double y = luminance(); // y should be between 0.0 and 1.0 + + if (y <= 216.0 / 24389.0 ) { // The CIE standard states 0.008856, but ( 6/29)^3 = 216/24389 is the intent for 0.008856451679036. + return y * (24389.0 / 27.0); // The CIE standard states 903.3 , but (29/ 3)^3 = 24389/ 27 is the intent for 903.296296296296296. + } else { + return Math.cbrt(y) * 116.0 - 16.0; + } + } + + + public Vec3 hsb() { + // TODO Test, this was never tested. + double brightness = Math.max(Math.max(r, g), b); + double delta = brightness - Math.min(Math.min(r, g), b); + double saturation = brightness == 0 ? 0 : delta / brightness; + double hue; + + if (saturation == 0) { + hue = 0; + } else { + double redC = (brightness - r) / delta; + double greenC = (brightness - g) / delta; + double blueC = (brightness - b) / delta; + if (r == brightness) { + hue = blueC - greenC; + } else if (g == brightness) { + hue = 2.0 + redC - blueC; + } else { + hue = 4.0 + greenC - redC; + } + hue = hue / 6.0; + if (hue < 0) { + hue = hue + 1.0; + } + } + + return Vec3.xyz(hue, saturation, brightness); + } + + public double max() { + return Math.max(r, Math.max(g, b)); + } + + + public int code() { + return + (0xFF000000) | + (valueToByte(r) << 16) | + (valueToByte(g) << 8) | + (valueToByte(b) ); + } + + + public int codeClamp() { + return + (0xFF000000) | + (valueToByteClamp(r) << 16) | + (valueToByteClamp(g) << 8) | + (valueToByteClamp(b) ); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Color color = (Color) o; + + if (Double.compare(color.r, r) != 0) return false; + if (Double.compare(color.g, g) != 0) return false; + return Double.compare(color.b, b) == 0; + } + + + @Override + public int hashCode() { + long temp = + Double.doubleToLongBits(r) + + 31 * Double.doubleToLongBits(g) + + 997 * Double.doubleToLongBits(b); + return (int) (temp ^ (temp >>> 32)); + } + + + public boolean zero() { + return (r == 0.0 && g == 0.0 && b == 0.0); + } + + public boolean notZero() { + return (r != 0.0 || g != 0.0 || b != 0.0); + } + + public boolean one() { + return (r == 1.0 && g == 1.0 && b == 1.0); + } + + public boolean notOne() { + return (r != 1.0 || g != 1.0 || b != 1.0); + } + + + @Override + public String toString() { + return String.format("(r: %6.3f, g: %6.3f, b: %6.3f | Y: %6.3f)", r, g, b, luminance()); + } + + + // ==================================================================================================== + + + /** + * sRGB gamma function. Approx. pow(v, 2.2). + */ + public static double inverseGamma(double v) { + if (v <= 0.04045) { + return v / 12.92; + } else { + return Math.pow((v + 0.055) / 1.055, 2.4); + } + } + + + /** + * Inverse of sRGB gamma function. Approx. pow(v, 1 / 2.2). + */ + public static double gamma(double v) { + if (v <= 0.0031308) { + return v * 12.92; + } else { + return 1.055 * Math.pow(v, 1.0 / 2.4) - 0.055; + } + } + + + public static int valueToByte(double x) { + return (int) (gamma(x) * 255 + 0.5); + } + + + public static int valueToByteClamp(double x) { + return Math.min(valueToByte(x), 255); + } + + + public static double byteToValue(int x) { + return inverseGamma(x / 255.0); + } + +} diff --git a/src/xyz/marsavic/gfxlab/ColorFunction.java b/src/xyz/marsavic/gfxlab/ColorFunction.java new file mode 100644 index 0000000..42892b8 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/ColorFunction.java @@ -0,0 +1,13 @@ +package xyz.marsavic.gfxlab; + +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.geometry.Vector; + + +public interface ColorFunction extends F1 { + + default Color at(double t, Vector p) { + return at(Vec3.xp(t, p)); + } + +} diff --git a/src/xyz/marsavic/gfxlab/ColorFunctionT.java b/src/xyz/marsavic/gfxlab/ColorFunctionT.java new file mode 100644 index 0000000..e993371 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/ColorFunctionT.java @@ -0,0 +1,17 @@ +package xyz.marsavic.gfxlab; + +import xyz.marsavic.geometry.Vector; + + +public interface ColorFunctionT extends ColorFunction { + + /** p is a point in (x, y) space. */ + Color at(double t, Vector p); + + /** p is a point in (t, x, y) space. */ + @Override + default Color at(Vec3 p) { + return at(p.x(), p.p12()); + } + +} diff --git a/src/xyz/marsavic/gfxlab/Matrix.java b/src/xyz/marsavic/gfxlab/Matrix.java new file mode 100644 index 0000000..81aa726 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/Matrix.java @@ -0,0 +1,135 @@ +package xyz.marsavic.gfxlab; + +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.gui.UtilsGL; + + +public interface Matrix { + + Vector size(); + + + E get(int x, int y); + + + default E get(Vector p) { + return get(p.xInt(), p.yInt()); + } + + + void set(int x, int y, E value); + + + default void set(Vector p, E value) { + set(p.xInt(), p.yInt(), value); + } + + + default void fill(E value) { + int sizeX = size().xInt(); + UtilsGL.parallelY(size(), y -> { + for (int x = 0; x < sizeX; x++) { + set(x, y, value); + } + }); + } + + + default void fill(F1 f) { + UtilsGL.parallel(size(), p -> set(p, f.at(p))); // OPT? + } + + + default void copyFrom(Matrix o) { + Vector size = Matrix.assertEqualSizes(this, o); + + int sizeX = size.xInt(); + UtilsGL.parallelY(size, y -> { + for (int x = 0; x < sizeX; x++) { + set(x, y, o.get(x, y)); + } + }); + + // A pretty equivalent: UtilsGL.parallelYVec(size, p -> set(p, o.get(p))); + } + + // ........................... + + + static Vector assertEqualSizes(Matrix a, Matrix b) { + if (!b.size().equals(a.size())) { + throw new IllegalArgumentException("Matrix sizes are not equal."); + } + return a.size(); + } + + + static Matrix createBlack(Vector size) { + return new MatrixData<>(size, Color.BLACK); + } + + + static void add(Matrix a, Matrix b, Matrix result) { + Vector size = Matrix.assertEqualSizes(a, result); + + int sizeX = size.xInt(); + UtilsGL.parallelY(size, y -> { + for (int x = 0; x < sizeX; x++) { + result.set(x, y, a.get(x, y).add(b.get(x, y))); + } + }); + } + + + static Matrix add(Matrix a, Matrix b) { + Matrix result = new MatrixData<>(a.size()); + add(a, b, result); + return result; + } + + + + static void addInPlace(Matrix toChange, Matrix byHowMuch) { + Vector size = assertEqualSizes(toChange, byHowMuch); + + int sizeX = size.xInt(); + UtilsGL.parallelY(size, y -> { + for (int x = 0; x < sizeX; x++) { + toChange.set(x, y, toChange.get(x, y).add(byHowMuch.get(x, y))); + } + }); + } + + + static void mul(Matrix a, double k, Matrix result) { + Vector size = Matrix.assertEqualSizes(a, result); + + int sizeX = size.xInt(); + UtilsGL.parallelY(size, y -> { + for (int x = 0; x < sizeX; x++) { + result.set(x, y, a.get(x, y).mul(k)); + } + }); + } + + + static Matrix mul(Matrix a, double k) { + Matrix result = new MatrixData<>(a.size()); + mul(a, k, result); + return result; + } + + + static void mulInPlace(Matrix toChange, double factor) { + Vector size = toChange.size(); + + int sizeX = size.xInt(); + UtilsGL.parallelY(size, y -> { + for (int x = 0; x < sizeX; x++) { + toChange.set(x, y, toChange.get(x, y).mul(factor)); + } + }); + } + +} diff --git a/src/xyz/marsavic/gfxlab/MatrixData.java b/src/xyz/marsavic/gfxlab/MatrixData.java new file mode 100644 index 0000000..8528dcf --- /dev/null +++ b/src/xyz/marsavic/gfxlab/MatrixData.java @@ -0,0 +1,53 @@ +package xyz.marsavic.gfxlab; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.gui.UtilsGL; + +import java.util.Arrays; + + +public class MatrixData implements Matrix { + + private final Vector size; + private final E[][] data; + + + public MatrixData(Vector size, E initialValue) { + this.size = size.floor(); + //noinspection unchecked + data = (E[][]) new Object[this.size.yInt()][this.size.xInt()]; + + if (initialValue != null) { + fill(initialValue); + } + } + + + public MatrixData(Vector size) { + this(size, null); + } + + + @Override + public Vector size() { + return size; + } + + + @Override + public E get(int x, int y) { + return data[y][x]; + } + + @Override + public void set(int x, int y, E value) { + data[y][x] = value; + } + + + @Override + public void fill(E value) { + UtilsGL.parallel(data.length, y -> Arrays.fill(data[y], value)); + } + +} diff --git a/src/xyz/marsavic/gfxlab/MatrixInts.java b/src/xyz/marsavic/gfxlab/MatrixInts.java new file mode 100644 index 0000000..ccda130 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/MatrixInts.java @@ -0,0 +1,66 @@ +package xyz.marsavic.gfxlab; + + +import xyz.marsavic.geometry.Vector; + +import java.util.Arrays; + + +public final class MatrixInts implements Matrix { + + private final int width; + private final int[] data; // TODO test an implementation with int[][] and compare performances. + + + + public MatrixInts(Vector size) { + this.width = size.xInt(); + this.data = new int[size.areaInt()]; + } + + + public int height() { + return array().length / width; + } + + + public int width() { + return width; + } + + + @Override + public Vector size() { + return Vector.xy(width(), height()); + } + + + @Override + public Integer get(int x, int y) { + return data[y * width + x]; + } + + + @Override + public void set(int x, int y, Integer value) { + data[y * width + x] = value; + } + + + public void copyFrom(MatrixInts source) { + Matrix.assertEqualSizes(this, source); + System.arraycopy(source.array(), 0, array(), 0, width); + } + + + @Override + public void fill(Integer value) { + Arrays.fill(data, value); // Optimize: Parallelism on blocks might be faster? + } + + + public int[] array() { + return data; + } + +} diff --git a/src/xyz/marsavic/gfxlab/Renderer.java b/src/xyz/marsavic/gfxlab/Renderer.java new file mode 100644 index 0000000..6a829f4 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/Renderer.java @@ -0,0 +1,17 @@ +package xyz.marsavic.gfxlab; + +import xyz.marsavic.functions.interfaces.A1; +import xyz.marsavic.functions.interfaces.A2; +import xyz.marsavic.gfxlab.gui.UtilsGL; +import xyz.marsavic.utils.Numeric; + +public record Renderer(Vec3 size, A2, Double> aFillFrameInt) { + + public void process(double t, A1> aProcess) { + UtilsGL.matricesInt.borrow(size.p12(), m -> { + aFillFrameInt.execute(m, Numeric.mod(t, size.x())); + aProcess.execute(m); + }); + } + +} diff --git a/src/xyz/marsavic/gfxlab/Transformation.java b/src/xyz/marsavic/gfxlab/Transformation.java new file mode 100644 index 0000000..65565a1 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/Transformation.java @@ -0,0 +1,48 @@ +package xyz.marsavic.gfxlab; + + +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.gfxlab.graphics3d.Affine; +import xyz.marsavic.gfxlab.graphics3d.Ray; + + +public interface Transformation extends F1 { + + /** The default implementation might not be meaningful with non-affine transformations. */ + default Ray at(Ray r) { + return Ray.pq(at(r.p()), at(r.p().add(r.d()))); + } + + default Transformation then(Transformation outer) { + return p -> outer.at(at(p)); + } + + + // -------------------- + + + Transformation identity = p -> p; + + + /** Returns a linear transformation which transforms unit vectors the same way as this transform. */ + default Affine linearize() { + return Affine.unitVectors( + at(Vec3.EX), + at(Vec3.EY), + at(Vec3.EZ) + ); + } + + + default Affine gradient(Vec3 p) { + // ? + final double eps = 0x1p-16; + + return Affine.unitVectors( + (at(p.add(Vec3.EX.mul(eps))).sub(at(p))).div(eps), + (at(p.add(Vec3.EY.mul(eps))).sub(at(p))).div(eps), + (at(p.add(Vec3.EZ.mul(eps))).sub(at(p))).div(eps) + ); + } + +} diff --git a/src/xyz/marsavic/gfxlab/TransformationsFromSize.java b/src/xyz/marsavic/gfxlab/TransformationsFromSize.java new file mode 100644 index 0000000..676c2e5 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/TransformationsFromSize.java @@ -0,0 +1,31 @@ +package xyz.marsavic.gfxlab; + +import xyz.marsavic.functions.interfaces.F1; + + +public class TransformationsFromSize { + + public static final F1 toIdentity = TransformationsFromSize::toIdentity; + public static final F1 toUnitBox = TransformationsFromSize::toUnitBox; + public static final F1 toGeometric = TransformationsFromSize::toGeometric; + + + + public static Transformation toIdentity(Vec3 s) { + return Transformation.identity; + } + + + public static Transformation toUnitBox(Vec3 s) { + return p -> p.div(s); + } + + + private static final Vec3 t = Vec3.xyz(-1, -1, 1); + + public static Transformation toGeometric(Vec3 s) { + Vec3 c = Vec3.xyz( 2, 2, -2).div(s); + return p -> p.mul(c).add(t); + } + +} diff --git a/src/xyz/marsavic/gfxlab/Vec3.java b/src/xyz/marsavic/gfxlab/Vec3.java new file mode 100644 index 0000000..0d0dcb5 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/Vec3.java @@ -0,0 +1,457 @@ +package xyz.marsavic.gfxlab; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.elements.Immutable; +import xyz.marsavic.random.RNG; +import xyz.marsavic.utils.Numeric; + +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleUnaryOperator; + +@Immutable +public class Vec3 { + public static final Vec3 ZERO = xyz(0, 0, 0); + public static final Vec3 ONES = xyz(1, 1, 1); + public static final Vec3 EX = xyz(1, 0, 0); + public static final Vec3 EY = xyz(0, 1, 0); + public static final Vec3 EZ = xyz(0, 0, 1); + public static final Vec3 EXY = xyz(1, 1, 0); + public static final Vec3 EYZ = xyz(0, 1, 1); + public static final Vec3 EZX = xyz(1, 0, 1); + public static final Vec3 EXYZ = xyz(1, 1, 1); + public static final Vec3[] E = {EX, EY, EZ}; + + public static final Vec3 P012 = xyz(0, 1, 2); + public static final Vec3 P021 = xyz(0, 2, 1); + public static final Vec3 P102 = xyz(1, 0, 2); + public static final Vec3 P120 = xyz(1, 2, 0); + public static final Vec3 P201 = xyz(2, 0, 1); + public static final Vec3 P210 = xyz(2, 1, 0); + public static final Vec3[] PERMUTATIONS = {P012, P021, P102, P120, P201, P210}; + + private final double x, y, z; + + + + public Vec3(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + + public static Vec3 xyz(double x, double y, double z) { + return new Vec3(x, y, z); + } + + + public static Vec3 fromArray(int[] i) { + return xyz(i[0], i[1], i[2]); + } + + + public static Vec3 fromArray(double[] i) { + return xyz(i[0], i[1], i[2]); + } + + + public static Vec3 xp(double x, Vector p) { + return xyz(x, p.x(), p.y()); + } + + + public static Vec3 yp(double y, Vector p) { + return xyz(p.x(), y, p.y()); + } + + + public static Vec3 zp(double z, Vector p) { + return xyz(p.x(), p.y(), z); + } + + + + + public double x() { + return x; + } + + + public double y() { + return y; + } + + + public double z() { + return z; + } + + + public double get(int i) { + return switch (i) { + case 0 -> x(); + case 1 -> y(); + case 2 -> z(); + default -> throw new IllegalArgumentException(); + }; + } + + + public double[] toArray() { + return new double[] { x(), y(), z() }; + } + + + public int[] toArrayInt() { + return new int[] { (int) x(), (int) y(), (int) z() }; + } + + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o instanceof Vec3 vec3) { + return + (vec3.x() == x()) && + (vec3.y() == y()) && + (vec3.z() == z()) + ; + } else { + return false; + } + + } + + @Override + public int hashCode() { + long temp = + Double.doubleToLongBits(x) + + 31 * Double.doubleToLongBits(y) + + 997 * Double.doubleToLongBits(z); + return (int) (temp ^ (temp >>> 32)); + } + + public Vec3 add(Vec3 o) { + return xyz(x() + o.x(), y() + o.y(), z() + o.z()); + } + + + public Vec3 sub(Vec3 o) { + return xyz(x() - o.x(), y() - o.y(), z() - o.z()); + } + + + public Vec3 mul(double k) { + return xyz(x() * k, y() * k, z() * k); + } + + + public Vec3 mul(Vec3 o) { + return xyz(x() * o.x(), y() * o.y(), z() * o.z()); + } + + + public Vec3 div(double k) { + return xyz(x() / k, y() / k, z() / k); + } + + + public Vec3 div(Vec3 o) { + return xyz(x() / o.x(), y() / o.y(), z() / o.z()); + } + + + + public double lengthSquared() { + return x() * x() + y() * y() + z() * z(); + } + + + public double length() { + return Math.sqrt(lengthSquared()); + } + + + public Vec3 normalized_() { + return div(length()); + } + + + public Vec3 normalizedTo(double l) { + return mul(l / length()); + } + + + public Vec3 inverse() { + return xyz(-x(), -y(), -z()); + } + + + public double dot(Vec3 o) { + return x() * o.x() + y() * o.y() + z() * o.z(); + } + + + public Vec3 cross(Vec3 o) { + return xyz( + y() * o.z() - z() * o.y(), + z() * o.x() - x() * o.z(), + x() * o.y() - y() * o.x() + ); + } + + + public Vec3 projection(Vec3 d) { + return d.mul(this.dot(d) / d.lengthSquared()); + } + + + public Vec3 projectionN(Vec3 d_) { + return d_.mul(this.dot(d_)); + } + + + public Vec3 rejection(Vec3 d) { + return this.sub(projection(d)); + } + + + public Vec3 rejectionN(Vec3 d_) { + return this.sub(projectionN(d_)); + } + + + public double min() { + return Math.min(Math.min(x(), y()), z()); + } + + + public double max() { + return Math.max(Math.max(x(), y()), z()); + } + + + public Vec3 minIndicator() { + if (x() < y()) { + return x() < z() ? Vec3.EX : Vec3.EZ; + } else { + return y() < z() ? Vec3.EY : Vec3.EZ; + } + } + + + public Vec3 maxIndicator() { + if (x() > y()) { + return x() > z() ? Vec3.EX : Vec3.EZ; + } else { + return y() > z() ? Vec3.EY : Vec3.EZ; + } + } + + + public int minIndex() { + if (x() < y()) { + return x() < z() ? 0 : 2; + } else { + return y() < z() ? 1 : 2; + } + } + + + public int maxIndex() { + if (x() > y()) { + return x() > z() ? 0 : 2; + } else { + return y() > z() ? 1 : 2; + } + } + + + public Vec3 ranks() { + if (x() < y()) { + if (y() < z()) { + return P012; + } else { + if (x() < z()) { + return P021; + } else { + return P201; + } + } + } else { + if (x() < z()) { + return P102; + } else { + if (y() < z()) { + return P120; + } else { + return P210; + } + } + } + } + + + public Vec3 sign() { + return xyz(Numeric.sign(x()), Numeric.sign(y()), Numeric.sign(z())); + } + + + public Vec3 abs() { + return xyz(Math.abs(x()), Math.abs(y()), Math.abs(z())); + } + + + public boolean allZero() { + return (x() == 0) && (y() == 0) && (z() == 0); + } + + public boolean anyZero() { + return (x() == 0) || (y() == 0) || (z() == 0); + } + + + public Vec3 floor() { + return xyz(Math.floor(x()), Math.floor(y()), Math.floor(z())); + } + + public Vec3 floor(Vec3 d) { + return this.div(d).floor().mul(d); + } + + public Vec3 ceil() { + return xyz(Math.ceil(x()), Math.ceil(y()), Math.ceil(z())); + } + + public Vec3 ceil(Vec3 d) { + return this.div(d).ceil().mul(d); + } + + public Vec3 round() { + return xyz(Math.round(x()), Math.round(y()), Math.round(z())); + } + + public Vec3 round(Vec3 d) { + return this.div(d).round().mul(d); + } + +/** Snaps each component to floor or ceil, depending on whether the corresponding component of s is not one. */ + + public Vec3 snap(Vec3 s) { + return Vec3.xyz ( + s.x() != 1 ? Math.floor(x()) : Math.ceil(x()), + s.y() != 1 ? Math.floor(y()) : Math.ceil(y()), + s.z() != 1 ? Math.floor(z()) : Math.ceil(z()) + ); + } + + + + public double product() { + return x() * y() * z(); + } + + + public Vec3 f(DoubleUnaryOperator f) { + return Vec3.f(f, this); + } + + + public Vec3 f(DoubleBinaryOperator f, Vec3 v) { + return Vec3.f(f, this, v); + } + + + public Vec3[] split() { + return new Vec3[] { + Vec3.xyz(x(), 0, 0), + Vec3.xyz(0, y(), 0), + Vec3.xyz(0, 0, z()), + }; + } + + + public Vec3 only(int i) { + return switch (i) { + case 0 -> Vec3.xyz(x(), 0, 0); + case 1 -> Vec3.xyz(0, y(), 0); + case 2 -> Vec3.xyz(0, 0, z()); + default -> throw new IllegalArgumentException(); + }; + } + + + public Vector p01() { + return Vector.xy(x(), y()); + } + + public Vector p12() { + return Vector.xy(y(), z()); + } + + /** The result is obtained by applying the affine transform that maps 0 to -1 and 1 to 1. */ + public Vec3 ZOtoMP() { + return mul(2).sub(Vec3.EXYZ); + } + + /** The result is obtained by applying the affine transform that maps -1 to 0 and 1 to 1. */ + public Vec3 MPtoZO() { + return add(Vec3.EXYZ).mul(0.5); + } + + + + @Override + public String toString() { + return String.format("(%6.2f, %6.2f, %6.2f)", x(), y(), z()); + } + + + public static Vec3 f(DoubleUnaryOperator f, Vec3 u) { + return Vec3.xyz( + f.applyAsDouble(u.x()), + f.applyAsDouble(u.y()), + f.applyAsDouble(u.z()) + ); + } + + + public static Vec3 f(DoubleBinaryOperator f, Vec3 u, Vec3 v) { + return Vec3.xyz( + f.applyAsDouble(u.x(), v.x()), + f.applyAsDouble(u.y(), v.y()), + f.applyAsDouble(u.z(), v.z()) + ); + } + + + public static Vec3 min(Vec3 a, Vec3 b) { + return xyz( + Math.min(a.x(), b.x()), + Math.min(a.y(), b.y()), + Math.min(a.z(), b.z()) + ); + } + + + public static Vec3 max(Vec3 a, Vec3 b) { + return xyz( + Math.max(a.x(), b.x()), + Math.max(a.y(), b.y()), + Math.max(a.z(), b.z()) + ); + } + + + public static Vec3 lerp(Vec3 v0, Vec3 v1, double t) { + return v0.mul(1 - t).add(v1.mul(t)); + } + + + public static Vec3 random(RNG rng) { + return Vec3.xyz(rng.nextDouble(), rng.nextDouble(), rng.nextDouble()); + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/Element.java b/src/xyz/marsavic/gfxlab/elements/Element.java new file mode 100644 index 0000000..f37211d --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/Element.java @@ -0,0 +1,114 @@ +package xyz.marsavic.gfxlab.elements; + +import xyz.marsavic.functions.interfaces.A1; +import xyz.marsavic.functions.interfaces.F0; +import xyz.marsavic.reactions.values.EventInvalidated; +import xyz.marsavic.utils.Caches; +import xyz.marsavic.utils.Reflection; +import xyz.marsavic.utils.Utils; + +import java.util.List; + + +/** + * If any of element's inputs is changed or invalidated, the element will fire an invalidation event. + * All elements must be thread safe. All methods must use lockForReading or lockForWriting. + * lockForReading guarantees that nothing will change while inside the lock's action. + * lockForWriting guarantees that + * + * When other objects need to do a block operation by repeatedly calling this element's functions, they should + * lock it for reading to ensure the same state throughout the block operation. + */ +public abstract class Element { + + public class Input { + + private Output output; + + + private final A1 onInvalidated = e -> { + onInputInvalidated(this, e); + onInputChangedOrInvalidated(this); + }; + + + // TODO Public constructor is a problem. Anyone can "attach" an input to an element and send invalidation events. + public Input(Output output) { + connect(output); + } + + + public T get() { + return output.get(); + } + + + public void connect(Output newOutput) { + if (newOutput == output) return; + + if (output != null) { + output.onInvalidated().remove(onInvalidated); + } + output = newOutput; + output.onInvalidated().add(onInvalidated); + + onInputChanged(this); + onInputChangedOrInvalidated(this); + } + + } + + + /** Convenience method that can be called from constructor and when re-initializing data after the inputs change or + * get invalidated. This is the default behaviour unless the method onInputChangedOrInvalidated is overridden. */ + protected void buildItUp() { + } + + + /** Convenience method that can be called when disposing the object and when re-initializing data after the inputs + * change or get invalidated. This is the default behaviour unless the method onInputChangedOrInvalidated is + * overridden. */ + protected void tearItDown() { + } + + + /** Override this to react to the internal changes in the input object. + * This is called when the input object changed internally, but it is still the same object, i.e. input.get() + * will return the same reference as before. */ + protected void onInputInvalidated(Input input, EventInvalidated event) { + } + + /** Override this to react when input object changes. + * This is called when input.get() is not the same reference as before. + * Use lock for + **/ + protected void onInputChanged(Input input) { + } + + + protected void onInputChangedOrInvalidated(Input input) { + tearItDown(); + buildItUp(); + outputs().forEach(Invalidatable.Base::fireInvalidated); + } + + + protected final F0>> inputsFromFields = Caches.cached(() -> + Utils.cast(Reflection.fieldsOfType(this, Input.class)) + ); + + protected final F0>> outputsFromFields = Caches.cached(() -> + Utils.cast(Reflection.fieldsOfType(this, Output.class)) + ); + + + + public List> inputs() { + return inputsFromFields.at(); + } + + public List> outputs() { + return outputsFromFields.at(); + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/ElementF.java b/src/xyz/marsavic/gfxlab/elements/ElementF.java new file mode 100644 index 0000000..b6a19b7 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/ElementF.java @@ -0,0 +1,30 @@ +package xyz.marsavic.gfxlab.elements; + +import xyz.marsavic.functions.interfaces.F0; +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.functions.interfaces.F2; +import xyz.marsavic.functions.interfaces.F3; + +public abstract class ElementF extends Element implements HasOutput { + + public abstract R object(); + + + public final Output out = new Output<>(this::object); + + @Override + public Output out() { + return out; + } + + + public static ElementF0 e(R result) { + return e(() -> result); + } + + public static ElementF0 e(F0 f ) { return new ElementF0.Lazy<>(f ); } + public static ElementF1 e(F1 f, HasOutput p0 ) { return new ElementF1.Lazy<>(f, p0 ); } + public static ElementF2 e(F2 f, HasOutput p0, HasOutput p1 ) { return new ElementF2.Lazy<>(f, p0, p1 ); } + public static ElementF3 e(F3 f, HasOutput p0, HasOutput p1, HasOutput p2) { return new ElementF3.Lazy<>(f, p0, p1, p2); } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/ElementF0.java b/src/xyz/marsavic/gfxlab/elements/ElementF0.java new file mode 100644 index 0000000..fc314fe --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/ElementF0.java @@ -0,0 +1,40 @@ +package xyz.marsavic.gfxlab.elements; + +import xyz.marsavic.functions.interfaces.F0; + + +public abstract class ElementF0 extends ElementF { + + protected final F0 f; + + + + public ElementF0(F0 f) { + this.f = f; + } + + + + public static class Lazy extends ElementF0 { + + public Lazy(F0 f) { + super(f); + } + + private R object; + + @Override + protected void buildItUp() { + object = null; + } + + public R object() { + if (object == null) { + object = f.at(); + } + + return object; + } + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/ElementF1.java b/src/xyz/marsavic/gfxlab/elements/ElementF1.java new file mode 100644 index 0000000..9ce340a --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/ElementF1.java @@ -0,0 +1,43 @@ +package xyz.marsavic.gfxlab.elements; + +import xyz.marsavic.functions.interfaces.F1; + + +public abstract class ElementF1 extends ElementF { + + protected final F1 f; + + protected final Input in0; + + + + public ElementF1(F1 f, HasOutput p0) { + this.f = f; + in0 = new Input<>(p0.out()); + } + + + + public static class Lazy extends ElementF1 { + + public Lazy(F1 f, HasOutput p0) { + super(f, p0); + } + + private R object; + + @Override + protected void buildItUp() { + object = null; + } + + public R object() { + if (object == null) { + object = f.at(in0.get()); + } + + return object; + } + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/ElementF2.java b/src/xyz/marsavic/gfxlab/elements/ElementF2.java new file mode 100644 index 0000000..c91aa33 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/ElementF2.java @@ -0,0 +1,45 @@ +package xyz.marsavic.gfxlab.elements; + +import xyz.marsavic.functions.interfaces.F2; + + +public abstract class ElementF2 extends ElementF { + + protected final F2 f; + + protected final Input in0; + protected final Input in1; + + + + public ElementF2(F2 f, HasOutput p0, HasOutput p1) { + this.f = f; + in0 = new Input<>(p0.out()); + in1 = new Input<>(p1.out()); + } + + + + public static class Lazy extends ElementF2 { + + public Lazy(F2 f, HasOutput p0, HasOutput p1) { + super(f, p0, p1); + } + + private R object; + + @Override + protected void buildItUp() { + object = null; + } + + public R object() { + if (object == null) { + object = f.at(in0.get(), in1.get()); + } + + return object; + } + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/ElementF3.java b/src/xyz/marsavic/gfxlab/elements/ElementF3.java new file mode 100644 index 0000000..1144d6d --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/ElementF3.java @@ -0,0 +1,47 @@ +package xyz.marsavic.gfxlab.elements; + +import xyz.marsavic.functions.interfaces.F3; + + +public abstract class ElementF3 extends ElementF { + + protected final F3 f; + + protected final Input in0; + protected final Input in1; + protected final Input in2; + + + + public ElementF3(F3 f, HasOutput p0, HasOutput p1, HasOutput p2) { + this.f = f; + in0 = new Input<>(p0.out()); + in1 = new Input<>(p1.out()); + in2 = new Input<>(p2.out()); + } + + + + public static class Lazy extends ElementF3 { + + public Lazy(F3 f, HasOutput p0, HasOutput p1, HasOutput p2) { + super(f, p0, p1, p2); + } + + private R object; + + @Override + protected void buildItUp() { + object = null; + } + + public R object() { + if (object == null) { + object = f.at(in0.get(), in1.get(), in2.get()); + } + + return object; + } + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/ElementPureLazy.java b/src/xyz/marsavic/gfxlab/elements/ElementPureLazy.java new file mode 100644 index 0000000..0dc3288 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/ElementPureLazy.java @@ -0,0 +1,86 @@ +package xyz.marsavic.gfxlab.elements; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +// Remove this class, it's not needed any more? Or keep it because it allows arbitrarily many inputs. +public class ElementPureLazy extends Element { + + private final List> inputs; + public final Output out = new Output<>(this::object); + + private T object; + private final Constructor constructor; + private final int nParameters; + + + public ElementPureLazy(Class type, Output... outputs) { +// if (!UtilsElements.looksImmutable(type)) { +// throw new IllegalArgumentException("The type does not look immutable. If it is, annotate it with @Immutable."); +// } + + //noinspection unchecked + Constructor[] constructors = (Constructor[]) type.getConstructors(); + if (constructors.length != 1) { + throw new IllegalArgumentException("Specified class must have exactly one public constructor."); + } + + constructor = constructors[0]; + nParameters = constructor.getParameterCount(); + if (nParameters != outputs.length) { + throw new IllegalArgumentException("The number of provided outputs does not match the number of arguments of the constructor."); + } + + inputs = new ArrayList<>(nParameters); +// Class[] types = constructor.getParameterTypes(); + for (Output output : outputs) { + // TODO Type check somehow!? + inputs.add(new Input<>(output)); + } + } + + public static ElementPureLazy of(Class type, Object... args) { + //noinspection SuspiciousToArrayCall + return new ElementPureLazy( + type, + Arrays.stream(args) + .map(o -> o.getClass() == Output.class ? o : new Output<>(() -> o)) + .toArray(Output[]::new)); + } + + // TODO Constructor with named parameters (like in the class Defaults) + + + @Override + public List> inputs() { + return inputs; + } + + + @Override + protected void buildItUp() { + object = null; + } + + + public T object() { + if (object == null) { + Object[] args = new Object[nParameters]; + for (int i = 0; i < nParameters; i++) { + args[i] = inputs.get(i).get(); + } + try { + object = constructor.newInstance(args); + } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + return object; + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/HasOutput.java b/src/xyz/marsavic/gfxlab/elements/HasOutput.java new file mode 100644 index 0000000..9a79af0 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/HasOutput.java @@ -0,0 +1,5 @@ +package xyz.marsavic.gfxlab.elements; + +public interface HasOutput { + Output out(); +} diff --git a/src/xyz/marsavic/gfxlab/elements/Immutable.java b/src/xyz/marsavic/gfxlab/elements/Immutable.java new file mode 100644 index 0000000..2139269 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/Immutable.java @@ -0,0 +1,13 @@ +package xyz.marsavic.gfxlab.elements; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** A class annotation with this is "purely functional". This means that it effectively does not have any modifiable + * state, and only provides methods whose result is a function only of its parameters and constructor parameters. + * - We usually want all these parameters to also be pure or immutable. + * - We allow a method to write to a buffer passed as a parameter, that is considered to be "a result" of the function. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Immutable { +} diff --git a/src/xyz/marsavic/gfxlab/elements/Invalidatable.java b/src/xyz/marsavic/gfxlab/elements/Invalidatable.java new file mode 100644 index 0000000..ff65ec6 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/Invalidatable.java @@ -0,0 +1,25 @@ +package xyz.marsavic.gfxlab.elements; + +import xyz.marsavic.reactions.Dispatcher; +import xyz.marsavic.reactions.Reactions; +import xyz.marsavic.reactions.Reactive; +import xyz.marsavic.reactions.values.EventInvalidated; + + +public interface Invalidatable extends Reactive { + Reactions onInvalidated(); + + + class Base { + protected final Dispatcher dispatcherInvalidated = new Dispatcher<>(); + + public Reactions onInvalidated() { + return dispatcherInvalidated.reactions(); + } + + protected void fireInvalidated() { + dispatcherInvalidated.fire(new EventInvalidated()); + } + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/Motor.java b/src/xyz/marsavic/gfxlab/elements/Motor.java new file mode 100644 index 0000000..72f6728 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/Motor.java @@ -0,0 +1,10 @@ +package xyz.marsavic.gfxlab.elements; + + +public abstract class Motor { + + abstract void start(); + abstract void pause(); + abstract void stop(); + +} diff --git a/src/xyz/marsavic/gfxlab/elements/Output.java b/src/xyz/marsavic/gfxlab/elements/Output.java new file mode 100644 index 0000000..c533fd2 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/Output.java @@ -0,0 +1,32 @@ +package xyz.marsavic.gfxlab.elements; + +import xyz.marsavic.functions.interfaces.F0; + + +public class Output extends Invalidatable.Base implements HasOutput { + + private final F0 resultProvider; + + + + public Output(F0 resultProvider) { + this.resultProvider = resultProvider; + } + + + public T get() { + return resultProvider.at(); + } + + + @Override + public Output out() { + return this; + } + + + public static Output val(T object) { + return new Output<>(() -> object); + } + +} diff --git a/src/xyz/marsavic/gfxlab/elements/UtilsElements.java b/src/xyz/marsavic/gfxlab/elements/UtilsElements.java new file mode 100644 index 0000000..7bb92a7 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/elements/UtilsElements.java @@ -0,0 +1,21 @@ +package xyz.marsavic.gfxlab.elements; + +import com.google.common.primitives.Primitives; + +// TODO not needed - remove +public class UtilsElements { + + @SuppressWarnings("RedundantIfStatement") + static boolean looksImmutable(Class c) { + if (c.isRecord()) return true; + if (c.isAnnotationPresent(Immutable.class)) return true; + if (Primitives.isWrapperType(c)) return true; + + return false; + } + + static boolean looksImmutable(Object o) { + return looksImmutable(o.getClass()); + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/Affine.java b/src/xyz/marsavic/gfxlab/graphics3d/Affine.java new file mode 100644 index 0000000..2e4a0f7 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/Affine.java @@ -0,0 +1,198 @@ +package xyz.marsavic.gfxlab.graphics3d; + + +import xyz.marsavic.gfxlab.Transformation; +import xyz.marsavic.gfxlab.Vec3; +import xyz.marsavic.gfxlab.elements.Immutable; +import xyz.marsavic.utils.Numeric; + + +@Immutable +public record Affine ( + double m00, double m01, double m02, double m03, + double m10, double m11, double m12, double m13, + double m20, double m21, double m22, double m23 +// 0, 0, 0, 1 +) implements Transformation { + + public static Affine IDENTITY = new Affine( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0 + ); + + + public static Affine unitVectors(Vec3 ex, Vec3 ey, Vec3 ez) { + return new Affine( + ex.x(), ey.x(), ez.x(), 0, + ex.y(), ey.y(), ez.y(), 0, + ex.z(), ey.z(), ez.z(), 0 + ); + } + + + public Affine then(Affine t) { + return new Affine( + t.m00 * m00 + t.m01 * m10 + t.m02 * m20, t.m00 * m01 + t.m01 * m11 + t.m02 * m21, t.m00 * m02 + t.m01 * m12 + t.m02 * m22, t.m00 * m03 + t.m01 * m13 + t.m02 * m23 + t.m03, + t.m10 * m00 + t.m11 * m10 + t.m12 * m20, t.m10 * m01 + t.m11 * m11 + t.m12 * m21, t.m10 * m02 + t.m11 * m12 + t.m12 * m22, t.m10 * m03 + t.m11 * m13 + t.m12 * m23 + t.m13, + t.m20 * m00 + t.m21 * m10 + t.m22 * m20, t.m20 * m01 + t.m21 * m11 + t.m22 * m21, t.m20 * m02 + t.m21 * m12 + t.m22 * m22, t.m20 * m03 + t.m21 * m13 + t.m22 * m23 + t.m23 + ); + } + + + @Override + public Vec3 at(Vec3 v) { + return new Vec3( + m00 * v.x() + m01 * v.y() + m02 * v.z() + m03, + m10 * v.x() + m11 * v.y() + m12 * v.z() + m13, + m20 * v.x() + m21 * v.y() + m22 * v.z() + m23 + ); + } + + + public Vec3 applyWithoutTranslationTo(Vec3 v) { + return new Vec3( + m00 * v.x() + m01 * v.y() + m02 * v.z(), + m10 * v.x() + m11 * v.y() + m12 * v.z(), + m20 * v.x() + m21 * v.y() + m22 * v.z() + ); + } + + + @Override + public Ray at(Ray r) { + return new Ray(at(r.p()), applyWithoutTranslationTo(r.d())); + } + + + public Affine inverse() { + double det = determinant(); + return new Affine( + (m11 * m22 - m12 * m21) / det, -(m01 * m22 - m02 * m21) / det, (m01 * m12 - m02 * m11) / det, -(m01 * m12 * m23 + m02 * m13 * m21 + m03 * m11 * m22 - m03 * m12 * m21 - m02 * m11 * m23 - m01 * m13 * m22) / det, + -(m10 * m22 - m12 * m20) / det, (m00 * m22 - m02 * m20) / det, -(m00 * m12 - m02 * m10) / det, (m00 * m12 * m23 + m02 * m13 * m20 + m03 * m10 * m22 - m03 * m12 * m20 - m02 * m10 * m23 - m00 * m13 * m22) / det, + (m10 * m21 - m11 * m20) / det, -(m00 * m21 - m01 * m20) / det, (m00 * m11 - m01 * m10) / det, -(m00 * m11 * m23 + m01 * m13 * m20 + m03 * m10 * m21 - m03 * m11 * m20 - m01 * m10 * m23 - m00 * m13 * m21) / det + ); + } + + + public Affine transposeWithoutTranslation() { + return new Affine( + m00, m10, m20, 0, + m01, m11, m21, 0, + m02, m12, m22, 0 + ); + } + + + private double determinant() { + //noinspection UnaryPlus + return ( + + (m00 * m11 * m22) + + (m01 * m12 * m20) + + (m02 * m10 * m21) + - (m02 * m11 * m20) + - (m01 * m10 * m22) + - (m00 * m12 * m21) + ); + } + + + public static Affine translation(Vec3 d) { + return new Affine( + 1.0, 0.0, 0.0, d.x(), + 0.0, 1.0, 0.0, d.y(), + 0.0, 0.0, 1.0, d.z() + ); + } + + public static Affine rotationAboutX(double angle) { + return new Affine( + 1.0, 0.0, 0.0, 0.0, + 0.0, Numeric.cosT(angle), -Numeric.sinT(angle), 0.0, + 0.0, Numeric.sinT(angle), Numeric.cosT(angle), 0.0 + ); + } + + public static Affine rotationAboutY(double angle) { + return new Affine( + Numeric.cosT(angle), 0.0, Numeric.sinT(angle), 0.0, + 0.0, 1.0, 0.0, 0.0, + -Numeric.sinT(angle), 0.0, Numeric.cosT(angle), 0.0 + ); + } + + public static Affine rotationAboutZ(double angle) { + return new Affine( + Numeric.cosT(angle), -Numeric.sinT(angle), 0.0, 0.0, + Numeric.sinT(angle), Numeric.cosT(angle), 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0 + ); + } + + public static Affine scaling(double c) { + return new Affine( + c, 0.0, 0.0, 0.0, + 0.0, c, 0.0, 0.0, + 0.0, 0.0, c, 0.0 + ); + } + + public static Affine scaling(Vec3 c) { + return new Affine( + c.x(), 0.0, 0.0, 0.0, + 0.0, c.y(), 0.0, 0.0, + 0.0, 0.0, c.z(), 0.0 + ); + } + + + /** + * One possible linear transformation mapping EX to u and preserving angles between vectors. + */ + public static Affine asEX(Vec3 u) { + // A simpler implementation using vector products is possible, but this is more efficient. + if (u.x() != 0 || u.y() != 0) { + double mSqr = u.x() * u.x() + u.y() * u.y(); + double lSqr = mSqr + u.z() * u.z(); + double l = Math.sqrt(lSqr); + double m = 1.0 / Math.sqrt(mSqr); + double q = l * m; + + return Affine.unitVectors( + u, + Vec3.xyz(-u.y() * q, u.x() * q, 0), + Vec3.xyz(-u.x() * u.z() * m, -u.z() * u.y() * m, (u.x() * u.x() + u.y() * u.y()) * m) + ); + } else { + double q = 1.0 / u.z(); + return Affine.unitVectors( + u, + Vec3.xyz(q, 0, 0), + Vec3.xyz(0, Math.abs(q), 0) + ); + } + } + + /** + * One possible linear transformation mapping EX to a vector u of unit length and preserving angles between vectors. + */ + public static Affine asEXN(Vec3 u_) { + if (u_.x() != 0 || u_.y() != 0) { + double mSqr = 1 - u_.z() * u_.z(); + double m = 1.0 / Math.sqrt(mSqr); + + return Affine.unitVectors( + u_, + Vec3.xyz(-u_.y() * m, u_.x() * m, 0), + Vec3.xyz(-u_.x() * u_.z() * m, -u_.z() * u_.y() * m, (u_.x() * u_.x() + u_.y() * u_.y()) * m) + ); + } else { + return Affine.unitVectors( + u_, + Vec3.xyz(u_.z(), 0, 0), + Vec3.xyz(0, Math.abs(u_.z()), 0) + ); + } + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/GeometryUtils.java b/src/xyz/marsavic/gfxlab/graphics3d/GeometryUtils.java new file mode 100644 index 0000000..e627381 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/GeometryUtils.java @@ -0,0 +1,29 @@ +package xyz.marsavic.gfxlab.graphics3d; + + +import xyz.marsavic.gfxlab.Vec3; +import xyz.marsavic.random.sampling.Sampler; +import xyz.marsavic.utils.Numeric; + +public class GeometryUtils { + + /** An orthogonal vector to v. */ + public static Vec3 normal(Vec3 v) { + if (v.x() != 0 || v.y() != 0) { + return Vec3.xyz(-v.y(), v.x(), 0); + } else { + return Vec3.EX; + } + } + +/* + public static Vec3 normal(Vec3 v) { + Vec3 p = v.cross(Vec3.EX); + if (p.allZero()) { + p = v.cross(Vec3.EY); + } + return p; + } +*/ + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/Hit.java b/src/xyz/marsavic/gfxlab/graphics3d/Hit.java new file mode 100644 index 0000000..c46ab54 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/Hit.java @@ -0,0 +1,49 @@ +package xyz.marsavic.gfxlab.graphics3d; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Vec3; + + +/** Interaction of a ray with a solid.*/ +public interface Hit { + + /** The time of the hit */ + double t(); + + /** The normal at the point of the hit */ + Vec3 n(); + + /** The normalized normal at the point of the hit */ + default Vec3 n_() { + return n().normalized_(); + } + + + // ===================================================================================================== + + + static double t(Hit hit) { + return hit == null ? Double.POSITIVE_INFINITY : hit.t(); + } + + + abstract class RayT implements Hit { + private final Ray ray; + private final double t; + + protected RayT(Ray ray, double t) { + this.ray = ray; + this.t = t; + } + + public Ray ray() { + return ray; + } + + @Override + public double t() { + return t; + } + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/Ray.java b/src/xyz/marsavic/gfxlab/graphics3d/Ray.java new file mode 100644 index 0000000..0a9d717 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/Ray.java @@ -0,0 +1,33 @@ +package xyz.marsavic.gfxlab.graphics3d; + +import xyz.marsavic.gfxlab.Vec3; + + +public record Ray(Vec3 p, Vec3 d) { + + + public static Ray pd(Vec3 p, Vec3 d) { + return new Ray(p, d); + } + + + public static Ray pq(Vec3 p, Vec3 q) { + return pd(p, q.sub(p)); + } + + + public Vec3 at(double t) { + return p.add(d.mul(t)); + } + + + public Ray moveTo(double t) { + return Ray.pd(at(t), d()); + } + + + public Ray normalized_() { + return Ray.pd(p(), d().normalized_()); + } + +} \ No newline at end of file diff --git a/src/xyz/marsavic/gfxlab/graphics3d/Solid.java b/src/xyz/marsavic/gfxlab/graphics3d/Solid.java new file mode 100644 index 0000000..b8bd81b --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/Solid.java @@ -0,0 +1,22 @@ +package xyz.marsavic.gfxlab.graphics3d; + +import java.util.ArrayList; +import java.util.List; + + +public interface Solid { + + /** + * Returns the first hit of the ray into the surface of the solid, occurring strictly after the given time. + * The default implementation is based on hits method, but implementations of Solid can choose to override + * this method to increase performance when only the first hit is needed. + * If there is no hit, returns null. + */ + Hit firstHit(Ray ray, double afterTime); + + + default Hit firstHit(Ray ray) { + return firstHit(ray, 0); + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracingTest.java b/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracingTest.java new file mode 100644 index 0000000..ede21f2 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/raytracers/RayTracingTest.java @@ -0,0 +1,30 @@ +package xyz.marsavic.gfxlab.graphics3d.raytracers; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.ColorFunctionT; +import xyz.marsavic.gfxlab.Vec3; +import xyz.marsavic.gfxlab.graphics3d.Hit; +import xyz.marsavic.gfxlab.graphics3d.Ray; +import xyz.marsavic.gfxlab.graphics3d.solids.Ball; +import xyz.marsavic.gfxlab.graphics3d.solids.HalfSpace; + + +public class RayTracingTest implements ColorFunctionT { + + Ball ball = Ball.cr(Vec3.xyz(0, 0, 2), 1); + HalfSpace halfSpace = HalfSpace.pn(Vec3.xyz(0, -1, 0), Vec3.xyz(0, 1, 0)); + + @Override + public Color at(double t, Vector p) { + Ray ray = Ray.pq(Vec3.ZERO, Vec3.zp(1, p)); + + Hit hit1 = ball.firstHit(ray); + Hit hit2 = halfSpace.firstHit(ray); + + double tMin = Math.min(Hit.t(hit1), Hit.t(hit2)); + + return Color.gray(1.0 / (1.0 + tMin)); + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/solids/Ball.java b/src/xyz/marsavic/gfxlab/graphics3d/solids/Ball.java new file mode 100644 index 0000000..789588b --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/solids/Ball.java @@ -0,0 +1,79 @@ +package xyz.marsavic.gfxlab.graphics3d.solids; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Vec3; +import xyz.marsavic.gfxlab.graphics3d.Hit; +import xyz.marsavic.gfxlab.graphics3d.Ray; +import xyz.marsavic.gfxlab.graphics3d.Solid; +import xyz.marsavic.utils.Numeric; + + +public class Ball implements Solid { + + private final Vec3 c; + private final double r; + + // transient + private final double rSqr; + + + + private Ball(Vec3 c, double r) { + this.c = c; + this.r = r; + rSqr = r * r; + } + + + public static Ball cr(Vec3 c, double r) { + return new Ball(c, r); + } + + + public Vec3 c() { + return c; + } + + + public double r() { + return r; + } + + + + @Override + public HitBall firstHit(Ray ray, double afterTime) { + Vec3 e = c().sub(ray.p()); // Vector from the ray origin to the ball center + + double dSqr = ray.d().lengthSquared(); + double l = e.dot(ray.d()) / dSqr; + double mSqr = l * l - (e.lengthSquared() - rSqr) / dSqr; + + if (mSqr > 0) { + double m = Math.sqrt(mSqr); + if (l - m > afterTime) return new HitBall(ray, l - m); + if (l + m > afterTime) return new HitBall(ray, l + m); + } + return null; + } + + + class HitBall extends Hit.RayT { + + protected HitBall(Ray ray, double t) { + super(ray, t); + } + + @Override + public Vec3 n() { + return ray().at(t()).sub(c()); + } + + @Override + public Vec3 n_() { + return n().div(r); + } + + } + +} diff --git a/src/xyz/marsavic/gfxlab/graphics3d/solids/HalfSpace.java b/src/xyz/marsavic/gfxlab/graphics3d/solids/HalfSpace.java new file mode 100644 index 0000000..1c18214 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/graphics3d/solids/HalfSpace.java @@ -0,0 +1,123 @@ +package xyz.marsavic.gfxlab.graphics3d.solids; + +import xyz.marsavic.gfxlab.Vec3; +import xyz.marsavic.gfxlab.graphics3d.GeometryUtils; +import xyz.marsavic.gfxlab.graphics3d.Hit; +import xyz.marsavic.gfxlab.graphics3d.Ray; +import xyz.marsavic.gfxlab.graphics3d.Solid; + + +public class HalfSpace implements Solid { + + private final Vec3 p; // A point on the boundary plane + private final Vec3 e; // A vector parallel to the boundary plane. + private final Vec3 f; // A vector parallel to the boundary plane, not parallel to e. + + // transient + private final Vec3 n; // A normal vector to the boundary plane + private final Vec3 n_; // A normalized normal vector to the boundary plane + private final double e_f, f_e, eLSqr, fLSqr, sinSqr; + + + + private HalfSpace(Vec3 p, Vec3 e, Vec3 f) { + this.p = p; + this.e = e; + this.f = f; + this.n = e.cross(f); + + n_ = n.normalized_(); + + eLSqr = e.lengthSquared(); + fLSqr = f.lengthSquared(); + double ef = e.dot(f); + e_f = ef / fLSqr; + f_e = ef / eLSqr; + sinSqr = 1 - e_f * f_e; + } + + + public static HalfSpace pef(Vec3 p, Vec3 e, Vec3 f) { + return new HalfSpace(p, e, f); + } + + + public static HalfSpace pqr(Vec3 p, Vec3 q, Vec3 r) { + return pef(p, q.sub(p), r.sub(p)); + } + + + public static HalfSpace pn(Vec3 p, Vec3 n) { + double nl = n.length(); + Vec3 e = GeometryUtils.normal(n).normalizedTo(nl); + Vec3 f = n.cross(e).normalizedTo(nl); + return new HalfSpace(p, e, f); + } + + + public Vec3 p() { + return p; + } + + + public Vec3 e() { + return e; + } + + + public Vec3 f() { + return f; + } + + + public Vec3 n() { + return n; + } + + + public Vec3 n_() { + return n_; + } + + + @Override + public Hit firstHit(Ray ray, double afterTime) { + double o = n().dot(ray.d()); + if (o == 0) return null; + double l = n().dot(p().sub(ray.p())); + double t = l / o; + return (t > afterTime) ? new HitHalfSpace(ray, t) : null; + } + + + @Override + public String toString() { + return "HalfSpace{" + + "p=" + p + + ", e=" + e + + ", f=" + f + + ", n=" + n + + '}'; + } + + + + class HitHalfSpace extends Hit.RayT { + + protected HitHalfSpace(Ray ray, double t) { + super(ray, t); + } + + @Override + public Vec3 n() { + return n; + } + + @Override + public Vec3 n_() { + return n_; + } + + } + +} diff --git a/src/xyz/marsavic/gfxlab/gui/App.java b/src/xyz/marsavic/gfxlab/gui/App.java new file mode 100644 index 0000000..bdb286a --- /dev/null +++ b/src/xyz/marsavic/gfxlab/gui/App.java @@ -0,0 +1,102 @@ +package xyz.marsavic.gfxlab.gui; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.stage.Stage; +import xyz.marsavic.gfxlab.gui.instruments.InstrumentRenderer; +import xyz.marsavic.gfxlab.playground.GfxLab; +import xyz.marsavic.gfxlab.resources.Resources; +import xyz.marsavic.objectinstruments.Context; +import xyz.marsavic.objectinstruments.Panel; +import xyz.marsavic.objectinstruments.instruments.InstrumentText; + +import java.util.List; + + +public class App extends Application { + + private final GfxLab gfxLab = new GfxLab(); + +// private final ReactiveSet specialObjects = ReactiveSetBacked.withLinkedHashSet(); + + + private final List instrumentedAtStart = List.of( +// Catalog.INSTANCE, + gfxLab + ); + + + @Override + public void start(Stage primaryStage) { + primaryStage.setTitle("GFX Lab"); + + Panel panelL = new Panel(); +// Context contextL = Context.defaultForObjectSet(new ReactiveSetUnion<>(specialObjects, panelL.objectSet())); + Context contextL = Context.defaultForPanel(panelL); + for (var object : instrumentedAtStart) { +// panelL.addInstrument(InstrumentObject.of(object, f -> gfxLab.onChange(), contextL)); + } + + Panel panelR = new Panel(); + + panelR.addInstruments( +// new PollingInstrument<>(new InstrumentAnimationRawImageNew(), gfxLab::toneMappedAnimation), + new InstrumentRenderer(gfxLab.outRenderer), + new InstrumentText(() -> Profiling.infoTextSystem() + Profiling.infoTextProfilers(), 150) + ); + + + /* + ReactiveObjectSet objectSet = panel.objectSet(); + + List, Function0>>> factoryCreators = + new ArrayList<>(Factory.defaultFactoryCreatorsList); + + factoryCreators.add( + new Tuple2<>(RawImage.class, FactoryBoolean::new) + ); + + + Context context = new Context( + objectSet, + TypeMap.concat( + TypeMap.firstMatch(factoryCreators), + TypeMap.f(type -> () -> new FactoryReference<>(objectSet.ofType(type))) + ) + ); +*/ + + +// ScrollPane sp = new ScrollPane(panelL.region()); + +// sp.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); +// sp.setFitToWidth(true); +// sp.setFitToHeight(true); +// sp.setPannable(true); + + Pane root = new HBox(panelL.region(), panelR.region()); + HBox.setHgrow(panelL.region(), Priority.ALWAYS); + + Scene scene = new Scene(root, 1066, 866); + + scene.getStylesheets().setAll(Resources.stylesheetURL); + + primaryStage.getIcons().setAll(Resources.iconsApplication()); + primaryStage.setScene(scene); +// primaryStage.size.setMaximized(true); + + primaryStage.addEventHandler(KeyEvent.KEY_PRESSED, event -> { + if (event.getCode().equals(KeyCode.ESCAPE)) { + Platform.exit(); + } + }); + + primaryStage.show(); + } +} diff --git a/src/xyz/marsavic/gfxlab/gui/Profiling.java b/src/xyz/marsavic/gfxlab/gui/Profiling.java new file mode 100644 index 0000000..d5acd76 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/gui/Profiling.java @@ -0,0 +1,90 @@ +package xyz.marsavic.gfxlab.gui; + +import xyz.marsavic.time.Profiler; +import xyz.marsavic.tuples.Tuple2; + +import javax.management.*; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.OperatingSystemMXBean; +import java.util.Comparator; + + +public class Profiling { + + private static final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + + private static final OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); + private static final int nCpus = operatingSystemMXBean.getAvailableProcessors(); + + private static final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + private static ObjectName name; + static { + try { + name = ObjectName.getInstance("java.lang:type=OperatingSystem"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static double lastValue = Double.NaN; + private static double nextTime = 0.0; + private static final double cpuQueryInterval = 1.0; + + + + private static double getProcessCpuLoad() { + double time = Profiler.timerDefault.getTime(); + if (time < nextTime) { + return lastValue; + } + nextTime = time + cpuQueryInterval; + try { + Attribute attribute = (Attribute) mbs.getAttributes(name, new String[] {"ProcessCpuLoad"}).get(0); + return lastValue = (Double) attribute.getValue(); + } catch (InstanceNotFoundException | ReflectionException e) { + e.printStackTrace(); + return Double.NaN; + } + } + + + public static String infoTextSystem() { + StringBuilder sb = new StringBuilder(); + + MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); + + double conversion = 1024*1024; + String unit = "MB"; + sb.append(String.format("Heap used : %8.2f %s\n", heapMemoryUsage.getUsed () / conversion, unit)); + sb.append(String.format("Heap max : %8.2f %s\n", heapMemoryUsage.getMax () / conversion, unit)); + sb.append(String.format("Memory committed: %8.2f %s\n", heapMemoryUsage.getCommitted() / conversion, unit)); + + sb.append(String.format("Parallelism : %d/%d\n", UtilsGL.parallelism, nCpus)); + sb.append(String.format("CPU load average: %.2f\n", getProcessCpuLoad())); + + sb.append("\n"); + + return sb.toString(); + } + + + public static String infoTextProfilers() { + StringBuilder sb = new StringBuilder(); + + // Profilers change, can't sort them while running, so... + UtilsGL.profilers().stream() + .map(p -> new Tuple2<>(p, p.durationPerSecond())) + .sorted(Comparator.comparingDouble(t -> -t.p1())) + .forEach(t -> { + sb.append(t.p0().toStringDetailed()); + sb.append("\n"); + }); + + sb.append("\n"); + + return sb.toString(); + } + +} diff --git a/src/xyz/marsavic/gfxlab/gui/UtilsGL.java b/src/xyz/marsavic/gfxlab/gui/UtilsGL.java new file mode 100644 index 0000000..0f886e7 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/gui/UtilsGL.java @@ -0,0 +1,224 @@ +package xyz.marsavic.gfxlab.gui; + +import com.google.common.collect.ImmutableList; +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.Image; +import javafx.scene.image.PixelFormat; +import javafx.scene.image.WritableImage; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.stage.FileChooser; +import xyz.marsavic.functions.interfaces.A1; +import xyz.marsavic.functions.interfaces.A2; +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.Matrix; +import xyz.marsavic.gfxlab.MatrixData; +import xyz.marsavic.gfxlab.MatrixInts; +import xyz.marsavic.resources.ResourceManagerMap; +import xyz.marsavic.random.RNG; +import xyz.marsavic.time.Profiler; +import xyz.marsavic.utils.Utils; + +import javax.imageio.ImageIO; +import java.io.File; +import java.io.IOException; +import java.nio.IntBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; +import java.util.function.IntConsumer; +import java.util.stream.IntStream; + + +public class UtilsGL { + + private static final Set profilers = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + + + public static Profiler profiler(Object object, String description) { + String name = + String.format("%08x", System.identityHashCode(object)) + + " " + + object.getClass().getSimpleName() + + " " + + description; + + Profiler profiler = new Profiler(name); + profilers.add(profiler); + return profiler; + } + + + /** + * Live profilers, but not a live collection. + * The returned collection is immutable and contains only the profilers present at the moment of calling. + */ + public static Collection profilers() { + synchronized (profilers) { + return ImmutableList.copyOf(profilers); + } + } + + + // JavaFX helpers + + static final PixelFormat pixelFormat = PixelFormat.getIntArgbPreInstance(); + + + public static Image writeRawImageToImage(MatrixInts rawImage) { + int sx = rawImage.width(); + int sy = rawImage.height(); + WritableImage image = new WritableImage(sx, sy); + writeRawImageToImage(image, rawImage); + return image; + } + + + public static void writeRawImageToImage(WritableImage outImage, Matrix inRawImage) { + int sx = inRawImage.size().xInt(); + int sy = inRawImage.size().yInt(); + + if (inRawImage instanceof MatrixInts matrixInts) { + outImage.getPixelWriter().setPixels(0, 0, sx, sy, pixelFormat, matrixInts.array(), 0, sx); + } else { + // TODO + throw new UnsupportedOperationException("To be implemented..."); + } + } + + + public static Vector imageSize(Image image) { + return Vector.xy(image.getWidth(), image.getHeight()); + } + + + public static WritableImage createWritableImage(Vector size) { + return new WritableImage(size.xInt(), size.yInt()); + } + + + public static void saveImageToFileWithDialog(Image image) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Export Image"); + fileChooser.setInitialDirectory(new File(".")); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("Image Files", "*.png"), + new FileChooser.ExtensionFilter("All Files", "*.*") + ); + File file = fileChooser.showSaveDialog(null); + + if (file != null) { + try { + ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + + public static void copyImageToClipboard(Image image) { + ClipboardContent content = new ClipboardContent(); + content.putImage(image); + Clipboard.getSystemClipboard().setContent(content); + } + + + // --- Parallel utils ------- + + + public static final ExecutorService pool; + public static final int parallelism; + + static { + int p = ForkJoinPool.getCommonPoolParallelism(); + try { + boolean obsRunning = false; + obsRunning |= ProcessHandle.allProcesses().anyMatch(ph -> ph.info().command().orElse("").contains("obs64")); // Windows + obsRunning |= !Utils.runCommand("top -b -n 1 | grep \" obs\"").isEmpty(); // Linux + obsRunning |= true; + if (obsRunning) { + p -= 3; + } + } catch (Exception e) { + e.printStackTrace(); + } + parallelism = p; + pool = new ForkJoinPool(parallelism); + } + + + public static void parallel(int iFrom, int iTo, IntConsumer action) { + Future f = pool.submit(() -> { + try { + IntStream.range(iFrom, iTo).parallel().forEach(action); + } catch (Exception e) { + e.printStackTrace(); + } + }); + while (true) { + try { + f.get(); + break; + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } + } + + public static void parallel(int iTo, IntConsumer action) { + parallel(0, iTo, action); + } + + public static void parallelY(Vector size, IntConsumer action) { + parallel(size.yInt(), action); + } + + public static void parallelY(Vector size, A2 action) { + parallelY(size, y -> { + for (int x = 0; x < size.xInt(); x++) { + action.execute(x, y); + } + }); + } + + public static void parallel(Vector size, A1 action) { + parallelY(size, y -> { + for (int x = 0; x < size.xInt(); x++) { + action.execute(Vector.xy(x, y)); + } + }); + } + + + public static RNG[] rngArray(int n, long seed) { + RNG seeds = new RNG(seed); + RNG[] rngs = new RNG[n]; + for (int i = 0; i < n; i++) { + rngs[i] = new RNG(seeds.nextLong()); + } + return rngs; + } + + + public static RNG[] rngArray(int n) { + return rngArray(n, new RNG().nextLong()); + } + + + public static final ResourceManagerMap> matricesColor = new ResourceManagerMap<>( + MatrixData::new, (m, sz) -> m.fill(Color.BLACK) + ); + + public static final ResourceManagerMap> matricesInt = new ResourceManagerMap<>( + MatrixInts::new, (m, sz) -> m.fill(0) + ); + +} + diff --git a/src/xyz/marsavic/gfxlab/gui/instruments/InstrumentRenderer.java b/src/xyz/marsavic/gfxlab/gui/instruments/InstrumentRenderer.java new file mode 100644 index 0000000..a3abeef --- /dev/null +++ b/src/xyz/marsavic/gfxlab/gui/instruments/InstrumentRenderer.java @@ -0,0 +1,58 @@ +package xyz.marsavic.gfxlab.gui.instruments; + +import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Renderer; +import xyz.marsavic.gfxlab.elements.Output; +import xyz.marsavic.gfxlab.gui.UtilsGL; +import xyz.marsavic.objectinstruments.instruments.ObjectInstrument; +import xyz.marsavic.time.Profiler; + + +public class InstrumentRenderer extends ObjectInstrument> { + + private final Pane pane; + private final ImageView imageView; + + private WritableImage image; + + private int iFrameNext = 0; + + private final Profiler profilerUpdate = UtilsGL.profiler(this, "update"); + + + + public InstrumentRenderer(Output outRenderer) { + imageView = new ImageView(); + pane = new VBox(imageView); + + setObject(outRenderer); + } + + + + @Override + public void update() { + profilerUpdate.enter(); + object().get().process(iFrameNext++, m -> { + Vector size = m.size(); + if ((image == null) || !UtilsGL.imageSize(image).equals(size)) { + image = UtilsGL.createWritableImage(size); + imageView.setImage(image); + } + UtilsGL.writeRawImageToImage(image, m); + }); + profilerUpdate.exit(); + } + + + @Override + public Region node() { + return pane; + } + +} diff --git a/src/xyz/marsavic/gfxlab/playground/GfxLab.java b/src/xyz/marsavic/gfxlab/playground/GfxLab.java new file mode 100644 index 0000000..1cffe32 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/playground/GfxLab.java @@ -0,0 +1,77 @@ +package xyz.marsavic.gfxlab.playground; + +import xyz.marsavic.functions.interfaces.A2; +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.gfxlab.*; +import xyz.marsavic.gfxlab.elements.Output; +import xyz.marsavic.gfxlab.graphics3d.raytracers.RayTracingTest; +import xyz.marsavic.gfxlab.gui.UtilsGL; +import xyz.marsavic.gfxlab.tonemapping.ColorTransform; +import xyz.marsavic.gfxlab.tonemapping.ToneMapping; +import xyz.marsavic.gfxlab.tonemapping.colortransforms.Identity; + +import static xyz.marsavic.gfxlab.elements.ElementF.e; +import static xyz.marsavic.gfxlab.elements.Output.val; + + +public class GfxLab { + + public Output outRenderer; + + public GfxLab() { + var eSize = e(Vec3::new, val(1200.0), val(640.0), val(640.0)); + + var eRenderer = + e(Renderer::new, + eSize, + e(Fs::aFillFrameInt, + e(Fs::aFillFrameColor, + e(Fs::transformedColorFunction, +// e(Blobs::new, val(5), val(0.1), val(0.2)), + e(RayTracingTest::new), + e(TransformationsFromSize.toGeometric, eSize) + ) + ), + e(Fs::toneMapping, + e(ColorTransform::asColorTransformFromMatrixColor, + e(Identity::new) + ) + ) + ) + ); + + outRenderer = eRenderer.out(); + } + +} + + +class Fs { + + public static ColorFunction transformedColorFunction(ColorFunction colorFunction, Transformation transformation) { + return p -> colorFunction.at(transformation.at(p)); + } + + public static A2, Double> aFillFrameColor(ColorFunction colorFunction) { + return (Matrix result, Double t) -> { + result.fill(p -> colorFunction.at(t, p)); + }; + } + + public static A2, Double> aFillFrameInt(A2, Double> aFillFrameColor, ToneMapping toneMapping) { + return (Matrix result, Double t) -> { + UtilsGL.matricesColor.borrow(result.size(), true, mC -> { + aFillFrameColor.execute(mC, t); + toneMapping.apply(mC, result); + }); + }; + } + + public static ToneMapping toneMapping(F1> f_ColorTransform_MatrixColor) { + return (input, output) -> { + ColorTransform f = f_ColorTransform_MatrixColor.at(input); + output.fill(p -> f.at(input.get(p)).code()); + }; + } + +} diff --git a/src/xyz/marsavic/gfxlab/playground/colorfunctions/Blobs.java b/src/xyz/marsavic/gfxlab/playground/colorfunctions/Blobs.java new file mode 100644 index 0000000..45b6f7d --- /dev/null +++ b/src/xyz/marsavic/gfxlab/playground/colorfunctions/Blobs.java @@ -0,0 +1,54 @@ +package xyz.marsavic.gfxlab.playground.colorfunctions; + +import xyz.marsavic.geometry.Box; +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.ColorFunctionT; +import xyz.marsavic.utils.Defaults; +import xyz.marsavic.gfxlab.elements.Immutable; +import xyz.marsavic.random.sampling.Sampler; + + +@Immutable +public class Blobs implements ColorFunctionT { + + private final int n; + private final double m; + private final double threshold; + + + private final Vector[] o, c; + + + + public static final Defaults $ = Defaults.args(5, 0.1, 0.2); + + public Blobs(int n, double m, double threshold) { + this.n = n; + this.m = m; + this.threshold = threshold; + + o = new Vector[n]; + c = new Vector[n]; + + Sampler sampler = new Sampler(-923147595275902678L); + for (int i = 0; i < n; i++) { + o[i] = sampler.randomInBox(Box.cr(10)).round(); + c[i] = sampler.randomInBox(); + } + } + + + @Override + public Color at(double t, Vector p) { + double s = 0; + for (int i = 0; i < n; i++) { + Vector c = Vector.polar(o[i].mul(t).add(this.c[i])).mul(0.5); + double d = p.sub(c).lengthSquared(); + s += Math.exp(-d / m); + } + double k = 0.3 * s - threshold; + return (k < 0 ? Color.rgb(0.3, 0.0, 0.4) : Color.rgb(0.9, 0.8, 0.1)).mul(Math.abs(k)); + } + +} diff --git a/src/xyz/marsavic/gfxlab/playground/colorfunctions/GammaTest.java b/src/xyz/marsavic/gfxlab/playground/colorfunctions/GammaTest.java new file mode 100644 index 0000000..10ddaba --- /dev/null +++ b/src/xyz/marsavic/gfxlab/playground/colorfunctions/GammaTest.java @@ -0,0 +1,15 @@ +package xyz.marsavic.gfxlab.playground.colorfunctions; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.ColorFunctionT; +import xyz.marsavic.gfxlab.elements.Element; + +public class GammaTest extends Element implements ColorFunctionT { + + @Override + public Color at(double t, Vector p) { + return Color.gray(p.x() < 320 ? 0.5 : (p.xInt() ^ p.yInt()) & 1); + } + +} diff --git a/src/xyz/marsavic/gfxlab/playground/colorfunctions/Gradient.java b/src/xyz/marsavic/gfxlab/playground/colorfunctions/Gradient.java new file mode 100644 index 0000000..c2a8908 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/playground/colorfunctions/Gradient.java @@ -0,0 +1,17 @@ +package xyz.marsavic.gfxlab.playground.colorfunctions; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.ColorFunctionT; +import xyz.marsavic.gfxlab.elements.Element; +import xyz.marsavic.utils.Numeric; + + +public class Gradient extends Element implements ColorFunctionT { + + @Override + public Color at(double t, Vector p) { + return Color.rgb(p.x(), 1-p.x(), 0.5 + 0.5 * Numeric.sinT(t)); + } + +} diff --git a/src/xyz/marsavic/gfxlab/playground/colorfunctions/OkLab.java b/src/xyz/marsavic/gfxlab/playground/colorfunctions/OkLab.java new file mode 100644 index 0000000..d7889ac --- /dev/null +++ b/src/xyz/marsavic/gfxlab/playground/colorfunctions/OkLab.java @@ -0,0 +1,16 @@ +package xyz.marsavic.gfxlab.playground.colorfunctions; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.ColorFunctionT; +import xyz.marsavic.gfxlab.elements.Element; + + +public class OkLab extends Element implements ColorFunctionT { + + @Override + public Color at(double t, Vector p) { + return Color.oklabPolar(t, p.x(), p.y()); + } + +} diff --git a/src/xyz/marsavic/gfxlab/playground/colorfunctions/Spirals.java b/src/xyz/marsavic/gfxlab/playground/colorfunctions/Spirals.java new file mode 100644 index 0000000..aef18b1 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/playground/colorfunctions/Spirals.java @@ -0,0 +1,21 @@ +package xyz.marsavic.gfxlab.playground.colorfunctions; + +import xyz.marsavic.geometry.Vector; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.ColorFunctionT; +import xyz.marsavic.gfxlab.elements.Element; +import xyz.marsavic.utils.Numeric; + + +public class Spirals extends Element implements ColorFunctionT { + + @Override + public Color at(double t, Vector p) { + return Color.rgb( + Math.max(0, Numeric.sinT(-t + 7 * p.angle())), + Math.max(0, Numeric.sinT(2 * t + 0.25 / p.length() + p.angle())), + 0.4 + ); + } + +} diff --git a/src/xyz/marsavic/gfxlab/resources/Resources.java b/src/xyz/marsavic/gfxlab/resources/Resources.java new file mode 100644 index 0000000..e6c9f31 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/resources/Resources.java @@ -0,0 +1,36 @@ +package xyz.marsavic.gfxlab.resources; + +import javafx.scene.image.Image; + +import java.util.Arrays; +import java.util.Objects; +import org.kordamp.ikonli.Ikon; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; + + +public class Resources { + + private static Image[] iconsApplication = null; + + public static final String stylesheetURL = xyz.marsavic.objectinstruments.resources.Resources.stylesheetURL; + + + public static Image[] iconsApplication() { + if (iconsApplication == null) { + String iconName = "mars"; + + iconsApplication = Arrays.stream((new String[]{"016", "024", "032", "048", "064", "128"})).map(size -> + new Image(Objects.requireNonNull(Resources.class.getResourceAsStream("icons/" + iconName + " " + size + ".png"))) + ).toArray(Image[]::new); + } + return iconsApplication; + } + + + public static class Ikons { + // https://kordamp.org/ikonli/cheat-sheet-materialdesign2.html + public static final Ikon COPY = MaterialDesignC.CONTENT_COPY; + public static final Ikon SAVE = MaterialDesignC.CONTENT_SAVE; + } + +} diff --git a/src/xyz/marsavic/gfxlab/tonemapping/ColorTransform.java b/src/xyz/marsavic/gfxlab/tonemapping/ColorTransform.java new file mode 100644 index 0000000..3be17b9 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/tonemapping/ColorTransform.java @@ -0,0 +1,15 @@ +package xyz.marsavic.gfxlab.tonemapping; + +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.Matrix; + + +public interface ColorTransform extends F1 { + + default F1> asColorTransformFromMatrixColor() { + return colorMatrix -> ColorTransform.this; + } + +} + diff --git a/src/xyz/marsavic/gfxlab/tonemapping/ToneMapping.java b/src/xyz/marsavic/gfxlab/tonemapping/ToneMapping.java new file mode 100644 index 0000000..225b329 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/tonemapping/ToneMapping.java @@ -0,0 +1,21 @@ +package xyz.marsavic.gfxlab.tonemapping; + +import xyz.marsavic.functions.interfaces.F1; +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.Matrix; +import xyz.marsavic.gfxlab.MatrixInts; + + +public interface ToneMapping extends F1, Matrix> { + void apply(Matrix input, Matrix output); + + + /** A better option is to use ResourceManager to borrow an output Matrix and call apply on it. */ + @Override + default Matrix at(Matrix input) { + Matrix output = new MatrixInts(input.size()); + apply(input, output); + return output; + } + +} diff --git a/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/HueShift.java b/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/HueShift.java new file mode 100644 index 0000000..738edbd --- /dev/null +++ b/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/HueShift.java @@ -0,0 +1,20 @@ +package xyz.marsavic.gfxlab.tonemapping.colortransforms; + +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.Vec3; +import xyz.marsavic.gfxlab.elements.Immutable; +import xyz.marsavic.gfxlab.tonemapping.ColorTransform; + + +@Immutable +public record HueShift( + double hueShift +) implements ColorTransform { + + @Override + public Color at(Color color) { + Vec3 hsb = color.hsb(); + return Color.hsb(hsb.add(Vec3.xyz(hueShift, 0, 0))); + } + +} diff --git a/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/Identity.java b/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/Identity.java new file mode 100644 index 0000000..168d9fd --- /dev/null +++ b/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/Identity.java @@ -0,0 +1,18 @@ +package xyz.marsavic.gfxlab.tonemapping.colortransforms; + +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.elements.Immutable; +import xyz.marsavic.gfxlab.tonemapping.ColorTransform; + + +@Immutable +public record Identity( + +) implements ColorTransform { + + @Override + public Color at(Color color) { + return color; + } + +} diff --git a/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/Multiply.java b/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/Multiply.java new file mode 100644 index 0000000..22d01a8 --- /dev/null +++ b/src/xyz/marsavic/gfxlab/tonemapping/colortransforms/Multiply.java @@ -0,0 +1,18 @@ +package xyz.marsavic.gfxlab.tonemapping.colortransforms; + +import xyz.marsavic.gfxlab.Color; +import xyz.marsavic.gfxlab.elements.Immutable; +import xyz.marsavic.gfxlab.tonemapping.ColorTransform; + + +@Immutable +public record Multiply( + double factor +) implements ColorTransform { + + @Override + public Color at(Color color) { + return color.mul(factor); + } + +} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..94aa483 --- /dev/null +++ b/todo.md @@ -0,0 +1,65 @@ +## Animations + +### Workers + +Workers are animations that compute iterations inside an infinite loop. + +- Workers have to be registered with the app, so that they all can be controlled (started/paused/disposed) from outside an animation. + - Ideally, the app will provide that infinite loop, and a the worker will just provide the action to be performed inside. + + +### Pulling + +Pullers should be the only type of workers? + +`PullingCache` +- A worker with last frame cached and immediately available to the caller. + + +### Buffering + +- Buffer manager provides matrix buffers per request. + - Has a set of matrices, reuses them. +- Clients work with buffers only inside a callback block. +- The caller needs to provide a buffer to be filled by the called animation. + - Benefit: same buffer can propagate through several layers. +- Buffer manager sets private data field inside Matrix buffer to null when the block is exited. + - If anyone saved the reference to the Matrix it will now be useless, throwing a NPE if trying to read/write to it. + +What to do with animations needing to store matrices as fields? + - Well, are they really replacing these matrices or just overwriting them? + + +### Synchronization + +Animation T might need to work on the sources A and B obtained from the same iteration of S. + + +``` + A + / \ +S T + \ / + B +``` + +Keep a history of *touches* at every frame. The history is a map (animation -> iteration number) containing all the animations that processed the frame. + +If synchronization is needed (at T), wait until all the sources (frames from A and B) have the same iteration number at the wanted animation (S). + +How to unite histories at T? +- Keep only entries that are the same in A and B. + +## Graphics + +### Noise removal + +1. Make ray tracers return detailed info about each pixel: body, normal, uv. +1. Make a matrix containing the ratio between the obtained color and the texture color at (body, uv) for each pixel. +1. Blur that matrix, observing thresholds based on (body, normal, uv). +1. Recompute the colors by multiplying the texture color with blurred value. + +- Be adaptive, reduce the blur radius if there is less noise. + - Not only per frame, but per pixel? + +* Alas, what shell we do with lens blur? \ No newline at end of file