mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-04-14 14:43:33 +02:00
Merge branch 'main' into tasks
This commit is contained in:
@@ -3,17 +3,15 @@ package schule.ngb.zm;
|
|||||||
/**
|
/**
|
||||||
* Repräsentiert eine Farbe in der Zeichenmaschine.
|
* Repräsentiert eine Farbe in der Zeichenmaschine.
|
||||||
* <p>
|
* <p>
|
||||||
* Farben bestehen entweder aus einem Grauwert (zwischen 0 und
|
* Farben bestehen entweder aus einem Grauwert (zwischen 0 und 255) oder einem
|
||||||
* 255) oder einem Rot-, Grün- und Blauanteil (jeweils zwischen
|
* Rot-, Grün- und Blauanteil (jeweils zwischen 0 und 255).
|
||||||
* 0 und 255).
|
|
||||||
* <p>
|
* <p>
|
||||||
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0
|
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
||||||
* (unsichtbar) und 255 (deckend).
|
* (deckend).
|
||||||
*/
|
*/
|
||||||
public class Color {
|
public class Color {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
/**
|
/**
|
||||||
* Die Farbe Schwarz (Grauwert 0).
|
* Die Farbe Schwarz (Grauwert 0).
|
||||||
@@ -124,12 +122,12 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
||||||
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die
|
||||||
* liegen zwischen 0 und 255.
|
* Werte liegen zwischen 0 und 255.
|
||||||
*
|
*
|
||||||
* @param red Rotwert zwischen 0 und 255.
|
* @param red Rotwert zwischen 0 und 255.
|
||||||
* @param green Grünwert zwischen 0 und 255.
|
* @param green Grünwert zwischen 0 und 255.
|
||||||
* @param blue Blauwert zwischen 0 und 255.
|
* @param blue Blauwert zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
public Color( int red, int green, int blue ) {
|
public Color( int red, int green, int blue ) {
|
||||||
this(red, green, blue, 255);
|
this(red, green, blue, 255);
|
||||||
@@ -137,20 +135,18 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
||||||
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die
|
||||||
* liegen zwischen 0 und 255.
|
* Werte liegen zwischen 0 und 255.
|
||||||
* <var>alpha</var> gibt den den Transparentwert an (auch zwischen
|
* <var>alpha</var> gibt den den Transparentwert an (auch zwischen
|
||||||
* 0 und 255), wobei
|
* 0 und 255), wobei 0 komplett durchsichtig ist und 255 komplett deckend.
|
||||||
* 0 komplett durchsichtig ist und 255 komplett
|
|
||||||
* deckend.
|
|
||||||
*
|
*
|
||||||
* @param red Rotwert zwischen 0 und 255.
|
* @param red Rotwert zwischen 0 und 255.
|
||||||
* @param green Grünwert zwischen 0 und 255.
|
* @param green Grünwert zwischen 0 und 255.
|
||||||
* @param blue Blauwert zwischen 0 und 255.
|
* @param blue Blauwert zwischen 0 und 255.
|
||||||
* @param alpha Transparentwert zwischen 0 und 255.
|
* @param alpha Transparentwert zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
public Color( int red, int green, int blue, int alpha ) {
|
public Color( int red, int green, int blue, int alpha ) {
|
||||||
rgba = (alpha << 24) | (red << 16) | (green << 8) | blue;
|
rgba = ((alpha&0xFF) << 24) | ((red&0xFF) << 16) | ((green&0xFF) << 8) | ((blue&0xFF) << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,10 +180,11 @@ public class Color {
|
|||||||
/**
|
/**
|
||||||
* Interner Konstruktor für die Initialisierung einer Farbe mit einem
|
* Interner Konstruktor für die Initialisierung einer Farbe mit einem
|
||||||
* RGBA-Wert.
|
* RGBA-Wert.
|
||||||
*
|
* <p>
|
||||||
* Da der Konstruktor {@link #Color(int)} schon besetzt ist, muss hier der
|
* Da der Konstruktor {@link #Color(int)} schon besetzt ist, muss hier der
|
||||||
* Parameter {@code isRGBA} auf {@code true} gesetzt werden, damit {@code rgba}
|
* Parameter {@code isRGBA} auf {@code true} gesetzt werden, damit
|
||||||
* korrekt interpretiert wird.
|
* {@code rgba} korrekt interpretiert wird.
|
||||||
|
*
|
||||||
* @param rgba RGBA-wert der Farbe.
|
* @param rgba RGBA-wert der Farbe.
|
||||||
* @param isRGBA Sollte immer {@code true} sein.
|
* @param isRGBA Sollte immer {@code true} sein.
|
||||||
*/
|
*/
|
||||||
@@ -210,12 +207,12 @@ public class Color {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color getHSBColor(double h, double s, double b) {
|
public static Color getHSBColor( double h, double s, double b ) {
|
||||||
return new Color(java.awt.Color.getHSBColor((float)h, (float)s, (float)b));
|
return new Color(java.awt.Color.getHSBColor((float) h, (float) s, (float) b));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color getHSLColor(double h, double s, double l) {
|
public static Color getHSLColor( double h, double s, double l ) {
|
||||||
int rgb = Color.HSLtoRGB(new float[]{(float)h, (float)s, (float)l});
|
int rgb = Color.HSLtoRGB(new float[]{(float) h, (float) s, (float) l});
|
||||||
return Color.getRGBColor(rgb);
|
return Color.getRGBColor(rgb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,9 +232,9 @@ public class Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann
|
* Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann sechs-
|
||||||
* sechs- oder achtstellig sein (wenn ein Transparentwert vorhanden ist).
|
* oder achtstellig sein (wenn ein Transparentwert vorhanden ist). Dem Code
|
||||||
* Dem Code kann ein {@code #} Zeichen vorangestellt sein.
|
* kann ein {@code #} Zeichen vorangestellt sein.
|
||||||
*
|
*
|
||||||
* @param hexcode
|
* @param hexcode
|
||||||
* @return
|
* @return
|
||||||
@@ -336,10 +333,11 @@ public class Color {
|
|||||||
/**
|
/**
|
||||||
* Konvertiert eine Farbe mit Komponenten im HSL-Farbraum in den
|
* Konvertiert eine Farbe mit Komponenten im HSL-Farbraum in den
|
||||||
* RGB-Farbraum.
|
* RGB-Farbraum.
|
||||||
*
|
* <p>
|
||||||
* Die Farbkomponenten werden als Float-Array übergeben. Im Index 0 steht
|
* Die Farbkomponenten werden als Float-Array übergeben. Im Index 0 steht
|
||||||
* der h-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den s- und
|
* der h-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den s- und
|
||||||
* l-Wert im Bereich von 0 bis 1.
|
* l-Wert im Bereich von 0 bis 1.
|
||||||
|
*
|
||||||
* @param hsl Die Farbkomponenten im HSL-Farbraum.
|
* @param hsl Die Farbkomponenten im HSL-Farbraum.
|
||||||
* @param alpha Ein Transparenzwert im Bereich 0 bis 255.
|
* @param alpha Ein Transparenzwert im Bereich 0 bis 255.
|
||||||
* @return Der RGBA-Wert der Farbe.
|
* @return Der RGBA-Wert der Farbe.
|
||||||
@@ -392,6 +390,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine Kopie dieser Farbe.
|
* Erzeugt eine Kopie dieser Farbe.
|
||||||
|
*
|
||||||
* @return Ein neues Farbobjekt.
|
* @return Ein neues Farbobjekt.
|
||||||
*/
|
*/
|
||||||
public Color copy() {
|
public Color copy() {
|
||||||
@@ -400,10 +399,11 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den RGBA-Wert dieser Farbe zurück.
|
* Gibt den RGBA-Wert dieser Farbe zurück.
|
||||||
*
|
* <p>
|
||||||
* Eine Farbe wird als 32-Bit Integer gespeichert. Bits 24-31 enthalten
|
* Eine Farbe wird als 32-Bit Integer gespeichert. Bits 24-31 enthalten den
|
||||||
* den Transparenzwert, 16-23 den Rotwert, 8-15 den Grünwert und 0-7 den
|
* Transparenzwert, 16-23 den Rotwert, 8-15 den Grünwert und 0-7 den
|
||||||
* Blauwert der Farbe.
|
* Blauwert der Farbe.
|
||||||
|
*
|
||||||
* @return Der RGBA-Wert der Farbe.
|
* @return Der RGBA-Wert der Farbe.
|
||||||
* @see #getRed()
|
* @see #getRed()
|
||||||
* @see #getGreen()
|
* @see #getGreen()
|
||||||
@@ -416,6 +416,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den Rotwert dieser Farbe zurück.
|
* Gibt den Rotwert dieser Farbe zurück.
|
||||||
|
*
|
||||||
* @return Der Rotwert der Farbe zwischen 0 und 255.
|
* @return Der Rotwert der Farbe zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
public int getRed() {
|
public int getRed() {
|
||||||
@@ -424,6 +425,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den Grünwert dieser Farbe zurück.
|
* Gibt den Grünwert dieser Farbe zurück.
|
||||||
|
*
|
||||||
* @return Der Grünwert der Farbe zwischen 0 und 255.
|
* @return Der Grünwert der Farbe zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
public int getGreen() {
|
public int getGreen() {
|
||||||
@@ -432,6 +434,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den Blauwert dieser Farbe zurück.
|
* Gibt den Blauwert dieser Farbe zurück.
|
||||||
|
*
|
||||||
* @return Der Blauwert der Farbe zwischen 0 und 255.
|
* @return Der Blauwert der Farbe zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
public int getBlue() {
|
public int getBlue() {
|
||||||
@@ -440,6 +443,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den Transparenzwert dieser Farbe zurück.
|
* Gibt den Transparenzwert dieser Farbe zurück.
|
||||||
|
*
|
||||||
* @return Der Transparenzwert der Farbe zwischen 0 und 255.
|
* @return Der Transparenzwert der Farbe zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
public int getAlpha() {
|
public int getAlpha() {
|
||||||
@@ -448,9 +452,10 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein {@link java.awt.Color}-Objekt aus dieser Farbe.
|
* Erzeugt ein {@link java.awt.Color}-Objekt aus dieser Farbe.
|
||||||
|
* <p>
|
||||||
|
* Das erzeugte Farbobjekt hat dieselben Rot-, Grün-, Blau- und
|
||||||
|
* Transparenzwerte wie diese Farbe.
|
||||||
*
|
*
|
||||||
* Das erzeugte Farbobjekt hat dieselben Rot-, Grün-, Blau-
|
|
||||||
* und Transparenzwerte wie diese Farbe.
|
|
||||||
* @return Ein Java-Farbobjekt.
|
* @return Ein Java-Farbobjekt.
|
||||||
*/
|
*/
|
||||||
public java.awt.Color getJavaColor() {
|
public java.awt.Color getJavaColor() {
|
||||||
@@ -468,11 +473,18 @@ public class Color {
|
|||||||
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
||||||
*/
|
*/
|
||||||
public boolean equals( Object obj ) {
|
public boolean equals( Object obj ) {
|
||||||
return obj instanceof Color && ((Color)obj).getRGBA() == this.rgba;
|
if( obj == null ) { return false; }
|
||||||
|
if( obj instanceof Color ) {
|
||||||
|
return ((Color) obj).getRGBA() == this.rgba;
|
||||||
|
} else if( obj instanceof java.awt.Color ) {
|
||||||
|
return ((java.awt.Color) obj).getRGB() == this.rgba;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt einen Text-String, der diese Farbe beschreibt.
|
* Erzeugt einen Text-String, der diese Farbe beschreibt.
|
||||||
|
*
|
||||||
* @return Eine Textversion der Farbe.
|
* @return Eine Textversion der Farbe.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -482,6 +494,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Berechnet einen Hashcode für dieses Farbobjekt.
|
* Berechnet einen Hashcode für dieses Farbobjekt.
|
||||||
|
*
|
||||||
* @return Ein Hashcode für diese Rabe.
|
* @return Ein Hashcode für diese Rabe.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -490,7 +503,8 @@ public class Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine um 30% hellere Version dieser Farbe.
|
* Erzeugt eine um 30% hellere Version dieser Farbe.
|
||||||
|
*
|
||||||
* @return Ein Farbobjekt mit einer helleren Farbe.
|
* @return Ein Farbobjekt mit einer helleren Farbe.
|
||||||
*/
|
*/
|
||||||
public Color brighter() {
|
public Color brighter() {
|
||||||
@@ -499,6 +513,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine um {@code percent} hellere Version dieser Farbe.
|
* Erzeugt eine um {@code percent} hellere Version dieser Farbe.
|
||||||
|
*
|
||||||
* @param percent Eine Prozentzahl zwischen 0 und 100.
|
* @param percent Eine Prozentzahl zwischen 0 und 100.
|
||||||
* @return Ein Farbobjekt mit einer helleren Farbe.
|
* @return Ein Farbobjekt mit einer helleren Farbe.
|
||||||
*/
|
*/
|
||||||
@@ -510,6 +525,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine um 30% dunklere Version dieser Farbe.
|
* Erzeugt eine um 30% dunklere Version dieser Farbe.
|
||||||
|
*
|
||||||
* @return Ein Farbobjekt mit einer dunkleren Farbe.
|
* @return Ein Farbobjekt mit einer dunkleren Farbe.
|
||||||
*/
|
*/
|
||||||
public Color darker() {
|
public Color darker() {
|
||||||
@@ -518,6 +534,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine um {@code percent} dunklere Version dieser Farbe.
|
* Erzeugt eine um {@code percent} dunklere Version dieser Farbe.
|
||||||
|
*
|
||||||
* @param percent Eine Prozentzahl zwischen 0 und 100.
|
* @param percent Eine Prozentzahl zwischen 0 und 100.
|
||||||
* @return Ein Farbobjekt mit einer dunkleren Farbe.
|
* @return Ein Farbobjekt mit einer dunkleren Farbe.
|
||||||
*/
|
*/
|
||||||
@@ -528,11 +545,12 @@ public class Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Color greyscale() {
|
public Color greyscale() {
|
||||||
return new Color((int)(getRed()*.299 + getGreen()*.587 + getBlue()*0.114));
|
return new Color((int) (getRed() * .299 + getGreen() * .587 + getBlue() * 0.114));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine zu dieser invertierte Farbe.
|
* Erzeugt eine zu dieser invertierte Farbe.
|
||||||
|
*
|
||||||
* @return Ein Farbobjekt mit der invertierten Farbe.
|
* @return Ein Farbobjekt mit der invertierten Farbe.
|
||||||
*/
|
*/
|
||||||
public Color inverted() {
|
public Color inverted() {
|
||||||
@@ -542,6 +560,7 @@ public class Color {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt die Komplementärfarbe zu dieser.
|
* Erzeugt die Komplementärfarbe zu dieser.
|
||||||
|
*
|
||||||
* @return Ein Farbobjekt mit der Komplementärfarbe.
|
* @return Ein Farbobjekt mit der Komplementärfarbe.
|
||||||
*/
|
*/
|
||||||
public Color complement() {
|
public Color complement() {
|
||||||
@@ -554,11 +573,12 @@ public class Color {
|
|||||||
* Wählt entweder {@link #WHITE weiß} oder {@link #BLACK schwarz} aus, je
|
* Wählt entweder {@link #WHITE weiß} oder {@link #BLACK schwarz} aus, je
|
||||||
* nachdem, welche der Farben besser als Textfarbe mit dieser Farbe als
|
* nachdem, welche der Farben besser als Textfarbe mit dieser Farbe als
|
||||||
* Hintergrund funktioniert (besser lesbar ist).
|
* Hintergrund funktioniert (besser lesbar ist).
|
||||||
|
*
|
||||||
* @return Schwarz oder weiß.
|
* @return Schwarz oder weiß.
|
||||||
*/
|
*/
|
||||||
public Color textcolor() {
|
public Color textcolor() {
|
||||||
// Basiert auf https://stackoverflow.com/questions/946544/good-text-foreground-color-for-a-given-background-color
|
// Basiert auf https://stackoverflow.com/questions/946544/good-text-foreground-color-for-a-given-background-color
|
||||||
if( (getRed()*.299 + getGreen()*.587 + getBlue()*0.114) < 186 ) {
|
if( (getRed() * .299 + getGreen() * .587 + getBlue() * 0.114) < 186 ) {
|
||||||
return WHITE;
|
return WHITE;
|
||||||
} else {
|
} else {
|
||||||
return BLACK;
|
return BLACK;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -191,7 +191,8 @@ public class DrawingLayer extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void pixel( double x, double y ) {
|
public void pixel( double x, double y ) {
|
||||||
square(x, y, 1);
|
// square(x, y, 1);
|
||||||
|
buffer.setRGB((int)x, (int)y, fillColor.getRGBA());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void square( double x, double y, double w ) {
|
public void square( double x, double y, double w ) {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public final class Options {
|
|||||||
|
|
||||||
public enum Direction {
|
public enum Direction {
|
||||||
CENTER(0, 0),
|
CENTER(0, 0),
|
||||||
|
|
||||||
NORTH(0, -1),
|
NORTH(0, -1),
|
||||||
EAST(1, 0),
|
EAST(1, 0),
|
||||||
SOUTH(0, 1),
|
SOUTH(0, 1),
|
||||||
@@ -49,14 +50,19 @@ public final class Options {
|
|||||||
|
|
||||||
NORTHEAST(1, -1),
|
NORTHEAST(1, -1),
|
||||||
SOUTHEAST(1, 1),
|
SOUTHEAST(1, 1),
|
||||||
NORTHWEST(-1, -1),
|
|
||||||
SOUTHWEST(-1, 1),
|
SOUTHWEST(-1, 1),
|
||||||
|
NORTHWEST(-1, -1),
|
||||||
|
|
||||||
MIDDLE(CENTER),
|
MIDDLE(CENTER),
|
||||||
UP(NORTH),
|
UP(NORTH),
|
||||||
|
RIGHT(EAST),
|
||||||
DOWN(SOUTH),
|
DOWN(SOUTH),
|
||||||
LEFT(WEST),
|
LEFT(WEST),
|
||||||
RIGHT(EAST);
|
|
||||||
|
UPLEFT(NORTHWEST),
|
||||||
|
DOWNLEFT(SOUTHWEST),
|
||||||
|
DOWNRIGHT(SOUTHEAST),
|
||||||
|
UPRIGHT(NORTHEAST);
|
||||||
|
|
||||||
public final byte x, y;
|
public final byte x, y;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import schule.ngb.zm.shapes.ShapesLayer;
|
|
||||||
|
|
||||||
import java.awt.Canvas;
|
import java.awt.Canvas;
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
@@ -37,12 +35,12 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
super.setSize(width, height);
|
super.setSize(width, height);
|
||||||
this.setPreferredSize(this.getSize());
|
this.setPreferredSize(this.getSize());
|
||||||
this.setMinimumSize(this.getSize());
|
this.setMinimumSize(this.getSize());
|
||||||
this.setBackground(Constants.STD_BACKGROUND.getJavaColor());
|
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
||||||
|
|
||||||
// Liste der Ebenen initialisieren und die Standardebenen einfügen
|
// Liste der Ebenen initialisieren und die Standardebenen einfügen
|
||||||
layers = new LinkedList<>();
|
layers = new LinkedList<>();
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
layers.add(new ColorLayer(width, height, Constants.STD_BACKGROUND));
|
layers.add(new ColorLayer(width, height, Constants.DEFAULT_BACKGROUND));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -277,8 +277,8 @@ public class Zeichenmaschine extends Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
|
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
|
||||||
this.width = width;
|
this.canvasWidth = width;
|
||||||
this.height = height;
|
this.canvasHeight = height;
|
||||||
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds();
|
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds();
|
||||||
this.screenWidth = (int) displayBounds.getWidth();
|
this.screenWidth = (int) displayBounds.getWidth();
|
||||||
this.screenHeight = (int) displayBounds.getHeight();
|
this.screenHeight = (int) displayBounds.getHeight();
|
||||||
@@ -291,6 +291,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
// Das Icon des Fensters ändern
|
// Das Icon des Fensters ändern
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// TODO: Add image sizes
|
||||||
ImageIcon icon = new ImageIcon(ImageIO.read(new File("res/icon_64.png")));
|
ImageIcon icon = new ImageIcon(ImageIO.read(new File("res/icon_64.png")));
|
||||||
|
|
||||||
if( MACOS ) {
|
if( MACOS ) {
|
||||||
@@ -334,7 +335,10 @@ public class Zeichenmaschine extends Constants {
|
|||||||
frame.addWindowListener(new WindowAdapter() {
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosing( WindowEvent e ) {
|
public void windowClosing( WindowEvent e ) {
|
||||||
exit();
|
//exit();
|
||||||
|
teardown();
|
||||||
|
cleanup();
|
||||||
|
quit(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -408,8 +412,8 @@ public class Zeichenmaschine extends Constants {
|
|||||||
frame.setResizable(false); // Should be set anyway
|
frame.setResizable(false); // Should be set anyway
|
||||||
displayDevice.setFullScreenWindow(frame);
|
displayDevice.setFullScreenWindow(frame);
|
||||||
// Update width / height
|
// Update width / height
|
||||||
initialWidth = width;
|
initialWidth = canvasWidth;
|
||||||
initialHeight = height;
|
initialHeight = canvasHeight;
|
||||||
changeSize(screenWidth, screenHeight);
|
changeSize(screenWidth, screenHeight);
|
||||||
// Register ESC as exit fullscreen
|
// Register ESC as exit fullscreen
|
||||||
canvas.addKeyListener(fullscreenExitListener);
|
canvas.addKeyListener(fullscreenExitListener);
|
||||||
@@ -595,11 +599,11 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @see #setFullscreen(boolean)
|
* @see #setFullscreen(boolean)
|
||||||
*/
|
*/
|
||||||
private void changeSize( int newWidth, int newHeight ) {
|
private void changeSize( int newWidth, int newHeight ) {
|
||||||
width = Math.min(Math.max(newWidth, 100), screenWidth);
|
canvasWidth = Math.min(Math.max(newWidth, 100), screenWidth);
|
||||||
height = Math.min(Math.max(newHeight, 100), screenHeight);
|
canvasHeight = Math.min(Math.max(newHeight, 100), screenHeight);
|
||||||
|
|
||||||
if( canvas != null ) {
|
if( canvas != null ) {
|
||||||
canvas.setSize(width, height);
|
canvas.setSize(canvasWidth, canvasHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,7 +633,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @return Die Breite der {@link Zeichenleinwand}.
|
* @return Die Breite der {@link Zeichenleinwand}.
|
||||||
*/
|
*/
|
||||||
public final int getWidth() {
|
public final int getWidth() {
|
||||||
return width;
|
return canvasWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -638,7 +642,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @return Die Höhe der {@link Zeichenleinwand}.
|
* @return Die Höhe der {@link Zeichenleinwand}.
|
||||||
*/
|
*/
|
||||||
public final int getHeight() {
|
public final int getHeight() {
|
||||||
return height;
|
return canvasHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -718,7 +722,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
public final ColorLayer getBackgroundLayer() {
|
public final ColorLayer getBackgroundLayer() {
|
||||||
ColorLayer layer = canvas.getLayer(ColorLayer.class);
|
ColorLayer layer = canvas.getLayer(ColorLayer.class);
|
||||||
if( layer == null ) {
|
if( layer == null ) {
|
||||||
layer = new ColorLayer(STD_BACKGROUND);
|
layer = new ColorLayer(DEFAULT_BACKGROUND);
|
||||||
canvas.addLayer(0, layer);
|
canvas.addLayer(0, layer);
|
||||||
}
|
}
|
||||||
return layer;
|
return layer;
|
||||||
@@ -812,7 +816,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight());
|
BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight());
|
||||||
|
|
||||||
Graphics2D g = img.createGraphics();
|
Graphics2D g = img.createGraphics();
|
||||||
g.setColor(STD_BACKGROUND.getJavaColor());
|
g.setColor(DEFAULT_BACKGROUND.getJavaColor());
|
||||||
g.fillRect(0, 0, img.getWidth(), img.getHeight());
|
g.fillRect(0, 0, img.getWidth(), img.getHeight());
|
||||||
canvas.draw(g);
|
canvas.draw(g);
|
||||||
g.dispose();
|
g.dispose();
|
||||||
@@ -836,7 +840,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight());
|
BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight());
|
||||||
|
|
||||||
Graphics2D g = img.createGraphics();
|
Graphics2D g = img.createGraphics();
|
||||||
g.setColor(STD_BACKGROUND.getJavaColor());
|
g.setColor(DEFAULT_BACKGROUND.getJavaColor());
|
||||||
g.fillRect(0, 0, img.getWidth(), img.getHeight());
|
g.fillRect(0, 0, img.getWidth(), img.getHeight());
|
||||||
canvas.draw(g);
|
canvas.draw(g);
|
||||||
g.dispose();
|
g.dispose();
|
||||||
@@ -1033,7 +1037,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @param delta
|
* @param delta
|
||||||
*/
|
*/
|
||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
//running = !run_once;
|
running = !run_once;
|
||||||
stop_after_update = run_once;
|
stop_after_update = run_once;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,7 +1052,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* dar, da hier die Zeichnung des Programms erstellt wird.
|
* dar, da hier die Zeichnung des Programms erstellt wird.
|
||||||
*/
|
*/
|
||||||
public void draw() {
|
public void draw() {
|
||||||
running = !stop_after_update;
|
//running = !stop_after_update;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1166,7 +1170,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
break;
|
break;
|
||||||
case MouseEvent.MOUSE_RELEASED:
|
case MouseEvent.MOUSE_RELEASED:
|
||||||
mousePressed = false;
|
mousePressed = false;
|
||||||
mouseButton = NOBUTTON;
|
mouseButton = NOMOUSE;
|
||||||
mousePressed(evt);
|
mousePressed(evt);
|
||||||
break;
|
break;
|
||||||
case MouseEvent.MOUSE_DRAGGED:
|
case MouseEvent.MOUSE_DRAGGED:
|
||||||
|
|||||||
26
src/main/java/schule/ngb/zm/anim/Animation.java
Normal file
26
src/main/java/schule/ngb/zm/anim/Animation.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import schule.ngb.zm.events.EventDispatcher;
|
||||||
|
|
||||||
|
public class Animation {
|
||||||
|
|
||||||
|
EventDispatcher<Animation, AnimationListener> eventDispatcher;
|
||||||
|
|
||||||
|
private EventDispatcher<Animation, AnimationListener> initializeEventDispatcher() {
|
||||||
|
if( eventDispatcher == null ) {
|
||||||
|
eventDispatcher = new EventDispatcher<>();
|
||||||
|
eventDispatcher.registerEventType("start", ( a, l ) -> l.animationStarted(a));
|
||||||
|
eventDispatcher.registerEventType("stop", ( a, l ) -> l.animationStopped(a));
|
||||||
|
}
|
||||||
|
return eventDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener( AnimationListener listener ) {
|
||||||
|
initializeEventDispatcher().addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeListener( AnimationListener listener ) {
|
||||||
|
initializeEventDispatcher().removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
src/main/java/schule/ngb/zm/anim/AnimationListener.java
Normal file
11
src/main/java/schule/ngb/zm/anim/AnimationListener.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import schule.ngb.zm.events.Listener;
|
||||||
|
|
||||||
|
public interface AnimationListener extends Listener<Animation> {
|
||||||
|
|
||||||
|
void animationStarted( Animation anim );
|
||||||
|
|
||||||
|
void animationStopped( Animation anim );
|
||||||
|
|
||||||
|
}
|
||||||
200
src/main/java/schule/ngb/zm/anim/Animations.java
Normal file
200
src/main/java/schule/ngb/zm/anim/Animations.java
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
import schule.ngb.zm.Constants;
|
||||||
|
import schule.ngb.zm.Vector;
|
||||||
|
import schule.ngb.zm.tasks.FrameSynchronizedTask;
|
||||||
|
import schule.ngb.zm.tasks.TaskRunner;
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
public class Animations {
|
||||||
|
|
||||||
|
public static final <T> Future<T> animateProperty( String propName, T target, double to, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
double from;
|
||||||
|
try {
|
||||||
|
from = callGetter(target, propName, double.class);
|
||||||
|
} catch( InvocationTargetException | NoSuchMethodException |
|
||||||
|
IllegalAccessException ex ) {
|
||||||
|
throw new RuntimeException("Can't access property getter for animation.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Method propSetter;
|
||||||
|
try {
|
||||||
|
propSetter = findSetter(target, propName, double.class);
|
||||||
|
} catch( NoSuchMethodException ex ) {
|
||||||
|
throw new RuntimeException("Can't find property setter for animation.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
||||||
|
try {
|
||||||
|
propSetter.invoke(target, d);
|
||||||
|
} catch( IllegalAccessException | InvocationTargetException e ) {
|
||||||
|
throw new RuntimeException("Can't access property setter for animation.", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> Future<T> animateProperty( String propName, T target, Color to, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
Color from;
|
||||||
|
try {
|
||||||
|
from = callGetter(target, propName, Color.class);
|
||||||
|
} catch( InvocationTargetException | NoSuchMethodException |
|
||||||
|
IllegalAccessException ex ) {
|
||||||
|
throw new RuntimeException("Can't access property getter for animation.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Method propSetter;
|
||||||
|
try {
|
||||||
|
propSetter = findSetter(target, propName, Color.class);
|
||||||
|
} catch( NoSuchMethodException ex ) {
|
||||||
|
throw new RuntimeException("Can't find property setter for animation.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
||||||
|
try {
|
||||||
|
propSetter.invoke(target, d);
|
||||||
|
} catch( IllegalAccessException | InvocationTargetException e ) {
|
||||||
|
throw new RuntimeException("Can't access property setter for animation.", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> Future<T> animateProperty( String propName, T target, Vector to, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
Vector from;
|
||||||
|
try {
|
||||||
|
from = callGetter(target, propName, Vector.class);
|
||||||
|
} catch( InvocationTargetException | NoSuchMethodException |
|
||||||
|
IllegalAccessException ex ) {
|
||||||
|
throw new RuntimeException("Can't access property getter for animation.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Method propSetter;
|
||||||
|
try {
|
||||||
|
propSetter = findSetter(target, propName, Vector.class);
|
||||||
|
} catch( NoSuchMethodException ex ) {
|
||||||
|
throw new RuntimeException("Can't find property setter for animation.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
||||||
|
try {
|
||||||
|
propSetter.invoke(target, d);
|
||||||
|
} catch( IllegalAccessException | InvocationTargetException e ) {
|
||||||
|
throw new RuntimeException("Can't access property setter for animation.", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final <T, R> R callGetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||||
|
String getterName = makeMethodName("get", propName);
|
||||||
|
Method getter = target.getClass().getMethod(getterName);
|
||||||
|
if( getter != null && getter.getReturnType().equals(propType) ) {
|
||||||
|
return (R) getter.invoke(target);
|
||||||
|
} else {
|
||||||
|
throw new NoSuchMethodException(String.format("No getter for property <%s> found.", propName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final <T, R> Method findSetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException {
|
||||||
|
String setterName = makeMethodName("set", propName);
|
||||||
|
Method setter = target.getClass().getMethod(setterName, propType);
|
||||||
|
if( setter != null && setter.getReturnType().equals(void.class) && setter.getParameterCount() == 1 ) {
|
||||||
|
return setter;
|
||||||
|
} else {
|
||||||
|
throw new NoSuchMethodException(String.format("No setter for property <%s> found.", propName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String makeMethodName( String prefix, String propName ) {
|
||||||
|
String firstChar = propName.substring(0, 1).toUpperCase();
|
||||||
|
String tail = "";
|
||||||
|
if( propName.length() > 1 ) {
|
||||||
|
tail = propName.substring(1);
|
||||||
|
}
|
||||||
|
return prefix + firstChar + tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
|
||||||
|
Validator.requireNotNull(target);
|
||||||
|
Validator.requireNotNull(propSetter);
|
||||||
|
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> Future<T> animateProperty( T target, final Color from, final Color to, int runtime, DoubleUnaryOperator easing, Consumer<Color> propSetter ) {
|
||||||
|
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Color.interpolate(from, to, e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final <T> Future<T> animateProperty( T target, final Vector from, final Vector to, int runtime, DoubleUnaryOperator easing, Consumer<Vector> propSetter ) {
|
||||||
|
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Vector.interpolate(from, to, e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T, R> Future<T> animateProperty( T target, R from, R to, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, Consumer<R> propSetter ) {
|
||||||
|
return animate(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final <T, R> Future<T> animate( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, BiConsumer<T, R> applicator ) {
|
||||||
|
return animate(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> Future<T> animate( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
||||||
|
/*final long starttime = System.currentTimeMillis();
|
||||||
|
return TaskRunner.run(() -> {
|
||||||
|
double t = 0.0;
|
||||||
|
do {
|
||||||
|
// One animation step for t in [0,1]
|
||||||
|
stepper.accept(easing.applyAsDouble(t));
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000 / Constants.framesPerSecond);
|
||||||
|
} catch( InterruptedException ex ) {
|
||||||
|
}
|
||||||
|
t = (double) (System.currentTimeMillis() - starttime) / (double) runtime;
|
||||||
|
} while( t < 1.0 );
|
||||||
|
stepper.accept(easing.applyAsDouble(1.0));
|
||||||
|
}, target);*/
|
||||||
|
return TaskRunner.run(new FrameSynchronizedTask() {
|
||||||
|
double t = 0.0;
|
||||||
|
final long starttime = System.currentTimeMillis();
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
// One animation step for t in [0,1]
|
||||||
|
stepper.accept(easing.applyAsDouble(t));
|
||||||
|
t = (double) (System.currentTimeMillis() - starttime) / (double) runtime;
|
||||||
|
running = (t <= 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finish() {
|
||||||
|
stepper.accept(easing.applyAsDouble(1.0));
|
||||||
|
}
|
||||||
|
}, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T, R> Future<T> animate( T target, int runtime, Animator<T, R> animator ) {
|
||||||
|
return animate(
|
||||||
|
target, runtime,
|
||||||
|
animator::easing,
|
||||||
|
animator::interpolator,
|
||||||
|
animator::applicator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public static <T> Future<?> animate( Animation<T> animation ) {
|
||||||
|
animation.start();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Future<?> animate( Animation<T> animation, DoubleUnaryOperator easing ) {
|
||||||
|
animation.start(easing);
|
||||||
|
return null;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public static final Log LOG = Log.getLogger(Animations.class);
|
||||||
|
|
||||||
|
}
|
||||||
11
src/main/java/schule/ngb/zm/anim/Animator.java
Normal file
11
src/main/java/schule/ngb/zm/anim/Animator.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
public interface Animator<T, R> {
|
||||||
|
|
||||||
|
double easing(double t);
|
||||||
|
|
||||||
|
R interpolator(double e);
|
||||||
|
|
||||||
|
void applicator(T target, R value);
|
||||||
|
|
||||||
|
}
|
||||||
320
src/main/java/schule/ngb/zm/anim/Easing.java
Normal file
320
src/main/java/schule/ngb/zm/anim/Easing.java
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://easings.net/de">Cheat Sheet für Easing-Funktionen</a>
|
||||||
|
*/
|
||||||
|
public class Easing {
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator DEFAULT_EASING = Easing::smooth;
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator thereAndBack() {
|
||||||
|
return Easing::thereAndBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator thereAndBack( final DoubleUnaryOperator baseEasing ) {
|
||||||
|
return ( t ) -> thereAndBack(t, baseEasing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double thereAndBack( double t ) {
|
||||||
|
return thereAndBack(t, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double thereAndBack( double t, DoubleUnaryOperator baseEasing ) {
|
||||||
|
if( t < 0.5 ) {
|
||||||
|
return baseEasing.applyAsDouble(2 * t);
|
||||||
|
} else {
|
||||||
|
return baseEasing.applyAsDouble(2 - 2 * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator halfAndHalf( final DoubleUnaryOperator firstEasing, final DoubleUnaryOperator secondEasing ) {
|
||||||
|
return ( t ) -> halfAndHalf(t, firstEasing, secondEasing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator halfAndHalf( final DoubleUnaryOperator firstEasing, final DoubleUnaryOperator secondEasing, final double split ) {
|
||||||
|
return ( t ) -> halfAndHalf(t, firstEasing, secondEasing, split);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double halfAndHalf( double t, DoubleUnaryOperator firstEasing, DoubleUnaryOperator secondEasing ) {
|
||||||
|
return halfAndHalf(t, firstEasing, secondEasing, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double halfAndHalf( double t, DoubleUnaryOperator firstEasing, DoubleUnaryOperator secondEasing, double split ) {
|
||||||
|
if( t < split ) {
|
||||||
|
return firstEasing.applyAsDouble(2 * t);
|
||||||
|
} else {
|
||||||
|
return secondEasing.applyAsDouble(1 - 2 * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions taken from easings.net
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator linear() {
|
||||||
|
return Easing::linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double linear( double t ) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator quadIn() {
|
||||||
|
return Easing::quadIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double quadIn( double t ) {
|
||||||
|
return t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator quadOut() {
|
||||||
|
return Easing::quadOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double quadOut( double t ) {
|
||||||
|
return 1 - (1 - t) * (1 - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator quadInOut() {
|
||||||
|
return Easing::quadInOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double quadInOut( double t ) {
|
||||||
|
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator cubicIn() {
|
||||||
|
return Easing::cubicIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double cubicIn( double t ) {
|
||||||
|
return t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator cubicOut() {
|
||||||
|
return Easing::cubicOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double cubicOut( double t ) {
|
||||||
|
return 1 - Math.pow(1 - t, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator cubicInOut() {
|
||||||
|
return Easing::cubicInOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double cubicInOut( double t ) {
|
||||||
|
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator sineIn() {
|
||||||
|
return Easing::sineIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double sineIn( double t ) {
|
||||||
|
return 1 - Math.cos((t * Math.PI) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator sineOut() {
|
||||||
|
return Easing::sineOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double sineOut( double t ) {
|
||||||
|
return Math.sin((t * Math.PI) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator sineInOut() {
|
||||||
|
return Easing::sineInOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double sineInOut( double t ) {
|
||||||
|
return -(Math.cos(Math.PI * t) - 1) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator elasticIn() {
|
||||||
|
return Easing::elasticIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double elasticIn( double t ) {
|
||||||
|
double c4 = (2 * Math.PI) / 3;
|
||||||
|
|
||||||
|
return t == 0
|
||||||
|
? 0
|
||||||
|
: t == 1
|
||||||
|
? 1
|
||||||
|
: -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator elasticOut() {
|
||||||
|
return Easing::elasticOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double elasticOut( double t ) {
|
||||||
|
double c4 = (2 * Math.PI) / 3;
|
||||||
|
|
||||||
|
return t == 0
|
||||||
|
? 0
|
||||||
|
: t == 1
|
||||||
|
? 1
|
||||||
|
: Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator elasticInOut() {
|
||||||
|
return Easing::elasticInOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double elasticInOut( double t ) {
|
||||||
|
double c5 = (2 * Math.PI) / 4.5;
|
||||||
|
|
||||||
|
return t == 0
|
||||||
|
? 0
|
||||||
|
: t == 1
|
||||||
|
? 1
|
||||||
|
: t < 0.5
|
||||||
|
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2
|
||||||
|
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator bounceIn() {
|
||||||
|
return Easing::bounceIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double bounceIn( double t ) {
|
||||||
|
return 1 - bounceOut(1 - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator bounceOut() {
|
||||||
|
return Easing::bounceOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double bounceOut( double t ) {
|
||||||
|
double n1 = 7.5625;
|
||||||
|
double d1 = 2.75;
|
||||||
|
|
||||||
|
if( t < 1.0 / d1 ) {
|
||||||
|
return n1 * t * t;
|
||||||
|
} else if( t < 2.0 / d1 ) {
|
||||||
|
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
||||||
|
} else if( t < 2.5 / d1 ) {
|
||||||
|
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
||||||
|
} else {
|
||||||
|
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator bounceInOut() {
|
||||||
|
return Easing::bounceInOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double bounceInOut( double t ) {
|
||||||
|
return t < 0.5
|
||||||
|
? (1 - bounceOut(1 - 2 * t)) / 2
|
||||||
|
: (1 + bounceOut(2 * t - 1)) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator backIn() {
|
||||||
|
return Easing::backIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double backIn( double t ) {
|
||||||
|
double c1 = 1.70158;
|
||||||
|
double c3 = c1 + 1;
|
||||||
|
|
||||||
|
return c3 * t * t * t - c1 * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator backOut() {
|
||||||
|
return Easing::backOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double backOut( double t ) {
|
||||||
|
double c1 = 1.70158;
|
||||||
|
double c3 = c1 + 1;
|
||||||
|
|
||||||
|
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator backInOut() {
|
||||||
|
return Easing::backInOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double backInOut( double t ) {
|
||||||
|
double c1 = 1.70158;
|
||||||
|
double c2 = c1 * 1.525;
|
||||||
|
|
||||||
|
return t < 0.5
|
||||||
|
? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
|
||||||
|
: (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions from manim community
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator smooth() {
|
||||||
|
return Easing::smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double smooth( double t ) {
|
||||||
|
double error = sigmoid(-INFLECTION / 2.0);
|
||||||
|
return Math.min(
|
||||||
|
Math.max(
|
||||||
|
(sigmoid(INFLECTION * (t - 0.5)) - error) / (1 - 2 * error),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double rushIn( double t ) {
|
||||||
|
return 2 * smooth(t / 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double rushOut( double t ) {
|
||||||
|
return 2 * smooth(t / 2.0 + 0.5) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double doubleSmooth( double t ) {
|
||||||
|
if( t < 0.5 )
|
||||||
|
return 0.5 * smooth(2 * t);
|
||||||
|
else
|
||||||
|
return 0.5 * (1 + smooth(2 * t - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double hobbit( double t ) {
|
||||||
|
double new_t = t < 0.5 ? 2 * t : 2 * (1 - t);
|
||||||
|
return smooth(new_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator wiggle() {
|
||||||
|
return Easing::wiggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final DoubleUnaryOperator wiggle( final int wiggles ) {
|
||||||
|
return (t) -> Easing.wiggle(t, wiggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double wiggle( double t ) {
|
||||||
|
return wiggle(t, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final double wiggle( double t, int wiggles ) {
|
||||||
|
return hobbit(t) * Math.sin(wiggles * Math.PI * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double INFLECTION = 10.0;
|
||||||
|
|
||||||
|
public static final double sigmoid( double x ) {
|
||||||
|
return 1.0 / (1 + Math.exp(-x));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Easing() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
58
src/main/java/schule/ngb/zm/events/EventDispatcher.java
Normal file
58
src/main/java/schule/ngb/zm/events/EventDispatcher.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package schule.ngb.zm.events;
|
||||||
|
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public class EventDispatcher<E, L extends Listener<E>> {
|
||||||
|
|
||||||
|
private CopyOnWriteArraySet<L> listeners;
|
||||||
|
|
||||||
|
private ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
||||||
|
|
||||||
|
public EventDispatcher() {
|
||||||
|
listeners = new CopyOnWriteArraySet<>();
|
||||||
|
eventRegistry = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
|
||||||
|
Validator.requireNotNull(eventKey);
|
||||||
|
Validator.requireNotNull(dispatcher);
|
||||||
|
|
||||||
|
if( !eventRegistered(eventKey) ) {
|
||||||
|
eventRegistry.put(eventKey, dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener( L listener ) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeListener( L listener ) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasListeners() {
|
||||||
|
return !listeners.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean eventRegistered( String eventKey ) {
|
||||||
|
return eventRegistry.containsKey(eventKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispatchEvent( String eventKey, final E event ) {
|
||||||
|
Validator.requireNotNull(eventKey);
|
||||||
|
Validator.requireNotNull(event);
|
||||||
|
|
||||||
|
if( eventRegistered(eventKey) ) {
|
||||||
|
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
||||||
|
listeners.forEach(( listener ) -> {
|
||||||
|
dispatcher.accept(event, listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
7
src/main/java/schule/ngb/zm/events/Listener.java
Normal file
7
src/main/java/schule/ngb/zm/events/Listener.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package schule.ngb.zm.events;
|
||||||
|
|
||||||
|
public interface Listener<E> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ package schule.ngb.zm.media;
|
|||||||
*/
|
*/
|
||||||
public interface Audio {
|
public interface Audio {
|
||||||
|
|
||||||
|
String getSource();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob das Medium gerade abgespielt wird.
|
* Prüft, ob das Medium gerade abgespielt wird.
|
||||||
*
|
*
|
||||||
|
|||||||
11
src/main/java/schule/ngb/zm/media/AudioListener.java
Normal file
11
src/main/java/schule/ngb/zm/media/AudioListener.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
|
import schule.ngb.zm.events.Listener;
|
||||||
|
|
||||||
|
public interface AudioListener extends Listener<Audio> {
|
||||||
|
|
||||||
|
void start( Audio source );
|
||||||
|
|
||||||
|
void stop( Audio source );
|
||||||
|
|
||||||
|
}
|
||||||
@@ -41,6 +41,10 @@ public class Mixer implements Audio {
|
|||||||
this.audios = new ArrayList<>(4);
|
this.audios = new ArrayList<>(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
public void add( Audio pAudio ) {
|
public void add( Audio pAudio ) {
|
||||||
add(pAudio, 1f);
|
add(pAudio, 1f);
|
||||||
}
|
}
|
||||||
@@ -50,6 +54,13 @@ public class Mixer implements Audio {
|
|||||||
pAudio.setVolume(pVolumeFactor * volume);
|
pAudio.setVolume(pVolumeFactor * volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void remove( Audio pAudio ) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAll() {
|
||||||
|
audios.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
|
import schule.ngb.zm.anim.Animation;
|
||||||
|
import schule.ngb.zm.anim.AnimationListener;
|
||||||
|
import schule.ngb.zm.events.EventDispatcher;
|
||||||
import schule.ngb.zm.tasks.TaskRunner;
|
import schule.ngb.zm.tasks.TaskRunner;
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||||
@@ -55,17 +58,17 @@ public class Music implements Audio {
|
|||||||
*/
|
*/
|
||||||
private float volume = 0.8f;
|
private float volume = 0.8f;
|
||||||
|
|
||||||
/**
|
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
||||||
* Erstellt eine Musik aus der angegebenen Datei oder Webadresse.
|
|
||||||
*
|
|
||||||
* @param source Ein Dateipfad oder eine Webadresse.
|
|
||||||
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
|
||||||
*/
|
|
||||||
public Music( String source ) {
|
public Music( String source ) {
|
||||||
Validator.requireNotNull(source);
|
Validator.requireNotNull(source);
|
||||||
this.audioSource = source;
|
this.audioSource = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return audioSource;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@@ -191,6 +194,9 @@ public class Music implements Audio {
|
|||||||
private void stream() {
|
private void stream() {
|
||||||
audioLine.start();
|
audioLine.start();
|
||||||
playing = true;
|
playing = true;
|
||||||
|
if( eventDispatcher != null ) {
|
||||||
|
eventDispatcher.dispatchEvent("start", Music.this);
|
||||||
|
}
|
||||||
|
|
||||||
byte[] bytesBuffer = new byte[BUFFER_SIZE];
|
byte[] bytesBuffer = new byte[BUFFER_SIZE];
|
||||||
int bytesRead = -1;
|
int bytesRead = -1;
|
||||||
@@ -217,6 +223,9 @@ public class Music implements Audio {
|
|||||||
|
|
||||||
playing = false;
|
playing = false;
|
||||||
streamingStopped();
|
streamingStopped();
|
||||||
|
if( eventDispatcher != null ) {
|
||||||
|
eventDispatcher.dispatchEvent("stop", Music.this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openLine() {
|
private boolean openLine() {
|
||||||
@@ -270,6 +279,28 @@ public class Music implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addListener( AudioListener listener ) {
|
||||||
|
initializeEventDispatcher().addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeListener( AudioListener listener ) {
|
||||||
|
initializeEventDispatcher().removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
||||||
|
* aufgerufen, soblad sich auch ein Listener registrieren möchte.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
||||||
|
if( eventDispatcher == null ) {
|
||||||
|
eventDispatcher = new EventDispatcher<>();
|
||||||
|
eventDispatcher.registerEventType("start", (a,l) -> l.start(a));
|
||||||
|
eventDispatcher.registerEventType("stop", (a,l) -> l.stop(a));
|
||||||
|
}
|
||||||
|
return eventDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(Music.class);
|
private static final Log LOG = Log.getLogger(Music.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import schule.ngb.zm.util.Validator;
|
|||||||
import javax.sound.sampled.*;
|
import javax.sound.sampled.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden.
|
* Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden.
|
||||||
@@ -66,6 +65,10 @@ public class Sound implements Audio {
|
|||||||
this.audioSource = source;
|
this.audioSource = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return audioSource;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@@ -245,7 +248,7 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
InputStream in = ResourceStreamProvider.getResourceStream(audioSource);
|
InputStream in = ResourceStreamProvider.getInputStream(audioSource);
|
||||||
if( in != null ) {
|
if( in != null ) {
|
||||||
AudioInputStream audioStream = AudioSystem.getAudioInputStream(in);
|
AudioInputStream audioStream = AudioSystem.getAudioInputStream(in);
|
||||||
AudioFormat format = audioStream.getFormat();
|
AudioFormat format = audioStream.getFormat();
|
||||||
|
|||||||
183
src/main/java/schule/ngb/zm/ml/MLMath.java
Normal file
183
src/main/java/schule/ngb/zm/ml/MLMath.java
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
// See https://github.com/wheresvic/neuralnet
|
||||||
|
public final class MLMath {
|
||||||
|
|
||||||
|
public static double sigmoid( double x ) {
|
||||||
|
return 1 / (1 + Math.exp(-x));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double sigmoidDerivative( double x ) {
|
||||||
|
return x * (1 - x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double tanh( double x ) {
|
||||||
|
return Math.tanh(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double tanhDerivative( double x ) {
|
||||||
|
return 1 - Math.tanh(x) * Math.tanh(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static double[] normalize( double[] vector ) {
|
||||||
|
final double sum = Arrays.stream(vector).sum();
|
||||||
|
return Arrays.stream(vector).map(( d ) -> d / sum).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] matrixMultiply( double[][] A, double[][] B ) {
|
||||||
|
int a = A.length, b = A[0].length, c = B[0].length;
|
||||||
|
if( B.length != b ) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Matrix A needs equal columns to matrix B rows. (Currently <%d> vs <%d>)", a, B.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntStream.range(0, a).parallel().mapToObj(
|
||||||
|
( i ) -> IntStream.range(0, c).mapToDouble(
|
||||||
|
( j ) -> IntStream.range(0, b).mapToDouble(
|
||||||
|
( k ) -> A[i][k] * B[k][j]
|
||||||
|
).sum()
|
||||||
|
).toArray()
|
||||||
|
).toArray(double[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] matrixScale( final double[][] A, final double[][] S ) {
|
||||||
|
if( A.length != S.length || A[0].length != S[0].length ) {
|
||||||
|
throw new IllegalArgumentException("Matrices need to be same size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntStream.range(0, A.length).parallel().mapToObj(
|
||||||
|
( i ) -> IntStream.range(0, A[i].length).mapToDouble(
|
||||||
|
( j ) -> A[i][j] * S[i][j]
|
||||||
|
).toArray()
|
||||||
|
).toArray(double[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] matrixSub( double[][] A, double[][] B ) {
|
||||||
|
if( A.length != B.length || A[0].length != B[0].length ) {
|
||||||
|
throw new IllegalArgumentException("Cannot subtract unequal matrices");
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntStream.range(0, A.length).parallel().mapToObj(
|
||||||
|
( i ) -> IntStream.range(0, A[i].length).mapToDouble(
|
||||||
|
( j ) -> A[i][j] - B[i][j]
|
||||||
|
).toArray()
|
||||||
|
).toArray(double[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] matrixAdd( double[][] A, double[][] B ) {
|
||||||
|
if( A.length != B.length || A[0].length != B[0].length ) {
|
||||||
|
throw new IllegalArgumentException("Cannot add unequal matrices");
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntStream.range(0, A.length).parallel().mapToObj(
|
||||||
|
( i ) -> IntStream.range(0, A[i].length).mapToDouble(
|
||||||
|
( j ) -> A[i][j] + B[i][j]
|
||||||
|
).toArray()
|
||||||
|
).toArray(double[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] matrixTranspose( double[][] matrix ) {
|
||||||
|
int a = matrix.length, b = matrix[0].length;
|
||||||
|
|
||||||
|
double[][] result = new double[matrix[0].length][matrix.length];
|
||||||
|
for( int i = 0; i < a; i++ ) {
|
||||||
|
for( int j = 0; j < b; ++j ) {
|
||||||
|
result[j][i] = matrix[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] matrixApply( double[][] A, DoubleUnaryOperator op ) {
|
||||||
|
return Arrays.stream(A).parallel().map(
|
||||||
|
( arr ) -> Arrays.stream(arr).map(op).toArray()
|
||||||
|
).toArray(double[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] copyMatrix( double[][] matrix ) {
|
||||||
|
/*return Arrays.stream(matrix).map(
|
||||||
|
(arr) -> Arrays.copyOf(arr, arr.length)
|
||||||
|
).toArray(double[][]::new);*/
|
||||||
|
|
||||||
|
double[][] result = new double[matrix.length][matrix[0].length];
|
||||||
|
for( int i = 0; i < matrix.length; i++ ) {
|
||||||
|
result[i] = Arrays.copyOf(matrix[i], matrix[i].length);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[] toVector( double[][] matrix ) {
|
||||||
|
return Arrays.stream(matrix).mapToDouble(
|
||||||
|
( arr ) -> arr[0]
|
||||||
|
).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] toMatrix( double[] vector ) {
|
||||||
|
return Arrays.stream(vector).mapToObj(
|
||||||
|
( d ) -> new double[]{d}
|
||||||
|
).toArray(double[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double entropy(double[][] A, double[][] Y, int batch_size) {
|
||||||
|
int m = A.length;
|
||||||
|
int n = A[0].length;
|
||||||
|
double[][] z = new double[m][n];
|
||||||
|
|
||||||
|
for (int i = 0; i < m; i++) {
|
||||||
|
for (int j = 0; j < n; j++) {
|
||||||
|
z[i][j] = (Y[i][j] * Math.log(A[i][j])) + ((1 - Y[i][j]) * Math.log(1 - A[i][j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double sum = 0;
|
||||||
|
for (int i = 0; i < m; i++) {
|
||||||
|
for (int j = 0; j < n; j++) {
|
||||||
|
sum += z[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -sum / batch_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] biasAdd( double[][] A, double[] V ) {
|
||||||
|
if( A[0].length != V.length ) {
|
||||||
|
throw new IllegalArgumentException("Can't add bias vector to matrix with wrong column count");
|
||||||
|
}
|
||||||
|
|
||||||
|
double[][] result = new double[A.length][A[0].length];
|
||||||
|
for( int j = 0; j < A[0].length; j++ ) {
|
||||||
|
for( int i = 0; i < A.length; i++ ) {
|
||||||
|
result[i][j] = A[i][j] + V[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[] biasAdjust( double[] biases, double[][] delta ) {
|
||||||
|
if( biases.length != delta[0].length ) {
|
||||||
|
throw new IllegalArgumentException("Can't add adjust bias vector by delta with wrong column count");
|
||||||
|
}
|
||||||
|
|
||||||
|
double[] result = new double[biases.length];
|
||||||
|
for( int j = 0; j < delta[0].length; j++ ) {
|
||||||
|
for( int i = 0; i < delta.length; i++ ) {
|
||||||
|
result[j] += biases[j] - delta[i][j];
|
||||||
|
}
|
||||||
|
result[j] /= delta.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MLMath() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
82
src/main/java/schule/ngb/zm/ml/Matrix.java
Normal file
82
src/main/java/schule/ngb/zm/ml/Matrix.java
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Constants;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
// TODO: Move Math into Matrix class
|
||||||
|
// TODO: Implement support for optional sci libs
|
||||||
|
public class Matrix {
|
||||||
|
|
||||||
|
private int columns, rows;
|
||||||
|
|
||||||
|
double[][] coefficients;
|
||||||
|
|
||||||
|
public Matrix( int rows, int cols ) {
|
||||||
|
this.rows = rows;
|
||||||
|
this.columns = cols;
|
||||||
|
coefficients = new double[rows][cols];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix( double[][] coefficients ) {
|
||||||
|
this.coefficients = coefficients;
|
||||||
|
this.rows = coefficients.length;
|
||||||
|
this.columns = coefficients[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColumns() {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRows() {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[][] getCoefficients() {
|
||||||
|
return coefficients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double get( int row, int col ) {
|
||||||
|
return coefficients[row][col];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeRandom() {
|
||||||
|
coefficients = MLMath.matrixApply(coefficients, (d) -> Constants.randomGaussian());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeRandom( double lower, double upper ) {
|
||||||
|
coefficients = MLMath.matrixApply(coefficients, (d) -> ((upper-lower) * (Constants.randomGaussian()+1) * .5) + lower);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeIdentity() {
|
||||||
|
initializeZero();
|
||||||
|
for( int i = 0; i < Math.min(rows, columns); i++ ) {
|
||||||
|
this.coefficients[i][i] = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeOne() {
|
||||||
|
coefficients = MLMath.matrixApply(coefficients, (d) -> 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeZero() {
|
||||||
|
coefficients = MLMath.matrixApply(coefficients, (d) -> 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
//return Arrays.deepToString(coefficients);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append('[');
|
||||||
|
sb.append('\n');
|
||||||
|
for( int i = 0; i < coefficients.length; i++ ) {
|
||||||
|
sb.append('\t');
|
||||||
|
sb.append(Arrays.toString(coefficients[i]));
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
194
src/main/java/schule/ngb/zm/ml/NeuralNetwork.java
Normal file
194
src/main/java/schule/ngb/zm/ml/NeuralNetwork.java
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
public class NeuralNetwork {
|
||||||
|
|
||||||
|
public static void saveToFile( String source, NeuralNetwork network ) {
|
||||||
|
try(
|
||||||
|
Writer writer = ResourceStreamProvider.getWriter(source);
|
||||||
|
PrintWriter out = new PrintWriter(writer)
|
||||||
|
) {
|
||||||
|
for( NeuronLayer layer: network.layers ) {
|
||||||
|
out.print(layer.getNeuronCount());
|
||||||
|
out.print(' ');
|
||||||
|
out.print(layer.getInputCount());
|
||||||
|
out.println();
|
||||||
|
|
||||||
|
for( int i = 0; i < layer.getInputCount(); i++ ) {
|
||||||
|
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||||
|
out.print(layer.weights.coefficients[i][j]);
|
||||||
|
out.print(' ');
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
}
|
||||||
|
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||||
|
out.print(layer.biases[j]);
|
||||||
|
out.print(' ');
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
}
|
||||||
|
out.flush();
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LOG.warn(ex, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NeuralNetwork loadFromFile( String source ) {
|
||||||
|
try(
|
||||||
|
Reader reader = ResourceStreamProvider.getReader(source);
|
||||||
|
BufferedReader in = new BufferedReader(reader)
|
||||||
|
) {
|
||||||
|
List<NeuronLayer> layers = new LinkedList<>();
|
||||||
|
String line;
|
||||||
|
while( (line = in.readLine()) != null ) {
|
||||||
|
String[] split = line.split(" ");
|
||||||
|
int neurons = Integer.parseInt(split[0]);
|
||||||
|
int inputs = Integer.parseInt(split[1]);
|
||||||
|
|
||||||
|
NeuronLayer layer = new NeuronLayer(neurons, inputs);
|
||||||
|
for( int i = 0; i < inputs; i++ ) {
|
||||||
|
split = in.readLine().split(" ");
|
||||||
|
for( int j = 0; j < neurons; j++ ) {
|
||||||
|
layer.weights.coefficients[i][j] = Double.parseDouble(split[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Load Biases
|
||||||
|
split = in.readLine().split(" ");
|
||||||
|
for( int j = 0; j < neurons; j++ ) {
|
||||||
|
layer.biases[j] = Double.parseDouble(split[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
layers.add(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NeuralNetwork(layers);
|
||||||
|
} catch( IOException | NoSuchElementException ex ) {
|
||||||
|
LOG.warn(ex, "Could not load neural network from source <%s>", source);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public static NeuralNetwork loadFromFile( String source ) {
|
||||||
|
try(
|
||||||
|
InputStream stream = ResourceStreamProvider.getInputStream(source);
|
||||||
|
Scanner in = new Scanner(stream)
|
||||||
|
) {
|
||||||
|
List<NeuronLayer> layers = new LinkedList<>();
|
||||||
|
while( in.hasNext() ) {
|
||||||
|
int neurons = in.nextInt();
|
||||||
|
int inputs = in.nextInt();
|
||||||
|
|
||||||
|
NeuronLayer layer = new NeuronLayer(neurons, inputs);
|
||||||
|
for( int i = 0; i < inputs; i++ ) {
|
||||||
|
for( int j = 0; j < neurons; j++ ) {
|
||||||
|
layer.weights.coefficients[i][j] = in.nextDouble();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for( int j = 0; j < neurons; j++ ) {
|
||||||
|
layer.biases[j] = in.nextDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
layers.add(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NeuralNetwork(layers);
|
||||||
|
} catch( IOException | NoSuchElementException ex ) {
|
||||||
|
LOG.warn(ex, "Could not load neural network from source <%s>", source);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private NeuronLayer[] layers;
|
||||||
|
|
||||||
|
private double[][] output;
|
||||||
|
|
||||||
|
private double learningRate = 0.1;
|
||||||
|
|
||||||
|
public NeuralNetwork( int inputs, int layer1, int outputs ) {
|
||||||
|
this(new NeuronLayer(layer1, inputs), new NeuronLayer(outputs, layer1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeuralNetwork( int inputs, int layer1, int layer2, int outputs ) {
|
||||||
|
this(new NeuronLayer(layer1, inputs), new NeuronLayer(layer2, layer1), new NeuronLayer(outputs, layer2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeuralNetwork( int inputs, int layer1, int layer2, int layer3, int outputs ) {
|
||||||
|
this(new NeuronLayer(layer1, inputs), new NeuronLayer(layer2, layer1), new NeuronLayer(layer3, layer2), new NeuronLayer(outputs, layer3));
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeuralNetwork( List<NeuronLayer> layers ) {
|
||||||
|
this.layers = new NeuronLayer[layers.size()];
|
||||||
|
for( int i = 0; i < layers.size(); i++ ) {
|
||||||
|
this.layers[i] = layers.get(i);
|
||||||
|
if( i > 0 ) {
|
||||||
|
this.layers[i-1].setNextLayer(this.layers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeuralNetwork( NeuronLayer... layers ) {
|
||||||
|
this.layers = new NeuronLayer[layers.length];
|
||||||
|
for( int i = 0; i < layers.length; i++ ) {
|
||||||
|
this.layers[i] = layers[i];
|
||||||
|
if( i > 0 ) {
|
||||||
|
this.layers[i-1].setNextLayer(this.layers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLayerCount() {
|
||||||
|
return layers.length;
|
||||||
|
}
|
||||||
|
public NeuronLayer[] getLayers() {
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeuronLayer getLayer( int i ) {
|
||||||
|
return layers[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLearningRate() {
|
||||||
|
return learningRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLearningRate( double pLearningRate ) {
|
||||||
|
this.learningRate = pLearningRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[][] getOutput() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[][] predict( double[][] inputs ) {
|
||||||
|
//this.output = layers[1].apply(layers[0].apply(inputs));
|
||||||
|
this.output = layers[0].apply(inputs);
|
||||||
|
return this.output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void learn( double[][] expected ) {
|
||||||
|
layers[layers.length - 1].backprop(expected, learningRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void train( double[][] inputs, double[][] expected, int iterations/*, double minChange, int timeout */ ) {
|
||||||
|
for( int i = 0; i < iterations; i++ ) {
|
||||||
|
// pass the training set through the network
|
||||||
|
predict(inputs);
|
||||||
|
// start backpropagation through all layers
|
||||||
|
learn(expected);
|
||||||
|
|
||||||
|
if( i % 10000 == 0 ) {
|
||||||
|
LOG.trace("Training iteration %d of %d", i, iterations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(NeuralNetwork.class);
|
||||||
|
|
||||||
|
}
|
||||||
167
src/main/java/schule/ngb/zm/ml/NeuronLayer.java
Normal file
167
src/main/java/schule/ngb/zm/ml/NeuronLayer.java
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class NeuronLayer implements Function<double[][], double[][]> {
|
||||||
|
|
||||||
|
public static NeuronLayer fromArray( double[][] weights ) {
|
||||||
|
NeuronLayer layer = new NeuronLayer(weights[0].length, weights.length);
|
||||||
|
for( int i = 0; i < weights[0].length; i++ ) {
|
||||||
|
for( int j = 0; j < weights.length; j++ ) {
|
||||||
|
layer.weights.coefficients[i][j] = weights[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NeuronLayer fromArray( double[][] weights, double[] biases ) {
|
||||||
|
NeuronLayer layer = new NeuronLayer(weights[0].length, weights.length);
|
||||||
|
for( int i = 0; i < weights[0].length; i++ ) {
|
||||||
|
for( int j = 0; j < weights.length; j++ ) {
|
||||||
|
layer.weights.coefficients[i][j] = weights[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for( int j = 0; j < biases.length; j++ ) {
|
||||||
|
layer.biases[j] = biases[j];
|
||||||
|
}
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix weights;
|
||||||
|
|
||||||
|
double[] biases;
|
||||||
|
|
||||||
|
NeuronLayer previous, next;
|
||||||
|
|
||||||
|
DoubleUnaryOperator activationFunction, activationFunctionDerivative;
|
||||||
|
|
||||||
|
double[][] lastOutput, lastInput;
|
||||||
|
|
||||||
|
public NeuronLayer( int neurons, int inputs ) {
|
||||||
|
weights = new Matrix(inputs, neurons);
|
||||||
|
weights.initializeRandom(-1, 1);
|
||||||
|
|
||||||
|
biases = new double[neurons];
|
||||||
|
Arrays.fill(biases, 0.0); // TODO: Random?
|
||||||
|
|
||||||
|
activationFunction = MLMath::sigmoid;
|
||||||
|
activationFunctionDerivative = MLMath::sigmoidDerivative;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect( NeuronLayer prev, NeuronLayer next ) {
|
||||||
|
setPreviousLayer(prev);
|
||||||
|
setNextLayer(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeuronLayer getPreviousLayer() {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPreviousLayer() {
|
||||||
|
return previous != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreviousLayer( NeuronLayer pPreviousLayer ) {
|
||||||
|
this.previous = pPreviousLayer;
|
||||||
|
if( pPreviousLayer != null ) {
|
||||||
|
pPreviousLayer.next = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeuronLayer getNextLayer() {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNextLayer() {
|
||||||
|
return next != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextLayer( NeuronLayer pNextLayer ) {
|
||||||
|
this.next = pNextLayer;
|
||||||
|
if( pNextLayer != null ) {
|
||||||
|
pNextLayer.previous = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix getWeights() {
|
||||||
|
return weights;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNeuronCount() {
|
||||||
|
return weights.coefficients[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInputCount() {
|
||||||
|
return weights.coefficients.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[][] getLastOutput() {
|
||||||
|
return lastOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWeights( double[][] newWeights ) {
|
||||||
|
weights.coefficients = MLMath.copyMatrix(newWeights);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void adjustWeights( double[][] adjustment ) {
|
||||||
|
weights.coefficients = MLMath.matrixAdd(weights.coefficients, adjustment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return weights.toString() + "\n" + Arrays.toString(biases);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double[][] apply( double[][] inputs ) {
|
||||||
|
lastInput = inputs;
|
||||||
|
lastOutput = MLMath.matrixApply(
|
||||||
|
MLMath.biasAdd(
|
||||||
|
MLMath.matrixMultiply(inputs, weights.coefficients),
|
||||||
|
biases
|
||||||
|
),
|
||||||
|
activationFunction
|
||||||
|
);
|
||||||
|
if( next != null ) {
|
||||||
|
return next.apply(lastOutput);
|
||||||
|
} else {
|
||||||
|
return lastOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Function<V, double[][]> compose( Function<? super V, ? extends double[][]> before ) {
|
||||||
|
return ( in ) -> apply(before.apply(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Function<double[][], V> andThen( Function<? super double[][], ? extends V> after ) {
|
||||||
|
return ( in ) -> after.apply(apply(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void backprop( double[][] expected, double learningRate ) {
|
||||||
|
double[][] error, delta, adjustment;
|
||||||
|
if( next == null ) {
|
||||||
|
error = MLMath.matrixSub(expected, this.lastOutput);
|
||||||
|
} else {
|
||||||
|
error = MLMath.matrixMultiply(expected, MLMath.matrixTranspose(next.weights.coefficients));
|
||||||
|
}
|
||||||
|
|
||||||
|
delta = MLMath.matrixScale(error, MLMath.matrixApply(this.lastOutput, this.activationFunctionDerivative));
|
||||||
|
// Hier schon leraningRate anwenden?
|
||||||
|
// See https://towardsdatascience.com/understanding-and-implementing-neural-networks-in-java-from-scratch-61421bb6352c
|
||||||
|
//delta = MLMath.matrixApply(delta, ( x ) -> learningRate * x);
|
||||||
|
if( previous != null ) {
|
||||||
|
previous.backprop(delta, learningRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
biases = MLMath.biasAdjust(biases, MLMath.matrixApply(delta, ( x ) -> learningRate * x));
|
||||||
|
|
||||||
|
adjustment = MLMath.matrixMultiply(MLMath.matrixTranspose(lastInput), delta);
|
||||||
|
adjustment = MLMath.matrixApply(adjustment, ( x ) -> learningRate * x);
|
||||||
|
this.adjustWeights(adjustment);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -168,8 +168,7 @@ public abstract class Shape extends FilledShape {
|
|||||||
* <p>
|
* <p>
|
||||||
* Unterklassen sollten diese Methode überschreiben, um weitere
|
* Unterklassen sollten diese Methode überschreiben, um weitere
|
||||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). Mit
|
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). Mit
|
||||||
* dem Aufruf
|
* dem Aufruf {@code super.copyFrom(shape)} sollten die Basiseigenschaften
|
||||||
* {@code super.copyFrom(shape)} sollten die Basiseigenschaften
|
|
||||||
* kopiert werden.
|
* kopiert werden.
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape
|
||||||
@@ -243,7 +242,7 @@ public abstract class Shape extends FilledShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void alignTo( Options.Direction dir, double buff ) {
|
public void alignTo( Options.Direction dir, double buff ) {
|
||||||
Point2D anchorShape = Shape.getAnchorPoint(width, height, dir);
|
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
|
||||||
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
||||||
|
|
||||||
this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
|
this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import schule.ngb.zm.Options;
|
|||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -28,9 +29,20 @@ import java.util.List;
|
|||||||
* benötigen, erst nach Hinzufügen der Gruppenelemente ausgeführt werden.
|
* benötigen, erst nach Hinzufügen der Gruppenelemente ausgeführt werden.
|
||||||
* Nachdem sich die Zusammensetzung der Gruppe geändert hat, muss die Gruppe
|
* Nachdem sich die Zusammensetzung der Gruppe geändert hat, muss die Gruppe
|
||||||
* ggf. neu ausgerichtet werden.
|
* ggf. neu ausgerichtet werden.
|
||||||
|
* <p>
|
||||||
|
* Für die Ausrichtung der Elemente in einer Gruppe können
|
||||||
|
* {@link #arrange(Options.Direction, double)},
|
||||||
|
* {@link #arrangeInGrid(int, Options.Direction, double, int)} und
|
||||||
|
* {@link #align(Options.Direction)} verwendet werden, die jeweils die Position
|
||||||
|
* der Formen in der Gruppe ändern und nicht die Position der Gruppe selbst (so
|
||||||
|
* wie z.B. {@link #alignTo(Shape, Options.Direction)}.
|
||||||
*/
|
*/
|
||||||
public class ShapeGroup extends Shape {
|
public class ShapeGroup extends Shape {
|
||||||
|
|
||||||
|
public static final int ARRANGE_ROWS = 0;
|
||||||
|
|
||||||
|
public static final int ARRANGE_COLS = 1;
|
||||||
|
|
||||||
private List<Shape> shapes;
|
private List<Shape> shapes;
|
||||||
|
|
||||||
private double groupWidth = -1.0;
|
private double groupWidth = -1.0;
|
||||||
@@ -132,6 +144,111 @@ public class ShapeGroup extends Shape {
|
|||||||
return groupHeight;
|
return groupHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void arrange( Options.Direction dir, double buffer ) {
|
||||||
|
Shape last = null;
|
||||||
|
for( Shape s : shapes ) {
|
||||||
|
if( last != null ) {
|
||||||
|
s.nextTo(last, dir, buffer);
|
||||||
|
} else {
|
||||||
|
s.moveTo(0, 0);
|
||||||
|
}
|
||||||
|
last = s;
|
||||||
|
}
|
||||||
|
invalidateBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void arrangeInRows( int n, Options.Direction dir, double buffer ) {
|
||||||
|
arrangeInGrid(n, dir, buffer, ARRANGE_ROWS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void arrangeInColumns( int n, Options.Direction dir, double buffer ) {
|
||||||
|
arrangeInGrid(n, dir, buffer, ARRANGE_COLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void arrangeInGrid( int n, Options.Direction dir, double buffer, int mode ) {
|
||||||
|
// Calculate grid size
|
||||||
|
int rows, cols;
|
||||||
|
if( mode == ARRANGE_ROWS ) {
|
||||||
|
rows = n;
|
||||||
|
cols = (int) ceil(shapes.size() / n);
|
||||||
|
} else {
|
||||||
|
cols = n;
|
||||||
|
rows = (int) ceil(shapes.size() / n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate grid cell size
|
||||||
|
double maxHeight = shapes.stream().mapToDouble(
|
||||||
|
( s ) -> s.getHeight()
|
||||||
|
).reduce(0.0, Double::max);
|
||||||
|
double maxWidth = shapes.stream().mapToDouble(
|
||||||
|
( s ) -> s.getWidth()
|
||||||
|
).reduce(0.0, Double::max);
|
||||||
|
double halfHeight = maxHeight * .5;
|
||||||
|
double halfWidth = maxWidth * .5;
|
||||||
|
|
||||||
|
// Layout shapes
|
||||||
|
for( int i = 0; i < shapes.size(); i++ ) {
|
||||||
|
// Calculate center of grid cell
|
||||||
|
int row, col;
|
||||||
|
switch( dir ) {
|
||||||
|
case UP:
|
||||||
|
case NORTH:
|
||||||
|
row = rows - i % rows;
|
||||||
|
col = cols - (i / rows);
|
||||||
|
break;
|
||||||
|
case LEFT:
|
||||||
|
case WEST:
|
||||||
|
row = rows - (i / cols);
|
||||||
|
col = cols - i % cols;
|
||||||
|
break;
|
||||||
|
case RIGHT:
|
||||||
|
case EAST:
|
||||||
|
row = i / cols;
|
||||||
|
col = i % cols;
|
||||||
|
break;
|
||||||
|
case DOWN:
|
||||||
|
case SOUTH:
|
||||||
|
default:
|
||||||
|
row = i % rows;
|
||||||
|
col = i / rows;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double centerX = halfWidth + col * (maxWidth + buffer);
|
||||||
|
double centerY = halfHeight + row * (maxHeight + buffer);
|
||||||
|
|
||||||
|
// Move shape to proper anchor location in cell
|
||||||
|
Shape s = shapes.get(i);
|
||||||
|
Point2D ap = Shape.getAnchorPoint(maxWidth, maxHeight, s.getAnchor());
|
||||||
|
s.moveTo(centerX + ap.getX(), centerY + ap.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void align( Options.Direction dir ) {
|
||||||
|
Shape target = shapes.stream().reduce(null,
|
||||||
|
( t, s ) -> {
|
||||||
|
if( t == null ) return s;
|
||||||
|
Point2D apt = t.getAbsAnchorPoint(dir);
|
||||||
|
Point2D aps = s.getAbsAnchorPoint(dir);
|
||||||
|
if( apt.getX() * dir.x >= aps.getX() * dir.x && apt.getY() * dir.y >= aps.getY() * dir.y ) {
|
||||||
|
return t;
|
||||||
|
} else {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
for( Shape s : shapes ) {
|
||||||
|
if( s != target ) {
|
||||||
|
s.alignTo(target, dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateBounds();
|
||||||
|
}
|
||||||
|
|
||||||
private void invalidateBounds() {
|
private void invalidateBounds() {
|
||||||
groupWidth = -1.0;
|
groupWidth = -1.0;
|
||||||
groupHeight = -1.0;
|
groupHeight = -1.0;
|
||||||
@@ -148,6 +265,25 @@ public class ShapeGroup extends Shape {
|
|||||||
maxy = Math.max(maxy, bounds.y + bounds.height);
|
maxy = Math.max(maxy, bounds.y + bounds.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//groupWidth = maxx - minx;
|
||||||
|
//groupHeight = maxy - miny;
|
||||||
|
groupWidth = maxx;
|
||||||
|
groupHeight = maxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pack() {
|
||||||
|
double minx = Double.MAX_VALUE, miny = Double.MAX_VALUE,
|
||||||
|
maxx = Double.MIN_VALUE, maxy = Double.MIN_VALUE;
|
||||||
|
for( Shape pShape : shapes ) {
|
||||||
|
Bounds bounds = pShape.getBounds();
|
||||||
|
minx = Math.min(minx, bounds.x);
|
||||||
|
maxx = Math.max(maxx, bounds.x + bounds.width);
|
||||||
|
miny = Math.min(miny, bounds.y);
|
||||||
|
maxy = Math.max(maxy, bounds.y + bounds.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
x = minx;
|
||||||
|
y = miny;
|
||||||
groupWidth = maxx - minx;
|
groupWidth = maxx - minx;
|
||||||
groupHeight = maxy - miny;
|
groupHeight = maxy - miny;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,14 @@ import schule.ngb.zm.Color;
|
|||||||
import schule.ngb.zm.Constants;
|
import schule.ngb.zm.Constants;
|
||||||
import schule.ngb.zm.Drawable;
|
import schule.ngb.zm.Drawable;
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Options;
|
||||||
|
import schule.ngb.zm.util.Noise;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.geom.FlatteningPathIterator;
|
||||||
|
import java.awt.geom.GeneralPath;
|
||||||
|
import java.awt.geom.PathIterator;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
public abstract class StrokedShape extends Constants implements Drawable {
|
public abstract class StrokedShape extends Constants implements Drawable {
|
||||||
@@ -26,7 +32,7 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
this.strokeColor = color;
|
this.strokeColor = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStrokeColor( Color color , int alpha ) {
|
public void setStrokeColor( Color color, int alpha ) {
|
||||||
setStrokeColor(new Color(color, alpha));
|
setStrokeColor(new Color(color, alpha));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,10 +72,11 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
/**
|
/**
|
||||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
||||||
* {@link #DOTTED} und {@link #SOLID}.
|
* {@link #DOTTED} und {@link #SOLID}.
|
||||||
|
*
|
||||||
* @param type
|
* @param type
|
||||||
*/
|
*/
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
public void setStrokeType( Options.StrokeType type ) {
|
||||||
this.strokeType = DASHED;
|
this.strokeType = type;
|
||||||
this.stroke = null;
|
this.stroke = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,9 +85,11 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt ein {@link Stroke} Objekt für den Konturtyp.
|
* Erstellt ein {@link Stroke} Objekt für den Konturtyp.
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
protected Stroke createStroke() {
|
protected Stroke createStroke() {
|
||||||
|
// TODO: Used global cached Stroke Objects?
|
||||||
if( stroke == null ) {
|
if( stroke == null ) {
|
||||||
switch( strokeType ) {
|
switch( strokeType ) {
|
||||||
case DOTTED:
|
case DOTTED:
|
||||||
|
|||||||
87
src/main/java/schule/ngb/zm/util/FileLoader.java
Normal file
87
src/main/java/schule/ngb/zm/util/FileLoader.java
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class FileLoader {
|
||||||
|
|
||||||
|
public static final Charset ASCII = StandardCharsets.US_ASCII;
|
||||||
|
|
||||||
|
public static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
public static final Charset UTF16 = StandardCharsets.UTF_16;
|
||||||
|
|
||||||
|
public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
|
||||||
|
|
||||||
|
|
||||||
|
public static List<String> loadLines( String source ) {
|
||||||
|
return loadLines(source, UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> loadLines( String source, Charset charset ) {
|
||||||
|
try {
|
||||||
|
return Files.readAllLines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset);
|
||||||
|
} catch( IOException | URISyntaxException ex ) {
|
||||||
|
LOG.warn(ex, "Error while loading lines from source <%s>", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String loadText( String source ) {
|
||||||
|
return loadText(source, UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String loadText( String source, Charset charset ) {
|
||||||
|
try {
|
||||||
|
return Files.readString(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset);
|
||||||
|
} catch( IOException | URISyntaxException ex ) {
|
||||||
|
LOG.warn(ex, "Error while loading text from source <%s>", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] loadDoubles( String source, char separator, boolean skipFirst ) {
|
||||||
|
return loadDoubles(source, separator, skipFirst, UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] loadDoubles( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||||
|
try {
|
||||||
|
int n = skipFirst ? 1 : 0;
|
||||||
|
return Files
|
||||||
|
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
||||||
|
.skip(n)
|
||||||
|
.map(
|
||||||
|
( line ) -> Arrays
|
||||||
|
.stream(line.split(Character.toString(separator)))
|
||||||
|
.mapToDouble(
|
||||||
|
( value ) -> {
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(value);
|
||||||
|
} catch( NumberFormatException nfe ) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).toArray()
|
||||||
|
).toArray(double[][]::new);
|
||||||
|
} catch( IOException | URISyntaxException ex ) {
|
||||||
|
LOG.warn(ex, "Error while loading double values from csv source <%s>", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new double[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileLoader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(FileLoader.class);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ public class FontLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load userfonts
|
// Load userfonts
|
||||||
try( InputStream in = ResourceStreamProvider.getResourceStream(source) ) {
|
try( InputStream in = ResourceStreamProvider.getInputStream(source) ) {
|
||||||
font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN);
|
font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN);
|
||||||
|
|
||||||
if( font != null ) {
|
if( font != null ) {
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class ImageLoader {
|
public final class ImageLoader {
|
||||||
|
|
||||||
public static boolean caching = true;
|
public static boolean caching = true;
|
||||||
|
|
||||||
@@ -29,7 +27,7 @@ public class ImageLoader {
|
|||||||
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Bilddatei wird nach den Regeln von
|
* Die Bilddatei wird nach den Regeln von
|
||||||
* {@link ResourceStreamProvider#getResourceStream(String)} gesucht und
|
* {@link ResourceStreamProvider#getInputStream(String)} gesucht und
|
||||||
* geöffnet. Tritt dabei ein Fehler auf oder konnte keine passende Datei
|
* geöffnet. Tritt dabei ein Fehler auf oder konnte keine passende Datei
|
||||||
* gefunden werden, wird {@code null} zurückgegeben.
|
* gefunden werden, wird {@code null} zurückgegeben.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -70,7 +68,7 @@ public class ImageLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage img = null;
|
BufferedImage img = null;
|
||||||
try( InputStream in = ResourceStreamProvider.getResourceStream(source) ) {
|
try( InputStream in = ResourceStreamProvider.getInputStream(source) ) {
|
||||||
//URL url = ResourceStreamProvider.getResourceURL(source);
|
//URL url = ResourceStreamProvider.getResourceURL(source);
|
||||||
//BufferedImage img = ImageIO.read(url);
|
//BufferedImage img = ImageIO.read(url);
|
||||||
|
|
||||||
|
|||||||
366
src/main/java/schule/ngb/zm/util/Noise.java
Normal file
366
src/main/java/schule/ngb/zm/util/Noise.java
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zufallsgenerator für Perlin-Noise.
|
||||||
|
* <p>
|
||||||
|
* Die Implementierung basiert auf dem von Ken Perlin entwickelten Algorithmus
|
||||||
|
* und wurde anhand der <a
|
||||||
|
* href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
|
||||||
|
* FLAFLA2</a> implementiert.
|
||||||
|
*/
|
||||||
|
public class Noise {
|
||||||
|
|
||||||
|
private static final int N = 256;
|
||||||
|
|
||||||
|
private static final int M = N - 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interne Permutationstabelle für diesen Generator
|
||||||
|
*/
|
||||||
|
private int[] p;
|
||||||
|
|
||||||
|
private double octaves = 1, persistence = .5;
|
||||||
|
|
||||||
|
private double frequency = 1;
|
||||||
|
|
||||||
|
private double amplitude = 1;
|
||||||
|
|
||||||
|
private int repeat = -1;
|
||||||
|
|
||||||
|
private double rangeMin = 0.0, rangeMax = 1.0;
|
||||||
|
|
||||||
|
public Noise() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Noise( long seed ) {
|
||||||
|
init(new Random(seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
|
||||||
|
*
|
||||||
|
* @param rand
|
||||||
|
*/
|
||||||
|
public Noise( Random rand ) {
|
||||||
|
init(rand);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getOctaves() {
|
||||||
|
return octaves;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOctaves( double pOctaves ) {
|
||||||
|
this.octaves = pOctaves;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPersistence() {
|
||||||
|
return persistence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPersistence( double pPersistence ) {
|
||||||
|
this.persistence = pPersistence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getFrequency() {
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrequency( double pFrequency ) {
|
||||||
|
this.frequency = pFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAmplitude() {
|
||||||
|
return amplitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmplitude( double pAmplitude ) {
|
||||||
|
this.amplitude = pAmplitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRange( double pRangeMin, double pRangeMax ) {
|
||||||
|
this.rangeMin = pRangeMin;
|
||||||
|
this.rangeMax = pRangeMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRangeMin() {
|
||||||
|
return rangeMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRangeMax() {
|
||||||
|
return rangeMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRepeat() {
|
||||||
|
return repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRepeat( int pRepeat ) {
|
||||||
|
this.repeat = pRepeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double noise( double x ) {
|
||||||
|
double total = 0;
|
||||||
|
double freq = this.frequency;
|
||||||
|
double amp = this.amplitude;
|
||||||
|
double maxValue = 0;
|
||||||
|
|
||||||
|
for( int i = 0; i < octaves; i++ ) {
|
||||||
|
total += perlin(x * freq) * amp;
|
||||||
|
|
||||||
|
maxValue += amp;
|
||||||
|
|
||||||
|
amp *= persistence;
|
||||||
|
freq *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lerp(rangeMin, rangeMax, (total / maxValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
public double noise( double x, double y ) {
|
||||||
|
double total = 0;
|
||||||
|
double freq = this.frequency;
|
||||||
|
double amp = this.amplitude;
|
||||||
|
double maxValue = 0;
|
||||||
|
|
||||||
|
for( int i = 0; i < octaves; i++ ) {
|
||||||
|
total += perlin(x * freq, y * freq) * amp;
|
||||||
|
|
||||||
|
maxValue += amp;
|
||||||
|
|
||||||
|
amp *= persistence;
|
||||||
|
freq *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lerp(rangeMin, rangeMax, (total / maxValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
public double noise( double x, double y, double z ) {
|
||||||
|
double total = 0;
|
||||||
|
double freq = this.frequency;
|
||||||
|
double amp = this.amplitude;
|
||||||
|
double maxValue = 0;
|
||||||
|
|
||||||
|
for( int i = 0; i < octaves; i++ ) {
|
||||||
|
total += perlin(x * freq, y * freq, z * freq) * amp;
|
||||||
|
|
||||||
|
maxValue += amp;
|
||||||
|
|
||||||
|
amp *= persistence;
|
||||||
|
freq *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lerp(rangeMin, rangeMax, (total / maxValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double perlin( double x ) {
|
||||||
|
// @formatter:off
|
||||||
|
if( repeat > 0 ) {
|
||||||
|
x %= repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xi = (int)x & M;
|
||||||
|
|
||||||
|
double xf = x - (int)x;
|
||||||
|
|
||||||
|
double u = fade(xf);
|
||||||
|
|
||||||
|
int a, b;
|
||||||
|
a = p[ xi ];
|
||||||
|
b = p[inc(xi)];
|
||||||
|
|
||||||
|
return (lerp(grad(a,xf), grad(b,xf-1), u) + 1) / 2;
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private double perlin( double x, double y ) {
|
||||||
|
// @formatter:off
|
||||||
|
if( repeat > 0 ) {
|
||||||
|
x %= repeat;
|
||||||
|
y %= repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xi = (int) x & M;
|
||||||
|
int yi = (int) y & M;
|
||||||
|
|
||||||
|
double xf = x - (int) x;
|
||||||
|
double yf = y - (int) y;
|
||||||
|
|
||||||
|
double u = fade(xf);
|
||||||
|
double v = fade(yf);
|
||||||
|
|
||||||
|
int aa, ab, ba, bb;
|
||||||
|
aa = p[p[ xi ] + yi ];
|
||||||
|
ab = p[p[ xi ] + inc(yi)];
|
||||||
|
ba = p[p[inc(xi)] + yi ];
|
||||||
|
bb = p[p[inc(xi)] + inc(yi)];
|
||||||
|
|
||||||
|
double x1, x2;
|
||||||
|
x1 = lerp(
|
||||||
|
grad(aa, xf , yf),
|
||||||
|
grad(ba, xf-1, yf),
|
||||||
|
u);
|
||||||
|
x2 = lerp(
|
||||||
|
grad(ab, xf , yf-1),
|
||||||
|
grad(bb, xf-1, yf-1),
|
||||||
|
u);
|
||||||
|
|
||||||
|
return (lerp(x1, x2, v) + 1) / 2;
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private double perlin( double x, double y, double z ) {
|
||||||
|
// @formatter:off
|
||||||
|
if( repeat > 0 ) {
|
||||||
|
x %= repeat;
|
||||||
|
y %= repeat;
|
||||||
|
z %= repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xi = (int)x & M;
|
||||||
|
int yi = (int)y & M;
|
||||||
|
int zi = (int)z & M;
|
||||||
|
double xf = x - (int)x;
|
||||||
|
double yf = y - (int)y;
|
||||||
|
double zf = z - (int)z;
|
||||||
|
|
||||||
|
double u = fade(xf);
|
||||||
|
double v = fade(yf);
|
||||||
|
double w = fade(zf);
|
||||||
|
|
||||||
|
int aaa, aba, aab, abb, baa, bba, bab, bbb;
|
||||||
|
aaa = p[p[p[ xi ] + yi ] + zi ];
|
||||||
|
aba = p[p[p[ xi ] + inc(yi)] + zi ];
|
||||||
|
aab = p[p[p[ xi ] + yi ] + inc(zi)];
|
||||||
|
abb = p[p[p[ xi ] + inc(yi)] + inc(zi)];
|
||||||
|
baa = p[p[p[inc(xi)] + yi ] + zi ];
|
||||||
|
bba = p[p[p[inc(xi)] + inc(yi)] + zi ];
|
||||||
|
bab = p[p[p[inc(xi)] + yi ] + inc(zi)];
|
||||||
|
bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)];
|
||||||
|
|
||||||
|
double x1, x2, y1, y2;
|
||||||
|
x1 = lerp(
|
||||||
|
grad(aaa, xf , yf, zf),
|
||||||
|
grad(baa, xf-1, yf, zf),
|
||||||
|
u);
|
||||||
|
x2 = lerp(
|
||||||
|
grad(aba, xf , yf-1, zf),
|
||||||
|
grad(bba, xf-1, yf-1, zf),
|
||||||
|
u);
|
||||||
|
y1 = lerp(x1, x2, v);
|
||||||
|
|
||||||
|
x1 = lerp(
|
||||||
|
grad(aab, xf , yf, zf-1),
|
||||||
|
grad(bab, xf-1, yf, zf-1),
|
||||||
|
u);
|
||||||
|
x2 = lerp(
|
||||||
|
grad(abb, xf , yf-1, zf-1),
|
||||||
|
grad(bbb, xf-1, yf-1, zf-1),
|
||||||
|
u);
|
||||||
|
y2 = lerp(x1, x2, v);
|
||||||
|
|
||||||
|
return (lerp(y1, y2, w) + 1) / 2;
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init( Random rand ) {
|
||||||
|
p = new int[N * 2];
|
||||||
|
|
||||||
|
if( rand == null ) {
|
||||||
|
System.arraycopy(PERLIN_PERMUTATION, 0, p, 0, N);
|
||||||
|
} else {
|
||||||
|
// Generate random permutation
|
||||||
|
for( int i = 0; i < N; i++ ) {
|
||||||
|
int n = rand.nextInt(N);
|
||||||
|
if( p[n] == 0 )
|
||||||
|
p[n] = i;
|
||||||
|
else
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicate permutation array to prevent overflow errors
|
||||||
|
System.arraycopy(p, 0, p, N, N);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double fade( double t ) {
|
||||||
|
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double lerp( double a, double b, double t ) {
|
||||||
|
return a + t * (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int inc( int i ) {
|
||||||
|
++i;
|
||||||
|
if( repeat > 0 )
|
||||||
|
i = i % repeat;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double grad( int hash, double x ) {
|
||||||
|
switch( hash & 0x1 ) {
|
||||||
|
// @formatter:off
|
||||||
|
case 0x0: return x;
|
||||||
|
case 0x1: return -x;
|
||||||
|
default: return 0;
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private double grad( int hash, double x, double y ) {
|
||||||
|
switch( hash & 0x3 ) {
|
||||||
|
// @formatter:off
|
||||||
|
case 0x0: return x;
|
||||||
|
case 0x1: return -x;
|
||||||
|
case 0x2: return y;
|
||||||
|
case 0x3: return -y;
|
||||||
|
default: return 0;
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double grad( int hash, double x, double y, double z ) {
|
||||||
|
switch( hash & 0xF ) {
|
||||||
|
// @formatter:off
|
||||||
|
case 0x0: return x + y;
|
||||||
|
case 0x1: return -x + y;
|
||||||
|
case 0x2: return x - y;
|
||||||
|
case 0x3: return -x - y;
|
||||||
|
case 0x4: return x + z;
|
||||||
|
case 0x5: return -x + z;
|
||||||
|
case 0x6: return x - z;
|
||||||
|
case 0x7: return -x - z;
|
||||||
|
case 0x8: return y + z;
|
||||||
|
case 0x9: return -y + z;
|
||||||
|
case 0xA: return y - z;
|
||||||
|
case 0xB: return -y - z;
|
||||||
|
case 0xC: return y + x;
|
||||||
|
case 0xD: return -y + z;
|
||||||
|
case 0xE: return y - x;
|
||||||
|
case 0xF: return -y - z;
|
||||||
|
default: return 0; // never happens
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int[] PERLIN_PERMUTATION = new int[]{
|
||||||
|
151, 160, 137, 91, 90, 15,
|
||||||
|
131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
|
||||||
|
190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
|
||||||
|
88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
|
||||||
|
77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
|
||||||
|
102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
|
||||||
|
135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
|
||||||
|
5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
|
||||||
|
223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
|
||||||
|
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
|
||||||
|
251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
|
||||||
|
49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
|
||||||
|
138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,10 +3,9 @@ package schule.ngb.zm.util;
|
|||||||
import schule.ngb.zm.Zeichenmaschine;
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.logging.Level;
|
import java.util.stream.StreamSupport;
|
||||||
import java.util.logging.LogManager;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helferklasse, um {@link InputStream}s für Resourcen zu erhalten.
|
* Helferklasse, um {@link InputStream}s für Resourcen zu erhalten.
|
||||||
@@ -50,15 +49,11 @@ public class ResourceStreamProvider {
|
|||||||
* einer bestehenden Resource oder falls
|
* einer bestehenden Resource oder falls
|
||||||
* keine passende Resource gefunden wurde.
|
* keine passende Resource gefunden wurde.
|
||||||
*/
|
*/
|
||||||
public static InputStream getResourceStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
public static InputStream getInputStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||||
if( source == null ) {
|
Validator.requireNotNull(source, "Resource source may not be null");
|
||||||
throw new NullPointerException("Resource source may not be null");
|
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
||||||
}
|
|
||||||
if( source.length() == 0 ) {
|
|
||||||
throw new IllegalArgumentException("Resource source may not be empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream in;
|
InputStream in = null;
|
||||||
|
|
||||||
// See if source is a readable file
|
// See if source is a readable file
|
||||||
File file = new File(source);
|
File file = new File(source);
|
||||||
@@ -72,7 +67,9 @@ public class ResourceStreamProvider {
|
|||||||
}
|
}
|
||||||
// File does not exist, try other means
|
// File does not exist, try other means
|
||||||
// load ressource relative to .class-file
|
// load ressource relative to .class-file
|
||||||
in = Zeichenmaschine.class.getResourceAsStream(source);
|
if( in == null ) {
|
||||||
|
in = Zeichenmaschine.class.getResourceAsStream(source);
|
||||||
|
}
|
||||||
|
|
||||||
// relative to ClassLoader
|
// relative to ClassLoader
|
||||||
if( in == null ) {
|
if( in == null ) {
|
||||||
@@ -89,6 +86,16 @@ public class ResourceStreamProvider {
|
|||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InputStream getInputStream( File file ) throws IOException {
|
||||||
|
Validator.requireNotNull(file, "Provided file can't be null.");
|
||||||
|
return new FileInputStream(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InputStream getInputStream( URL url ) throws IOException {
|
||||||
|
Validator.requireNotNull(url, "Provided URL can't be null.");
|
||||||
|
return url.openStream();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
|
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
|
||||||
* Resource Locator</em>). Eine passende Datei-Resource wird wie folgt
|
* Resource Locator</em>). Eine passende Datei-Resource wird wie folgt
|
||||||
@@ -119,12 +126,8 @@ public class ResourceStreamProvider {
|
|||||||
* einer bestehenden Resource.
|
* einer bestehenden Resource.
|
||||||
*/
|
*/
|
||||||
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||||
if( source == null ) {
|
Validator.requireNotNull(source, "Resource source may not be null");
|
||||||
throw new NullPointerException("Resource source may not be null");
|
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
||||||
}
|
|
||||||
if( source.length() == 0 ) {
|
|
||||||
throw new IllegalArgumentException("Resource source may not be empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = new File(source);
|
File file = new File(source);
|
||||||
if( file.isFile() ) {
|
if( file.isFile() ) {
|
||||||
@@ -146,20 +149,24 @@ public class ResourceStreamProvider {
|
|||||||
return new URL(source);
|
return new URL(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InputStream getResourceStream( File file ) throws FileNotFoundException, SecurityException {
|
public static OutputStream getOutputStream( String source ) throws IOException {
|
||||||
if( file == null ) {
|
Validator.requireNotNull(source, "Resource source may not be null");
|
||||||
throw new NullPointerException("Provided file can't be null.");
|
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
||||||
}
|
|
||||||
|
|
||||||
return new FileInputStream(file);
|
return getOutputStream(new File(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InputStream getResourceStream( URL url ) throws IOException {
|
public static OutputStream getOutputStream( File file ) throws IOException {
|
||||||
if( url == null ) {
|
Validator.requireNotNull(file, "Provided file can't be null.");
|
||||||
throw new NullPointerException("Provided URL can't be null.");
|
return new FileOutputStream(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.openStream();
|
public static Reader getReader( String source ) throws IOException {
|
||||||
|
return new InputStreamReader(getInputStream(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Writer getWriter( String source ) throws IOException {
|
||||||
|
return new OutputStreamWriter(getOutputStream(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceStreamProvider() {
|
private ResourceStreamProvider() {
|
||||||
|
|||||||
@@ -136,8 +136,27 @@ public class Validator {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr ) {
|
||||||
|
return requireNotEmpty(arr, (Supplier<String>)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence msg ) {
|
||||||
|
return requireNotEmpty(arr, msg::toString);
|
||||||
|
}
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
||||||
return requireSize(arr, 0, ()->"Parameter array may not be empty");
|
if( arr.length == 0 )
|
||||||
|
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be empty") : msg.get());
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireSize( T[] arr, int size ) {
|
||||||
|
return requireSize(arr, size, (Supplier<String>)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireSize( T[] arr, int size, CharSequence msg ) {
|
||||||
|
return requireSize(arr, size, msg::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> T[] requireSize( T[] arr, int size, Supplier<String> msg ) {
|
public static final <T> T[] requireSize( T[] arr, int size, Supplier<String> msg ) {
|
||||||
@@ -146,6 +165,20 @@ public class Validator {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireValid( T[] arr ) {
|
||||||
|
return requireValid(arr, (Supplier<String>)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final<T> T[] requireValid( T[] arr, CharSequence msg ) {
|
||||||
|
return requireValid(arr, msg::toString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
|
||||||
|
if( arr == null || arr.length > 0 )
|
||||||
|
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be null or empty") : msg.get());
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
private Validator() {
|
private Validator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ class ColorTest {
|
|||||||
assertEquals(255, c.getAlpha());
|
assertEquals(255, c.getAlpha());
|
||||||
|
|
||||||
c = Color.BLUE;
|
c = Color.BLUE;
|
||||||
assertEquals(0, c.getRed());
|
assertEquals(49, c.getRed());
|
||||||
assertEquals(0, c.getGreen());
|
assertEquals(197, c.getGreen());
|
||||||
assertEquals(255, c.getBlue());
|
assertEquals(244, c.getBlue());
|
||||||
assertEquals(255, c.getAlpha());
|
assertEquals(255, c.getAlpha());
|
||||||
|
|
||||||
c = new Color(50, 133, 64, 33);
|
c = new Color(50, 133, 64, 33);
|
||||||
@@ -28,7 +28,7 @@ class ColorTest {
|
|||||||
assertEquals(64, c.getBlue());
|
assertEquals(64, c.getBlue());
|
||||||
assertEquals(33, c.getAlpha());
|
assertEquals(33, c.getAlpha());
|
||||||
|
|
||||||
c = new Color(255, 0, 0);
|
c = new Color(240, 80, 37);
|
||||||
assertEquals(Color.RED, c);
|
assertEquals(Color.RED, c);
|
||||||
|
|
||||||
c = new Color(33, 50);
|
c = new Color(33, 50);
|
||||||
@@ -97,8 +97,9 @@ class ColorTest {
|
|||||||
assertEquals(c1, c2);
|
assertEquals(c1, c2);
|
||||||
|
|
||||||
Color yellow = new Color(255, 255, 0);
|
Color yellow = new Color(255, 255, 0);
|
||||||
assertEquals(java.awt.Color.YELLOW, yellow);
|
assertNotEquals(java.awt.Color.YELLOW, yellow);
|
||||||
assertNotEquals(java.awt.Color.YELLOW, Color.YELLOW);
|
assertEquals(yellow, java.awt.Color.YELLOW);
|
||||||
|
assertNotEquals(Color.YELLOW, java.awt.Color.YELLOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -132,7 +133,7 @@ class ColorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getRGBColor() {
|
void getRGBColor() {
|
||||||
Color c1 = Color.getRGBColor(0xFFFF0000);
|
Color c1 = Color.getRGBColor(0xFFF05025);
|
||||||
assertEquals(Color.RED, c1);
|
assertEquals(Color.RED, c1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ class ColorTest {
|
|||||||
Color c;
|
Color c;
|
||||||
float[] hsl;
|
float[] hsl;
|
||||||
|
|
||||||
c = Color.RED;
|
c = new Color(255, 0, 0);
|
||||||
hsl = Color.RGBtoHSL(c.getRGBA(), null);
|
hsl = Color.RGBtoHSL(c.getRGBA(), null);
|
||||||
assertArrayEquals(new float[]{0f,1f,.5f}, hsl, 0.0001f);
|
assertArrayEquals(new float[]{0f,1f,.5f}, hsl, 0.0001f);
|
||||||
|
|
||||||
@@ -183,28 +184,37 @@ class ColorTest {
|
|||||||
@Test
|
@Test
|
||||||
void getRGBA() {
|
void getRGBA() {
|
||||||
Color yellow = new Color(255, 255, 0);
|
Color yellow = new Color(255, 255, 0);
|
||||||
assertEquals(java.awt.Color.YELLOW.getRGB(), Color.YELLOW.getRGBA());
|
assertEquals(java.awt.Color.YELLOW.getRGB(), yellow.getRGBA());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getRed() {
|
void getRed() {
|
||||||
|
Color clr = new Color(123, 92, 0);
|
||||||
|
assertEquals(123, clr.getRed());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getGreen() {
|
void getGreen() {
|
||||||
|
Color clr = new Color(123, 92, 0);
|
||||||
|
assertEquals(92, clr.getGreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getBlue() {
|
void getBlue() {
|
||||||
|
Color clr = new Color(123, 92, 0);
|
||||||
|
assertEquals(0, clr.getBlue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getAlpha() {
|
void getAlpha() {
|
||||||
|
Color clr = new Color(123, 92, 0);
|
||||||
|
assertEquals(255, clr.getAlpha());
|
||||||
|
Color clr2 = new Color(123, 92, 0, 45);
|
||||||
|
assertEquals(45, clr2.getAlpha());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getJavaColor() {
|
void getJavaColor() {
|
||||||
assertEquals(java.awt.Color.YELLOW, Color.YELLOW.getJavaColor());
|
|
||||||
assertEquals(new java.awt.Color(255, 31, 124), new Color(255, 31, 124).getJavaColor());
|
assertEquals(new java.awt.Color(255, 31, 124), new Color(255, 31, 124).getJavaColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,16 +222,8 @@ class ColorTest {
|
|||||||
void brighter() {
|
void brighter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testBrighter() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void darker() {
|
void darker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDarker() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,23 +71,23 @@ class ConstantsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void b() {
|
void asBool() {
|
||||||
assertTrue(Constants.getBool(true));
|
assertTrue(Constants.asBool(true));
|
||||||
assertFalse(Constants.getBool(false));
|
assertFalse(Constants.asBool(false));
|
||||||
assertTrue(Constants.getBool(1));
|
assertTrue(Constants.asBool(1));
|
||||||
assertFalse(Constants.getBool(0));
|
assertFalse(Constants.asBool(0));
|
||||||
assertTrue(Constants.getBool(4.0));
|
assertTrue(Constants.asBool(4.0));
|
||||||
assertFalse(Constants.getBool(0.0));
|
assertFalse(Constants.asBool(0.0));
|
||||||
assertTrue(Constants.getBool(4.0f));
|
assertTrue(Constants.asBool(4.0f));
|
||||||
assertFalse(Constants.getBool(0.0f));
|
assertFalse(Constants.asBool(0.0f));
|
||||||
assertTrue(Constants.getBool(4L));
|
assertTrue(Constants.asBool(4L));
|
||||||
assertFalse(Constants.getBool(0L));
|
assertFalse(Constants.asBool(0L));
|
||||||
assertTrue(Constants.getBool("true"));
|
assertTrue(Constants.asBool("true"));
|
||||||
assertTrue(Constants.getBool("True"));
|
assertTrue(Constants.asBool("True"));
|
||||||
assertFalse(Constants.getBool("1"));
|
assertFalse(Constants.asBool("1"));
|
||||||
assertFalse(Constants.getBool("false"));
|
assertFalse(Constants.asBool("false"));
|
||||||
assertFalse(Constants.getBool("yes"));
|
assertFalse(Constants.asBool("yes"));
|
||||||
assertFalse(Constants.getBool("no"));
|
assertFalse(Constants.asBool("no"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -128,4 +128,45 @@ class ConstantsTest {
|
|||||||
assertEquals(.8f, Math.abs(t/(t+f)), .01f);
|
assertEquals(.8f, Math.abs(t/(t+f)), .01f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noise() {
|
||||||
|
double lastNoise = -1.0;
|
||||||
|
for( int i = 0; i < 100; i++ ) {
|
||||||
|
double thisNoise = Constants.noise(i * 0.005);
|
||||||
|
|
||||||
|
assertInRange(thisNoise);
|
||||||
|
assertNotEquals(lastNoise, thisNoise);
|
||||||
|
assertEquals(thisNoise, Constants.noise(i * 0.005), 0.0001);
|
||||||
|
|
||||||
|
lastNoise = thisNoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastNoise = -1.0;
|
||||||
|
for( int i = 0; i < 100; i++ ) {
|
||||||
|
double thisNoise = Constants.noise(i * 0.005, 0.1);
|
||||||
|
|
||||||
|
assertInRange(thisNoise);
|
||||||
|
assertNotEquals(lastNoise, thisNoise);
|
||||||
|
assertEquals(thisNoise, Constants.noise(i * 0.005, 0.1), 0.0001);
|
||||||
|
|
||||||
|
lastNoise = thisNoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastNoise = -1.0;
|
||||||
|
for( int i = 0; i < 100; i++ ) {
|
||||||
|
double thisNoise = Constants.noise(i * 0.005, 5.5, 100.0/(i+1));
|
||||||
|
|
||||||
|
assertInRange(thisNoise);
|
||||||
|
assertNotEquals(lastNoise, thisNoise);
|
||||||
|
assertEquals(thisNoise, Constants.noise(i * 0.005, 5.5, 100.0/(i+1)), 0.0001);
|
||||||
|
|
||||||
|
lastNoise = thisNoise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInRange( double d ) {
|
||||||
|
assertFalse(Double.isNaN(d), "Noise value can't be NaN.");
|
||||||
|
assertTrue(0.0 <= d && 1.0 >= d, "Noise should be in Range 0 to 1. Was <" + d + ">.");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class TestAttraction extends Zeichenmaschine {
|
|||||||
posC = new Vector(200, 100);
|
posC = new Vector(200, 100);
|
||||||
velC = new Vector(1, 14);
|
velC = new Vector(1, 14);
|
||||||
|
|
||||||
drawing.translate(width /2, height /2);
|
drawing.translate(canvasWidth /2, canvasHeight /2);
|
||||||
drawing.shear(0.1, 0.5);
|
drawing.shear(0.1, 0.5);
|
||||||
|
|
||||||
recht = new Rectangle(50, 50, 150, 75);
|
recht = new Rectangle(50, 50, 150, 75);
|
||||||
@@ -84,7 +84,7 @@ public class TestAttraction extends Zeichenmaschine {
|
|||||||
|
|
||||||
shapes.clear();
|
shapes.clear();
|
||||||
double x = recht.getX();
|
double x = recht.getX();
|
||||||
x = (x+100*delta)% width;
|
x = (x+100*delta)% canvasWidth;
|
||||||
recht.setX(x);
|
recht.setX(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import schule.ngb.zm.shapes.Rectangle;
|
|||||||
import schule.ngb.zm.shapes.Shape;
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
public class TestShapes extends Zeichenmaschine {
|
public class TestShapes extends Zeichenmaschine {
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ public class TestShapes extends Zeichenmaschine {
|
|||||||
|
|
||||||
public void shapePositions() {
|
public void shapePositions() {
|
||||||
int pad = 24;
|
int pad = 24;
|
||||||
Rectangle bounds = new Rectangle(pad, pad, width-pad, height-pad);
|
Rectangle bounds = new Rectangle(pad, pad, canvasWidth -pad, canvasHeight -pad);
|
||||||
|
|
||||||
Rectangle[] rects = new Rectangle[5];
|
Rectangle[] rects = new Rectangle[5];
|
||||||
for( int i = 0; i < rects.length; i++ ) {
|
for( int i = 0; i < rects.length; i++ ) {
|
||||||
|
|||||||
262
src/test/java/schule/ngb/zm/anim/AnimationsTest.java
Normal file
262
src/test/java/schule/ngb/zm/anim/AnimationsTest.java
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
import schule.ngb.zm.Constants;
|
||||||
|
import schule.ngb.zm.Options;
|
||||||
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
import schule.ngb.zm.shapes.*;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class AnimationsTest {
|
||||||
|
|
||||||
|
private static Zeichenmaschine zm;
|
||||||
|
|
||||||
|
private static ShapesLayer shapes;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
zm = new Zeichenmaschine(400, 400, "zm-test: Animations", false);
|
||||||
|
shapes = zm.getShapesLayer();
|
||||||
|
assertNotNull(shapes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void afterAll() {
|
||||||
|
zm.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
shapes.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animateMove() {
|
||||||
|
Shape s = new Circle(0, 0, 10);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
_animateMove(s, 2500, Easing.DEFAULT_EASING);
|
||||||
|
assertEquals(zm.getWidth(), s.getX(), 0.0001);
|
||||||
|
assertEquals(zm.getHeight(), s.getY(), 0.0001);
|
||||||
|
|
||||||
|
_animateMove(s, 2500, Easing.thereAndBack(Easing.linear()));
|
||||||
|
assertEquals(0.0, s.getX(), 0.0001);
|
||||||
|
assertEquals(0.0, s.getY(), 0.0001);
|
||||||
|
|
||||||
|
_animateMove(s, 4000, Easing::bounceInOut);
|
||||||
|
assertEquals(zm.getWidth(), s.getX(), 0.0001);
|
||||||
|
assertEquals(zm.getHeight(), s.getY(), 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _animateMove( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
s.moveTo(0, 0);
|
||||||
|
Future<Shape> future = Animations.animate(
|
||||||
|
s, runtime,
|
||||||
|
easing,
|
||||||
|
( e ) -> Constants.interpolate(0, zm.getWidth(), e),
|
||||||
|
( t, p ) -> {
|
||||||
|
t.moveTo(p, p);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assertNotNull(future);
|
||||||
|
try {
|
||||||
|
assertEquals(s, future.get());
|
||||||
|
} catch( Exception e ) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animateCircle() {
|
||||||
|
Shape s = new Circle(0, 0, 10);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
_animateCircle(s, 5000, Easing.linear());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _animateCircle( Shape s, final int runtime, final DoubleUnaryOperator easing ) {
|
||||||
|
final int midX = (int) (zm.getWidth() * .5);
|
||||||
|
final int midY = (int) (zm.getHeight() * .5);
|
||||||
|
final int radius = (int) (zm.getWidth() * .25);
|
||||||
|
|
||||||
|
Animator<Shape, Double> ani = new Animator<Shape, Double>() {
|
||||||
|
@Override
|
||||||
|
public double easing( double t ) {
|
||||||
|
return easing.applyAsDouble(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double interpolator( double e ) {
|
||||||
|
return Constants.interpolate(0, 360, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applicator( Shape s, Double angle ) {
|
||||||
|
double rad = Math.toRadians(angle);
|
||||||
|
s.moveTo(midX + radius * Math.cos(rad), midY + radius * Math.sin(rad));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<Shape> future = Animations.animate(s, runtime, ani);
|
||||||
|
assertNotNull(future);
|
||||||
|
try {
|
||||||
|
assertEquals(s, future.get());
|
||||||
|
} catch( Exception e ) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animateRotate() {
|
||||||
|
Shape s = new Rectangle(0, 0, 129, 80);
|
||||||
|
s.setAnchor(Constants.CENTER);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
_animateRotate(s, 3000, Easing::cubicIn);
|
||||||
|
assertEquals(zm.getWidth() * 0.5, s.getX(), 0.0001);
|
||||||
|
assertEquals(zm.getHeight() * 0.5, s.getY(), 0.0001);
|
||||||
|
assertEquals(0.0, s.getRotation(), 0.0001);
|
||||||
|
|
||||||
|
_animateRotate(s, 500, Easing::elasticInOut);
|
||||||
|
assertEquals(zm.getWidth() * 0.5, s.getX(), 0.0001);
|
||||||
|
assertEquals(zm.getHeight() * 0.5, s.getY(), 0.0001);
|
||||||
|
assertEquals(0.0, s.getRotation(), 0.0001);
|
||||||
|
|
||||||
|
_animateRotate(s, 1000, Easing::bounceOut);
|
||||||
|
assertEquals(zm.getWidth() * 0.5, s.getX(), 0.0001);
|
||||||
|
assertEquals(zm.getHeight() * 0.5, s.getY(), 0.0001);
|
||||||
|
assertEquals(0.0, s.getRotation(), 0.0001);
|
||||||
|
|
||||||
|
_animateRotate(s, 6000, Easing::backInOut);
|
||||||
|
assertEquals(zm.getWidth() * 0.5, s.getX(), 0.0001);
|
||||||
|
assertEquals(zm.getHeight() * 0.5, s.getY(), 0.0001);
|
||||||
|
assertEquals(0.0, s.getRotation(), 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _animateRotate( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
||||||
|
s.rotateTo(0);
|
||||||
|
Future<Shape> future = Animations.animate(
|
||||||
|
s, runtime,
|
||||||
|
easing,
|
||||||
|
( e ) -> s.rotateTo(Constants.interpolate(0, 720, e))
|
||||||
|
);
|
||||||
|
assertNotNull(future);
|
||||||
|
try {
|
||||||
|
assertEquals(s, future.get());
|
||||||
|
} catch( Exception e ) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animateColor() {
|
||||||
|
Shape s = new Ellipse(0, 0, 129, 80);
|
||||||
|
s.setAnchor(Constants.CENTER);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
_animateColor(s, Color.RED, 1000, Easing.DEFAULT_EASING);
|
||||||
|
assertEquals(Color.RED, s.getFillColor());
|
||||||
|
_animateColor(s, Color.BLUE, 1500, Easing::backInOut);
|
||||||
|
assertEquals(Color.BLUE, s.getFillColor());
|
||||||
|
_animateColor(s, Color.GREEN, 2000, Easing::bounceOut);
|
||||||
|
assertEquals(Color.GREEN, s.getFillColor());
|
||||||
|
_animateColor(s, Color.YELLOW, 300, Easing::thereAndBack);
|
||||||
|
assertEquals(Color.GREEN, s.getFillColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _animateColor( Shape s, Color to, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
||||||
|
final Color from = s.getFillColor();
|
||||||
|
Future<Shape> future = Animations.animate(
|
||||||
|
s, runtime,
|
||||||
|
easing,
|
||||||
|
( e ) -> Color.interpolate(from, to, e),
|
||||||
|
( t, c ) -> t.setFillColor(c)
|
||||||
|
);
|
||||||
|
assertNotNull(future);
|
||||||
|
try {
|
||||||
|
assertEquals(s, future.get());
|
||||||
|
} catch( Exception e ) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animatePropertyColor() {
|
||||||
|
Shape s = new Ellipse(0, 0, 129, 80);
|
||||||
|
s.setAnchor(Constants.CENTER);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
_animatePropertyColor(s, Color.RED, 1000, Easing.DEFAULT_EASING);
|
||||||
|
assertEquals(Color.RED, s.getFillColor());
|
||||||
|
_animatePropertyColor(s, Color.BLUE, 1500, Easing::backInOut);
|
||||||
|
assertEquals(Color.BLUE, s.getFillColor());
|
||||||
|
_animatePropertyColor(s, Color.GREEN, 2000, Easing::bounceOut);
|
||||||
|
assertEquals(Color.GREEN, s.getFillColor());
|
||||||
|
_animatePropertyColor(s, Color.YELLOW, 300, Easing::thereAndBack);
|
||||||
|
assertEquals(Color.GREEN, s.getFillColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _animatePropertyColor( Shape s, Color to, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
||||||
|
final Color from = s.getFillColor();
|
||||||
|
Future<Shape> future = Animations.animateProperty(
|
||||||
|
s, from, to, runtime, easing, s::setFillColor
|
||||||
|
);
|
||||||
|
assertNotNull(future);
|
||||||
|
try {
|
||||||
|
assertEquals(s, future.get());
|
||||||
|
} catch( Exception e ) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animatePropertyReflect() {
|
||||||
|
Shape s = new Ellipse(0, 200, 129, 80);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Animations.animateProperty("x", s, 400, 1000, Easing.DEFAULT_EASING);
|
||||||
|
Animations.animateProperty("strokeColor", s, Color.RED, 1000, Easing.DEFAULT_EASING).get();
|
||||||
|
} catch( InterruptedException | ExecutionException e ) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animateManim() {
|
||||||
|
Shape s = new Circle(0, 0, 10);
|
||||||
|
shapes.add(s);
|
||||||
|
Text t = new Text(0, 0, "Easing");
|
||||||
|
t.setAnchor(Options.Direction.EAST);
|
||||||
|
t.alignTo(Options.Direction.NORTHEAST, -20.0);
|
||||||
|
shapes.add(t);
|
||||||
|
|
||||||
|
t.setText("rushIn");
|
||||||
|
_animateMove(s, 2500, Easing::rushIn);
|
||||||
|
t.setText("rushOut");
|
||||||
|
_animateMove(s, 2500, Easing::rushOut);
|
||||||
|
t.setText("hobbit");
|
||||||
|
_animateMove(s, 2500, Easing::hobbit);
|
||||||
|
t.setText("wiggle(2)");
|
||||||
|
_animateMove(s, 2500, Easing::wiggle);
|
||||||
|
t.setText("wiggle(4)");
|
||||||
|
_animateMove(s, 2500, Easing.wiggle(4));
|
||||||
|
t.setText("doubleSmooth");
|
||||||
|
_animateMove(s, 2500, Easing::doubleSmooth);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
65
src/test/java/schule/ngb/zm/events/EventDispatcherTest.java
Normal file
65
src/test/java/schule/ngb/zm/events/EventDispatcherTest.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package schule.ngb.zm.events;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class EventDispatcherTest {
|
||||||
|
|
||||||
|
class TestEvent {
|
||||||
|
|
||||||
|
private String data;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
public TestEvent( String data, boolean isStart ) {
|
||||||
|
this.data = data;
|
||||||
|
this.type = isStart ? "start" : "stop";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestListener extends Listener<TestEvent> {
|
||||||
|
|
||||||
|
void startEvent( TestEvent t );
|
||||||
|
|
||||||
|
void stopEvent( TestEvent t );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void eventRegistry() {
|
||||||
|
EventDispatcher<TestEvent, TestListener> gen = new EventDispatcher<>();
|
||||||
|
|
||||||
|
gen.registerEventType("start", ( event, listener ) -> listener.startEvent(event));
|
||||||
|
gen.registerEventType("stop", ( event, listener ) -> listener.stopEvent(event));
|
||||||
|
|
||||||
|
gen.addListener(new TestListener() {
|
||||||
|
@Override
|
||||||
|
public void startEvent( TestEvent t ) {
|
||||||
|
assertEquals("start", t.getType());
|
||||||
|
assertTrue(t.getData().startsWith("Start Event"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopEvent( TestEvent t ) {
|
||||||
|
assertEquals("stop", t.getType());
|
||||||
|
assertTrue(t.getData().startsWith("Stop Event"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gen.dispatchEvent("start", new TestEvent("Start Event 1", true));
|
||||||
|
gen.dispatchEvent("stop", new TestEvent("Stop Event 1", false));
|
||||||
|
gen.dispatchEvent("stop", new TestEvent("Stop Event 2", false));
|
||||||
|
gen.dispatchEvent("start", new TestEvent("Start Event 2", true));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
101
src/test/java/schule/ngb/zm/ml/MLMathTest.java
Normal file
101
src/test/java/schule/ngb/zm/ml/MLMathTest.java
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class MLMathTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void matrixMultiply() {
|
||||||
|
double[][] A = new double[][]{
|
||||||
|
{1.0, 2.0, 3.0},
|
||||||
|
{-5.0, -4.0, -3.0},
|
||||||
|
{0.0, -10.0, 10.0}
|
||||||
|
};
|
||||||
|
double[][] B = new double[][]{
|
||||||
|
{0.0, 1.0},
|
||||||
|
{2.0, -2.0},
|
||||||
|
{5.0, -10.0}
|
||||||
|
};
|
||||||
|
double[][] result = MLMath.matrixMultiply(A, B);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(A.length, result.length);
|
||||||
|
assertEquals(B[0].length, result[0].length);
|
||||||
|
assertArrayEquals(new double[]{19.0, -33.0}, result[0]);
|
||||||
|
assertArrayEquals(new double[]{-23.0, 33.0}, result[1]);
|
||||||
|
assertArrayEquals(new double[]{30.0, -80.0}, result[2]);
|
||||||
|
|
||||||
|
assertThrowsExactly(IllegalArgumentException.class, () -> MLMath.matrixMultiply(B, A));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void matrixScale() {
|
||||||
|
double[][] matrix = new double[][]{
|
||||||
|
{1.0, 2.0, 3.0},
|
||||||
|
{-5.0, -4.0, -3.0},
|
||||||
|
{0.0, -10.0, 10.0}
|
||||||
|
};
|
||||||
|
double[][] scalars = new double[][]{
|
||||||
|
{0.0, 1.0, -1.0},
|
||||||
|
{2.0, -2.0, 10.0},
|
||||||
|
{5.0, -10.0, 10.0}
|
||||||
|
};
|
||||||
|
double[][] result = MLMath.matrixScale(matrix, scalars);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNotSame(matrix, result);
|
||||||
|
assertArrayEquals(new double[]{0.0, 2.0, -3.0}, result[0]);
|
||||||
|
assertArrayEquals(new double[]{-10.0, 8.0, -30.0}, result[1]);
|
||||||
|
assertArrayEquals(new double[]{0.0, 100.0, 100.0}, result[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void matrixApply() {
|
||||||
|
double[][] matrix = new double[][]{
|
||||||
|
{1.0, 2.0, 3.0},
|
||||||
|
{-5.0, -4.0, -3.0},
|
||||||
|
{0.0, -10.0, 10.0}
|
||||||
|
};
|
||||||
|
double[][] result = MLMath.matrixApply(matrix, (d) -> -1*d);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNotSame(matrix, result);
|
||||||
|
assertArrayEquals(new double[]{-1.0, -2.0, -3.0}, result[0]);
|
||||||
|
assertArrayEquals(new double[]{5.0, 4.0, 3.0}, result[1]);
|
||||||
|
assertArrayEquals(new double[]{-0.0, 10.0, -10.0}, result[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void matrixSubtract() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void matrixAdd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void matrixTranspose() {
|
||||||
|
double[][] matrix = new double[][]{
|
||||||
|
{1.0, 2.0, 3.0, 4.5},
|
||||||
|
{-5.0, -4.0, -3.0, 2.1},
|
||||||
|
{0.0, -10.0, 10.0, 0.9}
|
||||||
|
};
|
||||||
|
double[][] result = MLMath.matrixTranspose(matrix);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(4, result.length);
|
||||||
|
assertEquals(3, result[0].length);
|
||||||
|
|
||||||
|
assertArrayEquals(new double[]{1.0, -5.0, 0.0}, result[0]);
|
||||||
|
assertArrayEquals(new double[]{2.0, -4.0, -10.0}, result[1]);
|
||||||
|
assertArrayEquals(new double[]{3.0, -3.0, 10.0}, result[2]);
|
||||||
|
assertArrayEquals(new double[]{4.5, 2.1, 0.9}, result[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void normalize() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
57
src/test/java/schule/ngb/zm/ml/MatrixTest.java
Normal file
57
src/test/java/schule/ngb/zm/ml/MatrixTest.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class MatrixTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initializeIdentity() {
|
||||||
|
Matrix m = new Matrix(4, 4);
|
||||||
|
m.initializeIdentity();
|
||||||
|
|
||||||
|
assertArrayEquals(new double[]{1.0, 0.0, 0.0, 0.0}, m.coefficients[0]);
|
||||||
|
assertArrayEquals(new double[]{0.0, 1.0, 0.0, 0.0}, m.coefficients[1]);
|
||||||
|
assertArrayEquals(new double[]{0.0, 0.0, 1.0, 0.0}, m.coefficients[2]);
|
||||||
|
assertArrayEquals(new double[]{0.0, 0.0, 0.0, 1.0}, m.coefficients[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initializeOne() {
|
||||||
|
Matrix m = new Matrix(4, 4);
|
||||||
|
m.initializeOne();
|
||||||
|
|
||||||
|
double[] ones = new double[]{1.0, 1.0, 1.0, 1.0};
|
||||||
|
assertArrayEquals(ones, m.coefficients[0]);
|
||||||
|
assertArrayEquals(ones, m.coefficients[1]);
|
||||||
|
assertArrayEquals(ones, m.coefficients[2]);
|
||||||
|
assertArrayEquals(ones, m.coefficients[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initializeZero() {
|
||||||
|
Matrix m = new Matrix(4, 4);
|
||||||
|
m.initializeZero();
|
||||||
|
|
||||||
|
double[] zeros = new double[]{0.0, 0.0, 0.0, 0.0};
|
||||||
|
assertArrayEquals(zeros, m.coefficients[0]);
|
||||||
|
assertArrayEquals(zeros, m.coefficients[1]);
|
||||||
|
assertArrayEquals(zeros, m.coefficients[2]);
|
||||||
|
assertArrayEquals(zeros, m.coefficients[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initializeRandom() {
|
||||||
|
Matrix m = new Matrix(4, 4);
|
||||||
|
m.initializeRandom(-1, 1);
|
||||||
|
|
||||||
|
assertTrue(Arrays.stream(m.coefficients[0]).allMatch((d) -> -1.0 <= d && d < 1.0));
|
||||||
|
assertTrue(Arrays.stream(m.coefficients[1]).allMatch((d) -> -1.0 <= d && d < 1.0));
|
||||||
|
assertTrue(Arrays.stream(m.coefficients[2]).allMatch((d) -> -1.0 <= d && d < 1.0));
|
||||||
|
assertTrue(Arrays.stream(m.coefficients[3]).allMatch((d) -> -1.0 <= d && d < 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
252
src/test/java/schule/ngb/zm/ml/NeuralNetworkTest.java
Normal file
252
src/test/java/schule/ngb/zm/ml/NeuralNetworkTest.java
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class NeuralNetworkTest {
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void enableDebugging() {
|
||||||
|
Log.enableGlobalDebugging();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readWrite() {
|
||||||
|
// XOR Dataset
|
||||||
|
NeuralNetwork net = new NeuralNetwork(2, 4, 1);
|
||||||
|
double[][] inputs = new double[][]{
|
||||||
|
{0, 0}, {0, 1}, {1, 0}, {1, 1}
|
||||||
|
};
|
||||||
|
double[][] outputs = new double[][]{
|
||||||
|
{0}, {1}, {1}, {0}
|
||||||
|
};
|
||||||
|
|
||||||
|
System.out.println("Training the neural net to learn XOR...");
|
||||||
|
net.train(inputs, outputs, 10000);
|
||||||
|
System.out.println(" finished training");
|
||||||
|
|
||||||
|
NeuralNetwork.saveToFile("./ml-test.txt", net);
|
||||||
|
assertTrue(new File("./ml-test.txt").isFile());
|
||||||
|
|
||||||
|
NeuralNetwork net2 = NeuralNetwork.loadFromFile("./ml-test.txt");
|
||||||
|
assertEquals(net.getLayerCount(), net2.getLayerCount());
|
||||||
|
for( int l = 0; l < net2.getLayerCount(); l++ ) {
|
||||||
|
NeuronLayer layer = net.getLayer(l+1);
|
||||||
|
NeuronLayer layer2 = net2.getLayer(l+1);
|
||||||
|
|
||||||
|
for( int i = 0; i < layer.getInputCount(); i++ ) {
|
||||||
|
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||||
|
assertEquals(layer.weights.coefficients[i][j], layer2.weights.coefficients[i][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||||
|
assertEquals(layer.biases[j], layer2.biases[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArrayEquals(net.predict(inputs), net2.predict(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void learnXor() {
|
||||||
|
int TRAINING_CYCLES = 40000;
|
||||||
|
|
||||||
|
NeuralNetwork net = new NeuralNetwork(2, 4, 1);
|
||||||
|
|
||||||
|
double[][] inputs = new double[][]{
|
||||||
|
{0, 0}, {0, 1}, {1, 0}, {1, 1}
|
||||||
|
};
|
||||||
|
double[][] outputs = new double[][]{
|
||||||
|
{0}, {1}, {1}, {0}
|
||||||
|
};
|
||||||
|
|
||||||
|
System.out.println("Training the neural net to learn XOR...");
|
||||||
|
net.train(inputs, outputs, TRAINING_CYCLES);
|
||||||
|
System.out.println(" finished training");
|
||||||
|
|
||||||
|
for( int i = 1; i <= net.getLayerCount(); i++ ) {
|
||||||
|
System.out.println("Layer " +i + " weights");
|
||||||
|
System.out.println(net.getLayer(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate predictions
|
||||||
|
double[][] predictions = net.predict(inputs);
|
||||||
|
for( int i = 0; i < 4; i++ ) {
|
||||||
|
int parsed_pred = predictions[i][0] < 0.5 ? 0 : 1;
|
||||||
|
|
||||||
|
System.out.printf(
|
||||||
|
"{%.0f, %.0f} = %.4f (%d) -> %s\n",
|
||||||
|
inputs[i][0], inputs[i][1],
|
||||||
|
predictions[i][0],
|
||||||
|
parsed_pred,
|
||||||
|
parsed_pred == outputs[i][0] ? "correct" : "miss"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void learnCalc() {
|
||||||
|
int INPUT_SIZE = 50;
|
||||||
|
int PREDICT_SIZE = 4;
|
||||||
|
int TRAINING_CYCLES = 40000;
|
||||||
|
CalcType OPERATION = CalcType.ADD;
|
||||||
|
|
||||||
|
// Create neural network with layer1: 4 neurones, layer2: 1 neuron
|
||||||
|
NeuralNetwork net = new NeuralNetwork(2, 8, 4, 1);
|
||||||
|
|
||||||
|
List<TestData> trainingData = createTrainingSet(INPUT_SIZE, OPERATION);
|
||||||
|
|
||||||
|
double[][] inputs = new double[INPUT_SIZE][2];
|
||||||
|
double[][] outputs = new double[INPUT_SIZE][1];
|
||||||
|
for( int i = 0; i < trainingData.size(); i++ ) {
|
||||||
|
inputs[i][0] = trainingData.get(i).a;
|
||||||
|
inputs[i][1] = trainingData.get(i).b;
|
||||||
|
outputs[i][0] = trainingData.get(i).result;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Training the neural net to learn "+OPERATION+"...");
|
||||||
|
net.train(inputs, outputs, TRAINING_CYCLES);
|
||||||
|
System.out.println(" finished training");
|
||||||
|
|
||||||
|
for( int i = 1; i <= net.getLayerCount(); i++ ) {
|
||||||
|
System.out.println("Layer " +i + " weights");
|
||||||
|
System.out.println(net.getLayer(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the predictions on unknown data
|
||||||
|
List<TestData> predictionSet = createTrainingSet(PREDICT_SIZE, OPERATION);
|
||||||
|
for( TestData t : predictionSet ) {
|
||||||
|
predict(t, net);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void predict( TestData data, NeuralNetwork net ) {
|
||||||
|
double[][] testInput = new double[][]{{data.a, data.b}};
|
||||||
|
net.predict(testInput);
|
||||||
|
|
||||||
|
// then
|
||||||
|
System.out.printf(
|
||||||
|
"Prediction on data (%.2f, %.2f) was %.4f, expected %.2f (of by %.4f)\n",
|
||||||
|
data.a, data.b,
|
||||||
|
net.getOutput()[0][0],
|
||||||
|
data.result,
|
||||||
|
net.getOutput()[0][0] - data.result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TestData> createTrainingSet( int trainingSetSize, CalcType operation ) {
|
||||||
|
Random random = new Random();
|
||||||
|
List<TestData> tuples = new ArrayList<>();
|
||||||
|
|
||||||
|
for( int i = 0; i < trainingSetSize; i++ ) {
|
||||||
|
double s1 = random.nextDouble() * 0.5;
|
||||||
|
double s2 = random.nextDouble() * 0.5;
|
||||||
|
|
||||||
|
switch( operation ) {
|
||||||
|
case ADD:
|
||||||
|
tuples.add(new AddData(s1, s2));
|
||||||
|
break;
|
||||||
|
case SUB:
|
||||||
|
tuples.add(new SubData(s1, s2));
|
||||||
|
break;
|
||||||
|
case MUL:
|
||||||
|
tuples.add(new MulData(s1, s2));
|
||||||
|
break;
|
||||||
|
case DIV:
|
||||||
|
tuples.add(new DivData(s1, s2));
|
||||||
|
break;
|
||||||
|
case MOD:
|
||||||
|
tuples.add(new ModData(s1, s2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tuples;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static enum CalcType {
|
||||||
|
ADD, SUB, MUL, DIV, MOD
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class TestData {
|
||||||
|
|
||||||
|
double a;
|
||||||
|
double b;
|
||||||
|
double result;
|
||||||
|
CalcType type;
|
||||||
|
|
||||||
|
TestData( double a, double b ) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class AddData extends TestData {
|
||||||
|
|
||||||
|
CalcType type = CalcType.ADD;
|
||||||
|
|
||||||
|
public AddData( double a, double b ) {
|
||||||
|
super(a, b);
|
||||||
|
result = a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SubData extends TestData {
|
||||||
|
|
||||||
|
CalcType type = CalcType.SUB;
|
||||||
|
|
||||||
|
public SubData( double a, double b ) {
|
||||||
|
super(a, b);
|
||||||
|
result = a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MulData extends TestData {
|
||||||
|
|
||||||
|
CalcType type = CalcType.MUL;
|
||||||
|
|
||||||
|
public MulData( double a, double b ) {
|
||||||
|
super(a, b);
|
||||||
|
result = a * b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DivData extends TestData {
|
||||||
|
|
||||||
|
CalcType type = CalcType.DIV;
|
||||||
|
|
||||||
|
public DivData( double a, double b ) {
|
||||||
|
super(a, b);
|
||||||
|
if( b == 0.0 ) {
|
||||||
|
b = .1;
|
||||||
|
}
|
||||||
|
result = a / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ModData extends TestData {
|
||||||
|
|
||||||
|
CalcType type = CalcType.MOD;
|
||||||
|
|
||||||
|
public ModData( double b, double a ) {
|
||||||
|
super(b, a);
|
||||||
|
result = a % b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
81
src/test/java/schule/ngb/zm/util/FileLoaderTest.java
Normal file
81
src/test/java/schule/ngb/zm/util/FileLoaderTest.java
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class FileLoaderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loadLines() {
|
||||||
|
String[] data;
|
||||||
|
List<String> lines;
|
||||||
|
|
||||||
|
data = new String[]{
|
||||||
|
"Header1,Header2,Header3",
|
||||||
|
"1.1,1.2,1.3",
|
||||||
|
"2.1,2.2,2.3",
|
||||||
|
"3.1,3.2,3.3"
|
||||||
|
};
|
||||||
|
|
||||||
|
lines = FileLoader.loadLines("data_comma.csv");
|
||||||
|
assertEquals(data.length, lines.size());
|
||||||
|
for( int i = 0; i < lines.size(); i++ ) {
|
||||||
|
assertEquals(data[i], lines.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
data = new String[]{
|
||||||
|
"Nöme;Häder2;Straße",
|
||||||
|
"1.1;1.2;1.3",
|
||||||
|
"2.1;2.2;2.3",
|
||||||
|
"3.1;3.2;3.3"
|
||||||
|
};
|
||||||
|
|
||||||
|
lines = FileLoader.loadLines("data_semicolon_latin.csv", FileLoader.ISO_8859_1);
|
||||||
|
assertEquals(data.length, lines.size());
|
||||||
|
for( int i = 0; i < lines.size(); i++ ) {
|
||||||
|
assertEquals(data[i], lines.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loadText() {
|
||||||
|
String data;
|
||||||
|
String text;
|
||||||
|
|
||||||
|
data = "Header1,Header2,Header3\n" +
|
||||||
|
"1.1,1.2,1.3\n" +
|
||||||
|
"2.1,2.2,2.3\n" +
|
||||||
|
"3.1,3.2,3.3\n";
|
||||||
|
text = FileLoader.loadText("data_comma.csv");
|
||||||
|
assertEquals(data, text);
|
||||||
|
|
||||||
|
data = "Nöme;Häder2;Straße\n" +
|
||||||
|
"1.1;1.2;1.3\n" +
|
||||||
|
"2.1;2.2;2.3\n" +
|
||||||
|
"3.1;3.2;3.3\n";
|
||||||
|
text = FileLoader.loadText("data_semicolon_latin.csv", FileLoader.ISO_8859_1);
|
||||||
|
assertEquals(data, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loadCsv() {
|
||||||
|
double[][] data;
|
||||||
|
double[][] csv;
|
||||||
|
|
||||||
|
data = new double[][]{
|
||||||
|
{1.1,1.2,1.3},
|
||||||
|
{2.1,2.2,2.3},
|
||||||
|
{3.1,3.2,3.3}
|
||||||
|
};
|
||||||
|
csv = FileLoader.loadDoubles("data_comma.csv", ',', true);
|
||||||
|
assertArrayEquals(data, csv);
|
||||||
|
|
||||||
|
csv = FileLoader.loadDoubles("data_semicolon_latin.csv", ';', true, FileLoader.ISO_8859_1);
|
||||||
|
assertArrayEquals(data, csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
126
src/test/java/schule/ngb/zm/util/NoiseTest.java
Normal file
126
src/test/java/schule/ngb/zm/util/NoiseTest.java
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class NoiseTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noise() {
|
||||||
|
int N = 1000;
|
||||||
|
|
||||||
|
Noise perlin = new Noise();
|
||||||
|
Noise perlin1 = new Noise(1001);
|
||||||
|
Noise perlin2 = new Noise(2002);
|
||||||
|
Noise perlin3 = new Noise(2002);
|
||||||
|
|
||||||
|
int pp1 = 0, p1p2 = 0;
|
||||||
|
for( int i = 1; i < N; i++ ) {
|
||||||
|
double x = i * 0.005;
|
||||||
|
|
||||||
|
assertInRange(perlin.noise(x));
|
||||||
|
assertInRange(perlin1.noise(x));
|
||||||
|
assertInRange(perlin2.noise(x));
|
||||||
|
if( perlin.noise(x) == perlin1.noise(x) ) {
|
||||||
|
pp1++;
|
||||||
|
}
|
||||||
|
if( perlin1.noise(x) == perlin2.noise(x) ) {
|
||||||
|
p1p2++;
|
||||||
|
}
|
||||||
|
assertEquals(perlin2.noise(x), perlin3.noise(x), "perlin2 and perlin3 should be equal for input " + x);
|
||||||
|
}
|
||||||
|
assertTrue(pp1 < N * 0.75, "perlin and perlin1 should not be equal more than 75% (was " + pp1 / 1000.0 + ")");
|
||||||
|
assertTrue(p1p2 < N * 0.75, "perlin1 and perlin2 should not be equal more than 75% (was " + p1p2 / 1000.0 + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInRange( double d ) {
|
||||||
|
assertTrue(0.0 <= d && 1.0 >= d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInRange( double d, double min, double max ) {
|
||||||
|
assertTrue(min <= d && max >= d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noise2d() {
|
||||||
|
int N = 100;
|
||||||
|
|
||||||
|
Noise perlin = new Noise();
|
||||||
|
Noise perlin1 = new Noise(1001);
|
||||||
|
Noise perlin2 = new Noise(2002);
|
||||||
|
Noise perlin3 = new Noise(2002);
|
||||||
|
|
||||||
|
int pp1 = 0, p1p2 = 0;
|
||||||
|
for( int i = 1; i < N; i++ ) {
|
||||||
|
for( int j = 0; j < N; j++ ) {
|
||||||
|
double x = i * 0.005;
|
||||||
|
double y = j * 0.001;
|
||||||
|
|
||||||
|
assertInRange(perlin.noise(x, y));
|
||||||
|
assertInRange(perlin1.noise(x, y));
|
||||||
|
assertInRange(perlin2.noise(x, y));
|
||||||
|
if( perlin.noise(x, y) == perlin1.noise(x, y) ) {
|
||||||
|
pp1++;
|
||||||
|
}
|
||||||
|
if( perlin1.noise(x, y) == perlin2.noise(x, y) ) {
|
||||||
|
p1p2++;
|
||||||
|
}
|
||||||
|
assertEquals(perlin2.noise(x, y), perlin3.noise(x, y), "perlin2 and perlin3 should be equal for input " + x + "," + y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(pp1 < N * N * 0.75, "perlin and perlin1 should not be equal more than 75% (was " + (pp1 / 1.0 * N * N) + ")");
|
||||||
|
assertTrue(p1p2 < N * N * 0.75, "perlin1 and perlin2 should not be equal more than 75% (was " + (p1p2 / 1.0 * N * N) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noise3d() {
|
||||||
|
int N = 100;
|
||||||
|
|
||||||
|
Noise perlin = new Noise();
|
||||||
|
Noise perlin1 = new Noise(1001);
|
||||||
|
Noise perlin2 = new Noise(2002);
|
||||||
|
Noise perlin3 = new Noise(2002);
|
||||||
|
|
||||||
|
int pp1 = 0, p1p2 = 0;
|
||||||
|
for( int i = 1; i < N; i++ ) {
|
||||||
|
for( int j = 0; j < N; j++ ) {
|
||||||
|
for( int k = 0; k < N; k++ ) {
|
||||||
|
double x = i * 0.005;
|
||||||
|
double y = j * 0.001;
|
||||||
|
double z = k * 0.004;
|
||||||
|
|
||||||
|
assertInRange(perlin.noise(x, y, z));
|
||||||
|
assertInRange(perlin1.noise(x, y, z));
|
||||||
|
assertInRange(perlin2.noise(x, y, z));
|
||||||
|
if( perlin.noise(x, y, z) == perlin1.noise(x, y, z) ) {
|
||||||
|
pp1++;
|
||||||
|
}
|
||||||
|
if( perlin1.noise(x, y, z) == perlin2.noise(x, y, z) ) {
|
||||||
|
p1p2++;
|
||||||
|
}
|
||||||
|
assertEquals(perlin2.noise(x, y, z), perlin3.noise(x, y, z), "perlin2 and perlin3 should be equal for input " + x + "," + y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(pp1 < N * N * N* 0.75, "perlin and perlin1 should not be equal more than 75% (was " + (pp1 / 1.0 * N * N * N) + ")");
|
||||||
|
assertTrue(p1p2 < N * N * N * 0.75, "perlin1 and perlin2 should not be equal more than 75% (was " + (p1p2 / 1.0 * N * N * N) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void range() {
|
||||||
|
Noise perlin = new Noise(1001);
|
||||||
|
perlin.setRange(100, 255);
|
||||||
|
|
||||||
|
for( int i = 0; i < 1000; i++ ) {
|
||||||
|
assertInRange(perlin.noise(i * 0.005), 100, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
perlin.setRange(-100, 100);
|
||||||
|
|
||||||
|
for( int i = 0; i < 1000; i++ ) {
|
||||||
|
assertInRange(perlin.noise(i * 0.005), -100, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
4
src/test/resources/data_comma.csv
Normal file
4
src/test/resources/data_comma.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Header1,Header2,Header3
|
||||||
|
1.1,1.2,1.3
|
||||||
|
2.1,2.2,2.3
|
||||||
|
3.1,3.2,3.3
|
||||||
|
4
src/test/resources/data_semicolon.csv
Normal file
4
src/test/resources/data_semicolon.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Header1;Header2;Header3
|
||||||
|
1.1;1.2;1.3
|
||||||
|
2.1;2.2;2.3
|
||||||
|
3.1;3.2;3.3
|
||||||
|
4
src/test/resources/data_semicolon_latin.csv
Normal file
4
src/test/resources/data_semicolon_latin.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Nöme;Häder2;Straße
|
||||||
|
1.1;1.2;1.3
|
||||||
|
2.1;2.2;2.3
|
||||||
|
3.1;3.2;3.3
|
||||||
|
Reference in New Issue
Block a user