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