Initial commit

This commit is contained in:
Marko Savić 2022-11-15 02:21:15 +01:00
commit 72970d9f5e
95 changed files with 3426 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Project exclude paths
/out/

8
.idea/.gitignore vendored Normal file
View file

@ -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

6
.idea/compiler.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component>
</project>

View file

@ -0,0 +1,24 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="CodeBlock2Expr" enabled="true" level="TEXT ATTRIBUTES" enabled_by_default="true" />
<inspection_tool class="UnnecessaryBlockStatement" enabled="true" level="TEXT ATTRIBUTES" enabled_by_default="true" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES">
<option name="ignoreSwitchBranches" value="false" />
</inspection_tool>
<inspection_tool class="UnusedReturnValue" enabled="true" level="WARNING" enabled_by_default="true">
<option name="highestModifier" value="packageLocal" />
</inspection_tool>
<inspection_tool class="unused" enabled="true" level="WARNING" enabled_by_default="true" klass="packageLocal" inner_class="packageLocal" field="packageLocal" method="packageLocal" checkParameterExcludingHierarchy="false">
<option name="LOCAL_VARIABLE" value="true" />
<option name="FIELD" value="true" />
<option name="METHOD" value="true" />
<option name="CLASS" value="true" />
<option name="PARAMETER" value="true" />
<option name="REPORT_PARAMETER_FOR_PUBLIC_METHODS" value="true" />
<option name="ADD_MAINS_TO_ENTRIES" value="true" />
<option name="ADD_APPLET_TO_ENTRIES" value="true" />
<option name="ADD_SERVLET_TO_ENTRIES" value="true" />
<option name="ADD_NONJAVA_TO_ENTRIES" value="true" />
</inspection_tool>
</profile>
</component>

View file

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="caffeine-3.1.1">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/caffeine-3.1.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View file

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="checker-qual-3.22.0">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/checker-qual-3.22.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View file

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="error_prone_annotations-2.14.0">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/error_prone_annotations-2.14.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View file

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

View file

@ -0,0 +1,14 @@
<component name="libraryTable">
<library name="kordamp.ikonli.core" type="repository">
<properties maven-id="org.kordamp.ikonli:ikonli-core:RELEASE" />
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/ikonli-core-12.3.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$PROJECT_DIR$/lib/ikonli-core-12.3.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$PROJECT_DIR$/lib/ikonli-core-12.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

View file

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="kordamp.ikonli.javafx" type="repository">
<properties maven-id="org.kordamp.ikonli:ikonli-javafx:RELEASE" />
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/ikonli-javafx-12.3.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/ikonli-core-12.3.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View file

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="kordamp.ikonli.materialdesign2.pack" type="repository">
<properties maven-id="org.kordamp.ikonli:ikonli-materialdesign2-pack:RELEASE" />
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/ikonli-materialdesign2-pack-12.3.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/ikonli-core-12.3.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View file

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="mars-bits-2022-11-15">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/mars-bits-2022-11-15.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View file

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="object-instruments-2022-11-15">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/object-instruments-2022-11-15.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

6
.idea/misc.xml Normal file
View file

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

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/GfxLab.iml" filepath="$PROJECT_DIR$/GfxLab.iml" />
</modules>
</component>
</project>

124
.idea/uiDesigner.xml Normal file
View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

21
GfxLab.iml Normal file
View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="mars-bits-2022-11-15" level="project" />
<orderEntry type="library" name="object-instruments-2022-11-15" level="project" />
<orderEntry type="library" name="error_prone_annotations-2.14.0" level="project" />
<orderEntry type="library" name="caffeine-3.1.1" level="project" />
<orderEntry type="library" name="checker-qual-3.22.0" level="project" />
<orderEntry type="library" name="google.guava" level="project" />
<orderEntry type="library" name="kordamp.ikonli.materialdesign2.pack" level="project" />
<orderEntry type="library" name="kordamp.ikonli.core" level="project" />
<orderEntry type="library" name="kordamp.ikonli.javafx" level="project" />
</component>
</module>

12
README.md Normal file
View file

@ -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`.

BIN
lib/caffeine-3.1.1.jar Normal file

Binary file not shown.

BIN
lib/checker-qual-3.12.0.jar Normal file

Binary file not shown.

BIN
lib/checker-qual-3.22.0.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/failureaccess-1.0.1.jar Normal file

Binary file not shown.

BIN
lib/guava-31.1-jre.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/ikonli-core-12.3.1.jar Normal file

Binary file not shown.

BIN
lib/ikonli-core-12.3.11.jar Normal file

Binary file not shown.

BIN
lib/ikonli-core-12.3.12.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/jsr305-3.0.2.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

18
src/module-info.java Normal file
View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -0,0 +1,13 @@
package xyz.marsavic.gfxlab;
import xyz.marsavic.functions.interfaces.F1;
import xyz.marsavic.geometry.Vector;
public interface ColorFunction extends F1<Color, Vec3> {
default Color at(double t, Vector p) {
return at(Vec3.xp(t, p));
}
}

View file

@ -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());
}
}

View file

@ -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<E> {
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<E, Vector> f) {
UtilsGL.parallel(size(), p -> set(p, f.at(p))); // OPT?
}
default void copyFrom(Matrix<E> 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<Color> createBlack(Vector size) {
return new MatrixData<>(size, Color.BLACK);
}
static void add(Matrix<Color> a, Matrix<Color> b, Matrix<Color> 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<Color> add(Matrix<Color> a, Matrix<Color> b) {
Matrix<Color> result = new MatrixData<>(a.size());
add(a, b, result);
return result;
}
static void addInPlace(Matrix<Color> toChange, Matrix<Color> 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<Color> a, double k, Matrix<Color> 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<Color> mul(Matrix<Color> a, double k) {
Matrix<Color> result = new MatrixData<>(a.size());
mul(a, k, result);
return result;
}
static void mulInPlace(Matrix<Color> 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));
}
});
}
}

View file

@ -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<E> implements Matrix<E> {
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));
}
}

View file

@ -0,0 +1,66 @@
package xyz.marsavic.gfxlab;
import xyz.marsavic.geometry.Vector;
import java.util.Arrays;
public final class MatrixInts implements Matrix<Integer> {
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;
}
}

View file

@ -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<Matrix<Integer>, Double> aFillFrameInt) {
public void process(double t, A1<Matrix<Integer>> aProcess) {
UtilsGL.matricesInt.borrow(size.p12(), m -> {
aFillFrameInt.execute(m, Numeric.mod(t, size.x()));
aProcess.execute(m);
});
}
}

View file

@ -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<Vec3, Vec3> {
/** 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)
);
}
}

View file

@ -0,0 +1,31 @@
package xyz.marsavic.gfxlab;
import xyz.marsavic.functions.interfaces.F1;
public class TransformationsFromSize {
public static final F1<Transformation, Vec3> toIdentity = TransformationsFromSize::toIdentity;
public static final F1<Transformation, Vec3> toUnitBox = TransformationsFromSize::toUnitBox;
public static final F1<Transformation, Vec3> 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);
}
}

View file

@ -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());
}
}

View file

@ -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<T> {
private Output<T> output;
private final A1<EventInvalidated> 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<T> output) {
connect(output);
}
public T get() {
return output.get();
}
public void connect(Output<T> 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 <T> void onInputChanged(Input<T> input) {
}
protected <T> void onInputChangedOrInvalidated(Input<T> input) {
tearItDown();
buildItUp();
outputs().forEach(Invalidatable.Base::fireInvalidated);
}
protected final F0<List<Input<?>>> inputsFromFields = Caches.cached(() ->
Utils.cast(Reflection.fieldsOfType(this, Input.class))
);
protected final F0<List<Output<?>>> outputsFromFields = Caches.cached(() ->
Utils.cast(Reflection.fieldsOfType(this, Output.class))
);
public List<Input<?>> inputs() {
return inputsFromFields.at();
}
public List<Output<?>> outputs() {
return outputsFromFields.at();
}
}

View file

@ -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<R> extends Element implements HasOutput<R> {
public abstract R object();
public final Output<R> out = new Output<>(this::object);
@Override
public Output<R> out() {
return out;
}
public static <R> ElementF0<R> e(R result) {
return e(() -> result);
}
public static <R > ElementF0<R > e(F0<R> f ) { return new ElementF0.Lazy<>(f ); }
public static <R, P0 > ElementF1<R, P0 > e(F1<R, P0 > f, HasOutput<? extends P0> p0 ) { return new ElementF1.Lazy<>(f, p0 ); }
public static <R, P0, P1 > ElementF2<R, P0, P1 > e(F2<R, P0, P1 > f, HasOutput<? extends P0> p0, HasOutput<? extends P1> p1 ) { return new ElementF2.Lazy<>(f, p0, p1 ); }
public static <R, P0, P1, P2> ElementF3<R, P0, P1, P2> e(F3<R, P0, P1, P2> f, HasOutput<? extends P0> p0, HasOutput<? extends P1> p1, HasOutput<? extends P2> p2) { return new ElementF3.Lazy<>(f, p0, p1, p2); }
}

View file

@ -0,0 +1,40 @@
package xyz.marsavic.gfxlab.elements;
import xyz.marsavic.functions.interfaces.F0;
public abstract class ElementF0<R> extends ElementF<R> {
protected final F0<R> f;
public ElementF0(F0<R> f) {
this.f = f;
}
public static class Lazy<R> extends ElementF0<R> {
public Lazy(F0<R> f) {
super(f);
}
private R object;
@Override
protected void buildItUp() {
object = null;
}
public R object() {
if (object == null) {
object = f.at();
}
return object;
}
}
}

View file

@ -0,0 +1,43 @@
package xyz.marsavic.gfxlab.elements;
import xyz.marsavic.functions.interfaces.F1;
public abstract class ElementF1<R, P0> extends ElementF<R> {
protected final F1<R, P0> f;
protected final Input<? extends P0> in0;
public ElementF1(F1<R, P0> f, HasOutput<? extends P0> p0) {
this.f = f;
in0 = new Input<>(p0.out());
}
public static class Lazy<R, P0> extends ElementF1<R, P0> {
public Lazy(F1<R, P0> f, HasOutput<? extends P0> 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;
}
}
}

View file

@ -0,0 +1,45 @@
package xyz.marsavic.gfxlab.elements;
import xyz.marsavic.functions.interfaces.F2;
public abstract class ElementF2<R, P0, P1> extends ElementF<R> {
protected final F2<R, P0, P1> f;
protected final Input<? extends P0> in0;
protected final Input<? extends P1> in1;
public ElementF2(F2<R, P0, P1> f, HasOutput<? extends P0> p0, HasOutput<? extends P1> p1) {
this.f = f;
in0 = new Input<>(p0.out());
in1 = new Input<>(p1.out());
}
public static class Lazy<R, P0, P1> extends ElementF2<R, P0, P1> {
public Lazy(F2<R, P0, P1> f, HasOutput<? extends P0> p0, HasOutput<? extends P1> 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;
}
}
}

View file

@ -0,0 +1,47 @@
package xyz.marsavic.gfxlab.elements;
import xyz.marsavic.functions.interfaces.F3;
public abstract class ElementF3<R, P0, P1, P2> extends ElementF<R> {
protected final F3<R, P0, P1, P2> f;
protected final Input<? extends P0> in0;
protected final Input<? extends P1> in1;
protected final Input<? extends P2> in2;
public ElementF3(F3<R, P0, P1, P2> f, HasOutput<? extends P0> p0, HasOutput<? extends P1> p1, HasOutput<? extends P2> p2) {
this.f = f;
in0 = new Input<>(p0.out());
in1 = new Input<>(p1.out());
in2 = new Input<>(p2.out());
}
public static class Lazy<R, P0, P1, P2> extends ElementF3<R, P0, P1, P2> {
public Lazy(F3<R, P0, P1, P2> f, HasOutput<? extends P0> p0, HasOutput<? extends P1> p1, HasOutput<? extends P2> 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;
}
}
}

View file

@ -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<T> extends Element {
private final List<Input<?>> inputs;
public final Output<T> out = new Output<>(this::object);
private T object;
private final Constructor<T> constructor;
private final int nParameters;
public ElementPureLazy(Class<T> 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<T>[] constructors = (Constructor<T>[]) 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 <T> ElementPureLazy<T> of(Class<T> type, Object... args) {
//noinspection SuspiciousToArrayCall
return new ElementPureLazy<T>(
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<Input<?>> 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;
}
}

View file

@ -0,0 +1,5 @@
package xyz.marsavic.gfxlab.elements;
public interface HasOutput<R> {
Output<R> out();
}

View file

@ -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 {
}

View file

@ -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<EventInvalidated> onInvalidated();
class Base {
protected final Dispatcher<EventInvalidated> dispatcherInvalidated = new Dispatcher<>();
public Reactions<EventInvalidated> onInvalidated() {
return dispatcherInvalidated.reactions();
}
protected void fireInvalidated() {
dispatcherInvalidated.fire(new EventInvalidated());
}
}
}

View file

@ -0,0 +1,10 @@
package xyz.marsavic.gfxlab.elements;
public abstract class Motor {
abstract void start();
abstract void pause();
abstract void stop();
}

View file

@ -0,0 +1,32 @@
package xyz.marsavic.gfxlab.elements;
import xyz.marsavic.functions.interfaces.F0;
public class Output<T> extends Invalidatable.Base implements HasOutput<T> {
private final F0<T> resultProvider;
public Output(F0<T> resultProvider) {
this.resultProvider = resultProvider;
}
public T get() {
return resultProvider.at();
}
@Override
public Output<T> out() {
return this;
}
public static <T> Output<T> val(T object) {
return new Output<>(() -> object);
}
}

View file

@ -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());
}
}

View file

@ -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)
);
}
}
}

View file

@ -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;
}
*/
}

View file

@ -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;
}
}
}

View file

@ -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_());
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}
}

View file

@ -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_;
}
}
}

View file

@ -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<Object> specialObjects = ReactiveSetBacked.withLinkedHashSet();
private final List<Object> 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<Object> objectSet = panel.objectSet();
List<Tuple2<Class<?>, Function0<Factory<?>>>> 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();
}
}

View file

@ -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();
}
}

View file

@ -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<Profiler> 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<Profiler> profilers() {
synchronized (profilers) {
return ImmutableList.copyOf(profilers);
}
}
// JavaFX helpers
static final PixelFormat<IntBuffer> 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<Integer> 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<Integer, Integer> action) {
parallelY(size, y -> {
for (int x = 0; x < size.xInt(); x++) {
action.execute(x, y);
}
});
}
public static void parallel(Vector size, A1<Vector> 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<Vector, Matrix<Color>> matricesColor = new ResourceManagerMap<>(
MatrixData::new, (m, sz) -> m.fill(Color.BLACK)
);
public static final ResourceManagerMap<Vector, Matrix<Integer>> matricesInt = new ResourceManagerMap<>(
MatrixInts::new, (m, sz) -> m.fill(0)
);
}

View file

@ -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<Output<Renderer>> {
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<Renderer> 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;
}
}

View file

@ -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<Renderer> 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<Matrix<Color>, Double> aFillFrameColor(ColorFunction colorFunction) {
return (Matrix<Color> result, Double t) -> {
result.fill(p -> colorFunction.at(t, p));
};
}
public static A2<Matrix<Integer>, Double> aFillFrameInt(A2<Matrix<Color>, Double> aFillFrameColor, ToneMapping toneMapping) {
return (Matrix<Integer> result, Double t) -> {
UtilsGL.matricesColor.borrow(result.size(), true, mC -> {
aFillFrameColor.execute(mC, t);
toneMapping.apply(mC, result);
});
};
}
public static ToneMapping toneMapping(F1<ColorTransform, Matrix<Color>> f_ColorTransform_MatrixColor) {
return (input, output) -> {
ColorTransform f = f_ColorTransform_MatrixColor.at(input);
output.fill(p -> f.at(input.get(p)).code());
};
}
}

View file

@ -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<Blobs> $ = 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));
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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());
}
}

View file

@ -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
);
}
}

View file

@ -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;
}
}

View file

@ -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<Color, Color> {
default F1<ColorTransform, Matrix<Color>> asColorTransformFromMatrixColor() {
return colorMatrix -> ColorTransform.this;
}
}

View file

@ -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<Integer>, Matrix<Color>> {
void apply(Matrix<Color> input, Matrix<Integer> output);
/** A better option is to use ResourceManager to borrow an output Matrix and call apply on it. */
@Override
default Matrix<Integer> at(Matrix<Color> input) {
Matrix<Integer> output = new MatrixInts(input.size());
apply(input, output);
return output;
}
}

View file

@ -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)));
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

65
todo.md Normal file
View file

@ -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?