Compare commits
50 Commits
v0.0.22-SN
...
zeichenfen
| Author | SHA1 | Date | |
|---|---|---|---|
| e2e6f8c291 | |||
| 916a581768 | |||
| 5bb2f75193 | |||
| f0e4cd6c80 | |||
| a228b21c84 | |||
| 68c88ec9ca | |||
| 0d1dd771dd | |||
| e995bfc4fe | |||
| 97ff03990a | |||
| bd2364a8df | |||
| 617b915874 | |||
| 7772793e8d | |||
| bd8c0e37a7 | |||
| ecbe2b4f6b | |||
| 20fe700756 | |||
| 0100a3f574 | |||
| aceb79c44f | |||
| a4e29ccdba | |||
| 55014c8eec | |||
| 4f958cd57c | |||
| 04506f6e9c | |||
| 5a27e18634 | |||
| 8b23c658e8 | |||
| 1ca13c977a | |||
| 78c93666d0 | |||
| 917eb805c6 | |||
| fddd8d621b | |||
| 4bf0068051 | |||
| 371a962432 | |||
| 99848e47f8 | |||
| f75aaf4b7e | |||
| e5c6fa634a | |||
| ccc83414c7 | |||
| 16477463d4 | |||
| d3997561fc | |||
| b6b4ffe6a5 | |||
| bf261b5e9b | |||
| b79f26f51e | |||
| 538a8215e6 | |||
| cbda5c3077 | |||
| 2caa528a5e | |||
| bb50abb7bd | |||
| 38d5f22fb6 | |||
| d34c60505e | |||
| 4c8e5c8939 | |||
| 9a9a714050 | |||
| f0b064a3d5 | |||
| c922357ab7 | |||
| 17c31a1a03 | |||
| 6551bb75c9 |
@@ -7,12 +7,15 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- System für EventListener erstellt
|
||||
- System für EventListener.
|
||||
- `AudioListener` und `AnimationListener` als erste Anwendungsfälle.
|
||||
- Pakete für Animationen und Maschinelles-Lernen hinzugefügt
|
||||
- Pakete für Animationen und Maschinelles-Lernen.
|
||||
- Farbverläufe als Füllung.
|
||||
|
||||
### Changed
|
||||
- `update(double)` und `draw()` werden nun in einem eigenen Thread aufgerufen.
|
||||
- Die Standardwerte in `Constants` wurden mit dem Prefix `DEFAULT_` benannt (vorher `STD_`).
|
||||
- Die Standardwerte sind nun nicht mehr `final` und können vom Nutzer manuell gesetzt werden.
|
||||
|
||||
## Version 0.0.22
|
||||
|
||||
@@ -24,7 +27,7 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
### Changed
|
||||
- Neue Package-Struktur:
|
||||
- `schule.ngb.zm.media` für Audio-Klassen (und ggf. zukünftig Video).
|
||||
- `schule.ngb.zm.tasks` für alles Rund um Parallelität.
|
||||
- `schule.ngb.zm.util.tasks` für alles Rund um Parallelität.
|
||||
- `Zeichenthread` und `TaskRunner` setzen die Namen der Threads für besseres Debugging.
|
||||
|
||||
### Removed
|
||||
|
||||
@@ -28,8 +28,13 @@ dependencies {
|
||||
runtimeOnly 'com.googlecode.soundlibs:tritonus-share:0.3.7.4'
|
||||
runtimeOnly 'com.googlecode.soundlibs:mp3spi:1.9.5.4'
|
||||
|
||||
compileOnlyApi 'colt:colt:1.2.0'
|
||||
//api 'colt:colt:1.2.0'
|
||||
//api 'net.sourceforge.parallelcolt:parallelcolt:0.10.1'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.ColorModel;
|
||||
|
||||
/**
|
||||
* Repräsentiert eine Farbe in der Zeichenmaschine.
|
||||
* <p>
|
||||
@@ -9,7 +14,7 @@ package schule.ngb.zm;
|
||||
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
||||
* (deckend).
|
||||
*/
|
||||
public class Color {
|
||||
public class Color implements Paint {
|
||||
|
||||
|
||||
//@formatter:off
|
||||
@@ -146,7 +151,7 @@ public class Color {
|
||||
* @param alpha Transparentwert zwischen 0 und 255.
|
||||
*/
|
||||
public Color( int red, int green, int blue, int alpha ) {
|
||||
rgba = ((alpha&0xFF) << 24) | ((red&0xFF) << 16) | ((green&0xFF) << 8) | ((blue&0xFF) << 0);
|
||||
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | ((blue & 0xFF) << 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -462,6 +467,23 @@ public class Color {
|
||||
return new java.awt.Color(rgba, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaintContext createContext( ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints ) {
|
||||
return getJavaColor().createContext(cm, deviceBounds, userBounds, xform, hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTransparency() {
|
||||
int alpha = getAlpha();
|
||||
if( alpha == 0xff ) {
|
||||
return Transparency.OPAQUE;
|
||||
} else if( alpha == 0 ) {
|
||||
return Transparency.BITMASK;
|
||||
} else {
|
||||
return Transparency.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
||||
@@ -473,7 +495,9 @@ public class Color {
|
||||
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
||||
*/
|
||||
public boolean equals( Object obj ) {
|
||||
if( obj == null ) { return false; }
|
||||
if( obj == null ) {
|
||||
return false;
|
||||
}
|
||||
if( obj instanceof Color ) {
|
||||
return ((Color) obj).getRGBA() == this.rgba;
|
||||
} else if( obj instanceof java.awt.Color ) {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
public class ColorLayer extends Layer {
|
||||
|
||||
private Color background;
|
||||
|
||||
public ColorLayer( Color color ) {
|
||||
this.background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
public ColorLayer( int width, int height, Color color ) {
|
||||
super(width, height);
|
||||
this.background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSize( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
clear();
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return background;
|
||||
}
|
||||
|
||||
public void setColor( Color color ) {
|
||||
background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
public void setColor( int gray ) {
|
||||
setColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setColor( int gray, int alpha ) {
|
||||
setColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setColor( int red, int green, int blue ) {
|
||||
setColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setColor( int red, int green, int blue, int alpha ) {
|
||||
setColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
drawing.setColor(background.getJavaColor());
|
||||
drawing.fillRect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.anim.Easing;
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
import schule.ngb.zm.util.Noise;
|
||||
|
||||
import java.awt.Cursor;
|
||||
@@ -75,42 +75,42 @@ public class Constants {
|
||||
/**
|
||||
* Standardbreite eines Zeichenfensters.
|
||||
*/
|
||||
public static final int STD_WIDTH = 400;
|
||||
public static final int DEFAULT_WIDTH = 400;
|
||||
|
||||
/**
|
||||
* Standardhöhe eines Zeichenfensters.
|
||||
*/
|
||||
public static final int STD_HEIGHT = 400;
|
||||
public static final int DEFAULT_HEIGHT = 400;
|
||||
|
||||
/**
|
||||
* Standardwert für die Frames pro Sekunde einer Zeichenmaschine.
|
||||
*/
|
||||
public static final int STD_FPS = 60;
|
||||
public static final int DEFAULT_FPS = 60;
|
||||
|
||||
/**
|
||||
* Standardfarbe der Füllungen.
|
||||
*/
|
||||
public static final Color STD_FILLCOLOR = Color.WHITE;
|
||||
public static Color DEFAULT_FILLCOLOR = Color.WHITE;
|
||||
|
||||
/**
|
||||
* Standardfarbe der Konturen.
|
||||
*/
|
||||
public static final Color STD_STROKECOLOR = Color.BLACK;
|
||||
public static Color DEFAULT_STROKECOLOR = Color.BLACK;
|
||||
|
||||
/**
|
||||
* Standardwert für die Dicke der Konturen.
|
||||
*/
|
||||
public static final double STD_STROKEWEIGHT = 1.0;
|
||||
public static double DEFAULT_STROKEWEIGHT = 1.0;
|
||||
|
||||
/**
|
||||
* Standardwert für die Schriftgröße.
|
||||
*/
|
||||
public static final int STD_FONTSIZE = 14;
|
||||
public static int DEFAULT_FONTSIZE = 14;
|
||||
|
||||
/**
|
||||
* Standardwert für den Abstand von Formen.
|
||||
*/
|
||||
public static final int STD_BUFFER = 10;
|
||||
public static int DEFAULT_BUFFER = 10;
|
||||
|
||||
public static int DEFAULT_ANIM_RUNTIME = 1000;
|
||||
|
||||
@@ -410,11 +410,12 @@ public class Constants {
|
||||
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
|
||||
* Zeichenmaschine selbst vorgenommen werden.
|
||||
*/
|
||||
// TODO: (ngb) volatile ?
|
||||
|
||||
/**
|
||||
* Aktuell dargestellte Bilder pro Sekunde.
|
||||
*/
|
||||
public static int framesPerSecond = STD_FPS;
|
||||
public static int framesPerSecond = DEFAULT_FPS;
|
||||
|
||||
/**
|
||||
* Anzahl der Ticks (Frames), die das Programm bisher läuft.
|
||||
@@ -1269,7 +1270,8 @@ public class Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Pseudozufallszahl nach einer Gaussverteilung.
|
||||
* Erzeugt eine Pseudozufallszahl zwischen -1 und 1 nach einer
|
||||
* Normalverteilung mit Mittelwert 0 und Standardabweichung 1.
|
||||
*
|
||||
* @return Eine Zufallszahl.
|
||||
* @see Random#nextGaussian()
|
||||
|
||||
@@ -3,14 +3,29 @@ package schule.ngb.zm;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* Zeichenbare Objekte können auf eine Zeichenfläche gezeichnet werden.
|
||||
* In der Regel werden sie einmal pro Frame gezeichnet.
|
||||
* {@code Drawable} Objekte können auf eine Zeichenfläche gezeichnet werden. In
|
||||
* der Regel werden sie einmal pro Frame gezeichnet.
|
||||
*/
|
||||
public interface Drawable {
|
||||
|
||||
/**
|
||||
* Gibt an, ob das Objekt derzeit sichtbar ist (also gezeichnet werden
|
||||
* muss).
|
||||
* <p>
|
||||
* Wie mit dieser Information umgegangen wird, ist nicht weiter festgelegt.
|
||||
* In der Regel sollte eine aufrufende Instanz zunächst prüfen, ob das
|
||||
* Objekt aktiv ist, und nur dann{@link #draw(Graphics2D)} aufrufen. Für
|
||||
* implementierende Klassen ist es aber gegebenenfalls auch sinnvoll, bei
|
||||
* Inaktivität den Aufruf von {@code draw(Graphics2D)} schnell abzubrechen:
|
||||
* <pre><code>
|
||||
* void draw( Graphics2D graphics ) {
|
||||
* if( !isVisible() ) {
|
||||
* return;
|
||||
* }
|
||||
*
|
||||
* // Objekt zeichnen..
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @return {@code true}, wenn das Objekt sichtbar ist.
|
||||
*/
|
||||
@@ -18,7 +33,7 @@ public interface Drawable {
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, um das Objekt auf die Zeichenfläche <var>graphics</var>
|
||||
* zu draw.
|
||||
* zu zeichnen.
|
||||
* <p>
|
||||
* Das Objekt muss dafür Sorge tragen, dass der Zustand der Zeichenfläche
|
||||
* (Transformationsmatrix, Farbe, ...) erhalten bleibt. Das Objekt sollte
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
public class GraphicsLayer extends Layer {
|
||||
|
||||
public Graphics2D getGraphics() {
|
||||
return drawing;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,19 +6,45 @@ import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Basisklasse für Ebenen der {@link Zeichenleinwand}.
|
||||
* <p>
|
||||
* Die {@code Zeichenleinwand} besteht aus einer Reihe von Ebenen, die
|
||||
* übereinandergelegt und von "unten" nach "oben" gezeichnet werden. Die Inhalte
|
||||
* der oberen Ebenen können also Inhalte der darunterliegenden verdecken.
|
||||
*
|
||||
* Ebenen sind ein zentraler Bestandteil bei der Implementierung einer {@link Zeichenmaschine}.
|
||||
* Es werden
|
||||
* Sie erben von {@code Constants}, damit sie beim
|
||||
*/
|
||||
public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
|
||||
/**
|
||||
* Interner Puffer für die Ebene, der einmal pro Frame auf die
|
||||
* Zeichenleinwand übertragen wird.
|
||||
*/
|
||||
protected BufferedImage buffer;
|
||||
|
||||
/**
|
||||
* Der Grafikkontext der Ebene, der zum Zeichnen der Inhalte verwendet
|
||||
* wird.
|
||||
*/
|
||||
protected Graphics2D drawing;
|
||||
|
||||
/**
|
||||
* Ob die Ebene derzeit sichtbar ist.
|
||||
*/
|
||||
protected boolean visible = true;
|
||||
|
||||
/**
|
||||
* Ob die Ebene aktiv ist, also {@link #update(double) Updates} empfangen
|
||||
* soll.
|
||||
*/
|
||||
protected boolean active = true;
|
||||
|
||||
|
||||
public Layer() {
|
||||
this(STD_WIDTH, STD_HEIGHT);
|
||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
public Layer( int width, int height ) {
|
||||
@@ -33,6 +59,12 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
return buffer.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ändert die Größe der Ebene auf die angegebene Größe.
|
||||
*
|
||||
* @param width Die neue Breite.
|
||||
* @param height Die neue Höhe.
|
||||
*/
|
||||
public void setSize( int width, int height ) {
|
||||
if( buffer != null ) {
|
||||
if( buffer.getWidth() != width || buffer.getHeight() != height ) {
|
||||
@@ -44,8 +76,13 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Resourcen der Ebene frei.
|
||||
*/
|
||||
public void dispose() {
|
||||
drawing.dispose();
|
||||
drawing = null;
|
||||
buffer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,9 @@ public final class Options {
|
||||
PAUSED,
|
||||
STOPPED,
|
||||
TERMINATED,
|
||||
IDLE
|
||||
IDLE,
|
||||
DELAYED,
|
||||
DISPATCHING
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.util.LinkedList;
|
||||
import schule.ngb.zm.layers.DrawableLayer;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Spielemaschine extends Zeichenmaschine {
|
||||
|
||||
private LinkedList<Drawable> drawables;
|
||||
|
||||
private LinkedList<Updatable> updatables;
|
||||
|
||||
private GraphicsLayer mainLayer;
|
||||
private GameLayer mainLayer;
|
||||
|
||||
public Spielemaschine( String title ) {
|
||||
this(STD_WIDTH, STD_HEIGHT, title);
|
||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +28,7 @@ public class Spielemaschine extends Zeichenmaschine {
|
||||
drawables = new LinkedList<>();
|
||||
updatables = new LinkedList<>();
|
||||
|
||||
mainLayer = new GraphicsLayer();
|
||||
mainLayer = new GameLayer();
|
||||
canvas.addLayer(mainLayer);
|
||||
}
|
||||
|
||||
@@ -79,9 +85,10 @@ public class Spielemaschine extends Zeichenmaschine {
|
||||
@Override
|
||||
public final void update( double delta ) {
|
||||
synchronized( updatables ) {
|
||||
for( Updatable updatable : updatables ) {
|
||||
if( updatable.isActive() ) {
|
||||
updatable.update(delta);
|
||||
List<Updatable> it = List.copyOf(updatables);
|
||||
for( Updatable u: it ) {
|
||||
if( u.isActive() ) {
|
||||
u.update(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,14 +98,27 @@ public class Spielemaschine extends Zeichenmaschine {
|
||||
|
||||
@Override
|
||||
public final void draw() {
|
||||
mainLayer.clear();
|
||||
synchronized( drawables ) {
|
||||
for( Drawable drawable : drawables ) {
|
||||
if( drawable.isVisible() ) {
|
||||
drawable.draw(mainLayer.getGraphics());
|
||||
|
||||
}
|
||||
|
||||
private class GameLayer extends Layer {
|
||||
|
||||
public Graphics2D getGraphics() {
|
||||
return drawing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
clear();
|
||||
List<Drawable> it = List.copyOf(drawables);
|
||||
for( Drawable d: it ) {
|
||||
if( d.isVisible() ) {
|
||||
d.draw(drawing);
|
||||
}
|
||||
}
|
||||
super.draw(pGraphics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +1,42 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
/**
|
||||
* Aktualisierbare Objekte können in regelmäßigen Intervallen (meist einmal
|
||||
* pro Frame) ihren Zustand update. Diese Änderung kann abhängig vom
|
||||
* {@code Updatable} Objekte können in regelmäßigen Intervallen (meist einmal
|
||||
* pro Frame) ihren Zustand aktualisieren. Diese Änderung kann abhängig vom
|
||||
* Zeitintervall (in Sekunden) zum letzten Aufruf passieren.
|
||||
*/
|
||||
public interface Updatable {
|
||||
|
||||
/**
|
||||
* Gibt an, ob das Objekt gerade auf Aktualisierungen reagiert.
|
||||
* @return {@code true}, wenn das Objekt aktiv ist.
|
||||
* <p>
|
||||
* Wie mit dieser Information umgegangen wird, ist nicht weiter festgelegt.
|
||||
* In der Regel sollte eine aufrufende Instanz zunächst prüfen, ob das
|
||||
* Objekt aktiv ist, und nur dann{@link #update(double)} aufrufen. Für
|
||||
* implementierende Klassen ist es aber gegebenenfalls auch sinnvoll, bei
|
||||
* Inaktivität den Aufruf von {@code update(double)} schnell abzubrechen:
|
||||
* <pre><code>
|
||||
* void update( double delta ) {
|
||||
* if( !isActive() ) {
|
||||
* return;
|
||||
* }
|
||||
*
|
||||
* // Aktualisierung ausführen..
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @return {@code true}, wenn das Objekt aktiv ist, {@code false}
|
||||
* andernfalls.
|
||||
*/
|
||||
public boolean isActive();
|
||||
|
||||
/**
|
||||
* Änderung des Zustandes des Objekts abhängig vom Zeitintervall
|
||||
* <var>delta</var> in Sekunden.
|
||||
* {@code delta} in Sekunden.
|
||||
* <p>
|
||||
* Die kann, muss aber nicht, die Rückgabe von {@link #isActive()}
|
||||
* berücksichtigen.
|
||||
*
|
||||
* @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden).
|
||||
*/
|
||||
public void update( double delta );
|
||||
|
||||
@@ -1,6 +1,239 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
// TODO: Auslagern des Frames in diese Klasse (Trennung Swing-GUI/Canvas, Zeichenmaschine und Drawing-Thread)
|
||||
public class Zeichenfenster {
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Ein Zeichenfenster ist das Programmfenster für die Zeichenmaschine.
|
||||
* <p>
|
||||
* Das Zeichenfenster implementiert hilfreiche Funktionen, um ein
|
||||
* Programmfenster mit einer Zeichenleinwand als zentrales Element zu erstellen.
|
||||
* Ein Zeichenfenster kann auch ohne eine Zeichenmaschine verwendet werden, um
|
||||
* eigene Programmabläufe zu implementieren.
|
||||
*/
|
||||
public class Zeichenfenster extends JFrame {
|
||||
|
||||
public static final void setLookAndFeel() {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch( Exception ex ) {
|
||||
LOG.error(ex, "Couldn't set the look and feel: %s", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static final GraphicsDevice getGraphicsDevice() {
|
||||
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
|
||||
// das Zeichenfenster dort zu zentrieren.
|
||||
// TODO: (ngb) Wenn wir in BlueJ sind, sollte das Fenster neben dem Editor öffnen.
|
||||
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
|
||||
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice[] devices = environment.getScreenDevices();
|
||||
GraphicsDevice displayDevice = null;
|
||||
for( GraphicsDevice gd : devices ) {
|
||||
if( gd.getDefaultConfiguration().getBounds().contains(mouseLoc) ) {
|
||||
displayDevice = gd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Keinen passenden Bildschirm gefunden. Wir nutzen den Standard.
|
||||
if( displayDevice == null ) {
|
||||
displayDevice = environment.getDefaultScreenDevice();
|
||||
}
|
||||
return displayDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich dem
|
||||
* Aktuellen sein, wenn das Fenster verschoben wurde).
|
||||
*/
|
||||
private GraphicsDevice displayDevice;
|
||||
|
||||
/**
|
||||
* Bevorzugte Abmessungen der Zeichenleinwand. Für das Zeichenfenster hat
|
||||
* es Priorität die Leinwand auf dieser Größe zu halten. Gegebenenfalls unter
|
||||
* Missachtung anderer Größenvorgaben. Allerdings kann das Fenster keine
|
||||
* Garantie für die Größe der Leinwand übernehmen.
|
||||
*/
|
||||
private int canvasPreferredWidth, canvasPreferredHeight;
|
||||
|
||||
/**
|
||||
* Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)} in
|
||||
* den Vollbildmodus versetzt wurde.
|
||||
*/
|
||||
private boolean fullscreen = false;
|
||||
|
||||
/**
|
||||
* {@code KeyListener}, um den Vollbild-Modus mit der Escape-Taste zu
|
||||
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
|
||||
* hinzugefügt und entfernt.
|
||||
*/
|
||||
private KeyListener fullscreenExitListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed( KeyEvent e ) {
|
||||
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
|
||||
// canvas.removeKeyListener(this);
|
||||
setFullscreen(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private Zeichenleinwand canvas;
|
||||
|
||||
public Zeichenfenster( int width, int height, String title ) {
|
||||
this(new Zeichenleinwand(width, height), title, getGraphicsDevice());
|
||||
}
|
||||
|
||||
public Zeichenfenster( int width, int height, String title, GraphicsDevice displayDevice ) {
|
||||
this(new Zeichenleinwand(width, height), title, displayDevice);
|
||||
}
|
||||
|
||||
public Zeichenfenster( Zeichenleinwand canvas, String title ) {
|
||||
this(canvas, title, getGraphicsDevice());
|
||||
}
|
||||
|
||||
public Zeichenfenster( Zeichenleinwand canvas, String title, GraphicsDevice displayDevice ) {
|
||||
super(Validator.requireNotNull(displayDevice).getDefaultConfiguration());
|
||||
this.displayDevice = displayDevice;
|
||||
|
||||
Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got <null>.");
|
||||
|
||||
this.canvasPreferredWidth = canvas.getWidth();
|
||||
this.canvasPreferredHeight = canvas.getHeight();
|
||||
//this.add(canvas, BorderLayout.CENTER);
|
||||
this.add(canvas);
|
||||
this.canvas = canvas;
|
||||
|
||||
// Konfiguration des Frames
|
||||
this.setTitle(title == null ? "Zeichenfenster " + Constants.APP_VERSION: title);
|
||||
// Kann vom Aufrufenden überschrieben werden
|
||||
this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
// Das Icon des Fensters ändern
|
||||
try {
|
||||
if( Zeichenmaschine.MACOS ) {
|
||||
URL iconUrl = Zeichenmaschine.class.getResource("icon_512.png");
|
||||
Image icon = ImageIO.read(iconUrl);
|
||||
// Dock Icon in macOS setzen
|
||||
Taskbar taskbar = Taskbar.getTaskbar();
|
||||
taskbar.setIconImage(icon);
|
||||
} else {
|
||||
ArrayList<Image> icons = new ArrayList<>(4);
|
||||
for( int size = 32; size <= 512; size *= size ) {
|
||||
icons.add(ImageIO.read(new File("icon_" + size + ".png")));
|
||||
}
|
||||
|
||||
this.setIconImages(icons);
|
||||
}
|
||||
} catch( IllegalArgumentException | IOException e ) {
|
||||
LOG.warn("Could not load image icons: %s", e.getMessage());
|
||||
} catch( SecurityException | UnsupportedOperationException macex ) {
|
||||
// Dock Icon in macOS konnte nicht gesetzt werden :(
|
||||
LOG.warn("Could not set dock icon: %s", macex.getMessage());
|
||||
}
|
||||
// Fenster zusammenbauen, auf dem Bildschirm positionieren ...
|
||||
this.pack();
|
||||
this.setResizable(false);
|
||||
this.setLocationByPlatform(true);
|
||||
// this.centerFrame();
|
||||
}
|
||||
|
||||
public GraphicsDevice getDisplayDevice() {
|
||||
return displayDevice;
|
||||
}
|
||||
|
||||
public Rectangle getScreenBounds() {
|
||||
return displayDevice.getDefaultConfiguration().getBounds();
|
||||
}
|
||||
|
||||
public Zeichenleinwand getCanvas() {
|
||||
return canvas;
|
||||
}
|
||||
|
||||
public Rectangle getCanvasBounds() {
|
||||
return canvas.getBounds();
|
||||
}
|
||||
/**
|
||||
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
|
||||
*/
|
||||
public final void centerFrame() {
|
||||
java.awt.Rectangle screenBounds = getScreenBounds();
|
||||
java.awt.Rectangle frameBounds = getBounds();
|
||||
this.setLocation(
|
||||
(int) (screenBounds.x + (screenBounds.width - frameBounds.width) / 2.0),
|
||||
(int) (screenBounds.y + (screenBounds.height - frameBounds.height) / 2.0)
|
||||
);
|
||||
}
|
||||
|
||||
public void setCanvasSize( int newWidth, int newHeight ) {
|
||||
// TODO: (ngb) Put constains on max/min frame/canvas size
|
||||
if( fullscreen ) {
|
||||
canvasPreferredWidth = newWidth;
|
||||
canvasPreferredHeight = newHeight;
|
||||
setFullscreen(false);
|
||||
} else {
|
||||
canvas.setSize(newWidth, newHeight);
|
||||
canvasPreferredWidth = canvas.getWidth();
|
||||
canvasPreferredHeight = canvas.getHeight();
|
||||
this.pack();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktiviert oder deaktiviert den Vollbildmodus für die Zeichenmaschine.
|
||||
* <p>
|
||||
* Der Vollbildmodus wird abhängig von {@code pEnable} entweder aktiviert
|
||||
* oder deaktiviert. Wird die Zeichenmaschine in den Vollbildmodus versetzt,
|
||||
* dann wird automatisch ein {@link KeyListener} aktiviert, der bei
|
||||
* Betätigung der ESCAPE-Taste den Vollbildmodus verlässt. Wird der
|
||||
* Vollbildmodus verlassen, wird die zuletzt gesetzte Fenstergröße
|
||||
* wiederhergestellt.
|
||||
*
|
||||
* @param pEnable Wenn {@code true}, wird der Vollbildmodus aktiviert,
|
||||
* ansonsten deaktiviert.
|
||||
*/
|
||||
public final void setFullscreen( boolean pEnable ) {
|
||||
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
||||
if( displayDevice.isFullScreenSupported() ) {
|
||||
if( pEnable && !fullscreen ) {
|
||||
// frame.setUndecorated(true);
|
||||
this.setResizable(false); // Should be set anyway
|
||||
displayDevice.setFullScreenWindow(this);
|
||||
|
||||
java.awt.Rectangle bounds = getScreenBounds();
|
||||
// TODO: (ngb) We need to switch layouts to allow the LayoutManger to maximize the canvas
|
||||
canvas.setSize(bounds.width, bounds.height);
|
||||
// Register ESC to exit fullscreen
|
||||
canvas.addKeyListener(fullscreenExitListener);
|
||||
|
||||
fullscreen = true;
|
||||
} else if( !pEnable && fullscreen ) {
|
||||
fullscreen = false;
|
||||
|
||||
canvas.removeKeyListener(fullscreenExitListener);
|
||||
displayDevice.setFullScreenWindow(null);
|
||||
canvas.setSize(canvasPreferredWidth, canvasPreferredHeight);
|
||||
this.pack();
|
||||
// frame.setUndecorated(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFullscreen() {
|
||||
Window win = displayDevice.getFullScreenWindow();
|
||||
return fullscreen && win.equals(this);
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(Zeichenfenster.class);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Toolkit;
|
||||
import schule.ngb.zm.layers.ColorLayer;
|
||||
import schule.ngb.zm.layers.ShapesLayer;
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferStrategy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
|
||||
@@ -33,8 +37,8 @@ public class Zeichenleinwand extends Canvas {
|
||||
*/
|
||||
public Zeichenleinwand( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
this.setPreferredSize(this.getSize());
|
||||
this.setMinimumSize(this.getSize());
|
||||
this.setPreferredSize(getSize());
|
||||
this.setMinimumSize(getSize());
|
||||
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
||||
|
||||
// Liste der Ebenen initialisieren und die Standardebenen einfügen
|
||||
@@ -56,8 +60,8 @@ public class Zeichenleinwand extends Canvas {
|
||||
@Override
|
||||
public void setSize( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
this.setPreferredSize(this.getSize());
|
||||
this.setMinimumSize(this.getSize());
|
||||
this.setPreferredSize(getSize());
|
||||
this.setMinimumSize(getSize());
|
||||
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
@@ -195,7 +199,8 @@ public class Zeichenleinwand extends Canvas {
|
||||
|
||||
public void updateLayers( double delta ) {
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
List<Layer> it = List.copyOf(layers);
|
||||
for( Layer layer : it ) {
|
||||
layer.update(delta);
|
||||
}
|
||||
}
|
||||
@@ -232,7 +237,8 @@ public class Zeichenleinwand extends Canvas {
|
||||
public void draw( Graphics graphics ) {
|
||||
Graphics2D g2d = (Graphics2D) graphics.create();
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
List<Layer> it = List.copyOf(layers);
|
||||
for( Layer layer : it ) {
|
||||
layer.draw(g2d);
|
||||
}
|
||||
}
|
||||
@@ -256,7 +262,8 @@ public class Zeichenleinwand extends Canvas {
|
||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
||||
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
List<Layer> it = List.copyOf(layers);
|
||||
for( Layer layer : it ) {
|
||||
layer.draw(g2d);
|
||||
}
|
||||
}
|
||||
@@ -277,4 +284,6 @@ public class Zeichenleinwand extends Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(Zeichenleinwand.class);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.anim.Animation;
|
||||
import schule.ngb.zm.shapes.ShapesLayer;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.layers.ColorLayer;
|
||||
import schule.ngb.zm.layers.DrawingLayer;
|
||||
import schule.ngb.zm.layers.ImageLayer;
|
||||
import schule.ngb.zm.layers.ShapesLayer;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.MouseInputListener;
|
||||
import java.awt.*;
|
||||
@@ -15,7 +17,6 @@ import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Hauptklasse der Zeichenmaschine.
|
||||
@@ -24,7 +25,7 @@ import java.util.logging.Level;
|
||||
* Die Klasse übernimmt die Initialisierung eines Programmfensters und der
|
||||
* nötigen Komponenten.
|
||||
*/
|
||||
// TODO: Refactorings (besonders in Bezug auf Nebenläufigkeit)
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Zeichenmaschine extends Constants {
|
||||
|
||||
/**
|
||||
@@ -110,47 +111,7 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Das Zeichenfenster der Zeichenmaschine
|
||||
*/
|
||||
private JFrame frame;
|
||||
|
||||
/**
|
||||
* Die Graphics-Umgebung für das aktuelle Fenster.
|
||||
*/
|
||||
private GraphicsEnvironment environment;
|
||||
|
||||
/**
|
||||
* Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich
|
||||
* dem Aktuellen sein, wenn das Fenster verschoben wurde).
|
||||
*/
|
||||
private GraphicsDevice displayDevice;
|
||||
|
||||
/**
|
||||
* Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)}
|
||||
* in den Vollbildmodus versetzt wurde.
|
||||
*/
|
||||
private boolean fullscreen = false;
|
||||
|
||||
/**
|
||||
* Höhe und Breite der Zeichenmaschine, bevor sie mit
|
||||
* {@link #setFullscreen(boolean)} in den Vollbild-Modus versetzt wurde.
|
||||
* Wird verwendet, um die Fenstergröße wiederherzustellen, sobald der
|
||||
* Vollbild-Modus verlassen wird.
|
||||
*/
|
||||
private int initialWidth, initialHeight;
|
||||
|
||||
/**
|
||||
* {@code KeyListener}, um den Vollbild-Modus mit der Escape-Taste zu
|
||||
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
|
||||
* hinzugefügt und entfernt.
|
||||
*/
|
||||
private KeyListener fullscreenExitListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed( KeyEvent e ) {
|
||||
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
|
||||
// canvas.removeKeyListener(this);
|
||||
setFullscreen(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
private Zeichenfenster frame;
|
||||
|
||||
// Aktueller Zustand der Zeichenmaschine.
|
||||
|
||||
@@ -169,6 +130,8 @@ public class Zeichenmaschine extends Constants {
|
||||
*/
|
||||
private boolean running = false;
|
||||
|
||||
private boolean terminateImediately = false;
|
||||
|
||||
/**
|
||||
* Ob die ZM nach dem nächsten Frame pausiert werden soll.
|
||||
*/
|
||||
@@ -204,7 +167,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
|
||||
* oder das Zeichenfenster weiter geöffnet bleibt.
|
||||
*/
|
||||
private boolean quitAfterTeardown = false;
|
||||
private boolean quitAfterShutdown = false;
|
||||
|
||||
// Mauszeiger
|
||||
/**
|
||||
@@ -255,7 +218,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* @param title Der Titel, der oben im Fenster steht.
|
||||
*/
|
||||
public Zeichenmaschine( String title ) {
|
||||
this(STD_WIDTH, STD_HEIGHT, title, true);
|
||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,7 +233,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* Aufruf von {@code draw()}.
|
||||
*/
|
||||
public Zeichenmaschine( String title, boolean run_once ) {
|
||||
this(STD_WIDTH, STD_HEIGHT, title, run_once);
|
||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, run_once);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,75 +277,32 @@ public class Zeichenmaschine extends Constants {
|
||||
public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
|
||||
LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
|
||||
|
||||
// Setzen des "Look&Feel"
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch( Exception ex ) {
|
||||
LOG.log(Level.SEVERE, "Error setting the look and feel: " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
|
||||
// das Zeichenfenster dort zu zentrieren.
|
||||
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
|
||||
environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice[] devices = environment.getScreenDevices();
|
||||
for( GraphicsDevice gd : devices ) {
|
||||
if( gd.getDefaultConfiguration().getBounds().contains(mouseLoc) ) {
|
||||
displayDevice = gd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Keinen passenden Bildschirm gefunden. Wir nutzen den Standard.
|
||||
if( displayDevice == null ) {
|
||||
displayDevice = environment.getDefaultScreenDevice();
|
||||
}
|
||||
|
||||
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
|
||||
this.canvasWidth = width;
|
||||
this.canvasHeight = height;
|
||||
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds();
|
||||
this.screenWidth = (int) displayBounds.getWidth();
|
||||
this.screenHeight = (int) displayBounds.getHeight();
|
||||
|
||||
// Erstellen des Zeichenfensters
|
||||
frame = new JFrame(displayDevice.getDefaultConfiguration());
|
||||
frame.setTitle(title);
|
||||
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
// Das Icon des Fensters ändern
|
||||
|
||||
try {
|
||||
// TODO: Add image sizes
|
||||
ImageIcon icon = new ImageIcon(ImageIO.read(new File("res/icon_64.png")));
|
||||
|
||||
if( MACOS ) {
|
||||
// Dock Icon in macOS setzen
|
||||
Taskbar taskbar = Taskbar.getTaskbar();
|
||||
taskbar.setIconImage(icon.getImage());
|
||||
} else {
|
||||
// Kleines Icon des Frames setzen
|
||||
frame.setIconImage(icon.getImage());
|
||||
}
|
||||
} catch( IOException e ) {
|
||||
}
|
||||
|
||||
|
||||
// Erstellen der Leinwand
|
||||
canvas = new Zeichenleinwand(width, height);
|
||||
frame.add(canvas);
|
||||
|
||||
// Die drei Standardebenen merken, für den einfachen Zugriff aus unterklassen.
|
||||
// Erstellen des Zeichenfensters
|
||||
frame = createFrame(canvas, title);
|
||||
|
||||
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
|
||||
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
|
||||
this.canvasWidth = canvasBounds.width;
|
||||
this.canvasHeight = canvasBounds.height;
|
||||
java.awt.Rectangle displayBounds = frame.getScreenBounds();
|
||||
this.screenWidth = displayBounds.width;
|
||||
this.screenHeight = displayBounds.height;
|
||||
|
||||
// Die drei Standardebenen merken, für den einfachen Zugriff aus Unterklassen.
|
||||
background = getBackgroundLayer();
|
||||
drawing = getDrawingLayer();
|
||||
shapes = getShapesLayer();
|
||||
|
||||
// FPS setzen
|
||||
framesPerSecondInternal = STD_FPS;
|
||||
framesPerSecondInternal = DEFAULT_FPS;
|
||||
this.run_once = run_once;
|
||||
|
||||
// Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen
|
||||
// verändert werden soll.
|
||||
// TODO: When to call settings?
|
||||
// TODO: (ngb) Wann sollte settings() aufgerufen werden?
|
||||
settings();
|
||||
|
||||
// Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören.
|
||||
@@ -393,30 +313,20 @@ public class Zeichenmaschine extends Constants {
|
||||
canvas.addKeyListener(inputListener);
|
||||
|
||||
// Programm beenden, wenn Fenster geschlossen wird
|
||||
// TODO: (ngb) Der Listener hat zu viel FUnktionalität -> nach quit() / exit() auslagern
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing( WindowEvent e ) {
|
||||
if( running ) {
|
||||
running = false;
|
||||
teardown();
|
||||
cleanup();
|
||||
}
|
||||
// Give the app a minimum amount of time to shut down
|
||||
// then kill it.
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch( InterruptedException ex ) {
|
||||
} finally {
|
||||
if( isTerminated() ) {
|
||||
quit(true);
|
||||
} else {
|
||||
exitNow();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fenster zusammenbauen, auf dem Bildschirm zentrieren ...
|
||||
frame.pack();
|
||||
frame.setResizable(false);
|
||||
centerFrame();
|
||||
// ... und anzeigen!
|
||||
// Fenster anzeigen
|
||||
frame.centerFrame();
|
||||
frame.setVisible(true);
|
||||
|
||||
// Nach dem Anzeigen kann die Pufferstrategie erstellt werden.
|
||||
@@ -440,25 +350,30 @@ public class Zeichenmaschine extends Constants {
|
||||
*
|
||||
* @param title
|
||||
*/
|
||||
// TODO: Implement in conjunction with Zeichenfenster
|
||||
private final Zeichenfenster createFrame( String title ) {
|
||||
return null;
|
||||
private final Zeichenfenster createFrame( Zeichenleinwand c, String title ) {
|
||||
while( frame == null ) {
|
||||
try {
|
||||
TaskRunner.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Zeichenfenster.setLookAndFeel();
|
||||
frame = new Zeichenfenster(canvas, title);
|
||||
}
|
||||
}).get();
|
||||
} catch( InterruptedException e ) {
|
||||
} catch( ExecutionException e ) {
|
||||
LOG.error(e, "Error initializing application frame: %s", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
|
||||
*/
|
||||
public final void centerFrame() {
|
||||
// TODO: Center on current display (not main display by default)
|
||||
// TODO: Position at current BlueJ windows if IN_BLUEJ
|
||||
//frame.setLocationRelativeTo(null);
|
||||
//frame.setLocationRelativeTo(displayDevice.getFullScreenWindow());
|
||||
|
||||
java.awt.Rectangle bounds = displayDevice.getDefaultConfiguration().getBounds();
|
||||
frame.setLocation(
|
||||
(int) (bounds.x + (screenWidth - frame.getWidth()) / 2.0),
|
||||
(int) (bounds.y + (screenHeight - frame.getHeight()) / 2.0)
|
||||
);
|
||||
frame.centerFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -475,37 +390,25 @@ public class Zeichenmaschine extends Constants {
|
||||
* ansonsten deaktiviert.
|
||||
*/
|
||||
public final void setFullscreen( boolean pEnable ) {
|
||||
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
||||
if( displayDevice.isFullScreenSupported() ) {
|
||||
if( pEnable && !fullscreen ) {
|
||||
// frame.setUndecorated(true);
|
||||
frame.setResizable(false); // Should be set anyway
|
||||
displayDevice.setFullScreenWindow(frame);
|
||||
// Update width / height
|
||||
initialWidth = canvasWidth;
|
||||
initialHeight = canvasHeight;
|
||||
changeSize(screenWidth, screenHeight);
|
||||
// Register ESC as exit fullscreen
|
||||
canvas.addKeyListener(fullscreenExitListener);
|
||||
|
||||
fullscreen = true;
|
||||
if( pEnable && !frame.isFullscreen() ) {
|
||||
frame.setFullscreen(true);
|
||||
if( frame.isFullscreen() )
|
||||
fullscreenChanged();
|
||||
} else if( !pEnable && fullscreen ) {
|
||||
fullscreen = false;
|
||||
|
||||
canvas.removeKeyListener(fullscreenExitListener);
|
||||
displayDevice.setFullScreenWindow(null);
|
||||
changeSize(initialWidth, initialHeight);
|
||||
frame.pack();
|
||||
// frame.setUndecorated(false);
|
||||
} else if( !pEnable && frame.isFullscreen() ) {
|
||||
frame.setFullscreen(false);
|
||||
if( !frame.isFullscreen() )
|
||||
fullscreenChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob das Zeichenfenster im Vollbild läuft.
|
||||
*
|
||||
* @return {@code true}, wenn sich das Fesnter im Vollbildmodus befindet,
|
||||
* {@code false} sonst.
|
||||
*/
|
||||
public boolean isFullscreen() {
|
||||
Window win = displayDevice.getFullScreenWindow();
|
||||
return fullscreen && win != null;
|
||||
return frame.isFullscreen();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -583,14 +486,26 @@ public class Zeichenmaschine extends Constants {
|
||||
return state == Options.AppState.PAUSED;
|
||||
}
|
||||
|
||||
public final boolean isTerminated() {
|
||||
return state == Options.AppState.TERMINATED;
|
||||
}
|
||||
|
||||
public final boolean isTerminating() {
|
||||
return state == Options.AppState.STOPPED || state == Options.AppState.TERMINATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stoppt die Zeichenmaschine.
|
||||
* <p>
|
||||
* Nachdem der aktuelle Frame gezeichnet wurde wechselt die Zeichenmaschine
|
||||
* in den Zustand {@link Options.AppState#STOPPED} und ruft
|
||||
* {@link #teardown()} auf. Nachdem {@code teardown()} ausgeführt wurde
|
||||
* {@link #shutdown()} auf. Nachdem {@code teardown()} ausgeführt wurde
|
||||
* wechselt der Zustand zu {@link Options.AppState#TERMINATED}. Das
|
||||
* Zeichenfenster bleibt weiter geöffnet.
|
||||
* <p>
|
||||
* Die Zeichenmaschine reagiert in diesem Zustand weiter auf Eingaben,
|
||||
* allerdings muss die Zeichnung nun manuell mit {@link #redraw()}
|
||||
* aktualisiert werden.
|
||||
*/
|
||||
public final void stop() {
|
||||
running = false;
|
||||
@@ -601,7 +516,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* <p>
|
||||
* Wird nach dem {@link #stop() Stopp} der Zeichenmaschine aufgerufen und
|
||||
* verbleibende Threads, Tasks, etc. zu stoppen und aufzuräumen. Die
|
||||
* Äquivalente Methode für Unterklassen ist {@link #teardown()}, die direkt
|
||||
* Äquivalente Methode für Unterklassen ist {@link #shutdown()}, die direkt
|
||||
* vor {@code cleanup()} aufgerufen wird.
|
||||
*/
|
||||
private void cleanup() {
|
||||
@@ -624,7 +539,18 @@ public class Zeichenmaschine extends Constants {
|
||||
public final void exit() {
|
||||
if( running ) {
|
||||
running = false;
|
||||
this.quitAfterTeardown = true;
|
||||
quitAfterShutdown = true;
|
||||
} else {
|
||||
quit(true);
|
||||
}
|
||||
}
|
||||
|
||||
public final void exitNow() {
|
||||
if( running ) {
|
||||
running = false;
|
||||
terminateImediately = true;
|
||||
quitAfterShutdown = true;
|
||||
mainThread.interrupt();
|
||||
} else {
|
||||
quit(true);
|
||||
}
|
||||
@@ -632,6 +558,10 @@ public class Zeichenmaschine extends Constants {
|
||||
|
||||
/**
|
||||
* Beendet das Programm vollständig.
|
||||
* <p>
|
||||
* Enspricht dem Aufruf {@code quit(true)}.
|
||||
*
|
||||
* @see #quit(boolean)
|
||||
*/
|
||||
public final void quit() {
|
||||
//quit(!IN_BLUEJ);
|
||||
@@ -641,6 +571,14 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Beendet das Programm. Falls {@code exit} gleich {@code true} ist, wird
|
||||
* die komplette VM beendet.
|
||||
* <p>
|
||||
* Die Methode sorgt nicht für ein ordnungsgemäßes herunterfahren und
|
||||
* freigeben aller Ressourcen, da die Zeichenmaschine gegebenenfalls
|
||||
* geöffnet bleiben und weitere Aufgaben erfüllen soll. Aufrufende Methoden
|
||||
* sollten dies berücksichtigen.
|
||||
* <p>
|
||||
* Soll das Programm vollständig beendet werden, ist es ratsamer
|
||||
* {@link #exit()} zu verwenden.
|
||||
*
|
||||
* @param exit Ob die VM beendet werden soll.
|
||||
* @see System#exit(int)
|
||||
@@ -655,28 +593,6 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interne Methode um die Größe der Zeichenfläche zu ändern.
|
||||
* <p>
|
||||
* Die Methode berücksichtigt nicht den Zustand des Fensters (z.B.
|
||||
* Vollbildmodus) und geht davon aus, dass die aufrufende Methode
|
||||
* sichergestellt hat, dass eine Änderung der Größe der Zeichenfläche
|
||||
* zulässig und sinnvoll ist.
|
||||
*
|
||||
* @param newWidth Neue Breite der Zeichenleinwand.
|
||||
* @param newHeight Neue Höhe der Zeichenleinwand.
|
||||
* @see #setSize(int, int)
|
||||
* @see #setFullscreen(boolean)
|
||||
*/
|
||||
private void changeSize( int newWidth, int newHeight ) {
|
||||
canvasWidth = Math.min(Math.max(newWidth, 100), screenWidth);
|
||||
canvasHeight = Math.min(Math.max(newHeight, 100), screenHeight);
|
||||
|
||||
if( canvas != null ) {
|
||||
canvas.setSize(canvasWidth, canvasHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ändert die Größe der {@link Zeichenleinwand}.
|
||||
*
|
||||
@@ -685,16 +601,11 @@ public class Zeichenmaschine extends Constants {
|
||||
* @see Zeichenleinwand#setSize(int, int)
|
||||
*/
|
||||
public final void setSize( int width, int height ) {
|
||||
if( fullscreen ) {
|
||||
initialWidth = Math.min(Math.max(width, 100), screenWidth);
|
||||
initialHeight = Math.min(Math.max(height, 100), screenHeight);
|
||||
setFullscreen(false);
|
||||
} else {
|
||||
changeSize(width, height);
|
||||
frame.setSize(width, height);
|
||||
|
||||
//frame.setSize(width, height);
|
||||
frame.pack();
|
||||
}
|
||||
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
|
||||
canvasWidth = (int) canvasBounds.getWidth();
|
||||
canvasHeight = (int) canvasBounds.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -952,13 +863,15 @@ public class Zeichenmaschine extends Constants {
|
||||
return;
|
||||
}
|
||||
|
||||
if( state != Options.AppState.RUNNING ) {
|
||||
if( state == Options.AppState.INITIALIZING ||
|
||||
state == Options.AppState.INITIALIZED ) {
|
||||
LOG.warn("Don't use delay(int) from within settings() or setup().");
|
||||
return;
|
||||
}
|
||||
|
||||
long timer = 0L;
|
||||
if( updateState == Options.AppState.DRAWING ) {
|
||||
if( /*updateState == Options.AppState.DRAWING*/
|
||||
isTerminating() ) {
|
||||
// Falls gerade draw() ausgeführt wird, zeigen wir den aktuellen
|
||||
// Stand der Zeichnung auf der Leinwand an. Die Zeit für das
|
||||
// Rendern wird gemessen und von der Wartezeit abgezogen.
|
||||
@@ -967,6 +880,7 @@ public class Zeichenmaschine extends Constants {
|
||||
timer = System.nanoTime() - timer;
|
||||
}
|
||||
|
||||
Options.AppState oldState = updateState;
|
||||
try {
|
||||
int sub = (int) Math.ceil(timer / 1000000.0);
|
||||
|
||||
@@ -974,9 +888,12 @@ public class Zeichenmaschine extends Constants {
|
||||
return;
|
||||
}
|
||||
|
||||
updateState = Options.AppState.DELAYED;
|
||||
Thread.sleep(ms - sub, (int) (timer % 1000000L));
|
||||
} catch( InterruptedException ex ) {
|
||||
} catch( InterruptedException ignored ) {
|
||||
// Nothing
|
||||
} finally {
|
||||
updateState = oldState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1140,7 +1057,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* Spiels oder der Abspann einer Animation angezeigt werden, oder mit
|
||||
* {@link #saveImage()} die erstellte Zeichnung abgespeichert werden.
|
||||
*/
|
||||
public void teardown() {
|
||||
public void shutdown() {
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@@ -1175,39 +1092,41 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
|
||||
/*
|
||||
* Mouse handling
|
||||
* Input handling
|
||||
*/
|
||||
private void enqueueEvent( InputEvent evt ) {
|
||||
eventQueue.add(evt);
|
||||
if( updateState != Options.AppState.DELAYED ) {
|
||||
eventQueue.add(evt);
|
||||
}
|
||||
|
||||
if( isPaused() ) {
|
||||
if( isPaused() || isTerminated() ) {
|
||||
dispatchEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchEvents() {
|
||||
synchronized( eventQueue ) {
|
||||
while( !eventQueue.isEmpty() ) {
|
||||
InputEvent evt = eventQueue.poll();
|
||||
//synchronized( eventQueue ) {
|
||||
while( !eventQueue.isEmpty() ) {
|
||||
InputEvent evt = eventQueue.poll();
|
||||
|
||||
switch( evt.getID() ) {
|
||||
case KeyEvent.KEY_TYPED:
|
||||
case KeyEvent.KEY_PRESSED:
|
||||
case KeyEvent.KEY_RELEASED:
|
||||
handleKeyEvent((KeyEvent) evt);
|
||||
break;
|
||||
switch( evt.getID() ) {
|
||||
case KeyEvent.KEY_TYPED:
|
||||
case KeyEvent.KEY_PRESSED:
|
||||
case KeyEvent.KEY_RELEASED:
|
||||
handleKeyEvent((KeyEvent) evt);
|
||||
break;
|
||||
|
||||
case MouseEvent.MOUSE_CLICKED:
|
||||
case MouseEvent.MOUSE_PRESSED:
|
||||
case MouseEvent.MOUSE_RELEASED:
|
||||
case MouseEvent.MOUSE_MOVED:
|
||||
case MouseEvent.MOUSE_DRAGGED:
|
||||
case MouseEvent.MOUSE_WHEEL:
|
||||
handleMouseEvent((MouseEvent) evt);
|
||||
break;
|
||||
}
|
||||
case MouseEvent.MOUSE_CLICKED:
|
||||
case MouseEvent.MOUSE_PRESSED:
|
||||
case MouseEvent.MOUSE_RELEASED:
|
||||
case MouseEvent.MOUSE_MOVED:
|
||||
case MouseEvent.MOUSE_DRAGGED:
|
||||
case MouseEvent.MOUSE_WHEEL:
|
||||
handleMouseEvent((MouseEvent) evt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
private void handleKeyEvent( KeyEvent evt ) {
|
||||
@@ -1245,7 +1164,7 @@ public class Zeichenmaschine extends Constants {
|
||||
case MouseEvent.MOUSE_RELEASED:
|
||||
mousePressed = false;
|
||||
mouseButton = NOMOUSE;
|
||||
mousePressed(evt);
|
||||
mouseReleased(evt);
|
||||
break;
|
||||
case MouseEvent.MOUSE_DRAGGED:
|
||||
//saveMousePosition(evt);
|
||||
@@ -1376,9 +1295,9 @@ public class Zeichenmaschine extends Constants {
|
||||
* // Next frame has started
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* Die {@link schule.ngb.zm.tasks.FrameSynchronizedTask} implementiert eine
|
||||
* {@link schule.ngb.zm.tasks.Task}, die sich automatisch auf diese Wiese
|
||||
* mit dem Zeichenthread synchronisiert.
|
||||
* Die {@link schule.ngb.zm.util.tasks.FrameSynchronizedTask} implementiert
|
||||
* eine {@link schule.ngb.zm.util.tasks.Task}, die sich automatisch auf
|
||||
* diese Wiese mit dem Zeichenthread synchronisiert.
|
||||
*/
|
||||
public static final Object globalSyncLock = new Object[0];
|
||||
|
||||
@@ -1393,48 +1312,51 @@ public class Zeichenmaschine extends Constants {
|
||||
public final void run() {
|
||||
// Wait for full initialization before start
|
||||
while( state != Options.AppState.INITIALIZED ) {
|
||||
delay(1);
|
||||
Thread.yield();
|
||||
}
|
||||
|
||||
// ThreadExecutor for the update/draw Thread
|
||||
final UpdateThreadExecutor updateThreadExecutor = new UpdateThreadExecutor();
|
||||
|
||||
// start of thread in ms
|
||||
// Start des Thread in ms
|
||||
final long start = System.currentTimeMillis();
|
||||
// current time in ns
|
||||
long beforeTime = System.nanoTime();
|
||||
// Aktuelle Zeit in ns
|
||||
long beforeTime;
|
||||
long updateBeforeTime = System.nanoTime();
|
||||
// store for deltas
|
||||
// Speicher für Änderung
|
||||
long overslept = 0L;
|
||||
// internal counters for tick and runtime
|
||||
// Interne Zähler für tick und runtime
|
||||
int _tick = 0;
|
||||
long _runtime = 0;
|
||||
// public counters for access by subclasses
|
||||
// Öffentliche Zähler für Unterklassen
|
||||
tick = 0;
|
||||
runtime = 0;
|
||||
|
||||
// call setup of subclass and wait
|
||||
// setup() der Unterklasse aufrufen
|
||||
setup();
|
||||
|
||||
// Alles startklar ...
|
||||
state = Options.AppState.RUNNING;
|
||||
while( running ) {
|
||||
// delta in seconds
|
||||
// Aktuelle Zeit in ns merken
|
||||
beforeTime = System.nanoTime();
|
||||
|
||||
// Mausposition einmal pro Frame merken
|
||||
saveMousePosition(mouseEvent);
|
||||
|
||||
if( state != Options.AppState.PAUSED ) {
|
||||
//handleUpdate(delta);
|
||||
//handleDraw();
|
||||
|
||||
// Update and draw are executed in a new thread,
|
||||
// but we wait for them to finish unless the user
|
||||
// did call any blocking method, that would also block
|
||||
// rendering of new frames.
|
||||
// update() und draw() der Unterklasse werden in einem
|
||||
// eigenen Thread ausgeführt, aber der Zeichenthread
|
||||
// wartet, bis der Thread fertig ist. Außer die Unterklasse
|
||||
// ruft delay() auf und lässt den Thread eine länger Zeit
|
||||
// schlafen. Dann wird der nächst Frame vorzeitig gerendert,
|
||||
// bis der update-Thread wieder bereit ist. Dadurch können
|
||||
// nebenläufige Aufgaben (z.B. Animationen) weiterlaufen.
|
||||
if( !updateThreadExecutor.isRunning() ) {
|
||||
delta = (System.nanoTime() - updateBeforeTime) / 1000000000.0;
|
||||
updateBeforeTime = System.nanoTime();
|
||||
|
||||
// uddate()/draw() ausführen
|
||||
updateThreadExecutor.execute(() -> {
|
||||
if( state == Options.AppState.RUNNING
|
||||
&& updateState == Options.AppState.IDLE ) {
|
||||
@@ -1446,10 +1368,11 @@ public class Zeichenmaschine extends Constants {
|
||||
// Call to draw()
|
||||
updateState = Options.AppState.DRAWING;
|
||||
Zeichenmaschine.this.draw();
|
||||
updateState = Options.AppState.IDLE;
|
||||
updateState = Options.AppState.DISPATCHING;
|
||||
// Send latest input events after finishing draw
|
||||
// since these may also block
|
||||
dispatchEvents();
|
||||
updateState = Options.AppState.IDLE;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1458,6 +1381,12 @@ public class Zeichenmaschine extends Constants {
|
||||
while( updateThreadExecutor.isRunning()
|
||||
&& !updateThreadExecutor.isWaiting() ) {
|
||||
Thread.yield();
|
||||
|
||||
if( Thread.interrupted() ) {
|
||||
running = false;
|
||||
terminateImediately = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Display the current buffer content
|
||||
@@ -1467,7 +1396,6 @@ public class Zeichenmaschine extends Constants {
|
||||
// frame.repaint();
|
||||
}
|
||||
|
||||
|
||||
// dispatchEvents();
|
||||
}
|
||||
|
||||
@@ -1510,37 +1438,23 @@ public class Zeichenmaschine extends Constants {
|
||||
pause_pending = false;
|
||||
}
|
||||
}
|
||||
// Shutdown the updateThreads
|
||||
updateThreadExecutor.shutdownNow();
|
||||
state = Options.AppState.STOPPED;
|
||||
// Shutdown the updateThread
|
||||
while( !terminateImediately && updateThreadExecutor.isRunning() ) {
|
||||
Thread.yield();
|
||||
}
|
||||
updateThreadExecutor.shutdownNow();
|
||||
|
||||
// Cleanup
|
||||
teardown();
|
||||
shutdown();
|
||||
cleanup();
|
||||
state = Options.AppState.TERMINATED;
|
||||
|
||||
if( quitAfterTeardown ) {
|
||||
if( quitAfterShutdown ) {
|
||||
quit();
|
||||
}
|
||||
}
|
||||
|
||||
public void handleUpdate( double delta ) {
|
||||
if( state == Options.AppState.RUNNING ) {
|
||||
state = Options.AppState.UPDATING;
|
||||
update(delta);
|
||||
canvas.updateLayers(delta);
|
||||
state = Options.AppState.RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleDraw() {
|
||||
if( state == Options.AppState.RUNNING ) {
|
||||
state = Options.AppState.DRAWING;
|
||||
draw();
|
||||
state = Options.AppState.RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
@@ -1637,7 +1551,7 @@ public class Zeichenmaschine extends Constants {
|
||||
|
||||
private Thread updateThread;
|
||||
|
||||
private boolean running = false, waiting = false;
|
||||
private boolean running = false;
|
||||
|
||||
public UpdateThreadExecutor() {
|
||||
super(1, 1, 0L,
|
||||
@@ -1645,13 +1559,18 @@ public class Zeichenmaschine extends Constants {
|
||||
updateState = Options.AppState.IDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( Runnable command ) {
|
||||
running = true;
|
||||
super.execute(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeExecute( Thread t, Runnable r ) {
|
||||
// We store the one Thread this Executor holds
|
||||
// We store the one Thread this Executor holds,
|
||||
// but it might change if a new Thread needed to be spawned
|
||||
// due to en error.
|
||||
updateThread = t;
|
||||
running = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1682,7 +1601,9 @@ public class Zeichenmaschine extends Constants {
|
||||
* @return
|
||||
*/
|
||||
public boolean isWaiting() {
|
||||
return running && updateThread.getState() == Thread.State.TIMED_WAITING;
|
||||
//return running && updateThread.getState() == Thread.State.TIMED_WAITING;
|
||||
//return running && updateThread != null && updateThread.getState() == Thread.State.TIMED_WAITING;
|
||||
return running && updateThread != null && updateState == Options.AppState.DELAYED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@ package schule.ngb.zm.anim;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Updatable;
|
||||
import schule.ngb.zm.events.EventDispatcher;
|
||||
import schule.ngb.zm.tasks.FrameSynchronizedTask;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
import schule.ngb.zm.util.events.EventDispatcher;
|
||||
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public abstract class Animation<T> implements Updatable {
|
||||
public abstract class Animation<T> extends Constants implements Updatable {
|
||||
|
||||
protected int runtime;
|
||||
|
||||
protected int elapsed_time = 0;
|
||||
protected int elapsedTime = 0;
|
||||
|
||||
protected boolean running = false, finished = false;
|
||||
|
||||
@@ -25,7 +24,7 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
public Animation( DoubleUnaryOperator easing ) {
|
||||
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
||||
this.easing = easing;
|
||||
this.easing = Validator.requireNotNull(easing);
|
||||
}
|
||||
|
||||
public Animation( int runtime ) {
|
||||
@@ -35,7 +34,7 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
||||
this.runtime = runtime;
|
||||
this.easing = easing;
|
||||
this.easing = Validator.requireNotNull(easing);
|
||||
}
|
||||
|
||||
public int getRuntime() {
|
||||
@@ -58,17 +57,17 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
public final void start() {
|
||||
this.initialize();
|
||||
elapsed_time = 0;
|
||||
elapsedTime = 0;
|
||||
running = true;
|
||||
finished = false;
|
||||
interpolate(easing.applyAsDouble(0.0));
|
||||
animate(easing.applyAsDouble(0.0));
|
||||
initializeEventDispatcher().dispatchEvent("start", this);
|
||||
}
|
||||
|
||||
public final void stop() {
|
||||
running = false;
|
||||
// Make sure the last animation frame was interpolated correctly
|
||||
interpolate(easing.applyAsDouble((double) elapsed_time / (double) runtime));
|
||||
animate(easing.applyAsDouble((double) elapsedTime / (double) runtime));
|
||||
this.finish();
|
||||
finished = true;
|
||||
initializeEventDispatcher().dispatchEvent("stop", this);
|
||||
@@ -84,11 +83,7 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
public final void await() {
|
||||
while( !finished ) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch( InterruptedException ex ) {
|
||||
// Keep waiting
|
||||
}
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,16 +94,16 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
@Override
|
||||
public void update( double delta ) {
|
||||
elapsed_time += (int) (delta * 1000);
|
||||
if( elapsed_time > runtime )
|
||||
elapsed_time = runtime;
|
||||
elapsedTime += (int) (delta * 1000);
|
||||
if( elapsedTime > runtime )
|
||||
elapsedTime = runtime;
|
||||
|
||||
double t = (double) elapsed_time / (double) runtime;
|
||||
double t = (double) elapsedTime / (double) runtime;
|
||||
if( t >= 1.0 ) {
|
||||
running = false;
|
||||
stop();
|
||||
} else {
|
||||
interpolate(easing.applyAsDouble(t));
|
||||
animate(easing.applyAsDouble(t));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +118,10 @@ public abstract class Animation<T> implements Updatable {
|
||||
* e = Constants.limit(e, 0, 1);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param e
|
||||
* @param e Fortschritt der Animation nachdem die Easingfunktion angewandt
|
||||
* wurde.
|
||||
*/
|
||||
public abstract void interpolate( double e );
|
||||
public abstract void animate( double e );
|
||||
|
||||
EventDispatcher<Animation, AnimationListener> eventDispatcher;
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ public class AnimationFacade<S> extends Animation<S> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
anim.interpolate(e);
|
||||
public void animate( double e ) {
|
||||
anim.animate(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,76 +1,116 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
import schule.ngb.zm.shapes.Shape;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public class AnimationGroup extends Animation<Shape> {
|
||||
@SuppressWarnings( "unused" )
|
||||
public class AnimationGroup<T> extends Animation<T> {
|
||||
|
||||
Animation<? extends Shape>[] anims;
|
||||
|
||||
private boolean overrideRuntime = false;
|
||||
List<Animation<T>> anims;
|
||||
|
||||
|
||||
public AnimationGroup( DoubleUnaryOperator easing, Animation<? extends Shape>... anims ) {
|
||||
super(easing);
|
||||
this.anims = anims;
|
||||
private boolean overrideEasing = false;
|
||||
|
||||
int maxRuntime = Arrays.stream(this.anims).mapToInt((a) -> a.getRuntime()).reduce(0, Integer::max);
|
||||
setRuntime(maxRuntime);
|
||||
private int overrideRuntime = -1;
|
||||
|
||||
private int lag = 0;
|
||||
|
||||
private int active = 0;
|
||||
|
||||
public AnimationGroup( Collection<Animation<T>> anims ) {
|
||||
this(0, -1, null, anims);
|
||||
}
|
||||
|
||||
public AnimationGroup( int runtime, DoubleUnaryOperator easing, Animation<? extends Shape>... anims ) {
|
||||
super(runtime, easing);
|
||||
this.anims = anims;
|
||||
overrideRuntime = true;
|
||||
public AnimationGroup( int lag, Collection<Animation<T>> anims ) {
|
||||
this(lag, -1, null, anims);
|
||||
}
|
||||
|
||||
public AnimationGroup( DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
|
||||
this(0, -1, easing, anims);
|
||||
}
|
||||
|
||||
public AnimationGroup( int lag, DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
|
||||
this(lag, -1, easing, anims);
|
||||
}
|
||||
|
||||
public AnimationGroup( int lag, int runtime, DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
|
||||
super();
|
||||
|
||||
this.anims = List.copyOf(anims);
|
||||
this.lag = lag;
|
||||
|
||||
if( easing != null ) {
|
||||
this.easing = easing;
|
||||
overrideEasing = true;
|
||||
}
|
||||
|
||||
if( runtime > 0 ) {
|
||||
this.runtime = anims.size() * lag + runtime;
|
||||
this.overrideRuntime = runtime;
|
||||
} else {
|
||||
this.runtime = 0;
|
||||
for( int i = 0; i < this.anims.size(); i++ ) {
|
||||
if( i * lag + this.anims.get(i).getRuntime() > this.runtime ) {
|
||||
this.runtime = i * lag + this.anims.get(i).getRuntime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getAnimationTarget() {
|
||||
return null;
|
||||
public T getAnimationTarget() {
|
||||
for( Animation<T> anim : anims ) {
|
||||
if( anim.isActive() ) {
|
||||
return anim.getAnimationTarget();
|
||||
}
|
||||
}
|
||||
return anims.get(anims.size() - 1).getAnimationTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( double delta ) {
|
||||
if( overrideRuntime ) {
|
||||
synchronized( anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
if( anim.isActive() ) {
|
||||
anim.update(delta);
|
||||
}
|
||||
elapsedTime += (int) (delta * 1000);
|
||||
// Animation is done. Stop all Animations.
|
||||
if( elapsedTime > runtime ) {
|
||||
for( int i = 0; i < anims.size(); i++ ) {
|
||||
if( anims.get(i).isActive() ) {
|
||||
anims.get(i).elapsedTime = anims.get(i).runtime;
|
||||
anims.get(i).stop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.update(delta);
|
||||
running = false;
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
synchronized( anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
anim.interpolate(e);
|
||||
while( active < anims.size() && elapsedTime >= active * lag ) {
|
||||
anims.get(active).start();
|
||||
active += 1;
|
||||
}
|
||||
|
||||
for( int i = 0; i < active; i++ ) {
|
||||
double t = 0.0;
|
||||
if( overrideRuntime > 0 ) {
|
||||
t = (double) (elapsedTime - i*lag) / (double) overrideRuntime;
|
||||
} else {
|
||||
t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
|
||||
}
|
||||
|
||||
if( t >= 1.0 ) {
|
||||
anims.get(i).elapsedTime = anims.get(i).runtime;
|
||||
anims.get(i).stop();
|
||||
} else {
|
||||
double e = overrideEasing ?
|
||||
easing.applyAsDouble(t) :
|
||||
anims.get(i).easing.applyAsDouble(t);
|
||||
|
||||
anims.get(i).animate(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
synchronized( anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
anim.initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
synchronized( anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
anim.finish();
|
||||
}
|
||||
}
|
||||
public void animate( double e ) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
import schule.ngb.zm.events.Listener;
|
||||
import schule.ngb.zm.util.events.Listener;
|
||||
|
||||
public interface AnimationListener extends Listener<Animation> {
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ 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.FramerateLimitedTask;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.tasks.FramerateLimitedTask;
|
||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
@@ -15,6 +14,7 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.*;
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Animations {
|
||||
|
||||
public static final <T> Future<T> animateProperty( String propName, T target, double to, int runtime, DoubleUnaryOperator easing ) {
|
||||
@@ -92,27 +92,28 @@ public class Animations {
|
||||
});
|
||||
}
|
||||
|
||||
private static final <T, R> R callGetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
@SuppressWarnings( "unchecked" )
|
||||
private static <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) ) {
|
||||
if( 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 {
|
||||
private static <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 ) {
|
||||
if( 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 ) {
|
||||
private static String makeMethodName( String prefix, String propName ) {
|
||||
String firstChar = propName.substring(0, 1).toUpperCase();
|
||||
String tail = "";
|
||||
if( propName.length() > 1 ) {
|
||||
@@ -124,31 +125,33 @@ public class Animations {
|
||||
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)));
|
||||
return play(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)));
|
||||
return play(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)));
|
||||
return play(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));
|
||||
return play(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, R> Future<T> play( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, BiConsumer<T, R> applicator ) {
|
||||
return play(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 ) {
|
||||
public static final <T> Future<T> play( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
||||
return TaskRunner.run(new FramerateLimitedTask() {
|
||||
double t = 0.0;
|
||||
|
||||
final long starttime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void update( double delta ) {
|
||||
// One animation step for t in [0,1]
|
||||
@@ -164,8 +167,8 @@ public class Animations {
|
||||
}, target);
|
||||
}
|
||||
|
||||
public static final <T> T animateAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
||||
Future<T> future = animate(target, runtime, easing, stepper);
|
||||
public static final <T> T playAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
||||
Future<T> future = play(target, runtime, easing, stepper);
|
||||
while( !future.isDone() ) {
|
||||
try {
|
||||
return future.get();
|
||||
@@ -179,16 +182,17 @@ public class Animations {
|
||||
return target;
|
||||
}
|
||||
|
||||
public static final <T, R> Future<T> animate( T target, int runtime, Animator<T, R> animator ) {
|
||||
/*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<Animation<T>> animate( Animation<T> animation ) {
|
||||
public static <T> Future<Animation<T>> play( Animation<T> animation ) {
|
||||
// TODO: (ngb) Don't start when running
|
||||
return TaskRunner.run(new FramerateLimitedTask() {
|
||||
@Override
|
||||
protected void initialize() {
|
||||
@@ -203,13 +207,13 @@ public class Animations {
|
||||
}, animation);
|
||||
}
|
||||
|
||||
public static <T> Animation<T> animateAndWait( Animation<T> animation ) {
|
||||
Future<Animation<T>> future = animate(animation);
|
||||
public static <T> Animation<T> playAndWait( Animation<T> animation ) {
|
||||
Future<Animation<T>> future = play(animation);
|
||||
animation.await();
|
||||
return animation;
|
||||
}
|
||||
|
||||
public static <T> Future<Animation<T>> animate( Animation<T> animation, DoubleUnaryOperator easing ) {
|
||||
public static <T> Future<Animation<T>> play( Animation<T> animation, DoubleUnaryOperator easing ) {
|
||||
final AnimationFacade<T> facade = new AnimationFacade<>(animation, animation.getRuntime(), easing);
|
||||
return TaskRunner.run(new FramerateLimitedTask() {
|
||||
@Override
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
public interface Animator<T, R> {
|
||||
|
||||
double easing(double t);
|
||||
|
||||
R interpolator(double e);
|
||||
|
||||
void applicator(T target, R value);
|
||||
|
||||
}
|
||||
39
src/main/java/schule/ngb/zm/anim/CircleAnimation.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Vector;
|
||||
import schule.ngb.zm.shapes.Shape;
|
||||
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public class CircleAnimation extends Animation<Shape> {
|
||||
|
||||
private Shape object;
|
||||
|
||||
private double centerx, centery, radius, startangle;
|
||||
|
||||
public CircleAnimation( Shape target, double cx, double cy, int runtime, DoubleUnaryOperator easing ) {
|
||||
super(runtime, easing);
|
||||
object = target;
|
||||
centerx = cx;
|
||||
centery = cy;
|
||||
Vector vec = new Vector(target.getX(), target.getY()).sub(cx, cy);
|
||||
startangle = vec.heading();
|
||||
radius = vec.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getAnimationTarget() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animate( double e ) {
|
||||
double angle = startangle + Constants.radians(Constants.interpolate(0, 360, e));
|
||||
double x = centerx + radius * Constants.cos(angle);
|
||||
double y = centery + radius * Constants.sin(angle);
|
||||
object.moveTo(x, y);
|
||||
}
|
||||
|
||||
}
|
||||
74
src/main/java/schule/ngb/zm/anim/ContinousAnimation.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
public class ContinousAnimation<T> extends Animation<T> {
|
||||
|
||||
private final Animation<T> baseAnimation;
|
||||
|
||||
private int lag = 0;
|
||||
|
||||
/**
|
||||
* Speichert eine Approximation der aktuellen Steigung der Easing-Funktion,
|
||||
* um im Fall {@code easeInOnly == true} nach dem ersten Durchlauf die
|
||||
* passende Geschwindigkeit beizubehalten.
|
||||
*/
|
||||
private double m = 1.0, lastEase = 0.0;
|
||||
|
||||
private boolean easeInOnly = false;
|
||||
|
||||
public ContinousAnimation( Animation<T> baseAnimation ) {
|
||||
this(baseAnimation, 0, false);
|
||||
}
|
||||
|
||||
public ContinousAnimation( Animation<T> baseAnimation, int lag ) {
|
||||
this(baseAnimation, lag, false);
|
||||
}
|
||||
|
||||
public ContinousAnimation( Animation<T> baseAnimation, boolean easeInOnly ) {
|
||||
this(baseAnimation, 0, easeInOnly);
|
||||
}
|
||||
|
||||
private ContinousAnimation( Animation<T> baseAnimation, int lag, boolean easeInOnly ) {
|
||||
super(baseAnimation.getRuntime(), baseAnimation.getEasing());
|
||||
this.baseAnimation = baseAnimation;
|
||||
this.lag = lag;
|
||||
this.easeInOnly = easeInOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getAnimationTarget() {
|
||||
return baseAnimation.getAnimationTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( double delta ) {
|
||||
elapsedTime += (int) (delta * 1000);
|
||||
if( elapsedTime >= runtime + lag ) {
|
||||
elapsedTime %= (runtime + lag);
|
||||
|
||||
if( easeInOnly && easing != null ) {
|
||||
easing = null;
|
||||
// runtime = (int)((1.0/m)*(runtime + lag));
|
||||
}
|
||||
}
|
||||
|
||||
double t = (double) elapsedTime / (double) runtime;
|
||||
if( t >= 1.0 ) {
|
||||
t = 1.0;
|
||||
}
|
||||
if( easing != null ) {
|
||||
double e = easing.applyAsDouble(t);
|
||||
animate(e);
|
||||
m = (e-lastEase)/(delta*1000/(asDouble(runtime)));
|
||||
lastEase = e;
|
||||
} else {
|
||||
animate(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animate( double e ) {
|
||||
baseAnimation.animate(e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class FadeAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e)));
|
||||
object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e)));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class FillAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setFillColor(Color.interpolate(oFill, tFill, e));
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class MorphAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setX(Constants.interpolate(original.getX(), target.getX(), e));
|
||||
object.setY(Constants.interpolate(original.getY(), target.getY(), e));
|
||||
object.setFillColor(Color.interpolate(original.getFillColor(), target.getFillColor(), e));
|
||||
|
||||
@@ -31,7 +31,7 @@ public class MoveAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setX(Constants.interpolate(oX, tX, e));
|
||||
object.setY(Constants.interpolate(oY, tY, e));
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class RotateAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.rotateTo(Constants.interpolate(oA, tA, e));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class StrokeAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setStrokeColor(Color.interpolate(oFill, tFill, e));
|
||||
}
|
||||
|
||||
|
||||
37
src/main/java/schule/ngb/zm/anim/WaveAnimation.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.shapes.Shape;
|
||||
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public class WaveAnimation extends Animation<Shape> {
|
||||
|
||||
private Shape object;
|
||||
|
||||
private double strength, sinOffset, previousDelta = 0.0;
|
||||
|
||||
private Options.Direction dir;
|
||||
|
||||
public WaveAnimation( Shape target, double strength, Options.Direction dir, double sinOffset, int runtime, DoubleUnaryOperator easing ) {
|
||||
super(runtime, easing);
|
||||
this.object = target;
|
||||
this.dir = dir;
|
||||
this.strength = strength;
|
||||
this.sinOffset = sinOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getAnimationTarget() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animate( double e ) {
|
||||
double delta = this.strength * Constants.sin(Constants.interpolate(0.0, Constants.TWO_PI, e) + sinOffset);
|
||||
object.move((delta - previousDelta) * dir.x, (delta - previousDelta) * dir.y);
|
||||
previousDelta = delta;
|
||||
}
|
||||
|
||||
}
|
||||
143
src/main/java/schule/ngb/zm/layers/ColorLayer.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.Paint;
|
||||
import java.awt.RadialGradientPaint;
|
||||
|
||||
/**
|
||||
* Eine Ebene, die nur aus einer Farbe (oder einem Farbverlauf) besteht.
|
||||
* <p>
|
||||
* Die Farbe der Ebene kann beliebig gesetzt werden und kann gut als
|
||||
* Hintergundfarbe für eine Szene dienen, oder als halbtransparente "Abdeckung",
|
||||
* wenn ein {@code ColorLayer} über den anderen Ebenen eingefügt wird.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class ColorLayer extends Layer {
|
||||
|
||||
/**
|
||||
* Farbe der Ebene.
|
||||
*/
|
||||
private Color color;
|
||||
|
||||
/**
|
||||
* Verlauf der Ebene, falls verwendet.
|
||||
*/
|
||||
private Paint background;
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Farbebene mit der angegebenen Farbe.
|
||||
*
|
||||
* @param color Die Hintergrundfarbe.
|
||||
*/
|
||||
public ColorLayer( Color color ) {
|
||||
this.color = color;
|
||||
this.background = color.getJavaColor();
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Farbebene mit der angegebenen Größe und Farbe.
|
||||
*
|
||||
* @param width Breite der Ebene.
|
||||
* @param height Höhe der Ebene.
|
||||
* @param color Die Hintergrundfarbe.
|
||||
*/
|
||||
public ColorLayer( int width, int height, Color color ) {
|
||||
super(width, height);
|
||||
this.color = color;
|
||||
this.background = color.getJavaColor();
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setSize( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Hintergrundfarbe der Ebene zurück.
|
||||
*
|
||||
* @return Die aktuelle Hintergrundfarbe.
|
||||
*/
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Ebene neu.
|
||||
*
|
||||
* @param color Die neue Hintergrundfarbe.
|
||||
*/
|
||||
public void setColor( Color color ) {
|
||||
this.color = color;
|
||||
this.background = color.getJavaColor();
|
||||
clear();
|
||||
}
|
||||
|
||||
public void setColor( int gray ) {
|
||||
setColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setColor( int gray, int alpha ) {
|
||||
setColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setColor( int red, int green, int blue ) {
|
||||
setColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setColor( int red, int green, int blue, int alpha ) {
|
||||
setColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||
double halfW = getWidth() * .5;
|
||||
double halfH = getHeight() * .5;
|
||||
|
||||
Options.Direction inv = dir.inverse();
|
||||
int fromX = (int) (halfW + inv.x * halfW);
|
||||
int fromY = (int) (halfH + inv.y * halfH);
|
||||
|
||||
int toX = (int) (halfW + dir.x * halfW);
|
||||
int toY = (int) (halfH + dir.y * halfH);
|
||||
|
||||
setGradient(fromX, fromY, from, toX, toY, to);
|
||||
}
|
||||
|
||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||
this.color = from;
|
||||
background = new GradientPaint(
|
||||
(float) fromX, (float) fromY, from.getJavaColor(),
|
||||
(float) toX, (float) toY, to.getJavaColor()
|
||||
);
|
||||
clear();
|
||||
}
|
||||
|
||||
public void setGradient( Color from, Color to ) {
|
||||
setGradient(getWidth() * .5, getHeight() * .5, Math.min(getWidth() * .5, getHeight() * .5), from, to);
|
||||
}
|
||||
|
||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||
this.color = from;
|
||||
background = new RadialGradientPaint(
|
||||
(float) centerX, (float) centerY, (float) radius,
|
||||
new float[]{0f, 1f},
|
||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()});
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
drawing.setPaint(background);
|
||||
drawing.fillRect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
package schule.ngb.zm;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import java.awt.*;
|
||||
import schule.ngb.zm.Drawable;
|
||||
import schule.ngb.zm.Layer;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class DrawableLayer extends Layer {
|
||||
|
||||
protected LinkedList<Drawable> drawables = new LinkedList<>();
|
||||
protected List<Drawable> drawables = new LinkedList<>();
|
||||
|
||||
protected boolean clearBeforeDraw = true;
|
||||
|
||||
@@ -43,7 +48,8 @@ public class DrawableLayer extends Layer {
|
||||
}
|
||||
|
||||
synchronized( drawables ) {
|
||||
for( Drawable d : drawables ) {
|
||||
List<Drawable> it = List.copyOf(drawables);
|
||||
for( Drawable d : it ) {
|
||||
if( d.isVisible() ) {
|
||||
d.draw(drawing);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package schule.ngb.zm;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
@@ -8,11 +10,11 @@ import java.util.Stack;
|
||||
|
||||
public class DrawingLayer extends Layer {
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
protected schule.ngb.zm.Color fillColor = DEFAULT_FILLCOLOR;
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
protected schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
@@ -43,7 +45,7 @@ public class DrawingLayer extends Layer {
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
public schule.ngb.zm.Color getColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
@@ -51,7 +53,7 @@ public class DrawingLayer extends Layer {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( Color color ) {
|
||||
public void setFillColor( schule.ngb.zm.Color color ) {
|
||||
fillColor = color;
|
||||
drawing.setColor(color.getJavaColor());
|
||||
}
|
||||
@@ -69,10 +71,10 @@ public class DrawingLayer extends Layer {
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
setFillColor(new schule.ngb.zm.Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public Color getStrokeColor() {
|
||||
public schule.ngb.zm.Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
@@ -80,7 +82,7 @@ public class DrawingLayer extends Layer {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color ) {
|
||||
public void setStrokeColor( schule.ngb.zm.Color color ) {
|
||||
strokeColor = color;
|
||||
drawing.setColor(color.getJavaColor());
|
||||
}
|
||||
@@ -98,7 +100,7 @@ public class DrawingLayer extends Layer {
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new Color(red, green, blue, alpha));
|
||||
setStrokeColor(new schule.ngb.zm.Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void setStrokeWeight( double pWeight ) {
|
||||
@@ -147,8 +149,8 @@ public class DrawingLayer extends Layer {
|
||||
}
|
||||
|
||||
public void resetStroke() {
|
||||
setStrokeColor(STD_STROKECOLOR);
|
||||
setStrokeWeight(STD_STROKEWEIGHT);
|
||||
setStrokeColor(DEFAULT_STROKECOLOR);
|
||||
setStrokeWeight(DEFAULT_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
}
|
||||
|
||||
@@ -169,10 +171,10 @@ public class DrawingLayer extends Layer {
|
||||
}
|
||||
|
||||
public void clear( int red, int green, int blue, int alpha ) {
|
||||
clear(new Color(red, green, blue, alpha));
|
||||
clear(new schule.ngb.zm.Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void clear( Color pColor ) {
|
||||
public void clear( schule.ngb.zm.Color pColor ) {
|
||||
/*graphics.setBackground(pColor);
|
||||
graphics.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());*/
|
||||
java.awt.Color currentColor = drawing.getColor();
|
||||
@@ -1,9 +1,10 @@
|
||||
package schule.ngb.zm;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
|
||||
public class ImageLayer extends Layer {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package schule.ngb.zm;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public final class Shape2DLayer extends Layer {
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
protected schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
protected schule.ngb.zm.Color fillColor = DEFAULT_FILLCOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
@@ -39,7 +43,7 @@ public final class Shape2DLayer extends Layer {
|
||||
this.instantDraw = instantDraw;
|
||||
}
|
||||
|
||||
public Color getFillColor() {
|
||||
public schule.ngb.zm.Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
@@ -47,7 +51,7 @@ public final class Shape2DLayer extends Layer {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( Color pColor ) {
|
||||
public void setFillColor( schule.ngb.zm.Color pColor ) {
|
||||
fillColor = pColor;
|
||||
drawing.setColor(pColor.getJavaColor());
|
||||
}
|
||||
@@ -65,10 +69,10 @@ public final class Shape2DLayer extends Layer {
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
setFillColor(new schule.ngb.zm.Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public Color getStrokeColor() {
|
||||
public schule.ngb.zm.Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
@@ -76,7 +80,7 @@ public final class Shape2DLayer extends Layer {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color pColor ) {
|
||||
public void setStrokeColor( schule.ngb.zm.Color pColor ) {
|
||||
strokeColor = pColor;
|
||||
drawing.setColor(pColor.getJavaColor());
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.anim.Animation;
|
||||
import schule.ngb.zm.anim.AnimationFacade;
|
||||
import schule.ngb.zm.anim.Easing;
|
||||
import schule.ngb.zm.shapes.Shape;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public class ShapesLayer extends Layer {
|
||||
@@ -45,12 +43,12 @@ public class ShapesLayer extends Layer {
|
||||
return null;
|
||||
}
|
||||
|
||||
public java.util.List<Shape> getShapes() {
|
||||
public List<Shape> getShapes() {
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public <ST extends Shape> java.util.List<ST> getShapes( Class<ST> shapeClass ) {
|
||||
java.util.List<ST> result = new LinkedList<>();
|
||||
public <ST extends Shape> List<ST> getShapes( Class<ST> shapeClass ) {
|
||||
List<ST> result = new LinkedList<>();
|
||||
for( Shape s : shapes ) {
|
||||
if( shapeClass.isInstance(s) ) {
|
||||
result.add((ST) s);
|
||||
@@ -60,7 +58,7 @@ public class ShapesLayer extends Layer {
|
||||
}
|
||||
|
||||
public void add( Shape... shapes ) {
|
||||
synchronized( shapes ) {
|
||||
synchronized( this.shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
@@ -68,7 +66,7 @@ public class ShapesLayer extends Layer {
|
||||
}
|
||||
|
||||
public void add( Collection<Shape> shapes ) {
|
||||
synchronized( shapes ) {
|
||||
synchronized( this.shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
@@ -76,16 +74,16 @@ public class ShapesLayer extends Layer {
|
||||
}
|
||||
|
||||
public void remove( Shape... shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s: shapes ) {
|
||||
synchronized( this.shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove( Collection<Shape> shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s: shapes ) {
|
||||
synchronized( this.shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.remove(s);
|
||||
}
|
||||
}
|
||||
@@ -99,16 +97,16 @@ public class ShapesLayer extends Layer {
|
||||
|
||||
public void showAll() {
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
pShape.show();
|
||||
for( Shape s : shapes ) {
|
||||
s.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void hideAll() {
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
pShape.hide();
|
||||
for( Shape s : shapes ) {
|
||||
s.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +116,14 @@ public class ShapesLayer extends Layer {
|
||||
anim.start();
|
||||
}
|
||||
|
||||
|
||||
public void play( Animation<? extends Shape>... anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
this.animations.add(anim);
|
||||
anim.start();
|
||||
}
|
||||
}
|
||||
|
||||
public <S extends Shape> void play( Animation<S> anim, int runtime ) {
|
||||
play(anim, runtime, Easing.DEFAULT_EASING);
|
||||
}
|
||||
@@ -147,9 +153,10 @@ public class ShapesLayer extends Layer {
|
||||
}
|
||||
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
if( pShape.isVisible() ) {
|
||||
pShape.draw(drawing);
|
||||
List<Shape> it = List.copyOf(shapes);
|
||||
for( Shape s : it ) {
|
||||
if( s.isVisible() ) {
|
||||
s.draw(drawing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.turtle;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Layer;
|
||||
@@ -336,7 +336,7 @@ public class TurtleLayer extends Layer {
|
||||
if( strokeColor != null ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(STD_STROKECOLOR.getJavaColor());
|
||||
graphics.setColor(DEFAULT_STROKECOLOR.getJavaColor());
|
||||
}
|
||||
graphics.fill(shape);
|
||||
graphics.setColor(Color.BLACK.getJavaColor());
|
||||
@@ -1,6 +1,6 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.events.Listener;
|
||||
import schule.ngb.zm.util.events.Listener;
|
||||
|
||||
public interface AudioListener extends Listener<Audio> {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
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.util.events.EventDispatcher;
|
||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
@@ -168,7 +166,7 @@ public class Music implements Audio {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
public synchronized void dispose() {
|
||||
if( audioLine != null ) {
|
||||
if( audioLine.isRunning() ) {
|
||||
playing = false;
|
||||
@@ -177,7 +175,6 @@ public class Music implements Audio {
|
||||
if( audioLine.isOpen() ) {
|
||||
audioLine.drain();
|
||||
audioLine.close();
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -191,7 +188,7 @@ public class Music implements Audio {
|
||||
audioStream = null;
|
||||
}
|
||||
|
||||
private void stream() {
|
||||
private synchronized void stream() {
|
||||
audioLine.start();
|
||||
playing = true;
|
||||
if( eventDispatcher != null ) {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
@@ -206,8 +205,9 @@ public class Sound implements Audio {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt
|
||||
* die Wiedergabe dann.
|
||||
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und
|
||||
* stoppt die Wiedergabe dann.
|
||||
*
|
||||
* @param count Anzahl der Wiederholungen.
|
||||
*/
|
||||
public void loop( int count ) {
|
||||
@@ -232,7 +232,7 @@ public class Sound implements Audio {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
public synchronized void dispose() {
|
||||
if( audioClip != null ) {
|
||||
if( audioClip.isRunning() ) {
|
||||
audioClip.stop();
|
||||
@@ -242,7 +242,7 @@ public class Sound implements Audio {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean openClip() {
|
||||
private synchronized boolean openClip() {
|
||||
if( audioClip != null ) {
|
||||
audioClip.setFramePosition(0);
|
||||
return true;
|
||||
|
||||
400
src/main/java/schule/ngb/zm/ml/DoubleMatrix.java
Normal file
@@ -0,0 +1,400 @@
|
||||
package schule.ngb.zm.ml;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
/**
|
||||
* Eine einfache Implementierung der {@link MLMatrix} zur Verwendung in
|
||||
* {@link NeuralNetwork}s.
|
||||
* <p>
|
||||
* Diese Klasse stellt die interne Implementierung der Matrixoperationen dar,
|
||||
* die zur Berechnung der Gewichte in einem {@link NeuronLayer} notwendig sind.
|
||||
* <p>
|
||||
* Die Klasse ist nur minimal optimiert und sollte nur für kleine Netze
|
||||
* verwendet werden. Für größere Netze sollte auf eine der optionalen
|
||||
* Bibliotheken wie
|
||||
* <a href="https://dst.lbl.gov/ACSSoftware/colt/">Colt</a> zurückgegriffen
|
||||
* werden.
|
||||
*/
|
||||
public final class DoubleMatrix implements MLMatrix {
|
||||
|
||||
/**
|
||||
* Anzahl Zeilen der Matrix.
|
||||
*/
|
||||
private int rows;
|
||||
|
||||
/**
|
||||
* Anzahl Spalten der Matrix.
|
||||
*/
|
||||
private int columns;
|
||||
|
||||
/**
|
||||
* Die Koeffizienten der Matrix.
|
||||
* <p>
|
||||
* Um den Overhead bei Speicher und Zugriffszeiten von zweidimensionalen
|
||||
* Arrays zu vermeiden wird ein eindimensionales Array verwendet und die
|
||||
* Indizes mit Spaltenpriorität berechnet. Der Index i des Koeffizienten
|
||||
* {@code r,c} in Zeile {@code r} und Spalte {@code c} wird bestimmt durch
|
||||
* <pre>
|
||||
* i = c * rows + r
|
||||
* </pre>
|
||||
* <p>
|
||||
* Die Werte einer Spalte liegen also hintereinander im Array. Dies sollte
|
||||
* einen leichten Vorteil bei der {@link #colSums() Spaltensummen} geben.
|
||||
* Generell sollte eine Iteration über die Matrix der Form
|
||||
* <pre><code>
|
||||
* for( int j = 0; j < columns; j++ ) {
|
||||
* for( int i = 0; i < rows; i++ ) {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
* etwas schneller sein als
|
||||
* <pre><code>
|
||||
* for( int i = 0; i < rows; i++ ) {
|
||||
* for( int j = 0; j < columns; j++ ) {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*/
|
||||
double[] coefficients;
|
||||
|
||||
public DoubleMatrix( int rows, int cols ) {
|
||||
this.rows = rows;
|
||||
this.columns = cols;
|
||||
coefficients = new double[rows * cols];
|
||||
}
|
||||
|
||||
public DoubleMatrix( double[][] coefficients ) {
|
||||
this.rows = coefficients.length;
|
||||
this.columns = coefficients[0].length;
|
||||
this.coefficients = new double[rows * columns];
|
||||
for( int j = 0; j < columns; j++ ) {
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
this.coefficients[idx(i, j)] = coefficients[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert diese Matrix als Kopie der angegebenen Matrix.
|
||||
*
|
||||
* @param other Die zu kopierende Matrix.
|
||||
*/
|
||||
public DoubleMatrix( DoubleMatrix other ) {
|
||||
this.rows = other.rows();
|
||||
this.columns = other.columns();
|
||||
this.coefficients = new double[rows * columns];
|
||||
System.arraycopy(
|
||||
other.coefficients, 0,
|
||||
this.coefficients, 0,
|
||||
rows * columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int columns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int rows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
int idx( int r, int c ) {
|
||||
return c * rows + r;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double get( int row, int col ) {
|
||||
try {
|
||||
return coefficients[idx(row, col)];
|
||||
} catch( ArrayIndexOutOfBoundsException ex ) {
|
||||
throw new IllegalArgumentException("No element at row=" + row + ", column=" + col, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix set( int row, int col, double value ) {
|
||||
try {
|
||||
coefficients[idx(row, col)] = value;
|
||||
} catch( ArrayIndexOutOfBoundsException ex ) {
|
||||
throw new IllegalArgumentException("No element at row=" + row + ", column=" + col, ex);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix initializeRandom() {
|
||||
return initializeRandom(-1.0, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix initializeRandom( double lower, double upper ) {
|
||||
applyInPlace(( d ) -> ((upper - lower) * Constants.random()) + lower);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix initializeOne() {
|
||||
applyInPlace(( d ) -> 1.0);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix initializeZero() {
|
||||
applyInPlace(( d ) -> 0.0);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix duplicate() {
|
||||
return new DoubleMatrix(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix multiplyTransposed( MLMatrix B ) {
|
||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||
( i ) -> IntStream.range(0, B.rows()).mapToDouble(
|
||||
( j ) -> IntStream.range(0, columns).mapToDouble(
|
||||
( k ) -> get(i, k) * B.get(j, k)
|
||||
).sum()
|
||||
).toArray()
|
||||
).toArray(double[][]::new));*/
|
||||
DoubleMatrix result = new DoubleMatrix(rows, B.rows());
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
for( int j = 0; j < B.rows(); j++ ) {
|
||||
result.coefficients[result.idx(i, j)] = 0.0;
|
||||
for( int k = 0; k < columns; k++ ) {
|
||||
result.coefficients[result.idx(i, j)] += get(i, k) * B.get(j, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix multiplyAddBias( final MLMatrix B, final MLMatrix C ) {
|
||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||
( i ) -> IntStream.range(0, B.columns()).mapToDouble(
|
||||
( j ) -> IntStream.range(0, columns).mapToDouble(
|
||||
( k ) -> get(i, k) * B.get(k, j)
|
||||
).sum() + C.get(0, j)
|
||||
).toArray()
|
||||
).toArray(double[][]::new));*/
|
||||
DoubleMatrix result = new DoubleMatrix(rows, B.columns());
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
for( int j = 0; j < B.columns(); j++ ) {
|
||||
result.coefficients[result.idx(i, j)] = 0.0;
|
||||
for( int k = 0; k < columns; k++ ) {
|
||||
result.coefficients[result.idx(i, j)] += get(i, k) * B.get(k, j);
|
||||
}
|
||||
result.coefficients[result.idx(i, j)] += C.get(0, j);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
|
||||
/*return new DoubleMatrix(IntStream.range(0, columns).parallel().mapToObj(
|
||||
( i ) -> IntStream.range(0, B.columns()).mapToDouble(
|
||||
( j ) -> IntStream.range(0, rows).mapToDouble(
|
||||
( k ) -> get(k, i) * B.get(k, j) * scalar
|
||||
).sum()
|
||||
).toArray()
|
||||
).toArray(double[][]::new));*/
|
||||
DoubleMatrix result = new DoubleMatrix(columns, B.columns());
|
||||
for( int i = 0; i < columns; i++ ) {
|
||||
for( int j = 0; j < B.columns(); j++ ) {
|
||||
result.coefficients[result.idx(i, j)] = 0.0;
|
||||
for( int k = 0; k < rows; k++ ) {
|
||||
result.coefficients[result.idx(i, j)] += get(k, i) * B.get(k, j);
|
||||
}
|
||||
result.coefficients[result.idx(i, j)] *= scalar;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix add( MLMatrix B ) {
|
||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||
( i ) -> IntStream.range(0, columns).mapToDouble(
|
||||
( j ) -> get(i, j) + B.get(i, j)
|
||||
).toArray()
|
||||
).toArray(double[][]::new));*/
|
||||
DoubleMatrix sum = new DoubleMatrix(rows, columns);
|
||||
for( int j = 0; j < columns; j++ ) {
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
sum.coefficients[idx(i, j)] = coefficients[idx(i, j)] + B.get(i, j);
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix addInPlace( MLMatrix B ) {
|
||||
for( int j = 0; j < columns; j++ ) {
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
coefficients[idx(i, j)] += B.get(i, j);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix sub( MLMatrix B ) {
|
||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||
( i ) -> IntStream.range(0, columns).mapToDouble(
|
||||
( j ) -> get(i, j) - B.get(i, j)
|
||||
).toArray()
|
||||
).toArray(double[][]::new));*/
|
||||
DoubleMatrix diff = new DoubleMatrix(rows, columns);
|
||||
for( int j = 0; j < columns; j++ ) {
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
diff.coefficients[idx(i, j)] = coefficients[idx(i, j)] - B.get(i, j);
|
||||
}
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix colSums() {
|
||||
/*DoubleMatrix colSums = new DoubleMatrix(1, columns);
|
||||
colSums.coefficients = IntStream.range(0, columns).parallel().mapToDouble(
|
||||
( j ) -> IntStream.range(0, rows).mapToDouble(
|
||||
( i ) -> get(i, j)
|
||||
).sum()
|
||||
).toArray();
|
||||
return colSums;*/
|
||||
DoubleMatrix colSums = new DoubleMatrix(1, columns);
|
||||
for( int j = 0; j < columns; j++ ) {
|
||||
colSums.coefficients[j] = 0.0;
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
colSums.coefficients[j] += coefficients[idx(i, j)];
|
||||
}
|
||||
}
|
||||
return colSums;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix scaleInPlace( final double scalar ) {
|
||||
for( int i = 0; i < coefficients.length; i++ ) {
|
||||
coefficients[i] *= scalar;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix scaleInPlace( final MLMatrix S ) {
|
||||
for( int j = 0; j < columns; j++ ) {
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
coefficients[idx(i, j)] *= S.get(i, j);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix apply( DoubleUnaryOperator op ) {
|
||||
DoubleMatrix result = new DoubleMatrix(rows, columns);
|
||||
for( int i = 0; i < coefficients.length; i++ ) {
|
||||
result.coefficients[i] = op.applyAsDouble(coefficients[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
|
||||
for( int i = 0; i < coefficients.length; i++ ) {
|
||||
coefficients[i] = op.applyAsDouble(coefficients[i]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(rows);
|
||||
sb.append(" x ");
|
||||
sb.append(columns);
|
||||
sb.append(" Matrix");
|
||||
sb.append('\n');
|
||||
for( int i = 0; i < rows; i++ ) {
|
||||
for( int j = 0; j < columns; j++ ) {
|
||||
sb.append(get(i, j));
|
||||
if( j < columns - 1 )
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
316
src/main/java/schule/ngb/zm/ml/MLMatrix.java
Normal file
@@ -0,0 +1,316 @@
|
||||
package schule.ngb.zm.ml;
|
||||
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
/**
|
||||
* Interface für Matrizen, die in {@link NeuralNetwork} Klassen verwendet
|
||||
* werden.
|
||||
* <p>
|
||||
* Eine implementierende Klasse muss generell zwei Konstruktoren bereitstellen:
|
||||
* <ol>
|
||||
* <li> {@code MLMatrix(int rows, int columns)} erstellt eine Matrix mit den
|
||||
* angegebenen Dimensionen und setzt alle Koeffizienten auf 0.
|
||||
* <li> {@code MLMatrix(double[][] coefficients} erstellt eine Matrix mit der
|
||||
* durch das Array gegebenen Dimensionen und setzt die Werte auf die
|
||||
* jeweiligen Werte des Arrays.
|
||||
* </ol>
|
||||
* <p>
|
||||
* Das Interface ist nicht dazu gedacht eine allgemeine Umsetzung für
|
||||
* Matrizen-Algebra abzubilden, sondern soll gezielt die im Neuralen Netzwerk
|
||||
* verwendeten Algorithmen umsetzen. Einerseits würde eine ganz allgemeine
|
||||
* Matrizen-Klasse nicht im Rahmen der Zeichenmaschine liegen und auf der
|
||||
* anderen Seite bietet eine Konzentration auf die verwendeten Algorithmen mehr
|
||||
* Spielraum zur Optimierung.
|
||||
* <p>
|
||||
* Intern wird das Interface von {@link DoubleMatrix} implementiert. Die Klasse
|
||||
* ist eine weitestgehend naive Implementierung der Algorithmen mit kleineren
|
||||
* Optimierungen. Die Verwendung eines generalisierten Interfaces erlaubt aber
|
||||
* zukünftig die optionale Integration spezialisierterer Algebra-Bibliotheken
|
||||
* wie
|
||||
* <a href="https://dst.lbl.gov/ACSSoftware/colt/">Colt</a>, um auch große
|
||||
* Netze effizient berechnen zu können.
|
||||
*/
|
||||
public interface MLMatrix {
|
||||
|
||||
/**
|
||||
* Die Anzahl der Spalten der Matrix.
|
||||
*
|
||||
* @return Spaltenzahl.
|
||||
*/
|
||||
int columns();
|
||||
|
||||
/**
|
||||
* Die Anzahl der Zeilen der Matrix.
|
||||
*
|
||||
* @return Zeilenzahl.
|
||||
*/
|
||||
int rows();
|
||||
|
||||
/**
|
||||
* Gibt den Wert an der angegebenen Stelle der Matrix zurück.
|
||||
*
|
||||
* @param row Die Spaltennummer zwischen 0 und {@code rows()-1}.
|
||||
* @param col Die Zeilennummer zwischen 0 und {@code columns()-1}
|
||||
* @return Den Koeffizienten in der Zeile {@code row} und der Spalte
|
||||
* {@code col}.
|
||||
* @throws IllegalArgumentException Falls {@code row >= rows()} oder
|
||||
* {@code col >= columns()}.
|
||||
*/
|
||||
double get( int row, int col ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Setzt den Wert an der angegebenen Stelle der Matrix.
|
||||
*
|
||||
* @param row Die Spaltennummer zwischen 0 und {@code rows()-1}.
|
||||
* @param col Die Zeilennummer zwischen 0 und {@code columns()-1}
|
||||
* @param value Der neue Wert.
|
||||
* @return Diese Matrix selbst (method chaining).
|
||||
* @throws IllegalArgumentException Falls {@code row >= rows()} oder
|
||||
* {@code col >= columns()}.
|
||||
*/
|
||||
MLMatrix set( int row, int col, double value ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Setzt jeden Wert in der Matrix auf eine Zufallszahl zwischen -1 und 1.
|
||||
* <p>
|
||||
* Nach Möglichkeit sollte der
|
||||
* {@link schule.ngb.zm.Constants#random(int, int) Zufallsgenerator der
|
||||
* Zeichenmaschine} verwendet werden.
|
||||
*
|
||||
* @return Diese Matrix selbst (method chaining).
|
||||
*/
|
||||
MLMatrix initializeRandom();
|
||||
|
||||
/**
|
||||
* Setzt jeden Wert in der Matrix auf eine Zufallszahl innerhalb der
|
||||
* angegebenen Grenzen.
|
||||
* <p>
|
||||
* Nach Möglichkeit sollte der
|
||||
* {@link schule.ngb.zm.Constants#random(int, int) Zufallsgenerator der
|
||||
* Zeichenmaschine} verwendet werden.
|
||||
*
|
||||
* @param lower Untere Grenze der Zufallszahlen.
|
||||
* @param upper Obere Grenze der Zufallszahlen.
|
||||
* @return Diese Matrix selbst (method chaining).
|
||||
*/
|
||||
MLMatrix initializeRandom( double lower, double upper );
|
||||
|
||||
/**
|
||||
* Setzt alle Werte der Matrix auf 1.
|
||||
*
|
||||
* @return Diese Matrix selbst (method chaining).
|
||||
*/
|
||||
MLMatrix initializeOne();
|
||||
|
||||
/**
|
||||
* Setzt alle Werte der Matrix auf 0.
|
||||
*
|
||||
* @return Diese Matrix selbst (method chaining).
|
||||
*/
|
||||
MLMatrix initializeZero();
|
||||
|
||||
/**
|
||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
|
||||
* <pre>
|
||||
* C = this . B + V'
|
||||
* </pre>
|
||||
* wobei {@code this} dieses Matrixobjekt ist und {@code .} für die
|
||||
* Matrixmultiplikation steht. {@code V'} ist die Matrix {@code V}
|
||||
* {@code rows()}-mal untereinander wiederholt.
|
||||
* <p>
|
||||
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
|
||||
* die Dimension c x m haben und {@code V} eine 1 x m Matrix sein. Die
|
||||
* Matrix {@code V'} hat also die Dimension r x m, ebenso wie das Ergebnis
|
||||
* der Operation.
|
||||
*
|
||||
* @param B Eine {@code columns()} x m Matrix mit der Multipliziert wird.
|
||||
* @param V Eine 1 x {@code B.columns()} Matrix mit den Bias-Werten.
|
||||
* @return Eine {@code rows()} x m Matrix.
|
||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
||||
* zur Operation passen. Also
|
||||
* {@code this.columns() != B.rows()} oder
|
||||
* {@code B.columns() != V.columns()} oder
|
||||
* {@code V.rows() != 1}.
|
||||
*/
|
||||
MLMatrix multiplyAddBias( MLMatrix B, MLMatrix V ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
|
||||
* <pre>
|
||||
* C = this . t(B)
|
||||
* </pre>
|
||||
* wobei {@code this} dieses Matrixobjekt ist, {@code t(B)} die
|
||||
* Transposition der Matrix {@code B} ist und {@code .} für die
|
||||
* Matrixmultiplikation steht.
|
||||
* <p>
|
||||
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
|
||||
* die Dimension m x c haben und das Ergebnis ist eine r x m Matrix.
|
||||
*
|
||||
* @param B Eine m x {@code columns()} Matrix.
|
||||
* @return Eine {@code rows()} x m Matrix.
|
||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
||||
* zur Operation passen. Also
|
||||
* {@code this.columns() != B.columns()}.
|
||||
*/
|
||||
MLMatrix multiplyTransposed( MLMatrix B ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
|
||||
* <pre>
|
||||
* C = t(this) . B * scalar
|
||||
* </pre>
|
||||
* wobei {@code this} dieses Matrixobjekt ist, {@code t(this)} die
|
||||
* Transposition dieser Matrix ist und {@code .} für die
|
||||
* Matrixmultiplikation steht. {@code *} bezeichnet die
|
||||
* Skalarmultiplikation, bei der jeder Wert der Matrix mit {@code scalar}
|
||||
* multipliziert wird.
|
||||
* <p>
|
||||
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
|
||||
* die Dimension r x m haben und das Ergebnis ist eine c x m Matrix.
|
||||
*
|
||||
* @param B Eine m x {@code columns()} Matrix.
|
||||
* @return Eine {@code rows()} x m Matrix.
|
||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
||||
* zur Operation passen. Also
|
||||
* {@code this.rows() != B.rows()}.
|
||||
*/
|
||||
MLMatrix transposedMultiplyAndScale( MLMatrix B, double scalar ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der komponentenweisen
|
||||
* Matrix-Addition
|
||||
* <pre>
|
||||
* C = this + B
|
||||
* </pre>
|
||||
* wobei {@code this} dieses Matrixobjekt ist. Für ein Element {@code C_ij}
|
||||
* in {@code C} gilt
|
||||
* <pre>
|
||||
* C_ij = A_ij + B_ij
|
||||
* </pre>
|
||||
* <p>
|
||||
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
|
||||
*
|
||||
* @param B Eine {@code rows()} x {@code columns()} Matrix.
|
||||
* @return Eine {@code rows()} x {@code columns()} Matrix.
|
||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
||||
* zur Operation passen. Also
|
||||
* {@code this.rows() != B.rows()} oder
|
||||
* {@code this.columns() != B.columns()}.
|
||||
*/
|
||||
MLMatrix add( MLMatrix B ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Setzt diese Matrix auf das Ergebnis der komponentenweisen
|
||||
* Matrix-Addition
|
||||
* <pre>
|
||||
* A' = A + B
|
||||
* </pre>
|
||||
* wobei {@code A} dieses Matrixobjekt ist und {@code A'} diese Matrix nach
|
||||
* der Operation. Für ein Element {@code A'_ij} in {@code A'} gilt
|
||||
* <pre>
|
||||
* A'_ij = A_ij + B_ij
|
||||
* </pre>
|
||||
* <p>
|
||||
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
|
||||
*
|
||||
* @param B Eine {@code rows()} x {@code columns()} Matrix.
|
||||
* @return Eine {@code rows()} x {@code columns()} Matrix.
|
||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
||||
* zur Operation passen. Also
|
||||
* {@code this.rows() != B.rows()} oder
|
||||
* {@code this.columns() != B.columns()}.
|
||||
*/
|
||||
MLMatrix addInPlace( MLMatrix B ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der komponentenweisen
|
||||
* Matrix-Subtraktion
|
||||
* <pre>
|
||||
* C = A - B
|
||||
* </pre>
|
||||
* wobei {@code A} dieses Matrixobjekt ist. Für ein Element {@code C_ij} in
|
||||
* {@code C} gilt
|
||||
* <pre>
|
||||
* C_ij = A_ij - B_ij
|
||||
* </pre>
|
||||
* <p>
|
||||
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
|
||||
*
|
||||
* @param B Eine {@code rows()} x {@code columns()} Matrix.
|
||||
* @return Eine {@code rows()} x {@code columns()} Matrix.
|
||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
||||
* zur Operation passen. Also
|
||||
* {@code this.rows() != B.rows()} oder
|
||||
* {@code this.columns() != B.columns()}.
|
||||
*/
|
||||
MLMatrix sub( MLMatrix B ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Multipliziert jeden Wert dieser Matrix mit dem angegebenen Skalar.
|
||||
* <p>
|
||||
* Ist {@code A} dieses Matrixobjekt und {@code A'} diese Matrix nach der
|
||||
* Operation, dann gilt für ein Element {@code A'_ij} in {@code A'}
|
||||
* <pre>
|
||||
* A'_ij = A_ij * scalar
|
||||
* </pre>
|
||||
*
|
||||
* @param scalar Ein Skalar.
|
||||
* @return Diese Matrix selbst (method chaining)
|
||||
*/
|
||||
MLMatrix scaleInPlace( double scalar );
|
||||
|
||||
/**
|
||||
* Multipliziert jeden Wert dieser Matrix mit dem entsprechenden Wert in der
|
||||
* Matrix {@code S}.
|
||||
* <p>
|
||||
* Ist {@code A} dieses Matrixobjekt und {@code A'} diese Matrix nach der
|
||||
* Operation, dann gilt für ein Element {@code A'_ij} in {@code A'}
|
||||
* <pre>
|
||||
* A'_ij = A_ij * S_ij
|
||||
* </pre>
|
||||
*
|
||||
* @param S Eine {@code rows()} x {@code columns()} Matrix.
|
||||
* @return Diese Matrix selbst (method chaining)
|
||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
||||
* zur Operation passen. Also
|
||||
* {@code this.rows() != B.rows()} oder
|
||||
* {@code this.columns() != B.columns()}.
|
||||
*/
|
||||
MLMatrix scaleInPlace( MLMatrix S ) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Berechnet eine neue Matrix mit nur einer Zeile, die die Spaltensummen
|
||||
* dieser Matrix enthalten.
|
||||
*
|
||||
* @return Eine 1 x {@code columns()} Matrix.
|
||||
*/
|
||||
MLMatrix colSums();
|
||||
|
||||
/**
|
||||
* Erzeugt eine neue Matrix, deren Werte gleich den Werten dieser Matrix
|
||||
* nach der Anwendung der angegebenen Funktion sind.
|
||||
*
|
||||
* @param op Eine Operation {@code (double) -> double}.
|
||||
* @return Eine {@code rows()} x {@code columns()} Matrix.
|
||||
*/
|
||||
MLMatrix apply( DoubleUnaryOperator op );
|
||||
|
||||
/**
|
||||
* Endet die gegebene Funktion auf jeden Wert der Matrix an.
|
||||
*
|
||||
* @param op Eine Operation {@code (double) -> double}.
|
||||
* @return Diese Matrix selbst (method chaining)
|
||||
*/
|
||||
MLMatrix applyInPlace( DoubleUnaryOperator op );
|
||||
|
||||
/**
|
||||
* Erzeugt eine neue Matrix mit denselben Dimensionen und Koeffizienten wie
|
||||
* diese Matrix.
|
||||
*
|
||||
* @return Eine Kopie dieser Matrix.
|
||||
*/
|
||||
MLMatrix duplicate();
|
||||
|
||||
String toString();
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
246
src/main/java/schule/ngb/zm/ml/MatrixFactory.java
Normal file
@@ -0,0 +1,246 @@
|
||||
package schule.ngb.zm.ml;
|
||||
|
||||
import cern.colt.matrix.DoubleFactory2D;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
/**
|
||||
* Zentrale Klasse zur Erstellung neuer Matrizen. Generell sollten neue Matrizen
|
||||
* nicht direkt erstellt werden, sondern durch den Aufruf von
|
||||
* {@link #create(int, int)} oder {@link #create(double[][])}. Die Fabrik
|
||||
* ermittelt automatisch die beste verfügbare Implementierung und initialisiert
|
||||
* eine entsprechende Implementierung von {@link MLMatrix}.
|
||||
* <p>
|
||||
* Derzeit werden die optionale Bibliothek <a
|
||||
* href="https://dst.lbl.gov/ACSSoftware/colt/">Colt</a> und die interne
|
||||
* Implementierung {@link DoubleMatrix} unterstützt.
|
||||
*/
|
||||
public class MatrixFactory {
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Matrix mit den angegebenen Dimensionen und
|
||||
* initialisiert alle Werte mit 0.
|
||||
*
|
||||
* @param rows Anzahl der Zeilen.
|
||||
* @param cols Anzahl der Spalten.
|
||||
* @return Eine {@code rows} x {@code cols} Matrix.
|
||||
*/
|
||||
public static final MLMatrix create( int rows, int cols ) {
|
||||
try {
|
||||
return getMatrixType().getDeclaredConstructor(int.class, int.class).newInstance(rows, cols);
|
||||
} catch( Exception ex ) {
|
||||
LOG.error(ex, "Could not initialize matrix implementation for class <%s>. Using internal implementation.", matrixType);
|
||||
}
|
||||
return new DoubleMatrix(rows, cols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Matrix mit den Dimensionen des angegebenen Arrays und
|
||||
* initialisiert die Werte mit den entsprechenden Werten des Arrays.
|
||||
*
|
||||
* @param values Die Werte der Matrix.
|
||||
* @return Eine {@code values.length} x {@code values[0].length} Matrix mit
|
||||
* den Werten des Arrays.
|
||||
*/
|
||||
public static final MLMatrix create( double[][] values ) {
|
||||
try {
|
||||
return getMatrixType().getDeclaredConstructor(double[][].class).newInstance((Object) values);
|
||||
} catch( Exception ex ) {
|
||||
LOG.error(ex, "Could not initialize matrix implementation for class <%s>. Using internal implementation.", matrixType);
|
||||
}
|
||||
return new DoubleMatrix(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Die verwendete {@link MLMatrix} Implementierung, aus der Matrizen erzeugt
|
||||
* werden.
|
||||
*/
|
||||
static Class<? extends MLMatrix> matrixType = null;
|
||||
|
||||
/**
|
||||
* Ermittelt die beste verfügbare Implementierung von {@link MLMatrix}.
|
||||
*
|
||||
* @return Die verwendete {@link MLMatrix} Implementierung.
|
||||
*/
|
||||
private static final Class<? extends MLMatrix> getMatrixType() {
|
||||
if( matrixType == null ) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("cern.colt.matrix.impl.DenseDoubleMatrix2D", false, MatrixFactory.class.getClassLoader());
|
||||
matrixType = ColtMatrix.class;
|
||||
LOG.info("Colt library found. Using <cern.colt.matrix.impl.DenseDoubleMatrix2D> as matrix implementation.");
|
||||
} catch( ClassNotFoundException e ) {
|
||||
LOG.info("Colt library not found. Falling back on internal implementation.");
|
||||
matrixType = DoubleMatrix.class;
|
||||
}
|
||||
}
|
||||
return matrixType;
|
||||
}
|
||||
|
||||
private static final Log LOG = Log.getLogger(MatrixFactory.class);
|
||||
|
||||
/**
|
||||
* Interner Wrapper der DoubleMatrix2D Klasse aus der Colt Bibliothek, um
|
||||
* das {@link MLMatrix} Interface zu implementieren.
|
||||
*/
|
||||
static class ColtMatrix implements MLMatrix {
|
||||
|
||||
cern.colt.matrix.DoubleMatrix2D matrix;
|
||||
|
||||
public ColtMatrix( double[][] doubles ) {
|
||||
matrix = new cern.colt.matrix.impl.DenseDoubleMatrix2D(doubles);
|
||||
}
|
||||
|
||||
public ColtMatrix( int rows, int cols ) {
|
||||
matrix = new cern.colt.matrix.impl.DenseDoubleMatrix2D(rows, cols);
|
||||
}
|
||||
|
||||
public ColtMatrix( ColtMatrix matrix ) {
|
||||
this.matrix = matrix.matrix.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int columns() {
|
||||
return matrix.columns();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int rows() {
|
||||
return matrix.rows();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double get( int row, int col ) {
|
||||
return matrix.get(row, col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix set( int row, int col, double value ) {
|
||||
matrix.set(row, col, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix initializeRandom() {
|
||||
return initializeRandom(-1.0, 1.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix initializeRandom( double lower, double upper ) {
|
||||
matrix.assign(( d ) -> ((upper - lower) * Constants.random()) + lower);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix initializeOne() {
|
||||
this.matrix.assign(1.0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix initializeZero() {
|
||||
this.matrix.assign(0.0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix duplicate() {
|
||||
ColtMatrix newMatrix = new ColtMatrix(matrix.rows(), matrix.columns());
|
||||
newMatrix.matrix.assign(this.matrix);
|
||||
return newMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix multiplyTransposed( MLMatrix B ) {
|
||||
ColtMatrix CB = (ColtMatrix) B;
|
||||
ColtMatrix newMatrix = new ColtMatrix(0, 0);
|
||||
newMatrix.matrix = matrix.zMult(CB.matrix, null, 1.0, 0.0, false, true);
|
||||
return newMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix multiplyAddBias( MLMatrix B, MLMatrix C ) {
|
||||
ColtMatrix CB = (ColtMatrix) B;
|
||||
ColtMatrix newMatrix = new ColtMatrix(0, 0);
|
||||
newMatrix.matrix = DoubleFactory2D.dense.repeat(((ColtMatrix) C).matrix, rows(), 1);
|
||||
matrix.zMult(CB.matrix, newMatrix.matrix, 1.0, 1.0, false, false);
|
||||
return newMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
|
||||
ColtMatrix CB = (ColtMatrix) B;
|
||||
ColtMatrix newMatrix = new ColtMatrix(0, 0);
|
||||
newMatrix.matrix = matrix.zMult(CB.matrix, null, scalar, 0.0, true, false);
|
||||
return newMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix add( MLMatrix B ) {
|
||||
ColtMatrix CB = (ColtMatrix) B;
|
||||
ColtMatrix newMatrix = new ColtMatrix(this);
|
||||
newMatrix.matrix.assign(CB.matrix, ( d1, d2 ) -> d1 + d2);
|
||||
return newMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix addInPlace( MLMatrix B ) {
|
||||
ColtMatrix CB = (ColtMatrix) B;
|
||||
matrix.assign(CB.matrix, ( d1, d2 ) -> d1 + d2);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix sub( MLMatrix B ) {
|
||||
ColtMatrix CB = (ColtMatrix) B;
|
||||
ColtMatrix newMatrix = new ColtMatrix(this);
|
||||
newMatrix.matrix.assign(CB.matrix, ( d1, d2 ) -> d1 - d2);
|
||||
return newMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix colSums() {
|
||||
double[][] sums = new double[1][matrix.columns()];
|
||||
for( int c = 0; c < matrix.columns(); c++ ) {
|
||||
for( int r = 0; r < matrix.rows(); r++ ) {
|
||||
sums[0][c] += matrix.getQuick(r, c);
|
||||
}
|
||||
}
|
||||
return new ColtMatrix(sums);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix scaleInPlace( double scalar ) {
|
||||
this.matrix.assign(( d ) -> d * scalar);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix scaleInPlace( MLMatrix S ) {
|
||||
this.matrix.forEachNonZero(( r, c, d ) -> d * S.get(r, c));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix apply( DoubleUnaryOperator op ) {
|
||||
ColtMatrix newMatrix = new ColtMatrix(matrix.rows(), matrix.columns());
|
||||
newMatrix.matrix.assign(matrix);
|
||||
newMatrix.matrix.assign(( d ) -> op.applyAsDouble(d));
|
||||
return newMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
|
||||
this.matrix.assign(( d ) -> op.applyAsDouble(d));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return matrix.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package schule.ngb.zm.ml;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.LinkedList;
|
||||
@@ -15,7 +15,7 @@ public class NeuralNetwork {
|
||||
Writer writer = ResourceStreamProvider.getWriter(source);
|
||||
PrintWriter out = new PrintWriter(writer)
|
||||
) {
|
||||
for( NeuronLayer layer: network.layers ) {
|
||||
for( NeuronLayer layer : network.layers ) {
|
||||
out.print(layer.getNeuronCount());
|
||||
out.print(' ');
|
||||
out.print(layer.getInputCount());
|
||||
@@ -23,20 +23,44 @@ public class NeuralNetwork {
|
||||
|
||||
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(layer.weights.get(i, j));
|
||||
out.print(' ');
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||
out.print(layer.biases[j]);
|
||||
out.print(layer.biases.get(0, j));
|
||||
out.print(' ');
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
out.flush();
|
||||
} catch( IOException ex ) {
|
||||
LOG.warn(ex, "");
|
||||
LOG.error(ex, "");
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveToDataFile( String source, NeuralNetwork network ) {
|
||||
try(
|
||||
OutputStream stream = ResourceStreamProvider.getOutputStream(source);
|
||||
DataOutputStream out = new DataOutputStream(stream)
|
||||
) {
|
||||
for( NeuronLayer layer : network.layers ) {
|
||||
out.writeInt(layer.getNeuronCount());
|
||||
out.writeInt(layer.getInputCount());
|
||||
|
||||
for( int i = 0; i < layer.getInputCount(); i++ ) {
|
||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||
out.writeDouble(layer.weights.get(i, j));
|
||||
}
|
||||
}
|
||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||
out.writeDouble(layer.biases.get(0, j));
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
} catch( IOException ex ) {
|
||||
LOG.error(ex, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,13 +80,13 @@ public class NeuralNetwork {
|
||||
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]);
|
||||
layer.weights.set(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]);
|
||||
layer.biases.set(0, j, Double.parseDouble(split[j]));
|
||||
}
|
||||
|
||||
layers.add(layer);
|
||||
@@ -70,29 +94,30 @@ public class NeuralNetwork {
|
||||
|
||||
return new NeuralNetwork(layers);
|
||||
} catch( IOException | NoSuchElementException ex ) {
|
||||
LOG.warn(ex, "Could not load neural network from source <%s>", source);
|
||||
LOG.error(ex, "Could not load neural network from source <%s>", source);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*public static NeuralNetwork loadFromFile( String source ) {
|
||||
public static NeuralNetwork loadFromDataFile( String source ) {
|
||||
try(
|
||||
InputStream stream = ResourceStreamProvider.getInputStream(source);
|
||||
Scanner in = new Scanner(stream)
|
||||
DataInputStream in = new DataInputStream(stream)
|
||||
) {
|
||||
List<NeuronLayer> layers = new LinkedList<>();
|
||||
while( in.hasNext() ) {
|
||||
int neurons = in.nextInt();
|
||||
int inputs = in.nextInt();
|
||||
while( in.available() > 0 ) {
|
||||
int neurons = in.readInt();
|
||||
int inputs = in.readInt();
|
||||
|
||||
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();
|
||||
layer.weights.set(i, j, in.readDouble());
|
||||
}
|
||||
}
|
||||
// Load Biases
|
||||
for( int j = 0; j < neurons; j++ ) {
|
||||
layer.biases[j] = in.nextDouble();
|
||||
layer.biases.set(0, j, in.readDouble());
|
||||
}
|
||||
|
||||
layers.add(layer);
|
||||
@@ -100,14 +125,14 @@ public class NeuralNetwork {
|
||||
|
||||
return new NeuralNetwork(layers);
|
||||
} catch( IOException | NoSuchElementException ex ) {
|
||||
LOG.warn(ex, "Could not load neural network from source <%s>", source);
|
||||
LOG.error(ex, "Could not load neural network from source <%s>", source);
|
||||
}
|
||||
return null;
|
||||
}*/
|
||||
}
|
||||
|
||||
private NeuronLayer[] layers;
|
||||
|
||||
private double[][] output;
|
||||
private MLMatrix output;
|
||||
|
||||
private double learningRate = 0.1;
|
||||
|
||||
@@ -128,7 +153,7 @@ public class NeuralNetwork {
|
||||
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]);
|
||||
this.layers[i - 1].setNextLayer(this.layers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +163,7 @@ public class NeuralNetwork {
|
||||
for( int i = 0; i < layers.length; i++ ) {
|
||||
this.layers[i] = layers[i];
|
||||
if( i > 0 ) {
|
||||
this.layers[i-1].setNextLayer(this.layers[i]);
|
||||
this.layers[i - 1].setNextLayer(this.layers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +171,7 @@ public class NeuralNetwork {
|
||||
public int getLayerCount() {
|
||||
return layers.length;
|
||||
}
|
||||
|
||||
public NeuronLayer[] getLayers() {
|
||||
return layers;
|
||||
}
|
||||
@@ -162,17 +188,28 @@ public class NeuralNetwork {
|
||||
this.learningRate = pLearningRate;
|
||||
}
|
||||
|
||||
public double[][] getOutput() {
|
||||
public MLMatrix getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public double[][] predict( double[][] inputs ) {
|
||||
//this.output = layers[1].apply(layers[0].apply(inputs));
|
||||
public MLMatrix predict( double[] inputs ) {
|
||||
return predict(MatrixFactory.create(new double[][]{inputs}));
|
||||
}
|
||||
|
||||
public MLMatrix predict( double[][] inputs ) {
|
||||
return predict(MatrixFactory.create(inputs));
|
||||
}
|
||||
|
||||
public MLMatrix predict( MLMatrix inputs ) {
|
||||
this.output = layers[0].apply(inputs);
|
||||
return this.output;
|
||||
}
|
||||
|
||||
public void learn( double[][] expected ) {
|
||||
learn(MatrixFactory.create(expected));
|
||||
}
|
||||
|
||||
public void learn( MLMatrix expected ) {
|
||||
layers[layers.length - 1].backprop(expected, learningRate);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +1,66 @@
|
||||
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[][]> {
|
||||
/**
|
||||
* Implementierung einer Neuronenebene in einem Neuonalen Netz.
|
||||
* <p>
|
||||
* Eine Ebene besteht aus einer Anzahl an <em>Neuronen</em> die jeweils eine
|
||||
* Anzahl <em>Eingänge</em> haben. Die Eingänge erhalten als Signal die Ausgabe
|
||||
* der vorherigen Ebene und berechnen die Ausgabe des jeweiligen Neurons.
|
||||
*/
|
||||
public class NeuronLayer implements Function<MLMatrix, MLMatrix> {
|
||||
|
||||
public static NeuronLayer fromArray( double[][] weights, boolean transpose ) {
|
||||
NeuronLayer layer;
|
||||
if( transpose ) {
|
||||
layer = new NeuronLayer(weights.length, weights[0].length);
|
||||
} else {
|
||||
layer = new NeuronLayer(weights[0].length, weights.length);
|
||||
}
|
||||
|
||||
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];
|
||||
if( transpose ) {
|
||||
layer.weights.set(j, i, weights[i][j]);
|
||||
} else {
|
||||
layer.weights.set(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];
|
||||
}
|
||||
}
|
||||
public static NeuronLayer fromArray( double[][] weights, double[] biases, boolean transpose ) {
|
||||
NeuronLayer layer = fromArray(weights, transpose);
|
||||
|
||||
for( int j = 0; j < biases.length; j++ ) {
|
||||
layer.biases[j] = biases[j];
|
||||
layer.biases.set(0, j, biases[j]);
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
Matrix weights;
|
||||
|
||||
double[] biases;
|
||||
MLMatrix weights;
|
||||
|
||||
MLMatrix biases;
|
||||
|
||||
NeuronLayer previous, next;
|
||||
|
||||
DoubleUnaryOperator activationFunction, activationFunctionDerivative;
|
||||
|
||||
double[][] lastOutput, lastInput;
|
||||
MLMatrix lastOutput, lastInput;
|
||||
|
||||
public NeuronLayer( int neurons, int inputs ) {
|
||||
weights = new Matrix(inputs, neurons);
|
||||
weights.initializeRandom(-1, 1);
|
||||
weights = MatrixFactory
|
||||
.create(inputs, neurons)
|
||||
.initializeRandom();
|
||||
|
||||
biases = new double[neurons];
|
||||
Arrays.fill(biases, 0.0); // TODO: Random?
|
||||
biases = MatrixFactory
|
||||
.create(1, neurons)
|
||||
.initializeZero();
|
||||
|
||||
activationFunction = MLMath::sigmoid;
|
||||
activationFunctionDerivative = MLMath::sigmoidDerivative;
|
||||
@@ -85,45 +101,42 @@ public class NeuronLayer implements Function<double[][], double[][]> {
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix getWeights() {
|
||||
public MLMatrix getWeights() {
|
||||
return weights;
|
||||
}
|
||||
|
||||
public MLMatrix getBiases() {
|
||||
return biases;
|
||||
}
|
||||
|
||||
public int getNeuronCount() {
|
||||
return weights.coefficients[0].length;
|
||||
return weights.columns();
|
||||
}
|
||||
|
||||
public int getInputCount() {
|
||||
return weights.coefficients.length;
|
||||
return weights.rows();
|
||||
}
|
||||
|
||||
public double[][] getLastOutput() {
|
||||
public MLMatrix 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);
|
||||
public void setWeights( MLMatrix newWeights ) {
|
||||
weights = newWeights.duplicate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return weights.toString() + "\n" + Arrays.toString(biases);
|
||||
return "weights:\n" + weights.toString() + "\nbiases:\n" + biases.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[][] apply( double[][] inputs ) {
|
||||
lastInput = inputs;
|
||||
lastOutput = MLMath.matrixApply(
|
||||
MLMath.biasAdd(
|
||||
MLMath.matrixMultiply(inputs, weights.coefficients),
|
||||
biases
|
||||
),
|
||||
activationFunction
|
||||
);
|
||||
public MLMatrix apply( MLMatrix inputs ) {
|
||||
lastInput = inputs.duplicate();
|
||||
lastOutput = inputs
|
||||
.multiplyAddBias(weights, biases)
|
||||
.applyInPlace(activationFunction);
|
||||
|
||||
if( next != null ) {
|
||||
return next.apply(lastOutput);
|
||||
} else {
|
||||
@@ -132,36 +145,41 @@ public class NeuronLayer implements Function<double[][], double[][]> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Function<V, double[][]> compose( Function<? super V, ? extends double[][]> before ) {
|
||||
public <V> Function<V, MLMatrix> compose( Function<? super V, ? extends MLMatrix> before ) {
|
||||
return ( in ) -> apply(before.apply(in));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Function<double[][], V> andThen( Function<? super double[][], ? extends V> after ) {
|
||||
public <V> Function<MLMatrix, V> andThen( Function<? super MLMatrix, ? extends V> after ) {
|
||||
return ( in ) -> after.apply(apply(in));
|
||||
}
|
||||
|
||||
public void backprop( double[][] expected, double learningRate ) {
|
||||
double[][] error, delta, adjustment;
|
||||
public void backprop( MLMatrix expected, double learningRate ) {
|
||||
MLMatrix error, adjustment;
|
||||
if( next == null ) {
|
||||
error = MLMath.matrixSub(expected, this.lastOutput);
|
||||
error = expected.sub(lastOutput);
|
||||
} else {
|
||||
error = MLMath.matrixMultiply(expected, MLMath.matrixTranspose(next.weights.coefficients));
|
||||
error = expected.multiplyTransposed(next.weights);
|
||||
}
|
||||
|
||||
delta = MLMath.matrixScale(error, MLMath.matrixApply(this.lastOutput, this.activationFunctionDerivative));
|
||||
error.scaleInPlace(
|
||||
lastOutput.apply(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);
|
||||
previous.backprop(error, learningRate);
|
||||
}
|
||||
|
||||
biases = MLMath.biasAdjust(biases, MLMath.matrixApply(delta, ( x ) -> learningRate * x));
|
||||
biases.addInPlace(
|
||||
error.colSums().scaleInPlace(
|
||||
-learningRate / (double) error.rows()
|
||||
)
|
||||
);
|
||||
|
||||
adjustment = MLMath.matrixMultiply(MLMath.matrixTranspose(lastInput), delta);
|
||||
adjustment = MLMath.matrixApply(adjustment, ( x ) -> learningRate * x);
|
||||
this.adjustWeights(adjustment);
|
||||
adjustment = lastInput.transposedMultiplyAndScale(error, learningRate);
|
||||
weights.addInPlace(adjustment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,44 +2,201 @@ package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Paint;
|
||||
import java.awt.RadialGradientPaint;
|
||||
|
||||
/**
|
||||
* Basisklasse für Formen, die eine Füllung besitzen können.
|
||||
* <p>
|
||||
* Formen mit einer Füllung können auch immer eine Konturlinie besitzen.
|
||||
*/
|
||||
public abstract class FilledShape extends StrokedShape {
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
/**
|
||||
* Die aktuelle Füllfarbe der Form oder {@code null}, wenn die Form nicht
|
||||
* gefüllt werden soll.
|
||||
*/
|
||||
protected Color fillColor = DEFAULT_FILLCOLOR;
|
||||
|
||||
/**
|
||||
* Der aktuelle Farbverlauf der Form oder {@code null}, wenn die Form keinen
|
||||
* Farbverlauf besitzt.
|
||||
*/
|
||||
protected Paint fill = null;
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Füllfarbe der Form zurück.
|
||||
*
|
||||
* @return Die aktuelle Füllfarbe oder {@code null}.
|
||||
*/
|
||||
public Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die angegebene Farbe.
|
||||
*
|
||||
* @param color Die neue Füllfarbe oder {@code null}.
|
||||
* @see Color
|
||||
*/
|
||||
public void setFillColor( Color color ) {
|
||||
fillColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die angegebene Farbe und setzt die Transparenz
|
||||
* auf den angegebenen Wert. 0 is komplett durchsichtig und 255 komplett
|
||||
* deckend.
|
||||
*
|
||||
* @param color Die neue Füllfarbe oder {@code null}.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(Color, int)
|
||||
*/
|
||||
public void setFillColor( Color color, int alpha ) {
|
||||
setFillColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität. 0
|
||||
* entspricht schwarz, 255 entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @see Color#Color(int)
|
||||
*/
|
||||
public void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
setFillColor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität und
|
||||
* dem angegebenen Transparenzwert. Der Grauwert 0 entspricht schwarz, 255
|
||||
* entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(int, int)
|
||||
*/
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||
* Blauanteilen.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @see Color#Color(int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
||||
* Blauanteilen und dem angegebenen Transparenzwert.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
||||
* @see Color#Color(int, int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt die Füllung der Form.
|
||||
*/
|
||||
public void noFill() {
|
||||
setFillColor(null);
|
||||
noGradient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllfarbe auf den Standardwert zurück.
|
||||
*
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
||||
*/
|
||||
public void resetFill() {
|
||||
setFillColor(STD_FILLCOLOR);
|
||||
setFillColor(DEFAULT_FILLCOLOR);
|
||||
noGradient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen linearen Farbverlauf, der am Punkt
|
||||
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
||||
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
||||
*
|
||||
* @param fromX x-Koordinate des Startpunktes.
|
||||
* @param fromY y-Koordinate des Startpunktes.
|
||||
* @param from Farbe am Startpunkt.
|
||||
* @param toX x-Koordinate des Endpunktes.
|
||||
* @param toY y-Koordinate des Endpunktes.
|
||||
* @param to Farbe am Endpunkt.
|
||||
*/
|
||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||
setFillColor(from);
|
||||
fill = new GradientPaint(
|
||||
(float) fromX, (float) fromY, from.getJavaColor(),
|
||||
(float) toX, (float) toY, to.getJavaColor()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, mit dem
|
||||
* Zentrum im Punkt ({@code centerX}, {@code centerY}) und dem angegebenen
|
||||
* Radius. Der Verlauf starte im Zentrum mit der Farbe {@code from} und
|
||||
* endet am Rand des durch den Radius beschriebenen Kreises mit der Farbe
|
||||
* {@code to}.
|
||||
*
|
||||
* @param centerX x-Koordinate des Kreismittelpunktes.
|
||||
* @param centerY y-Koordinate des Kreismittelpunktes.
|
||||
* @param radius Radius des Kreises.
|
||||
* @param from Farbe im Zentrum des Kreises.
|
||||
* @param to Farbe am Rand des Kreises.
|
||||
*/
|
||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||
setFillColor(from);
|
||||
fill = new RadialGradientPaint(
|
||||
(float) centerX, (float) centerY, (float) radius,
|
||||
new float[]{0f, 1f},
|
||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()});
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt den Farbverlauf von der Form.
|
||||
*/
|
||||
public void noGradient() {
|
||||
fill = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit der aktuellen
|
||||
* Füllung auf den Grafik-Kontext zu zeichnen. Die Methode verändert
|
||||
* gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt sie nicht
|
||||
* auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
||||
*
|
||||
* @param shape Die zu zeichnende Java-AWT Form
|
||||
* @param graphics Das Grafikobjekt.
|
||||
*/
|
||||
protected void fillShape( java.awt.Shape shape, Graphics2D graphics ) {
|
||||
if( fill != null ) {
|
||||
graphics.setPaint(fill);
|
||||
graphics.fill(shape);
|
||||
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
graphics.setColor(fillColor.getJavaColor());
|
||||
graphics.fill(shape);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
@@ -15,6 +15,8 @@ public class Rectangle extends Shape {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.anchor = Options.Direction.NORTHWEST;
|
||||
|
||||
//this.cacheEnabled = getClass().equals(Rectangle.class);
|
||||
}
|
||||
|
||||
public Rectangle( Rectangle pRechteck ) {
|
||||
@@ -24,9 +26,25 @@ public class Rectangle extends Shape {
|
||||
copyFrom(pRechteck);
|
||||
}
|
||||
|
||||
public Rectangle( Bounds pBounds ) {
|
||||
this(
|
||||
pBounds.x, pBounds.y,
|
||||
pBounds.width, pBounds.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Rechteck zur Darstellung der
|
||||
* @param pShape
|
||||
*/
|
||||
public Rectangle( Shape pShape ) {
|
||||
this(pShape, true);
|
||||
}
|
||||
|
||||
public Rectangle( Shape pShape, boolean transformed ) {
|
||||
java.awt.Shape s = pShape.getShape();
|
||||
s = pShape.getTransform().createTransformedShape(s);
|
||||
if( transformed ) {
|
||||
s = pShape.getTransform().createTransformedShape(s);
|
||||
}
|
||||
Rectangle2D bounds = s.getBounds2D();
|
||||
x = bounds.getX();
|
||||
y = bounds.getY();
|
||||
|
||||
@@ -7,41 +7,147 @@ import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
/**
|
||||
* Basisklasse für alle Formen in der Zeichenmaschine.
|
||||
* <p>
|
||||
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
|
||||
* <p>
|
||||
* Neben den abstrakten Methoden implementieren Unterklassen mindestens zwei
|
||||
* Konstruktoren. Einen Konstruktor, der die Form mit vom Nutzer gegebenen
|
||||
* Parametern initialisiert und einen, der die Werten einer anderen Form
|
||||
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
||||
* beispielsweise so implementiert:
|
||||
*
|
||||
* <pre><code>
|
||||
* public Circle( double x, double y, double radius ) {
|
||||
* super(x, y);
|
||||
* this.radius = radius;
|
||||
* }
|
||||
*
|
||||
* public Circle( Circle circle ) {
|
||||
* super(circle.x, circle.y);
|
||||
* copyFrom(circle);
|
||||
* }
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* Außerdem implementieren Unterklassen eine passende {@link #toString()} und
|
||||
* eine {@link #equals(Object)} Methode.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public abstract class Shape extends FilledShape {
|
||||
|
||||
/**
|
||||
* x-Koordinate der Form.
|
||||
*/
|
||||
protected double x;
|
||||
|
||||
/**
|
||||
* y-Koordinate der Form.
|
||||
*/
|
||||
protected double y;
|
||||
|
||||
/**
|
||||
* Rotation in Grad um den Punkt (x, y).
|
||||
*/
|
||||
protected double rotation = 0.0;
|
||||
|
||||
/**
|
||||
* Skalierungsfaktor.
|
||||
*/
|
||||
protected double scale = 1.0;
|
||||
|
||||
/**
|
||||
* Ob die Form angezeigt werden soll.
|
||||
*/
|
||||
protected boolean visible = true;
|
||||
|
||||
/**
|
||||
* Ankerpunkt der Form.
|
||||
*/
|
||||
protected Options.Direction anchor = Options.Direction.CENTER;
|
||||
|
||||
/**
|
||||
* Setzt die x- und y-Koordinate der Form auf 0.
|
||||
*/
|
||||
public Shape() {
|
||||
this(0.0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die x- und y-Koordinate der Form.
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
public Shape( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Shape() {
|
||||
this(0.0, 0.0);
|
||||
/**
|
||||
* Ob die Form angezeigt wird oder nicht.
|
||||
*
|
||||
* @return {@code true}, wenn die From angezeigt werden soll, {@code false}
|
||||
* sonst.
|
||||
*/
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versteckt die Form.
|
||||
*/
|
||||
public void hide() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt die Form an.
|
||||
*/
|
||||
public void show() {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versteckt die Form, wenn sie derzeit angezeigt wird und zeigt sie
|
||||
* andernfalls an.
|
||||
*/
|
||||
public void toggle() {
|
||||
visible = !visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die x-Koordinate der Form zurück.
|
||||
*
|
||||
* @return Die x-Koordinate.
|
||||
*/
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die x-Koordinate der Form.
|
||||
*
|
||||
* @param x Die neue x-Koordinate.
|
||||
*/
|
||||
public void setX( double x ) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die y-Koordinate der Form zurück.
|
||||
*
|
||||
* @return Die y-Koordinate.
|
||||
*/
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die y-Koordinate der Form.
|
||||
*
|
||||
* @param y Die neue y-Koordinate.
|
||||
*/
|
||||
public void setY( double y ) {
|
||||
this.y = y;
|
||||
}
|
||||
@@ -49,8 +155,9 @@ public abstract class Shape extends FilledShape {
|
||||
/**
|
||||
* Gibt die Breite dieser Form zurück.
|
||||
* <p>
|
||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung, <b>bevor</b>
|
||||
* Drehungen und andere Transformationen auf sei angewandt wurden.
|
||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||
* angewandt wurden.
|
||||
* <p>
|
||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
||||
* {@link #getBounds()} abgerufen werden.
|
||||
@@ -62,8 +169,9 @@ public abstract class Shape extends FilledShape {
|
||||
/**
|
||||
* Gibt die Höhe dieser Form zurück.
|
||||
* <p>
|
||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung, <b>bevor</b>
|
||||
* Drehungen und andere Transformationen auf sei angewandt wurden.
|
||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||
* angewandt wurden.
|
||||
* <p>
|
||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
||||
* {@link #getBounds()} abgerufen werden.
|
||||
@@ -72,28 +180,70 @@ public abstract class Shape extends FilledShape {
|
||||
*/
|
||||
public abstract double getHeight();
|
||||
|
||||
/**
|
||||
* Gibt die Rotation in Grad zurück.
|
||||
*
|
||||
* @return Rotation in Grad.
|
||||
*/
|
||||
public double getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dreht die Form um den angegebenen Winkel um ihren Ankerpunkt.
|
||||
* @param angle Drehwinkel in Grad.
|
||||
*/
|
||||
public void rotate( double angle ) {
|
||||
this.rotation += angle % 360;
|
||||
}
|
||||
|
||||
public void rotateTo( double angle ) {
|
||||
this.rotation = angle % 360;
|
||||
}
|
||||
|
||||
public void rotate( Point2D center, double angle ) {
|
||||
rotate(center.getX(), center.getY(), angle);
|
||||
}
|
||||
|
||||
public void rotate( double x, double y, double angle ) {
|
||||
this.rotation += angle % 360;
|
||||
|
||||
// Rotate x/y position
|
||||
double x1 = this.x - x, y1 = this.y - y;
|
||||
|
||||
double rad = Math.toRadians(angle);
|
||||
double x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad);
|
||||
double y2 = x1 * Math.sin(rad) + y1 * Math.cos(rad);
|
||||
|
||||
this.x = x2 + x;
|
||||
this.y = y2 + y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den aktuellen Skalierungsfaktor zurück.
|
||||
*
|
||||
* @return Der Skalierungsfaktor.
|
||||
*/
|
||||
public double getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
public void scale( double factor ) {
|
||||
scale = factor;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
visible = false;
|
||||
public void scaleBy( double factor ) {
|
||||
scale(scale * factor);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
visible = true;
|
||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
||||
Point2D apDir = getAbsAnchorPoint(dir);
|
||||
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
||||
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
||||
}
|
||||
|
||||
public void toggle() {
|
||||
visible = !visible;
|
||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
||||
Point2D ap = getAbsAnchorPoint(CENTER);
|
||||
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
||||
}
|
||||
|
||||
public Options.Direction getAnchor() {
|
||||
@@ -117,6 +267,19 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkt basierend
|
||||
* auf der angegebenen Breite und Höhe des umschließenden Rechtecks.
|
||||
* <p>
|
||||
* Die Koordinaten des Ankerpunkt werden relativ zur oberen linken Ecke des
|
||||
* Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
||||
* bestimmt.
|
||||
*
|
||||
* @param width Breite des umschließdenden Rechtecks.
|
||||
* @param height Höhe des umschließdenden Rechtecks.
|
||||
* @param anchor Gesuchter Ankerpunkt.
|
||||
* @return Ein {@link Point2D} mit den relativen Koordinaten.
|
||||
*/
|
||||
protected static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) {
|
||||
double wHalf = width * .5, hHalf = height * .5;
|
||||
|
||||
@@ -164,14 +327,22 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
|
||||
/**
|
||||
* Kopiert die Eigenschaften der übergebenen Form in diese.
|
||||
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
||||
* <p>
|
||||
* Unterklassen sollten diese Methode überschreiben, um weitere
|
||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). Mit
|
||||
* dem Aufruf {@code super.copyFrom(shape)} sollten die Basiseigenschaften
|
||||
* kopiert werden.
|
||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises).
|
||||
* Unterklassen sollten immer mit dem Aufruf {@code super.copyFrom(shape)}
|
||||
* die Basiseigenschaften kopieren.
|
||||
* <p>
|
||||
* Die Methode sollte so viele Eigenschaften wie möglich von der anderen
|
||||
* Form in diese kopieren. Wenn die andere Form einen anderen Typ hat, dann
|
||||
* werden trotzdem die Basiseigenschaften (Konturlinie, Füllung, Position,
|
||||
* Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese Form kopiert.
|
||||
* Implementierende Unterklassen können soweit sinnvoll auch andere Werte
|
||||
* übernehmen. Eine {@link Ellipse} kann beispielsweise auch die Breite und
|
||||
* Höhe eines {@link Rectangle} übernehmen.
|
||||
*
|
||||
* @param shape
|
||||
* @param shape Die Originalform, von der kopiert werden soll.
|
||||
*/
|
||||
public void copyFrom( Shape shape ) {
|
||||
if( shape != null ) {
|
||||
@@ -187,10 +358,58 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Kopie dieser Form mit denselben Eigenschaften.
|
||||
* <p>
|
||||
* Unterklassen implementieren diese Methode mit dem genauen Typ der
|
||||
* Unterklasse. In {@link Rectangle} sieht die Umsetzung beispielsweise so
|
||||
* aus:
|
||||
* <pre><code>
|
||||
* public Rectangle copy() {
|
||||
* return new Rectangle(this);
|
||||
* }
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
|
||||
* Form zu erhalten. In der Regel sollte aber jede Form einen Konstruktor
|
||||
* besitzen, die alle Werte einer andern Form übernimmt. Die gezeigte
|
||||
* Implementierung dürfte daher im Regelfall ausreichend sein.
|
||||
*
|
||||
* @return Eine genaue Kopie dieser Form.
|
||||
*/
|
||||
public abstract Shape copy();
|
||||
|
||||
/**
|
||||
* Gibt eine {@link java.awt.Shape Java-AWT Shape} Version dieser Form
|
||||
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
||||
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
||||
* <p>
|
||||
* Da die AWT-Shape bei jedem Zeichnen (also mindestens einmal pro Frame)
|
||||
* benötigt wird, wird das aktuelle Shape-Objekt in {@link #awtShape}
|
||||
* zwischengespeichert. Bei Änderungen der Objekteigenschaften muss daher
|
||||
* intern {@link #invalidate()} aufgerufen werden, damit beim nächsten
|
||||
* Aufruf von {@link #draw(Graphics2D)} die Shape mit einem Aufurf von
|
||||
* {@code getShape()} neu erzeugt wird. Unterklassen können aber auch die
|
||||
* zwischengespeicherte Shape direkt modifizieren, um eine Neugenerierung zu
|
||||
* vermeiden.
|
||||
* <p>
|
||||
* Wenn diese Form nicht durch eine AWT-Shape dargstellt wird, kann die
|
||||
* Methode {@code null} zurückgeben.
|
||||
*
|
||||
* @return Eine Java-AWT {@code Shape} die diess Form repräsentiert oder
|
||||
* {@code null}.
|
||||
*/
|
||||
public abstract java.awt.Shape getShape();
|
||||
|
||||
/**
|
||||
* Gibt die Begrenzungen der Form zurück.
|
||||
* <p>
|
||||
* Ein {@code Bounds}-Objekt beschreibt eine "<a
|
||||
* href="https://gdbooks.gitbooks.io/3dcollisions/content/Chapter1/aabb.html">Axis
|
||||
* Aligned Bounding Box</a>" (AABB).
|
||||
*
|
||||
* @return Die Abgrenzungen der Form nach Anwendung der Transformationen.
|
||||
*/
|
||||
public Bounds getBounds() {
|
||||
return new Bounds(this);
|
||||
}
|
||||
@@ -273,7 +492,7 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
|
||||
public void nextTo( Shape shape, Options.Direction dir ) {
|
||||
nextTo(shape, dir, STD_BUFFER);
|
||||
nextTo(shape, dir, DEFAULT_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,40 +514,6 @@ public abstract class Shape extends FilledShape {
|
||||
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||
}
|
||||
|
||||
public void scale( double factor ) {
|
||||
scale = factor;
|
||||
}
|
||||
|
||||
public void scaleBy( double factor ) {
|
||||
scale(scale * factor);
|
||||
}
|
||||
|
||||
public void rotate( double angle ) {
|
||||
this.rotation += angle % 360;
|
||||
}
|
||||
|
||||
public void rotateTo( double angle ) {
|
||||
this.rotation = angle % 360;
|
||||
}
|
||||
|
||||
public void rotate( Point2D center, double angle ) {
|
||||
rotate(center.getX(), center.getY(), angle);
|
||||
}
|
||||
|
||||
public void rotate( double x, double y, double angle ) {
|
||||
this.rotation += angle % 360;
|
||||
|
||||
// Rotate x/y position
|
||||
double x1 = this.x - x, y1 = this.y - y;
|
||||
|
||||
double rad = Math.toRadians(angle);
|
||||
double x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad);
|
||||
double y2 = x1 * Math.sin(rad) + y1 * Math.cos(rad);
|
||||
|
||||
this.x = x2 + x;
|
||||
this.y = y2 + y;
|
||||
}
|
||||
|
||||
/*public void shear( double dx, double dy ) {
|
||||
verzerrung.shear(dx, dy);
|
||||
}*/
|
||||
@@ -376,16 +561,8 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
|
||||
Color currentColor = graphics.getColor();
|
||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
graphics.setColor(fillColor.getJavaColor());
|
||||
graphics.fill(shape);
|
||||
}
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.draw(shape);
|
||||
}
|
||||
fillShape(shape, graphics);
|
||||
strokeShape(shape, graphics);
|
||||
graphics.setColor(currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class ShapeGroup extends Shape {
|
||||
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
||||
LinkedList<ShapeType> list = new LinkedList<>();
|
||||
for( Shape s : shapes ) {
|
||||
if( typeClass.getClass().isInstance(s) ) {
|
||||
if( typeClass.isInstance(s) ) {
|
||||
list.add((ShapeType) s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,67 +4,173 @@ import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Drawable;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.Noise;
|
||||
|
||||
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;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Stroke;
|
||||
|
||||
|
||||
/**
|
||||
* Basisklasse für Formen, die eine Konturlinie besitzen.
|
||||
*/
|
||||
public abstract class StrokedShape extends Constants implements Drawable {
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
/**
|
||||
* Aktuelle Farbe der Konturlinie oder {@code null}, wenn die Form ohne
|
||||
* kontur dargestellt werden soll.
|
||||
*/
|
||||
protected Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
/**
|
||||
* Die Dicke der Konturlinie. Wird nicht kleiner als 0.
|
||||
*/
|
||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
|
||||
/**
|
||||
* Die Art der Konturlinie.
|
||||
*/
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
/**
|
||||
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
|
||||
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
|
||||
* Zeichnen neu erstellt.
|
||||
*/
|
||||
protected Stroke stroke = null;
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Farbe der Konturlinie zurück.
|
||||
*
|
||||
* @return Die Konturfarbe oder {@code null}.
|
||||
*/
|
||||
public Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe.
|
||||
*
|
||||
* @param color Die neue Farbe der Konturlinie.
|
||||
* @see Color
|
||||
*/
|
||||
public void setStrokeColor( Color color ) {
|
||||
this.strokeColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe und setzt die
|
||||
* Transparenz auf den angegebenen Wert. 0 is komplett durchsichtig und 255
|
||||
* komplett deckend.
|
||||
*
|
||||
* @param color Die neue Farbe der Konturlinie oder {@code null}.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(Color, int)
|
||||
*/
|
||||
public void setStrokeColor( Color color, int alpha ) {
|
||||
setStrokeColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf einen Grauwert mit der angegebenen
|
||||
* Intensität. 0 entspricht schwarz, 255 entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @see Color#Color(int)
|
||||
*/
|
||||
public void setStrokeColor( int gray ) {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
setStrokeColor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf einen Grauwert mit der angegebenen
|
||||
* Intensität und dem angegebenen Transparenzwert. Der Grauwert 0 entspricht
|
||||
* schwarz, 255 entspricht weiß.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
||||
* @see Color#Color(int, int)
|
||||
*/
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
setStrokeColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die Farbe mit den angegebenen Rot-,
|
||||
* Grün- und Blauanteilen.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @see Color#Color(int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
setStrokeColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die Farbe mit den angegebenen Rot-,
|
||||
* Grün- und Blauanteilen und dem angegebenen Transparenzwert.
|
||||
*
|
||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
||||
* @see Color#Color(int, int, int, int)
|
||||
* @see <a
|
||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
||||
*/
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt die Kontur der Form.
|
||||
*/
|
||||
public void noStroke() {
|
||||
setStrokeColor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Farbe der Konturlinie auf die Standardwerte zurück.
|
||||
*
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_STROKECOLOR
|
||||
* @see schule.ngb.zm.Constants#DEFAULT_STROKEWEIGHT
|
||||
* @see schule.ngb.zm.Constants#SOLID
|
||||
*/
|
||||
public void resetStroke() {
|
||||
setStrokeColor(DEFAULT_STROKECOLOR);
|
||||
setStrokeWeight(DEFAULT_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Dicke der Konturlinie zurück.
|
||||
*
|
||||
* @return Die aktuelle Dicke der Linie.
|
||||
*/
|
||||
public double getStrokeWeight() {
|
||||
return strokeWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Dicke der Konturlinie. Die Dicke muss größer 0 sein. Wird 0
|
||||
* übergeben, dann wird keine Kontur mehr angezeigt.
|
||||
*
|
||||
* @param weight Die Dicke der Konturlinie.
|
||||
*/
|
||||
public void setStrokeWeight( double weight ) {
|
||||
this.strokeWeight = weight;
|
||||
this.strokeWeight = max(0.0, weight);
|
||||
this.stroke = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Art der Konturlinie zurück.
|
||||
*
|
||||
* @return Die aktuelle Art der Konturlinie.
|
||||
* @see Options.StrokeType
|
||||
*/
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return strokeType;
|
||||
}
|
||||
@@ -73,7 +179,8 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
||||
* {@link #DOTTED} und {@link #SOLID}.
|
||||
*
|
||||
* @param type
|
||||
* @param type Eine der möglichen Konturarten.
|
||||
* @see Options.StrokeType
|
||||
*/
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
this.strokeType = type;
|
||||
@@ -84,9 +191,11 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
public abstract void draw( Graphics2D graphics );
|
||||
|
||||
/**
|
||||
* Erstellt ein {@link Stroke} Objekt für den Konturtyp.
|
||||
* Hilfsmethode, um ein {@link Stroke} Objekt mit den aktuellen
|
||||
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
||||
* zwischengespeichert.
|
||||
*
|
||||
* @return
|
||||
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||
*/
|
||||
protected Stroke createStroke() {
|
||||
// TODO: Used global cached Stroke Objects?
|
||||
@@ -118,10 +227,23 @@ public abstract class StrokedShape extends Constants implements Drawable {
|
||||
return stroke;
|
||||
}
|
||||
|
||||
public void resetStroke() {
|
||||
setStrokeColor(STD_STROKECOLOR);
|
||||
setStrokeWeight(STD_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
/**
|
||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit den aktuellen
|
||||
* Kontureigenschaften auf den Grafik-Kontext zu zeichnen. Die Methode
|
||||
* verändert gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt
|
||||
* sie nicht auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
||||
*
|
||||
* @param shape Die zu zeichnende Java-AWT Form
|
||||
* @param graphics Das Grafikobjekt.
|
||||
*/
|
||||
protected void strokeShape( java.awt.Shape shape, Graphics2D graphics ) {
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.draw(shape);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.FontLoader;
|
||||
import schule.ngb.zm.util.io.FontLoader;
|
||||
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Font;
|
||||
@@ -22,7 +22,7 @@ public class Text extends Shape {
|
||||
protected int width = 0, height = 0, ascent = 0;
|
||||
|
||||
public Text( double x, double y, String text ) {
|
||||
this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE));
|
||||
this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, DEFAULT_FONTSIZE));
|
||||
}
|
||||
public Text( double x, double y, String text, String fontname ) {
|
||||
super(x, y);
|
||||
@@ -30,7 +30,7 @@ public class Text extends Shape {
|
||||
if( userfont != null ) {
|
||||
font = userfont;
|
||||
} else {
|
||||
font = new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE);
|
||||
font = new Font(Font.SANS_SERIF, Font.PLAIN, DEFAULT_FONTSIZE);
|
||||
}
|
||||
setText(text);
|
||||
fillColor = null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Graphics2D;
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.shapes.Circle;
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.shapes.Circle;
|
||||
@@ -1,12 +1,9 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.*;
|
||||
@@ -34,7 +31,7 @@ import static java.util.logging.Level.*;
|
||||
*/
|
||||
public final class Log {
|
||||
|
||||
private static final String ROOT_LOGGER = "schule.ngb.zm";
|
||||
private static final String ROOT_LOGGER = "schule";
|
||||
|
||||
private static final String DEFAULT_LOG_FORMAT = "[%1$tT] [%4$11s] %5$s %6$s%n";
|
||||
|
||||
@@ -80,7 +77,7 @@ public final class Log {
|
||||
*/
|
||||
public static void enableGlobalLevel( Level level ) {
|
||||
int lvl = Validator.requireNotNull(level).intValue();
|
||||
ensureRootLoggerIntialized();
|
||||
ensureRootLoggerInitialized();
|
||||
|
||||
// Decrease level of root level ConsoleHandlers for output
|
||||
Logger rootLogger = Logger.getLogger("");
|
||||
@@ -119,23 +116,32 @@ public final class Log {
|
||||
}
|
||||
|
||||
public static Log getLogger( Class<?> clazz ) {
|
||||
ensureRootLoggerIntialized();
|
||||
ensureRootLoggerInitialized();
|
||||
return new Log(clazz);
|
||||
}
|
||||
|
||||
private static void ensureRootLoggerIntialized() {
|
||||
private static void ensureRootLoggerInitialized() {
|
||||
if( LOGGING_INIT ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null ) {
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT);
|
||||
}
|
||||
Logger rootLogger = Logger.getLogger(ROOT_LOGGER);
|
||||
rootLogger.setLevel(Level.INFO);
|
||||
|
||||
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null
|
||||
&& LogManager.getLogManager().getProperty("java.util.logging.SimpleFormatter.format") == null ) {
|
||||
System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT);
|
||||
rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()));
|
||||
rootLogger.setUseParentHandlers(false);
|
||||
// System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT);
|
||||
rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()) {
|
||||
@Override
|
||||
public synchronized void publish(final LogRecord record) {
|
||||
super.publish(record);
|
||||
flush();
|
||||
}
|
||||
});
|
||||
// rootLogger.setUseParentHandlers(false);
|
||||
}
|
||||
if( rootLogger.getUseParentHandlers() ) {
|
||||
// This logger was not configured somewhere else
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.events;
|
||||
package schule.ngb.zm.util.events;
|
||||
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.events;
|
||||
package schule.ngb.zm.util.events;
|
||||
|
||||
public interface Listener<E> {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
package schule.ngb.zm.util;
|
||||
package schule.ngb.zm.util.io;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -1,7 +1,10 @@
|
||||
package schule.ngb.zm.util;
|
||||
package schule.ngb.zm.util.io;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
@@ -12,20 +15,50 @@ public class FontLoader {
|
||||
|
||||
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Lädt eine Schrift aus einer Datei.
|
||||
* <p>
|
||||
* Die Schrift wird unter ihrem Dateinamen in den Schriftenspeicher geladen
|
||||
* und kann danach in der Zeichenmaschine benutzt werden.
|
||||
*
|
||||
* Ein Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
||||
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
||||
* verwendet werden.
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
public static Font loadFont( String source ) {
|
||||
String name = source;
|
||||
// Dateipfad entfernen
|
||||
int lastIndex = source.lastIndexOf(File.separatorChar);
|
||||
if( lastIndex > -1 ) {
|
||||
source.substring(lastIndex + 1);
|
||||
}
|
||||
// Dateiendung entfernen
|
||||
lastIndex = name.lastIndexOf('.');
|
||||
if( lastIndex > -1 ) {
|
||||
name = name.substring(0, lastIndex);
|
||||
}
|
||||
return loadFont(name, source);
|
||||
}
|
||||
|
||||
public static Font loadFont( String name, String source ) {
|
||||
Objects.requireNonNull(source, "Font source may not be null");
|
||||
if( source.length() == 0 ) {
|
||||
throw new IllegalArgumentException("Font source may not be empty.");
|
||||
}
|
||||
|
||||
if( fontCache.containsKey(source) ) {
|
||||
LOG.trace("Retrieved font <%s> from font cache.", source);
|
||||
return fontCache.get(source);
|
||||
if( fontCache.containsKey(name) ) {
|
||||
LOG.trace("Retrieved font <%s> from font cache.", name);
|
||||
return fontCache.get(name);
|
||||
}
|
||||
|
||||
// Look for System fonts
|
||||
Font font = Font.decode(source);
|
||||
if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
||||
fontCache.put(name, font);
|
||||
fontCache.put(source, font);
|
||||
LOG.debug("Loaded system font for <%s>.", source);
|
||||
return font;
|
||||
@@ -38,10 +71,11 @@ public class FontLoader {
|
||||
font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN);
|
||||
|
||||
if( font != null ) {
|
||||
fontCache.put(name, font);
|
||||
fontCache.put(source, font);
|
||||
//ge.registerFont(font);
|
||||
}
|
||||
LOG.debug("Loaded custom font from <%s>.", source);
|
||||
LOG.debug("Loaded custom font from source <%s>.", source);
|
||||
} catch( IOException ioex ) {
|
||||
LOG.error(ioex, "Error loading custom font file from source <%s>.", source);
|
||||
} catch( FontFormatException ffex ) {
|
||||
@@ -1,4 +1,7 @@
|
||||
package schule.ngb.zm.util;
|
||||
package schule.ngb.zm.util.io;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.Color;
|
||||
@@ -1,12 +1,11 @@
|
||||
package schule.ngb.zm.util;
|
||||
package schule.ngb.zm.util.io;
|
||||
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Helferklasse, um {@link InputStream}s für Resourcen zu erhalten.
|
||||
@@ -1,9 +1,6 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
package schule.ngb.zm.util.tasks;
|
||||
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class DelayedTask extends Task implements Delayed {
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
package schule.ngb.zm.util.tasks;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
package schule.ngb.zm.util.tasks;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
package schule.ngb.zm.util.tasks;
|
||||
|
||||
public abstract class RateLimitedTask extends Task {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
package schule.ngb.zm.util.tasks;
|
||||
|
||||
import schule.ngb.zm.Updatable;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
package schule.ngb.zm.util.tasks;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Führt Aufgaben (Tasks) parallel zum Hauptprogramm aus.
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
@@ -1,5 +1,6 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.layers.Shape2DLayer;
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.turtle.TurtleLayer;
|
||||
import schule.ngb.zm.turtle.TurtleLayer.Turtle;
|
||||
import schule.ngb.zm.layers.TurtleLayer;
|
||||
import schule.ngb.zm.layers.TurtleLayer.Turtle;
|
||||
|
||||
public class TestTurtle extends Zeichenmaschine {
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
import schule.ngb.zm.layers.ShapesLayer;
|
||||
import schule.ngb.zm.shapes.*;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -60,7 +61,7 @@ class AnimationsTest {
|
||||
|
||||
private void _animateMove( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
||||
s.moveTo(0, 0);
|
||||
Future<Shape> future = Animations.animate(
|
||||
Future<Shape> future = Animations.play(
|
||||
s, runtime,
|
||||
easing,
|
||||
( e ) -> Constants.interpolate(0, zm.getWidth(), e),
|
||||
@@ -89,25 +90,11 @@ class AnimationsTest {
|
||||
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);
|
||||
Future<Shape> future = Animations.play(
|
||||
s, runtime, easing, (e) -> {
|
||||
double rad = Math.toRadians(Constants.interpolate(0, 360, e));
|
||||
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());
|
||||
@@ -146,7 +133,7 @@ class AnimationsTest {
|
||||
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(
|
||||
Future<Shape> future = Animations.play(
|
||||
s, runtime,
|
||||
easing,
|
||||
( e ) -> s.rotateTo(Constants.interpolate(0, 720, e))
|
||||
@@ -178,7 +165,7 @@ class AnimationsTest {
|
||||
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(
|
||||
Future<Shape> future = Animations.play(
|
||||
s, runtime,
|
||||
easing,
|
||||
( e ) -> Color.interpolate(from, to, e),
|
||||
|
||||
426
src/test/java/schule/ngb/zm/ml/MLMatrixTest.java
Normal file
@@ -0,0 +1,426 @@
|
||||
package schule.ngb.zm.ml;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import schule.ngb.zm.util.Timer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class MLMatrixTest {
|
||||
|
||||
private TestInfo info;
|
||||
|
||||
@BeforeEach
|
||||
void saveTestInfo( TestInfo info ) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void get( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix M = MatrixFactory.create(new double[][]{
|
||||
{1, 2, 3},
|
||||
{4, 5, 6}
|
||||
});
|
||||
|
||||
assertEquals(mType, M.getClass());
|
||||
|
||||
assertEquals(1.0, M.get(0,0));
|
||||
assertEquals(4.0, M.get(1,0));
|
||||
assertEquals(6.0, M.get(1,2));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void initializeOne( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix m = MatrixFactory.create(4, 4);
|
||||
m.initializeOne();
|
||||
|
||||
assertEquals(mType, m.getClass());
|
||||
|
||||
for( int i = 0; i < m.rows(); i++ ) {
|
||||
for( int j = 0; j < m.columns(); j++ ) {
|
||||
assertEquals(1.0, m.get(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void initializeZero( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix m = MatrixFactory.create(4, 4);
|
||||
m.initializeZero();
|
||||
|
||||
assertEquals(mType, m.getClass());
|
||||
|
||||
for( int i = 0; i < m.rows(); i++ ) {
|
||||
for( int j = 0; j < m.columns(); j++ ) {
|
||||
assertEquals(0.0, m.get(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void initializeRandom( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix m = MatrixFactory.create(4, 4);
|
||||
m.initializeRandom();
|
||||
|
||||
assertEquals(mType, m.getClass());
|
||||
|
||||
for( int i = 0; i < m.rows(); i++ ) {
|
||||
for( int j = 0; j < m.columns(); j++ ) {
|
||||
double d = m.get(i, j);
|
||||
assertTrue(-1.0 <= d && d < 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void multiplyTransposed( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix A = MatrixFactory.create(new double[][]{
|
||||
{1.0, 2.0, 3.0, 4.0},
|
||||
{1.0, 2.0, 3.0, 4.0},
|
||||
{1.0, 2.0, 3.0, 4.0}
|
||||
});
|
||||
MLMatrix B = MatrixFactory.create(new double[][]{
|
||||
{1, 3, 5, 7},
|
||||
{2, 4, 6, 8}
|
||||
});
|
||||
|
||||
MLMatrix C = A.multiplyTransposed(B);
|
||||
|
||||
assertEquals(mType, A.getClass());
|
||||
assertEquals(mType, B.getClass());
|
||||
assertEquals(mType, C.getClass());
|
||||
|
||||
assertEquals(3, C.rows());
|
||||
assertEquals(2, C.columns());
|
||||
for( int i = 0; i < C.rows(); i++ ) {
|
||||
assertEquals(50.0, C.get(i, 0));
|
||||
assertEquals(60.0, C.get(i, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void multiplyAddBias( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix A = MatrixFactory.create(new double[][]{
|
||||
{1.0, 2.0, 3.0, 4.0},
|
||||
{1.0, 2.0, 3.0, 4.0},
|
||||
{1.0, 2.0, 3.0, 4.0}
|
||||
});
|
||||
MLMatrix B = MatrixFactory.create(new double[][]{
|
||||
{1.0, 2.0},
|
||||
{3.0, 4.0},
|
||||
{5.0, 6.0},
|
||||
{7.0, 8.0}
|
||||
});
|
||||
MLMatrix V = MatrixFactory.create(new double[][]{
|
||||
{1000.0, 2000.0}
|
||||
});
|
||||
|
||||
MLMatrix C = A.multiplyAddBias(B, V);
|
||||
|
||||
assertEquals(mType, A.getClass());
|
||||
assertEquals(mType, B.getClass());
|
||||
assertEquals(mType, C.getClass());
|
||||
assertEquals(mType, V.getClass());
|
||||
|
||||
assertEquals(3, C.rows());
|
||||
assertEquals(2, C.columns());
|
||||
for( int i = 0; i < C.rows(); i++ ) {
|
||||
assertEquals(1050.0, C.get(i, 0));
|
||||
assertEquals(2060.0, C.get(i, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void transposedMultiplyAndScale( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix A = MatrixFactory.create(new double[][]{
|
||||
{1, 1, 1},
|
||||
{2, 2, 2},
|
||||
{3, 3, 3},
|
||||
{4, 4, 4}
|
||||
});
|
||||
MLMatrix B = MatrixFactory.create(new double[][]{
|
||||
{1.0, 2.0},
|
||||
{3.0, 4.0},
|
||||
{5.0, 6.0},
|
||||
{7.0, 8.0}
|
||||
});
|
||||
|
||||
MLMatrix C = A.transposedMultiplyAndScale(B, 2.0);
|
||||
|
||||
assertEquals(mType, A.getClass());
|
||||
assertEquals(mType, B.getClass());
|
||||
assertEquals(mType, C.getClass());
|
||||
|
||||
assertEquals(3, C.rows());
|
||||
assertEquals(2, C.columns());
|
||||
for( int i = 0; i < C.rows(); i++ ) {
|
||||
assertEquals(100.0, C.get(i, 0));
|
||||
assertEquals(120.0, C.get(i, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void apply( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix M = MatrixFactory.create(new double[][]{
|
||||
{1, 1, 1},
|
||||
{2, 2, 2},
|
||||
{3, 3, 3},
|
||||
{4, 4, 4}
|
||||
});
|
||||
|
||||
MLMatrix R = M.apply(( d ) -> d * d);
|
||||
|
||||
assertEquals(mType, M.getClass());
|
||||
assertEquals(mType, R.getClass());
|
||||
assertNotSame(M, R);
|
||||
|
||||
for( int i = 0; i < M.rows(); i++ ) {
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
(i + 1) * (i + 1), R.get(i, j),
|
||||
msg("(%d,%d)", "apply", i, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MLMatrix M2 = M.applyInPlace(( d ) -> d * d * d);
|
||||
assertSame(M, M2);
|
||||
for( int i = 0; i < M.rows(); i++ ) {
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
(i + 1) * (i + 1) * (i + 1), M.get(i, j),
|
||||
msg("(%d,%d)", "applyInPlace", i, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void add( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix M = MatrixFactory.create(new double[][]{
|
||||
{1, 1, 1},
|
||||
{2, 2, 2},
|
||||
{3, 3, 3},
|
||||
{4, 4, 4}
|
||||
});
|
||||
|
||||
MLMatrix R = M.add(M);
|
||||
|
||||
assertEquals(mType, M.getClass());
|
||||
assertEquals(mType, R.getClass());
|
||||
assertNotSame(M, R);
|
||||
|
||||
for( int i = 0; i < M.rows(); i++ ) {
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
(i + 1) + (i + 1), R.get(i, j),
|
||||
msg("(%d,%d)", "add", i, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MLMatrix M2 = M.addInPlace(R);
|
||||
assertSame(M, M2);
|
||||
for( int i = 0; i < M.rows(); i++ ) {
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
(i + 1) + (i + 1) + (i + 1), M.get(i, j),
|
||||
msg("(%d,%d)", "addInPlace", i, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void sub( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix M = MatrixFactory.create(new double[][]{
|
||||
{1, 1, 1},
|
||||
{2, 2, 2},
|
||||
{3, 3, 3},
|
||||
{4, 4, 4}
|
||||
});
|
||||
|
||||
MLMatrix R = M.sub(M);
|
||||
|
||||
assertEquals(mType, M.getClass());
|
||||
assertEquals(mType, R.getClass());
|
||||
assertNotSame(M, R);
|
||||
|
||||
for( int i = 0; i < M.rows(); i++ ) {
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
0.0, R.get(i, j),
|
||||
msg("(%d,%d)", "sub", i, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void colSums( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix M = MatrixFactory.create(new double[][]{
|
||||
{1, 2, 3},
|
||||
{1, 2, 3},
|
||||
{1, 2, 3},
|
||||
{1, 2, 3}
|
||||
});
|
||||
|
||||
MLMatrix R = M.colSums();
|
||||
|
||||
assertEquals(mType, M.getClass());
|
||||
assertEquals(mType, R.getClass());
|
||||
assertNotSame(M, R);
|
||||
|
||||
assertEquals(1, R.rows());
|
||||
assertEquals(3, R.columns());
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
(j+1)*4, R.get(0, j),
|
||||
msg("(%d,%d)", "colSums", 0, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void duplicate( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix M = MatrixFactory.create(new double[][]{
|
||||
{1, 2, 3},
|
||||
{1, 2, 3},
|
||||
{1, 2, 3},
|
||||
{1, 2, 3}
|
||||
});
|
||||
|
||||
MLMatrix R = M.duplicate();
|
||||
|
||||
assertEquals(mType, M.getClass());
|
||||
assertEquals(mType, R.getClass());
|
||||
assertNotSame(M, R);
|
||||
|
||||
for( int i = 0; i < M.rows(); i++ ) {
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
M.get(i, j), R.get(i, j),
|
||||
msg("(%d,%d)", "duplicate", i, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource( classes = {DoubleMatrix.class, MatrixFactory.ColtMatrix.class} )
|
||||
void scale( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
MLMatrix M = MatrixFactory.create(new double[][]{
|
||||
{1, 1, 1},
|
||||
{2, 2, 2},
|
||||
{3, 3, 3},
|
||||
{4, 4, 4}
|
||||
});
|
||||
|
||||
MLMatrix M2 = M.scaleInPlace(2.0);
|
||||
|
||||
assertEquals(mType, M.getClass());
|
||||
assertEquals(mType, M2.getClass());
|
||||
assertSame(M, M2);
|
||||
|
||||
for( int i = 0; i < M.rows(); i++ ) {
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
(i+1)*2.0, M2.get(i, j),
|
||||
msg("(%d,%d)", "scaleInPlace", i, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MLMatrix M3 = M.scaleInPlace(M);
|
||||
assertSame(M, M3);
|
||||
for( int i = 0; i < M.rows(); i++ ) {
|
||||
for( int j = 0; j < M.columns(); j++ ) {
|
||||
assertEquals(
|
||||
((i+1)*2.0)*((i+1)*2.0), M.get(i, j),
|
||||
msg("(%d,%d)", "addInPlace", i, j)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String msg( String msg, String methodName, Object... args ) {
|
||||
String testName = this.info.getTestMethod().get().getName();
|
||||
String className = MatrixFactory.matrixType.getSimpleName();
|
||||
return String.format("[" + testName + "(" + className + ") " + methodName + "()] " + msg, args);
|
||||
}
|
||||
|
||||
//@ParameterizedTest
|
||||
//@ValueSource( classes = {MatrixFactory.ColtMatrix.class, DoubleMatrix.class} )
|
||||
void speed( Class<? extends MLMatrix> mType ) {
|
||||
MatrixFactory.matrixType = mType;
|
||||
|
||||
int N = 10;
|
||||
int rows = 1000;
|
||||
int cols = 1000;
|
||||
|
||||
Timer timer = new Timer();
|
||||
|
||||
MLMatrix M = MatrixFactory.create(rows, cols);
|
||||
timer.start();
|
||||
for( int i = 0; i < N; i++ ) {
|
||||
M.initializeRandom();
|
||||
}
|
||||
timer.stop();
|
||||
System.err.println(msg("%d iterations: %d ms", "initializeRandom", N, timer.getMillis()));
|
||||
|
||||
timer.reset();
|
||||
|
||||
MLMatrix B = MatrixFactory.create(rows*2, M.columns());
|
||||
B.initializeRandom();
|
||||
|
||||
timer.start();
|
||||
for( int i = 0; i < N; i++ ) {
|
||||
M.multiplyTransposed(B);
|
||||
}
|
||||
timer.stop();
|
||||
System.err.println(msg("%d iterations: %d ms", "multiplyTransposed", N, timer.getMillis()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,15 +2,14 @@ package schule.ngb.zm.ml;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Timer;
|
||||
|
||||
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
|
||||
@@ -18,7 +17,14 @@ class NeuralNetworkTest {
|
||||
Log.enableGlobalDebugging();
|
||||
}
|
||||
|
||||
@Test
|
||||
@BeforeAll
|
||||
static void setupMatrixLibrary() {
|
||||
Constants.setSeed(1001);
|
||||
//MatrixFactory.matrixType = MatrixFactory.ColtMatrix.class;
|
||||
MatrixFactory.matrixType = DoubleMatrix.class;
|
||||
}
|
||||
|
||||
/*@Test
|
||||
void readWrite() {
|
||||
// XOR Dataset
|
||||
NeuralNetwork net = new NeuralNetwork(2, 4, 1);
|
||||
@@ -53,7 +59,7 @@ class NeuralNetworkTest {
|
||||
}
|
||||
|
||||
assertArrayEquals(net.predict(inputs), net2.predict(inputs));
|
||||
}
|
||||
}*/
|
||||
|
||||
@Test
|
||||
void learnXor() {
|
||||
@@ -78,14 +84,14 @@ class NeuralNetworkTest {
|
||||
}
|
||||
|
||||
// calculate predictions
|
||||
double[][] predictions = net.predict(inputs);
|
||||
MLMatrix predictions = net.predict(inputs);
|
||||
for( int i = 0; i < 4; i++ ) {
|
||||
int parsed_pred = predictions[i][0] < 0.5 ? 0 : 1;
|
||||
int parsed_pred = predictions.get(i, 0) < 0.5 ? 0 : 1;
|
||||
|
||||
System.out.printf(
|
||||
"{%.0f, %.0f} = %.4f (%d) -> %s\n",
|
||||
inputs[i][0], inputs[i][1],
|
||||
predictions[i][0],
|
||||
predictions.get(i, 0),
|
||||
parsed_pred,
|
||||
parsed_pred == outputs[i][0] ? "correct" : "miss"
|
||||
);
|
||||
@@ -109,12 +115,16 @@ class NeuralNetworkTest {
|
||||
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;
|
||||
outputs[i][0] = trainingData.get(i).getResult();
|
||||
}
|
||||
|
||||
Timer timer = new Timer();
|
||||
|
||||
System.out.println("Training the neural net to learn "+OPERATION+"...");
|
||||
timer.start();
|
||||
net.train(inputs, outputs, TRAINING_CYCLES);
|
||||
System.out.println(" finished training");
|
||||
timer.stop();
|
||||
System.out.println(" finished training (" + timer.getMillis() + "ms)");
|
||||
|
||||
for( int i = 1; i <= net.getLayerCount(); i++ ) {
|
||||
System.out.println("Layer " +i + " weights");
|
||||
@@ -136,19 +146,18 @@ class NeuralNetworkTest {
|
||||
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
|
||||
net.getOutput().get(0, 0),
|
||||
data.getResult(),
|
||||
net.getOutput().get(0, 0) - data.getResult()
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
double s1 = Constants.random() * 0.5;
|
||||
double s2 = Constants.random() * 0.5;
|
||||
|
||||
switch( operation ) {
|
||||
case ADD:
|
||||
@@ -181,7 +190,6 @@ class NeuralNetworkTest {
|
||||
|
||||
double a;
|
||||
double b;
|
||||
double result;
|
||||
CalcType type;
|
||||
|
||||
TestData( double a, double b ) {
|
||||
@@ -189,6 +197,8 @@ class NeuralNetworkTest {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
abstract double getResult();
|
||||
|
||||
}
|
||||
|
||||
private static final class AddData extends TestData {
|
||||
@@ -197,7 +207,9 @@ class NeuralNetworkTest {
|
||||
|
||||
public AddData( double a, double b ) {
|
||||
super(a, b);
|
||||
result = a + b;
|
||||
}
|
||||
double getResult() {
|
||||
return a+b;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -208,7 +220,9 @@ class NeuralNetworkTest {
|
||||
|
||||
public SubData( double a, double b ) {
|
||||
super(a, b);
|
||||
result = a - b;
|
||||
}
|
||||
double getResult() {
|
||||
return a-b;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -219,7 +233,9 @@ class NeuralNetworkTest {
|
||||
|
||||
public MulData( double a, double b ) {
|
||||
super(a, b);
|
||||
result = a * b;
|
||||
}
|
||||
double getResult() {
|
||||
return a*b;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -233,7 +249,9 @@ class NeuralNetworkTest {
|
||||
if( b == 0.0 ) {
|
||||
b = .1;
|
||||
}
|
||||
result = a / b;
|
||||
}
|
||||
double getResult() {
|
||||
return a/b;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -244,7 +262,12 @@ class NeuralNetworkTest {
|
||||
|
||||
public ModData( double b, double a ) {
|
||||
super(b, a);
|
||||
result = a % b;
|
||||
if( b == 0.0 ) {
|
||||
b = .1;
|
||||
}
|
||||
}
|
||||
double getResult() {
|
||||
return a%b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import schule.ngb.zm.util.io.FileLoader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.events;
|
||||
package schule.ngb.zm.util.events;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||