Compare commits
82 Commits
v0.0.22-SN
...
ec30afd441
| Author | SHA1 | Date | |
|---|---|---|---|
| ec30afd441 | |||
| d3bdbdbffb | |||
| 8cc7167d7e | |||
| 9e4271c304 | |||
| 4f13f5177d | |||
| 6321a7d421 | |||
| 135af10729 | |||
| 912f68c58f | |||
| 7f1d9012e9 | |||
| 60ed045986 | |||
| dc16608333 | |||
| 7b6398fe52 | |||
| 8f98ddc56d | |||
| 782ce33540 | |||
| 537527e525 | |||
| 8e93866b5e | |||
| fcb536ff96 | |||
| 70c607f2e8 | |||
| 6126ed3c15 | |||
| b0353c53a0 | |||
| c93a203ab9 | |||
| f1d32685b4 | |||
| 91842b511f | |||
| 52b480b46b | |||
| 4d2ade899d | |||
| dcdca893b7 | |||
| ebf0135486 | |||
| fea1083926 | |||
| 250d9d17d3 | |||
| 2a71243fc6 | |||
| 03d37222bf | |||
| 687d7d35b7 | |||
| 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 |
8
.gitignore
vendored
@@ -48,3 +48,11 @@ gradle-app.setting
|
|||||||
|
|
||||||
# Cache of project
|
# Cache of project
|
||||||
.gradletasknamecache
|
.gradletasknamecache
|
||||||
|
|
||||||
|
# Python mkdocs
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|||||||
17
CHANGELOG.md
@@ -6,13 +6,24 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `FilledShape` und `StrokedShape` durch `Fillable` und `Strokeable` Interfaces ersetzt.
|
||||||
|
- `Shape` erweitert nun `BasisDrawable` als abstrakte Grundlage.
|
||||||
|
- `io` Klassen nutzen nun mehr der `java.nio` Funktionen.
|
||||||
|
- Package-Struktur angepasst.
|
||||||
|
|
||||||
|
## Version 0.0.23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- System für EventListener erstellt
|
- System für EventListener.
|
||||||
- `AudioListener` und `AnimationListener` als erste Anwendungsfälle.
|
- `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
|
### Changed
|
||||||
- `update(double)` und `draw()` werden nun in einem eigenen Thread aufgerufen.
|
- `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
|
## Version 0.0.22
|
||||||
|
|
||||||
@@ -24,7 +35,7 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
### Changed
|
### Changed
|
||||||
- Neue Package-Struktur:
|
- Neue Package-Struktur:
|
||||||
- `schule.ngb.zm.media` für Audio-Klassen (und ggf. zukünftig Video).
|
- `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.
|
- `Zeichenthread` und `TaskRunner` setzen die Namen der Threads für besseres Debugging.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|||||||
14
build.gradle
@@ -7,13 +7,14 @@ plugins {
|
|||||||
zmVersion {
|
zmVersion {
|
||||||
major = 0;
|
major = 0;
|
||||||
minor = 0;
|
minor = 0;
|
||||||
rev = 21;
|
rev = 32;
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
version '{$zmVersion.major}.{$zmVersion.minor}.{$zmVersion.rev}-SNAPSHOT'
|
||||||
|
*/
|
||||||
|
|
||||||
group 'schule.ngb'
|
group 'schule.ngb'
|
||||||
version '0.0.22-SNAPSHOT'
|
version '0.0.32-SNAPSHOT'
|
||||||
//version '{$zmVersion.major}.{$zmVersion.minor}.{$zmVersion.rev}-SNAPSHOT'
|
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
options.release = 11
|
options.release = 11
|
||||||
@@ -28,7 +29,12 @@ dependencies {
|
|||||||
runtimeOnly 'com.googlecode.soundlibs:tritonus-share:0.3.7.4'
|
runtimeOnly 'com.googlecode.soundlibs:tritonus-share:0.3.7.4'
|
||||||
runtimeOnly 'com.googlecode.soundlibs:mp3spi:1.9.5.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'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.1'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
0
docs/quickstart.md
Normal file
236
src/main/java/schule/ngb/zm/BasicDrawable.java
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basisimplementierung der {@link Strokeable} und {@link Fillable} APIs.
|
||||||
|
*
|
||||||
|
* Die Klasse bietet eine Grundlage zur Implementierung eigener Zeichenobjekte,
|
||||||
|
* die eine Füllung und eine Konturlinie haben können.
|
||||||
|
*/
|
||||||
|
public abstract class BasicDrawable extends Constants implements Strokeable, Fillable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob das Objekt gezeichnet werden soll.
|
||||||
|
*/
|
||||||
|
protected boolean visible = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktuelle Farbe der Konturlinie oder {@code null}, wenn das Objekt ohne
|
||||||
|
* Kontur dargestellt werden soll.
|
||||||
|
*/
|
||||||
|
protected schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die aktuelle Füllfarbe der Form oder {@code null}, wenn das Objekt nicht
|
||||||
|
* gefüllt werden soll.
|
||||||
|
*/
|
||||||
|
protected Color fillColor = DEFAULT_FILLCOLOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der aktuelle Farbverlauf des Objektes oder {@code null}, wenn es keinen
|
||||||
|
* Farbverlauf besitzt.
|
||||||
|
*/
|
||||||
|
protected MultipleGradientPaint fill = null;
|
||||||
|
|
||||||
|
|
||||||
|
// Implementierung Drawable Interface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob das Objekt angezeigt bzw. gezeichnet werden soll.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn das Objekt angezeigt werden soll,
|
||||||
|
* {@code false} sonst.
|
||||||
|
*/
|
||||||
|
public boolean isVisible() {
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versteckt das Objekt.
|
||||||
|
*/
|
||||||
|
public void hide() {
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt das Objekt an.
|
||||||
|
*/
|
||||||
|
public void show() {
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versteckt da Objekt, wenn es derzeit angezeigt wird und zeigt es
|
||||||
|
* andernfalls an.
|
||||||
|
*/
|
||||||
|
public void toggle() {
|
||||||
|
visible = !visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void draw( Graphics2D graphics );
|
||||||
|
|
||||||
|
|
||||||
|
// Implementierung Fillable Interface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFill( Paint fill ) {
|
||||||
|
if( fill == null ) {
|
||||||
|
this.fill = null;
|
||||||
|
} else if( fill instanceof Color ) {
|
||||||
|
this.fillColor = ((Color) fill);
|
||||||
|
this.fill = null;
|
||||||
|
} else if( fill instanceof MultipleGradientPaint ) {
|
||||||
|
this.fillColor = null;
|
||||||
|
this.fill = (MultipleGradientPaint) fill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Paint getFill() {
|
||||||
|
if( fill != null ) {
|
||||||
|
return fill;
|
||||||
|
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||||
|
return fillColor;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean hasFillColor() {
|
||||||
|
return fillColor != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean hasGradient() {
|
||||||
|
return fill != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Color getFillColor() {
|
||||||
|
return fillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFillColor( Color color ) {
|
||||||
|
fillColor = color;
|
||||||
|
fill = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public MultipleGradientPaint getGradient() {
|
||||||
|
return fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Implementierung Strokeable Interface
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setStroke( Stroke stroke ) {
|
||||||
|
this.stroke = stroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Stroke getStroke() {
|
||||||
|
if( stroke == null ) {
|
||||||
|
stroke = Strokeable.createStroke(strokeType, strokeWeight);
|
||||||
|
}
|
||||||
|
return stroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Color getStrokeColor() {
|
||||||
|
return strokeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( Color color ) {
|
||||||
|
strokeColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public double getStrokeWeight() {
|
||||||
|
return strokeWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setStrokeWeight( double weight ) {
|
||||||
|
strokeWeight = weight;
|
||||||
|
this.stroke = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Options.StrokeType getStrokeType() {
|
||||||
|
return strokeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setStrokeType( Options.StrokeType type ) {
|
||||||
|
strokeType = type;
|
||||||
|
this.stroke = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
package schule.ngb.zm;
|
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.
|
* Repräsentiert eine Farbe in der Zeichenmaschine.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -9,7 +14,8 @@ package schule.ngb.zm;
|
|||||||
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
||||||
* (deckend).
|
* (deckend).
|
||||||
*/
|
*/
|
||||||
public class Color {
|
@SuppressWarnings( "unused" )
|
||||||
|
public class Color implements Paint {
|
||||||
|
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
@@ -146,7 +152,7 @@ public class Color {
|
|||||||
* @param alpha Transparentwert zwischen 0 und 255.
|
* @param alpha Transparentwert zwischen 0 und 255.
|
||||||
*/
|
*/
|
||||||
public Color( int red, int green, int blue, int alpha ) {
|
public Color( int red, int green, int blue, int alpha ) {
|
||||||
rgba = ((alpha&0xFF) << 24) | ((red&0xFF) << 16) | ((green&0xFF) << 8) | ((blue&0xFF) << 0);
|
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,7 +161,7 @@ public class Color {
|
|||||||
* @param color
|
* @param color
|
||||||
*/
|
*/
|
||||||
public Color( Color color ) {
|
public Color( Color color ) {
|
||||||
this(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
this(color.getRGBA(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,8 +209,7 @@ public class Color {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static Color getRGBColor( int rgba ) {
|
public static Color getRGBColor( int rgba ) {
|
||||||
Color c = new Color(rgba, true);
|
return new Color(rgba, true);
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color getHSBColor( double h, double s, double b ) {
|
public static Color getHSBColor( double h, double s, double b ) {
|
||||||
@@ -254,8 +259,6 @@ public class Color {
|
|||||||
} else if( hexcode.length() == 8 ) {
|
} else if( hexcode.length() == 8 ) {
|
||||||
alpha = Integer.valueOf(hexcode.substring(6, 8), 16);
|
alpha = Integer.valueOf(hexcode.substring(6, 8), 16);
|
||||||
hexcode = hexcode.substring(0, 6);
|
hexcode = hexcode.substring(0, 6);
|
||||||
} else {
|
|
||||||
hexcode = hexcode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Color.getRGBColor((alpha << 24) | Integer.valueOf(hexcode, 16));
|
return Color.getRGBColor((alpha << 24) | Integer.valueOf(hexcode, 16));
|
||||||
@@ -302,12 +305,12 @@ public class Color {
|
|||||||
if( c == 0 ) {
|
if( c == 0 ) {
|
||||||
h_ = 0;
|
h_ = 0;
|
||||||
} else if( max == r ) {
|
} else if( max == r ) {
|
||||||
h_ = (float) (g - b) / c;
|
h_ = (g - b) / c;
|
||||||
if( h_ < 0 ) h_ += 6.f;
|
if( h_ < 0 ) h_ += 6.f;
|
||||||
} else if( max == g ) {
|
} else if( max == g ) {
|
||||||
h_ = (float) (b - r) / c + 2.f;
|
h_ = (b - r) / c + 2.f;
|
||||||
} else if( max == b ) {
|
} else if( max == b ) {
|
||||||
h_ = (float) (r - g) / c + 4.f;
|
h_ = (r - g) / c + 4.f;
|
||||||
}
|
}
|
||||||
float h = 60.f * h_;
|
float h = 60.f * h_;
|
||||||
|
|
||||||
@@ -463,6 +466,22 @@ public class Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
||||||
*
|
*
|
||||||
@@ -472,8 +491,11 @@ public class Color {
|
|||||||
* @param obj Das zu vergleichende Objekt.
|
* @param obj Das zu vergleichende Objekt.
|
||||||
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean equals( Object obj ) {
|
public boolean equals( Object obj ) {
|
||||||
if( obj == null ) { return false; }
|
if( obj == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if( obj instanceof Color ) {
|
if( obj instanceof Color ) {
|
||||||
return ((Color) obj).getRGBA() == this.rgba;
|
return ((Color) obj).getRGBA() == this.rgba;
|
||||||
} else if( obj instanceof java.awt.Color ) {
|
} 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,23 +1,26 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import schule.ngb.zm.anim.Easing;
|
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 schule.ngb.zm.util.Noise;
|
||||||
|
|
||||||
import java.awt.Cursor;
|
import java.awt.Cursor;
|
||||||
|
import java.awt.Font;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basisklasse für die meisten Objekte der Zeichemaschine, die von Nutzern
|
* Basisklasse für die meisten Objekte der Zeichenmaschine, die von Nutzern
|
||||||
* erweitert werden können.
|
* erweitert werden können.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Konstanten stellen viele Funktionen zur einfachen Programmierung bereit
|
* Die Konstanten stellen viele Funktionen zur einfachen Programmierung bereit
|
||||||
* und enthält auch einige dynamische Werte, die von der Zeichenmaschine laufend
|
* und enthält auch einige dynamische Werte, die von der Zeichenmaschine laufend
|
||||||
* aktuell gehalten werden (beispielsweise {@link #runtime}.
|
* aktuell gehalten werden (beispielsweise {@link #runtime}).
|
||||||
* <p>
|
* <p>
|
||||||
* Für die Implementierung eigener Klassen ist es oft hilfreich von
|
* Für die Implementierung eigener Klassen ist es oft hilfreich von
|
||||||
* {@code Constants} zu erben, um die Methoden und Konstanten einfach im
|
* {@code Constants} zu erben, um die Methoden und Konstanten einfach im
|
||||||
@@ -64,53 +67,89 @@ public class Constants {
|
|||||||
/**
|
/**
|
||||||
* Patchversion der Zeichenmaschine.
|
* Patchversion der Zeichenmaschine.
|
||||||
*/
|
*/
|
||||||
public static final int APP_VERSION_REV = 22;
|
public static final int APP_VERSION_REV = 32;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version der Zeichenmaschine als Text-String.
|
* Version der Zeichenmaschine als Text-String.
|
||||||
*/
|
*/
|
||||||
public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV;
|
public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob die Zeichenmaschine unter macOS gestartet wurde.
|
||||||
|
*/
|
||||||
|
public static final boolean MACOS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob die Zeichenmaschine unter Windows gestartet wurde.
|
||||||
|
*/
|
||||||
|
public static final boolean WINDOWS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob die Zeichenmaschine unter Linux gestartet wurde.
|
||||||
|
*/
|
||||||
|
public static final boolean LINUX;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final String name = System.getProperty("os.name");
|
||||||
|
|
||||||
|
if( name.contains("Mac") ) {
|
||||||
|
MACOS = true;
|
||||||
|
WINDOWS = false;
|
||||||
|
LINUX = false;
|
||||||
|
} else if( name.contains("Windows") ) {
|
||||||
|
MACOS = false;
|
||||||
|
WINDOWS = true;
|
||||||
|
LINUX = false;
|
||||||
|
} else if( name.equals("Linux") ) { // true for the ibm vm
|
||||||
|
MACOS = false;
|
||||||
|
WINDOWS = false;
|
||||||
|
LINUX = true;
|
||||||
|
} else {
|
||||||
|
MACOS = false;
|
||||||
|
WINDOWS = false;
|
||||||
|
LINUX = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standardbreite eines Zeichenfensters.
|
* Standardbreite eines Zeichenfensters.
|
||||||
*/
|
*/
|
||||||
public static final int STD_WIDTH = 400;
|
public static final int DEFAULT_WIDTH = 400;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standardhöhe eines Zeichenfensters.
|
* 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.
|
* 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.
|
* Standardfarbe der Füllungen.
|
||||||
*/
|
*/
|
||||||
public static final Color STD_FILLCOLOR = Color.WHITE;
|
public static Color DEFAULT_FILLCOLOR = Color.WHITE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standardfarbe der Konturen.
|
* 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.
|
* 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.
|
* 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.
|
* 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;
|
public static int DEFAULT_ANIM_RUNTIME = 1000;
|
||||||
|
|
||||||
@@ -405,16 +444,32 @@ public class Constants {
|
|||||||
*/
|
*/
|
||||||
public static final double TWO_PI = Math.PI * 2.0;
|
public static final double TWO_PI = Math.PI * 2.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstante für fette Schrift.
|
||||||
|
*/
|
||||||
|
public static final int BOLD = Font.BOLD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstante für kursive Schrift.
|
||||||
|
*/
|
||||||
|
public static final int ITALIC = Font.ITALIC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstante für normale Schrift.
|
||||||
|
*/
|
||||||
|
public static final int PLAIN = Font.PLAIN;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Globale Variablen, die von allen Klassen genutzt werden dürfen. Änderungen
|
* Globale Variablen, die von allen Klassen genutzt werden dürfen. Änderungen
|
||||||
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
|
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
|
||||||
* Zeichenmaschine selbst vorgenommen werden.
|
* Zeichenmaschine selbst vorgenommen werden.
|
||||||
*/
|
*/
|
||||||
|
// TODO: (ngb) volatile ?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktuell dargestellte Bilder pro Sekunde.
|
* 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.
|
* Anzahl der Ticks (Frames), die das Programm bisher läuft.
|
||||||
@@ -1269,7 +1324,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.
|
* @return Eine Zufallszahl.
|
||||||
* @see Random#nextGaussian()
|
* @see Random#nextGaussian()
|
||||||
@@ -1278,6 +1334,26 @@ public class Constants {
|
|||||||
return getRandom().nextGaussian();
|
return getRandom().nextGaussian();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt ein zufälliges Element aus dem Array aus.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
*/
|
||||||
|
public static final int choice( int[] values ) {
|
||||||
|
return values[random(0, values.length - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt ein zufälliges Element aus dem Array aus.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
*/
|
||||||
|
public static final double choice( double[] values ) {
|
||||||
|
return values[random(0, values.length - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wählt ein zufälliges Element aus dem Array aus.
|
* Wählt ein zufälliges Element aus dem Array aus.
|
||||||
*
|
*
|
||||||
@@ -1294,15 +1370,149 @@ public class Constants {
|
|||||||
*
|
*
|
||||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
* @param n Anzahl der auszuwählenden Elemente.
|
* @param n Anzahl der auszuwählenden Elemente.
|
||||||
|
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||||
|
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||||
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||||
|
* {@code values.length < n}, also nicht
|
||||||
|
* genug Werte zur Wahl stehen.
|
||||||
|
*/
|
||||||
|
public static final int[] choice( int[] values, int n, boolean unique ) {
|
||||||
|
if( unique && values.length < n )
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||||
|
);
|
||||||
|
|
||||||
|
int[] result = new int[n];
|
||||||
|
int[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
int j = random(0, valuesCopy.length - 1);
|
||||||
|
int l = valuesCopy.length - 1;
|
||||||
|
|
||||||
|
result[i] = valuesCopy[j];
|
||||||
|
valuesCopy[j] = valuesCopy[l];
|
||||||
|
valuesCopy[l] = result[i];
|
||||||
|
|
||||||
|
if( unique )
|
||||||
|
valuesCopy = Arrays.copyOf(valuesCopy, l);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt die angegebene Anzahl Elemente aus dem Array aus.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
|
* @param n Anzahl der auszuwählenden Elemente.
|
||||||
|
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||||
|
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||||
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||||
|
* {@code values.length < n}, also nicht
|
||||||
|
* genug Werte zur Wahl stehen.
|
||||||
|
*/
|
||||||
|
public static final double[] choice( double[] values, int n, boolean unique ) {
|
||||||
|
if( unique && values.length < n )
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||||
|
);
|
||||||
|
|
||||||
|
double[] result = new double[n];
|
||||||
|
double[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
int j = random(0, valuesCopy.length - 1);
|
||||||
|
int l = valuesCopy.length - 1;
|
||||||
|
|
||||||
|
result[i] = valuesCopy[j];
|
||||||
|
valuesCopy[j] = valuesCopy[l];
|
||||||
|
valuesCopy[l] = result[i];
|
||||||
|
|
||||||
|
if( unique )
|
||||||
|
valuesCopy = Arrays.copyOf(valuesCopy, l);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt die angegebene Anzahl Elemente aus dem Array aus.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
|
* @param n Anzahl der auszuwählenden Elemente.
|
||||||
|
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||||
|
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||||
* @param <T> Datentyp der Elemente.
|
* @param <T> Datentyp der Elemente.
|
||||||
* @return Ein zufälliges Element aus dem Array.
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
|
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||||
|
* {@code values.length < n}, also nicht
|
||||||
|
* genug Werte zur Wahl stehen.
|
||||||
*/
|
*/
|
||||||
public static final <T> T[] choice( T[] values, int n ) {
|
public static final <T> T[] choice( T[] values, int n, boolean unique ) {
|
||||||
Object[] result = new Object[n];
|
if( unique && values.length < n )
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||||
|
);
|
||||||
|
|
||||||
|
T[] result = Arrays.copyOf(values, n);
|
||||||
|
T[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||||
for( int i = 0; i < n; i++ ) {
|
for( int i = 0; i < n; i++ ) {
|
||||||
result[i] = choice(values);
|
int last = valuesCopy.length - 1;
|
||||||
|
int j = random(0, last);
|
||||||
|
|
||||||
|
result[i] = valuesCopy[j];
|
||||||
|
valuesCopy[j] = valuesCopy[last];
|
||||||
|
valuesCopy[last] = result[i];
|
||||||
|
|
||||||
|
if( unique )
|
||||||
|
valuesCopy = Arrays.copyOf(valuesCopy, last);
|
||||||
}
|
}
|
||||||
return (T[]) result;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bringt die Zahlen im Array in eine zufällige Reihenfolge.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Zahlen, die gemischt werden sollen.
|
||||||
|
* @return Das Array in zufälliger Reihenfolge.
|
||||||
|
*/
|
||||||
|
public static final int[] shuffle( int[] values ) {
|
||||||
|
for( int i = 0; i < values.length - 1; i++ ) {
|
||||||
|
int j = random(i, values.length - 1);
|
||||||
|
int tmp = values[i];
|
||||||
|
values[i] = values[j];
|
||||||
|
values[j] = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bringt die Zahlen im Array in eine zufällige Reihenfolge.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Zahlen, die gemischt werden sollen.
|
||||||
|
* @return Das Array in zufälliger Reihenfolge.
|
||||||
|
*/
|
||||||
|
public static final double[] shuffle( double[] values ) {
|
||||||
|
for( int i = 0; i < values.length - 1; i++ ) {
|
||||||
|
int j = random(i, values.length - 1);
|
||||||
|
double tmp = values[i];
|
||||||
|
values[i] = values[j];
|
||||||
|
values[j] = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bringt die Werte im Array in eine zufällige Reihenfolge.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werte, die gemischt werden sollen.
|
||||||
|
* @param <T> Datentyp der Elemente.
|
||||||
|
* @return Das Array in zufälliger Reihenfolge.
|
||||||
|
*/
|
||||||
|
public static final <T> T[] shuffle( T[] values ) {
|
||||||
|
java.util.List<T> valueList = Arrays.asList(values);
|
||||||
|
Collections.shuffle(valueList, random);
|
||||||
|
return valueList.toArray(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1472,7 +1682,7 @@ public class Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final double asDouble( char value ) {
|
public static final double asDouble( char value ) {
|
||||||
return (double) value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final double asDouble( byte value ) {
|
public static final double asDouble( byte value ) {
|
||||||
|
|||||||
@@ -3,14 +3,29 @@ package schule.ngb.zm;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeichenbare Objekte können auf eine Zeichenfläche gezeichnet werden.
|
* {@code Drawable} Objekte können auf eine Zeichenfläche gezeichnet werden. In
|
||||||
* In der Regel werden sie einmal pro Frame gezeichnet.
|
* der Regel werden sie einmal pro Frame gezeichnet.
|
||||||
*/
|
*/
|
||||||
public interface Drawable {
|
public interface Drawable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt an, ob das Objekt derzeit sichtbar ist (also gezeichnet werden
|
* Gibt an, ob das Objekt derzeit sichtbar ist (also gezeichnet werden
|
||||||
* muss).
|
* 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.
|
* @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>
|
* Wird aufgerufen, um das Objekt auf die Zeichenfläche <var>graphics</var>
|
||||||
* zu draw.
|
* zu zeichnen.
|
||||||
* <p>
|
* <p>
|
||||||
* Das Objekt muss dafür Sorge tragen, dass der Zustand der Zeichenfläche
|
* Das Objekt muss dafür Sorge tragen, dass der Zustand der Zeichenfläche
|
||||||
* (Transformationsmatrix, Farbe, ...) erhalten bleibt. Das Objekt sollte
|
* (Transformationsmatrix, Farbe, ...) erhalten bleibt. Das Objekt sollte
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
public class DrawableLayer extends Layer {
|
|
||||||
|
|
||||||
protected LinkedList<Drawable> drawables = new LinkedList<>();
|
|
||||||
|
|
||||||
protected boolean clearBeforeDraw = true;
|
|
||||||
|
|
||||||
public DrawableLayer() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrawableLayer( int width, int height ) {
|
|
||||||
super(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add( Drawable... drawables ) {
|
|
||||||
synchronized( drawables ) {
|
|
||||||
for( Drawable d : drawables ) {
|
|
||||||
this.drawables.add(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public java.util.List<Drawable> getDrawables() {
|
|
||||||
return drawables;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isClearBeforeDraw() {
|
|
||||||
return clearBeforeDraw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClearBeforeDraw( boolean pClearBeforeDraw ) {
|
|
||||||
this.clearBeforeDraw = pClearBeforeDraw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw( Graphics2D pGraphics ) {
|
|
||||||
if( clearBeforeDraw ) {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized( drawables ) {
|
|
||||||
for( Drawable d : drawables ) {
|
|
||||||
if( d.isVisible() ) {
|
|
||||||
d.draw(drawing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.draw(pGraphics);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,560 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.ImageLoader;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.geom.*;
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
public class DrawingLayer extends Layer {
|
|
||||||
|
|
||||||
protected Color fillColor = STD_FILLCOLOR;
|
|
||||||
|
|
||||||
protected Color strokeColor = STD_STROKECOLOR;
|
|
||||||
|
|
||||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
|
||||||
|
|
||||||
protected Options.StrokeType strokeType = SOLID;
|
|
||||||
|
|
||||||
private Options.Direction default_anchor = CENTER;
|
|
||||||
|
|
||||||
protected Line2D.Double line = new Line2D.Double();
|
|
||||||
protected Ellipse2D.Double ellipse = new Ellipse2D.Double();
|
|
||||||
protected Rectangle2D.Double rect = new Rectangle2D.Double();
|
|
||||||
protected Arc2D.Double arc = new Arc2D.Double();
|
|
||||||
|
|
||||||
protected Path2D.Double path = new Path2D.Double();
|
|
||||||
|
|
||||||
private boolean pathStarted = false;
|
|
||||||
|
|
||||||
private Stack<AffineTransform> transformStack = new Stack<>();
|
|
||||||
|
|
||||||
private FontMetrics fontMetrics = null;
|
|
||||||
|
|
||||||
public DrawingLayer() {
|
|
||||||
super();
|
|
||||||
transformStack.push(new AffineTransform());
|
|
||||||
fontMetrics = drawing.getFontMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrawingLayer( int width, int height ) {
|
|
||||||
super(width, height);
|
|
||||||
transformStack.push(new AffineTransform());
|
|
||||||
fontMetrics = drawing.getFontMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color getColor() {
|
|
||||||
return fillColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int gray ) {
|
|
||||||
setFillColor(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( Color color ) {
|
|
||||||
fillColor = color;
|
|
||||||
drawing.setColor(color.getJavaColor());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void noFill() {
|
|
||||||
fillColor = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int gray, int alpha ) {
|
|
||||||
setFillColor(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue ) {
|
|
||||||
setFillColor(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
|
||||||
setFillColor(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color getStrokeColor() {
|
|
||||||
return strokeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int gray ) {
|
|
||||||
setStrokeColor(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( Color color ) {
|
|
||||||
strokeColor = color;
|
|
||||||
drawing.setColor(color.getJavaColor());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void noStroke() {
|
|
||||||
strokeColor = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int gray, int alpha ) {
|
|
||||||
setStrokeColor(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int red, int green, int blue ) {
|
|
||||||
setStrokeColor(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
|
||||||
setStrokeColor(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeWeight( double pWeight ) {
|
|
||||||
strokeWeight = pWeight;
|
|
||||||
drawing.setStroke(createStroke());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Stroke createStroke() {
|
|
||||||
switch( strokeType ) {
|
|
||||||
case DOTTED:
|
|
||||||
return new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
BasicStroke.JOIN_ROUND,
|
|
||||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
|
||||||
case DASHED:
|
|
||||||
return new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
BasicStroke.JOIN_ROUND,
|
|
||||||
10.0f, new float[]{5.0f}, 0.0f);
|
|
||||||
default:
|
|
||||||
return new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
BasicStroke.JOIN_ROUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Options.StrokeType getStrokeType() {
|
|
||||||
return strokeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
|
||||||
switch( type ) {
|
|
||||||
case DASHED:
|
|
||||||
this.strokeType = DASHED;
|
|
||||||
break;
|
|
||||||
case DOTTED:
|
|
||||||
this.strokeType = DOTTED;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.strokeType = SOLID;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetStroke() {
|
|
||||||
setStrokeColor(STD_STROKECOLOR);
|
|
||||||
setStrokeWeight(STD_STROKEWEIGHT);
|
|
||||||
setStrokeType(SOLID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAnchor( Options.Direction anchor ) {
|
|
||||||
default_anchor = anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear( int gray ) {
|
|
||||||
clear(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear( int gray, int alpha ) {
|
|
||||||
clear(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear( int red, int green, int blue ) {
|
|
||||||
clear(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear( int red, int green, int blue, int alpha ) {
|
|
||||||
clear(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear( Color pColor ) {
|
|
||||||
/*graphics.setBackground(pColor);
|
|
||||||
graphics.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());*/
|
|
||||||
java.awt.Color currentColor = drawing.getColor();
|
|
||||||
pushMatrix();
|
|
||||||
resetMatrix();
|
|
||||||
drawing.setColor(pColor.getJavaColor());
|
|
||||||
drawing.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
|
|
||||||
drawing.setColor(currentColor);
|
|
||||||
popMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void line( double x1, double y1, double x2, double y2 ) {
|
|
||||||
//Shape line = new Line2D.Double(x1, y1, x2, y2);
|
|
||||||
line.setLine(x1, y1, x2, y2);
|
|
||||||
drawShape(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pixel( double x, double y ) {
|
|
||||||
// square(x, y, 1);
|
|
||||||
buffer.setRGB((int)x, (int)y, fillColor.getRGBA());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void square( double x, double y, double w ) {
|
|
||||||
rect(x, y, w, w);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void square( double x, double y, double w, Options.Direction anchor ) {
|
|
||||||
rect(x, y, w, w, anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rect( double x, double y, double w, double h ) {
|
|
||||||
rect(x, y, w, h, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rect( double x, double y, double w, double h, Options.Direction anchor ) {
|
|
||||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, anchor);
|
|
||||||
// Shape rect = new Rectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), w, h);
|
|
||||||
rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h);
|
|
||||||
fillShape(rect);
|
|
||||||
drawShape(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void point( double x, double y ) {
|
|
||||||
circle(x - 1, y - 1, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void circle( double x, double y, double r ) {
|
|
||||||
ellipse(x, y, r+r, r+r, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void circle( double x, double y, double r, Options.Direction anchor ) {
|
|
||||||
ellipse(x, y, r+r, r+r, anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ellipse( double x, double y, double w, double h ) {
|
|
||||||
ellipse(x, y, w, h, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ellipse( double x, double y, double w, double h, Options.Direction anchor ) {
|
|
||||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, anchor);
|
|
||||||
// Shape ellipse = new Ellipse2D.Double(anchorPoint.x, anchorPoint.y, w, h);
|
|
||||||
ellipse.setFrame(anchorPoint.x, anchorPoint.y, w, h);
|
|
||||||
fillShape(ellipse);
|
|
||||||
drawShape(ellipse);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void arc( double x, double y, double r, double angle1, double angle2 ) {
|
|
||||||
arc(x, y, r+r, r+r, angle1, angle2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void arc( double x, double y, double w, double h, double angle1, double angle2 ) {
|
|
||||||
while( angle2 < angle1 ) angle2 += 360.0;
|
|
||||||
|
|
||||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, CENTER);
|
|
||||||
/*Shape arc = new Arc2D.Double(
|
|
||||||
anchorPoint.x,
|
|
||||||
anchorPoint.y,
|
|
||||||
d, d,
|
|
||||||
angle1, angle2 - angle1,
|
|
||||||
Arc2D.OPEN
|
|
||||||
);*/
|
|
||||||
arc.setArc(
|
|
||||||
anchorPoint.x, anchorPoint.y,
|
|
||||||
w, h,
|
|
||||||
//Math.toRadians(angle1), Math.toRadians(angle2 - angle1),
|
|
||||||
angle1, angle2-angle1,
|
|
||||||
Arc2D.OPEN
|
|
||||||
);
|
|
||||||
|
|
||||||
drawShape(arc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pie( double x, double y, double r, double angle1, double angle2 ) {
|
|
||||||
while( angle2 < angle1 ) angle2 += 360.0;
|
|
||||||
|
|
||||||
double d = r+r;
|
|
||||||
|
|
||||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, d, d, CENTER);
|
|
||||||
/*Shape arc = new Arc2D.Double(
|
|
||||||
anchorPoint.x,
|
|
||||||
anchorPoint.y,
|
|
||||||
d, d,
|
|
||||||
angle1, angle2 - angle1,
|
|
||||||
Arc2D.PIE
|
|
||||||
);*/
|
|
||||||
arc.setArc(
|
|
||||||
anchorPoint.x, anchorPoint.y,
|
|
||||||
d, d,
|
|
||||||
angle1, angle2 - angle1,
|
|
||||||
Arc2D.PIE
|
|
||||||
);
|
|
||||||
|
|
||||||
fillShape(arc);
|
|
||||||
drawShape(arc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void curve( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
|
||||||
QuadCurve2D curve = new QuadCurve2D.Double(x1, y1, x2, y2, x3, y3);
|
|
||||||
drawShape(curve);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void curve( double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4 ) {
|
|
||||||
CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x2, y2, x3, y3, x4, y4);
|
|
||||||
drawShape(curve);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void triangle( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
|
||||||
Path2D path = new Path2D.Double();
|
|
||||||
path.moveTo(x1, y1);
|
|
||||||
path.lineTo(x2, y2);
|
|
||||||
path.lineTo(x3, y3);
|
|
||||||
path.lineTo(x1, y1);
|
|
||||||
|
|
||||||
fillShape(path);
|
|
||||||
drawShape(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rhombus( double x, double y, double width, double height ) {
|
|
||||||
rhombus(x, y, width, height, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rhombus( double x, double y, double width, double height, Options.Direction anchor ) {
|
|
||||||
double whalf = width / 2.0, hhalf = height / 2.0;
|
|
||||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, width, height, anchor);
|
|
||||||
polygon(anchorPoint.x + whalf, anchorPoint.y, anchorPoint.x + width, anchorPoint.y + hhalf, anchorPoint.x + whalf, anchorPoint.y + height, anchorPoint.x, anchorPoint.y + hhalf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void polygon( double... coordinates ) {
|
|
||||||
if( coordinates.length < 4 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Path2D path = new Path2D.Double();
|
|
||||||
path.moveTo(coordinates[0], coordinates[1]);
|
|
||||||
for( int i = 2; i < coordinates.length; i += 2 ) {
|
|
||||||
if( i + 1 < coordinates.length ) {
|
|
||||||
path.lineTo(coordinates[i], coordinates[i + 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int len = coordinates.length;
|
|
||||||
if( coordinates[len - 2] != coordinates[0] || coordinates[len - 1] != coordinates[1] ) {
|
|
||||||
path.lineTo(coordinates[0], coordinates[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fillShape(path);
|
|
||||||
drawShape(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void polygon( Point2D... points ) {
|
|
||||||
if( points.length < 2 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Path2D path = new Path2D.Double();
|
|
||||||
path.moveTo(points[0].getX(), points[0].getY());
|
|
||||||
for( int i = 1; i < points.length; i += 1 ) {
|
|
||||||
path.lineTo(points[i].getX(), points[i].getY());
|
|
||||||
}
|
|
||||||
|
|
||||||
int len = points.length;
|
|
||||||
if( points[len - 1].equals(points[0]) ) {
|
|
||||||
path.moveTo(points[0].getX(), points[0].getY());
|
|
||||||
}
|
|
||||||
|
|
||||||
fillShape(path);
|
|
||||||
drawShape(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: General shape drawing
|
|
||||||
/*
|
|
||||||
public void beginShape() {
|
|
||||||
path.reset();
|
|
||||||
pathStarted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void lineTo( double x, double y ) {
|
|
||||||
if( !pathStarted ) {
|
|
||||||
path.moveTo(x, y);
|
|
||||||
pathStarted = true;
|
|
||||||
} else {
|
|
||||||
path.lineTo(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endShape() {
|
|
||||||
path.closePath();
|
|
||||||
path.trimToSize();
|
|
||||||
pathStarted = false;
|
|
||||||
|
|
||||||
fillShape(path);
|
|
||||||
drawShape(path);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void image( String imageSource, double x, double y ) {
|
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void image( String imageSource, double x, double y, Options.Direction anchor ) {
|
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void image( String imageSource, double x, double y, double scale ) {
|
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, scale, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
|
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void image( Image image, double x, double y ) {
|
|
||||||
image(image, x, y, 1.0, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void image( Image image, double x, double y, double scale ) {
|
|
||||||
image(image, x, y, scale, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void image( Image image, double x, double y, double scale, Options.Direction anchor ) {
|
|
||||||
if( image != null ) {
|
|
||||||
double neww = image.getWidth(null) * scale;
|
|
||||||
double newh = image.getHeight(null) * scale;
|
|
||||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, neww, newh, anchor);
|
|
||||||
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void text( String text, double x, double y ) {
|
|
||||||
text(text, x, y, default_anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void text( String text, double x, double y, Options.Direction anchor ) {
|
|
||||||
FontMetrics fm = drawing.getFontMetrics();
|
|
||||||
Point2D.Double anchorPoint = getAnchorPoint(x, y + fm.getAscent(), fm.stringWidth(text), fm.getHeight(), anchor);
|
|
||||||
|
|
||||||
drawing.drawString(text, (float) anchorPoint.x, (float) anchorPoint.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFontSize( int size ) {
|
|
||||||
setFont(drawing.getFont().deriveFont((float)size));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFont( String fontName ) {
|
|
||||||
Font font = new Font(fontName, drawing.getFont().getStyle(), drawing.getFont().getSize());
|
|
||||||
setFont(font);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFont( String fontName, int size ) {
|
|
||||||
Font font = new Font(fontName, drawing.getFont().getStyle(), size);
|
|
||||||
setFont(font);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFont( String fontName, int size, int style ) {
|
|
||||||
Font font = new Font(fontName, style, size);
|
|
||||||
setFont(font);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFont( Font font ) {
|
|
||||||
drawing.setFont(font);
|
|
||||||
fontMetrics = drawing.getFontMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Point2D.Double transformToCanvas( double x, double y ) {
|
|
||||||
return transformToCanvas(new Point2D.Double(x,y));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D.Double transformToCanvas( Point2D.Double pPoint ) {
|
|
||||||
AffineTransform matrix = getMatrix();
|
|
||||||
matrix.transform(pPoint, pPoint);
|
|
||||||
return pPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D.Double transformToUser( double x, double y ) {
|
|
||||||
return transformToUser(new Point2D.Double(x,y));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D.Double transformToUser( Point2D.Double pPoint ) {
|
|
||||||
AffineTransform matrix = getMatrix();
|
|
||||||
|
|
||||||
try {
|
|
||||||
matrix.inverseTransform(pPoint, pPoint);
|
|
||||||
} catch( NoninvertibleTransformException e ) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return pPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D.Double getAnchorPoint( double x, double y, double w, double h, Options.Direction anchor ) {
|
|
||||||
double whalf = w * .5, hhalf = h * .5;
|
|
||||||
|
|
||||||
// anchor == CENTER
|
|
||||||
Point2D.Double anchorPoint = new Point2D.Double(x - whalf, y - hhalf);
|
|
||||||
|
|
||||||
if( NORTH.in(anchor) ) {
|
|
||||||
anchorPoint.y += hhalf;
|
|
||||||
}
|
|
||||||
if( SOUTH.in(anchor) ) {
|
|
||||||
anchorPoint.y -= hhalf;
|
|
||||||
}
|
|
||||||
if( WEST.in(anchor) ) {
|
|
||||||
anchorPoint.x += whalf;
|
|
||||||
}
|
|
||||||
if( EAST.in(anchor) ) {
|
|
||||||
anchorPoint.x -= whalf;
|
|
||||||
}
|
|
||||||
|
|
||||||
return anchorPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fillShape( Shape pShape ) {
|
|
||||||
if( fillColor != null && fillColor.getAlpha() > 0.0 ) {
|
|
||||||
drawing.setColor(fillColor.getJavaColor());
|
|
||||||
drawing.fill(pShape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawShape( Shape pShape ) {
|
|
||||||
if( strokeColor != null && strokeColor.getAlpha() > 0.0
|
|
||||||
&& strokeWeight > 0.0 ) {
|
|
||||||
drawing.setColor(strokeColor.getJavaColor());
|
|
||||||
drawing.setStroke(createStroke());
|
|
||||||
drawing.draw(pShape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void translate( double dx, double dy ) {
|
|
||||||
drawing.translate(dx, dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void scale( double factor ) {
|
|
||||||
drawing.scale(factor, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rotate( double pAngle ) {
|
|
||||||
drawing.rotate(Math.toRadians(pAngle));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shear( double dx, double dy ) {
|
|
||||||
drawing.shear(dx, dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AffineTransform getMatrix() {
|
|
||||||
return drawing.getTransform();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pushMatrix() {
|
|
||||||
transformStack.push(drawing.getTransform());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void popMatrix() {
|
|
||||||
if( transformStack.isEmpty() ) {
|
|
||||||
resetMatrix();
|
|
||||||
} else {
|
|
||||||
drawing.setTransform(transformStack.pop());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetMatrix() {
|
|
||||||
drawing.setTransform(new AffineTransform());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
279
src/main/java/schule/ngb/zm/Fillable.java
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Drawable} Klassen, die mit einer Füllung versehen werden können.
|
||||||
|
* <p>
|
||||||
|
* Das {@code Fillable} Interface dient hauptsächlich zur Vereinheitlichung der
|
||||||
|
* API für Füllungen. Durch Implementation wird sichergestellt, dass alle
|
||||||
|
* Objekte, die eine Füllung haben können, dieselben Methoden zur Verfügung
|
||||||
|
* stellen. Wenn eine {@link Shape} eine
|
||||||
|
* {@link Fillable#setFillColor(Color, int)} Methode hat, dann sollte auch eine
|
||||||
|
* {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode anbieten. Im
|
||||||
|
* Einzelfall kann es sinnvoll sein, weitere Methoden für Füllungen zur
|
||||||
|
* Verfügung zu stellen. Allerdings sollte davon nach Möglichkeit zugunsten
|
||||||
|
* einer einheitlichen API abgesehen werden.
|
||||||
|
* <p>
|
||||||
|
* Das Äquivalent für Konturlinien stellt {@link Strokeable} dar.
|
||||||
|
* <p>
|
||||||
|
* Im einfachsten Fall reicht es {@link #setFill(Paint)} und {@link #getFill()}
|
||||||
|
* zu implementieren. Die anderen Methoden besitzen Standardimplementierungen,
|
||||||
|
* die sich auf die beiden Methoden beziehen. Allerdings ist es in vielen Fällen
|
||||||
|
* sinnvoll, einige der Methoden gezielt zu überschreiben, um sie an spezifische
|
||||||
|
* Situationen anzupassen.
|
||||||
|
*/
|
||||||
|
public interface Fillable extends Drawable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung direkt auf das angegebene {@code Paint}-Objekt.
|
||||||
|
*
|
||||||
|
* @param fill Die neue Füllung.
|
||||||
|
*/
|
||||||
|
void setFill( Paint fill );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die aktuell gesetzte Füllung zurück.
|
||||||
|
* <p>
|
||||||
|
* Die Art der Füllung kann anhand der Abfragen {@link #hasFillColor()} und
|
||||||
|
* {@link #hasGradient()} ermittelt werden.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Füllung.
|
||||||
|
*/
|
||||||
|
Paint getFill();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob aktuell eine sichtbare Füllung konfiguriert ist.
|
||||||
|
* <p>
|
||||||
|
* Eine Füllung gilt als sichtbar, wenn eine nciht transparente Füllfarbe
|
||||||
|
* oder ein Farbverlauf eingestellt ist.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Füllung sichtbar ist, {@code false} sonst.
|
||||||
|
*/
|
||||||
|
default boolean hasFill() {
|
||||||
|
return (hasFillColor() && getFillColor().getAlpha() > 0) || hasGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob eine Füllfarbe konfiguriert ist.
|
||||||
|
* <p>
|
||||||
|
* Im Gegensatz zu {@link #hasFill()} prüft die Methode <em>nicht</em>, ob
|
||||||
|
* die Füllfarbe transparent ist.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn eine Füllfarbe gesetzt ist.
|
||||||
|
*/
|
||||||
|
default boolean hasFillColor() {
|
||||||
|
Paint fill = getFill();
|
||||||
|
return fill instanceof Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob ein Farbverlauf konfiguriert ist.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn ein Farbverlauf gesetzt ist.
|
||||||
|
*/
|
||||||
|
default boolean hasGradient() {
|
||||||
|
Paint fill = getFill();
|
||||||
|
return fill instanceof MultipleGradientPaint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die aktuelle Füllfarbe der Form zurück.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Füllfarbe oder {@code null}.
|
||||||
|
*/
|
||||||
|
default Color getFillColor() {
|
||||||
|
if( hasFillColor() ) {
|
||||||
|
return (Color) getFill();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf die angegebene Farbe.
|
||||||
|
*
|
||||||
|
* @param color Die neue Füllfarbe oder {@code null}.
|
||||||
|
* @see Color
|
||||||
|
*/
|
||||||
|
default void setFillColor( Color color ) {
|
||||||
|
setFill(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)
|
||||||
|
*/
|
||||||
|
default 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)
|
||||||
|
*/
|
||||||
|
default void setFillColor( int gray ) {
|
||||||
|
setFillColor(gray, gray, gray, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
*/
|
||||||
|
default 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>
|
||||||
|
*/
|
||||||
|
default 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>
|
||||||
|
*/
|
||||||
|
default void setFillColor( int red, int green, int blue, int alpha ) {
|
||||||
|
setFillColor(new Color(red, green, blue, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt die Füllung der Form.
|
||||||
|
*/
|
||||||
|
default void noFill() {
|
||||||
|
setFillColor(null);
|
||||||
|
noGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf den Standardwert zurück.
|
||||||
|
*
|
||||||
|
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
||||||
|
*/
|
||||||
|
default void resetFill() {
|
||||||
|
setFillColor(Constants.DEFAULT_FILLCOLOR);
|
||||||
|
noGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den aktuellen Farbverlauf der Form zurück.
|
||||||
|
*
|
||||||
|
* @return Der aktuelle Farbverlauf oder {@code null}.
|
||||||
|
*/
|
||||||
|
default MultipleGradientPaint getGradient() {
|
||||||
|
if( hasGradient() ) {
|
||||||
|
return (MultipleGradientPaint) getFill();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
default void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||||
|
setFill(new LinearGradientPaint(
|
||||||
|
(float) fromX, (float) fromY,
|
||||||
|
(float) toX, (float) toY,
|
||||||
|
new float[]{0f, 1f},
|
||||||
|
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung auf einen linearen Farbverlauf, der in die angegebene
|
||||||
|
* Richtung verläuft.
|
||||||
|
*
|
||||||
|
* @param from Farbe am Startpunkt.
|
||||||
|
* @param to Farbe am Endpunkt.
|
||||||
|
* @param dir Richtung des Farbverlaufs.
|
||||||
|
*/
|
||||||
|
default void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||||
|
int whalf = (Constants.canvasWidth / 2);
|
||||||
|
int hhalf = (Constants.canvasHeight / 2);
|
||||||
|
setGradient(whalf - dir.x * whalf, hhalf - dir.y * hhalf, from, whalf + dir.x * whalf, hhalf + dir.y * hhalf, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
default void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||||
|
setFill(new RadialGradientPaint(
|
||||||
|
(float) centerX, (float) centerY, (float) radius,
|
||||||
|
new float[]{0f, 1f},
|
||||||
|
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, der im
|
||||||
|
* Zentrum beginnt.
|
||||||
|
*
|
||||||
|
* @param from Farbe im Zentrum.
|
||||||
|
* @param to Farbe am Rand.
|
||||||
|
*/
|
||||||
|
default void setGradient( Color from, Color to ) {
|
||||||
|
int whalf = (Constants.canvasWidth / 2);
|
||||||
|
int hhalf = (Constants.canvasHeight / 2);
|
||||||
|
setGradient(whalf, hhalf, Math.min(whalf, hhalf), from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt den Farbverlauf von der Form.
|
||||||
|
*/
|
||||||
|
default void noGradient() {
|
||||||
|
setFill(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.RenderingHints;
|
||||||
import java.awt.image.BufferedImage;
|
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 {
|
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;
|
protected BufferedImage buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Grafikkontext der Ebene, der zum Zeichnen der Inhalte verwendet
|
||||||
|
* wird.
|
||||||
|
*/
|
||||||
protected Graphics2D drawing;
|
protected Graphics2D drawing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Ebene derzeit sichtbar ist.
|
||||||
|
*/
|
||||||
protected boolean visible = true;
|
protected boolean visible = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Ebene aktiv ist, also {@link #update(double) Updates} empfangen
|
||||||
|
* soll.
|
||||||
|
*/
|
||||||
protected boolean active = true;
|
protected boolean active = true;
|
||||||
|
|
||||||
|
|
||||||
public Layer() {
|
public Layer() {
|
||||||
this(STD_WIDTH, STD_HEIGHT);
|
this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Layer( int width, int height ) {
|
public Layer( int width, int height ) {
|
||||||
@@ -33,6 +59,12 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
return buffer.getHeight();
|
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 ) {
|
public void setSize( int width, int height ) {
|
||||||
if( buffer != null ) {
|
if( buffer != null ) {
|
||||||
if( buffer.getWidth() != width || buffer.getHeight() != height ) {
|
if( buffer.getWidth() != width || buffer.getHeight() != height ) {
|
||||||
@@ -44,8 +76,13 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Ressourcen der Ebene frei.
|
||||||
|
*/
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
drawing.dispose();
|
drawing.dispose();
|
||||||
|
drawing = null;
|
||||||
|
buffer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,14 +139,14 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeichnet den Puffer auf die Grafik-Instanz.
|
* Zeichnet den Puffer auf den Grafikkontext.
|
||||||
*
|
*
|
||||||
* @param pGraphics
|
* @param graphics Der Grafikkontext, auf den gezeichnet wird.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D pGraphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
if( visible ) {
|
if( visible ) {
|
||||||
pGraphics.drawImage(buffer, 0, 0, null);
|
graphics.drawImage(buffer, 0, 0, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,12 +36,15 @@ public final class Options {
|
|||||||
INITIALIZING,
|
INITIALIZING,
|
||||||
INITIALIZED,
|
INITIALIZED,
|
||||||
RUNNING,
|
RUNNING,
|
||||||
UPDATING,
|
|
||||||
DRAWING,
|
|
||||||
PAUSED,
|
PAUSED,
|
||||||
STOPPED,
|
STOPPED,
|
||||||
TERMINATED,
|
TERMINATED,
|
||||||
IDLE
|
QUITING,
|
||||||
|
IDLE,
|
||||||
|
UPDATING,
|
||||||
|
DRAWING,
|
||||||
|
DELAYED,
|
||||||
|
DISPATCHING
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import java.awt.Graphics;
|
import schule.ngb.zm.layers.DrawableLayer;
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
|
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 {
|
public class Spielemaschine extends Zeichenmaschine {
|
||||||
|
|
||||||
private LinkedList<Drawable> drawables;
|
private LinkedList<Drawable> drawables;
|
||||||
|
|
||||||
private LinkedList<Updatable> updatables;
|
private LinkedList<Updatable> updatables;
|
||||||
|
|
||||||
private GraphicsLayer mainLayer;
|
private GameLayer mainLayer;
|
||||||
|
|
||||||
public Spielemaschine( String title ) {
|
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<>();
|
drawables = new LinkedList<>();
|
||||||
updatables = new LinkedList<>();
|
updatables = new LinkedList<>();
|
||||||
|
|
||||||
mainLayer = new GraphicsLayer();
|
mainLayer = new GameLayer();
|
||||||
canvas.addLayer(mainLayer);
|
canvas.addLayer(mainLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +85,10 @@ public class Spielemaschine extends Zeichenmaschine {
|
|||||||
@Override
|
@Override
|
||||||
public final void update( double delta ) {
|
public final void update( double delta ) {
|
||||||
synchronized( updatables ) {
|
synchronized( updatables ) {
|
||||||
for( Updatable updatable : updatables ) {
|
List<Updatable> it = List.copyOf(updatables);
|
||||||
if( updatable.isActive() ) {
|
for( Updatable u: it ) {
|
||||||
updatable.update(delta);
|
if( u.isActive() ) {
|
||||||
|
u.update(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,14 +98,27 @@ public class Spielemaschine extends Zeichenmaschine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void draw() {
|
public final void draw() {
|
||||||
mainLayer.clear();
|
|
||||||
synchronized( drawables ) {
|
}
|
||||||
for( Drawable drawable : drawables ) {
|
|
||||||
if( drawable.isVisible() ) {
|
private class GameLayer extends Layer {
|
||||||
drawable.draw(mainLayer.getGraphics());
|
|
||||||
}
|
public Graphics2D getGraphics() {
|
||||||
|
return drawing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
clear();
|
||||||
|
List<Drawable> it = List.copyOf(drawables);
|
||||||
|
for( Drawable d: it ) {
|
||||||
|
if( d.isVisible() ) {
|
||||||
|
d.draw(drawing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
super.draw(graphics);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
229
src/main/java/schule/ngb/zm/Strokeable.java
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
|
||||||
|
import java.awt.BasicStroke;
|
||||||
|
import java.awt.Stroke;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Drawable} Klassen, die mit einer Konturlinie versehen werden können.
|
||||||
|
* <p>
|
||||||
|
* Das {@code Strokeable} Interface dient hauptsächlich zur Vereinheitlichung
|
||||||
|
* der API für Konturlinien. Durch Implementation wird sichergestellt, dass alle
|
||||||
|
* Objekte, die eine Konturlinie haben können, dieselben Methoden zur Verfügung
|
||||||
|
* stellen. Wenn eine {@link Shape} eine
|
||||||
|
* {@link Strokeable#setStrokeColor(Color, int)} Methode hat, dann sollte auch
|
||||||
|
* eine {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode
|
||||||
|
* anbieten. Im Einzelfall kann es sinnvoll sein, weitere Methoden für
|
||||||
|
* Konturlinien zur erfügung zu stellen. Allerdings sollte davon nach
|
||||||
|
* Möglichkeit zugunsten einer einheitlichen API abgesehen werden.
|
||||||
|
* <p>
|
||||||
|
* Das Äquivalent für Füllungen stellt {@link Fillable} dar.
|
||||||
|
*/
|
||||||
|
public interface Strokeable extends Drawable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den {@code Stroke} für die Konturlinie direkt.
|
||||||
|
*
|
||||||
|
* @param stroke Ein {@code Stroke}-Objekt.
|
||||||
|
*/
|
||||||
|
void setStroke( Stroke stroke );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt ein {@code Stroke}-Objekt mit den aktuell gesetzten Eigenschaften
|
||||||
|
* zurück.
|
||||||
|
*
|
||||||
|
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||||
|
*/
|
||||||
|
Stroke getStroke();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt an, ob die aktuell gesetzten Eigenschaften eine sichtbare
|
||||||
|
* Konturlinie erzeugen.
|
||||||
|
* <p>
|
||||||
|
* Die Konturlinie gilt als sichtbar, wenn sie eine nicht transparente Farbe
|
||||||
|
* und eine Dicke größer 0 besitzt.
|
||||||
|
* <p>
|
||||||
|
* Das bedeutet, falls die Methode {@code false} zurückgibt, dann kann
|
||||||
|
* {@link #getStroke()} trotzdem ein gültiges {@link Stroke}-Objekt
|
||||||
|
* zurückgeben, beispielsweise wenn keine Farbe gesetzt wurde.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Konturlinie sichtbar ist, {@code false}
|
||||||
|
* sonst.
|
||||||
|
*/
|
||||||
|
default boolean hasStroke() {
|
||||||
|
Color strokeColor = getStrokeColor();
|
||||||
|
double strokeWeight = getStrokeWeight();
|
||||||
|
return strokeColor != null && strokeColor.getAlpha() > 0 && strokeWeight > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die aktuelle Farbe der Konturlinie zurück.
|
||||||
|
*
|
||||||
|
* @return Die Konturfarbe oder {@code null}.
|
||||||
|
*/
|
||||||
|
Color getStrokeColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Farbe der Konturlinie auf die angegebene Farbe.
|
||||||
|
*
|
||||||
|
* @param color Die neue Farbe der Konturlinie.
|
||||||
|
* @see Color
|
||||||
|
*/
|
||||||
|
void setStrokeColor( Color 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)
|
||||||
|
*/
|
||||||
|
default 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)
|
||||||
|
*/
|
||||||
|
default void setStrokeColor( int gray ) {
|
||||||
|
setStrokeColor(gray, gray, gray, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
*/
|
||||||
|
default 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>
|
||||||
|
*/
|
||||||
|
default 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>
|
||||||
|
*/
|
||||||
|
default void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||||
|
setStrokeColor(new Color(red, green, blue, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt die Kontur der Form.
|
||||||
|
*/
|
||||||
|
default 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
|
||||||
|
*/
|
||||||
|
default void resetStroke() {
|
||||||
|
setStrokeColor(Constants.DEFAULT_STROKECOLOR);
|
||||||
|
setStrokeWeight(Constants.DEFAULT_STROKEWEIGHT);
|
||||||
|
setStrokeType(Constants.SOLID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Dicke der Konturlinie zurück.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Dicke der Linie.
|
||||||
|
*/
|
||||||
|
double getStrokeWeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
default void setStrokeWeight( double weight ) {
|
||||||
|
setStroke(createStroke(getStrokeType(), weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Art der Konturlinie zurück.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Art der Konturlinie.
|
||||||
|
* @see Options.StrokeType
|
||||||
|
*/
|
||||||
|
Options.StrokeType getStrokeType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link Constants#DASHED},
|
||||||
|
* {@link Constants#DOTTED} und {@link Constants#SOLID}.
|
||||||
|
*
|
||||||
|
* @param type Eine der möglichen Konturarten.
|
||||||
|
* @see Options.StrokeType
|
||||||
|
*/
|
||||||
|
default void setStrokeType( Options.StrokeType type ) {
|
||||||
|
setStroke(createStroke(type, getStrokeWeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hilfsmethode, um ein {@link Stroke} Objekt mit den aktuellen
|
||||||
|
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
||||||
|
* zwischengespeichert.
|
||||||
|
*
|
||||||
|
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||||
|
*/
|
||||||
|
static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight ) {
|
||||||
|
switch( strokeType ) {
|
||||||
|
case DOTTED:
|
||||||
|
return new BasicStroke(
|
||||||
|
(float) strokeWeight,
|
||||||
|
BasicStroke.CAP_ROUND,
|
||||||
|
BasicStroke.JOIN_ROUND,
|
||||||
|
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||||
|
case DASHED:
|
||||||
|
return new BasicStroke(
|
||||||
|
(float) strokeWeight,
|
||||||
|
BasicStroke.CAP_ROUND,
|
||||||
|
BasicStroke.JOIN_ROUND,
|
||||||
|
10.0f, new float[]{5.0f}, 0.0f);
|
||||||
|
case SOLID:
|
||||||
|
default:
|
||||||
|
return new BasicStroke(
|
||||||
|
(float) strokeWeight,
|
||||||
|
BasicStroke.CAP_ROUND,
|
||||||
|
BasicStroke.JOIN_ROUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,21 +1,42 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktualisierbare Objekte können in regelmäßigen Intervallen (meist einmal
|
* {@code Updatable} Objekte können in regelmäßigen Intervallen (meist einmal
|
||||||
* pro Frame) ihren Zustand update. Diese Änderung kann abhängig vom
|
* pro Frame) ihren Zustand aktualisieren. Diese Änderung kann abhängig vom
|
||||||
* Zeitintervall (in Sekunden) zum letzten Aufruf passieren.
|
* Zeitintervall (in Sekunden) zum letzten Aufruf passieren.
|
||||||
*/
|
*/
|
||||||
public interface Updatable {
|
public interface Updatable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt an, ob das Objekt gerade auf Aktualisierungen reagiert.
|
* 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();
|
public boolean isActive();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Änderung des Zustandes des Objekts abhängig vom Zeitintervall
|
* Ä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).
|
* @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden).
|
||||||
*/
|
*/
|
||||||
public void update( double delta );
|
public void update( double delta );
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.awt.geom.Point2D;
|
|||||||
* Der Vektor der Zeichenmaschine erweitert die Klasse {@link Point2D} und lässt
|
* Der Vektor der Zeichenmaschine erweitert die Klasse {@link Point2D} und lässt
|
||||||
* sich dadurch einfach mit den Klassen des {@link java.awt} Pakets benutzen.
|
* sich dadurch einfach mit den Klassen des {@link java.awt} Pakets benutzen.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class Vector extends Point2D.Double {
|
public class Vector extends Point2D.Double {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +128,7 @@ public class Vector extends Point2D.Double {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt einen neuen Vektor mit derselben Richtun wie der angegebene
|
* Erzeugt einen neuen Vektor mit derselben Richtung wie der angegebene
|
||||||
* Vektor und der Länge 1.
|
* Vektor und der Länge 1.
|
||||||
*
|
*
|
||||||
* @param vector Der original Vektor.
|
* @param vector Der original Vektor.
|
||||||
@@ -197,6 +198,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param y Der neue y-Wert.
|
* @param y Der neue y-Wert.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector set( double x, double y ) {
|
public Vector set( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
@@ -210,6 +212,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param vector Ein Vektor.
|
* @param vector Ein Vektor.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector set( Vector vector ) {
|
public Vector set( Vector vector ) {
|
||||||
x = vector.x;
|
x = vector.x;
|
||||||
y = vector.y;
|
y = vector.y;
|
||||||
@@ -223,6 +226,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param pPunkt Ein Punkt.
|
* @param pPunkt Ein Punkt.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector set( Point2D pPunkt ) {
|
public Vector set( Point2D pPunkt ) {
|
||||||
x = pPunkt.getX();
|
x = pPunkt.getX();
|
||||||
x = pPunkt.getY();
|
x = pPunkt.getY();
|
||||||
@@ -271,6 +275,7 @@ public class Vector extends Point2D.Double {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Legt die Länge des Vektors fest.
|
* Legt die Länge des Vektors fest.
|
||||||
|
*
|
||||||
* @param length Die neue Länge des Vektors.
|
* @param length Die neue Länge des Vektors.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
@@ -294,9 +299,9 @@ public class Vector extends Point2D.Double {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector normalize() {
|
public Vector normalize() {
|
||||||
double len = length();
|
double len = length();
|
||||||
if( len != 0 && len != 1 ) {
|
if( len != 0 && len != 1 ) {
|
||||||
@@ -308,9 +313,11 @@ public class Vector extends Point2D.Double {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Addiert den Vektor {@code vector} zu diesem.
|
* Addiert den Vektor {@code vector} zu diesem.
|
||||||
|
*
|
||||||
* @param vector Ein anderer Vektor.
|
* @param vector Ein anderer Vektor.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector add( Vector vector ) {
|
public Vector add( Vector vector ) {
|
||||||
x += vector.x;
|
x += vector.x;
|
||||||
y += vector.y;
|
y += vector.y;
|
||||||
@@ -319,10 +326,12 @@ public class Vector extends Point2D.Double {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Addiert die angegebenen Werte zur x- und y-Komponente des Vektors.
|
* Addiert die angegebenen Werte zur x- und y-Komponente des Vektors.
|
||||||
|
*
|
||||||
* @param x Summand x-Komponente.
|
* @param x Summand x-Komponente.
|
||||||
* @param y Summand y-Komponente.
|
* @param y Summand y-Komponente.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector add( double x, double y ) {
|
public Vector add( double x, double y ) {
|
||||||
this.x += x;
|
this.x += x;
|
||||||
this.y += y;
|
this.y += y;
|
||||||
@@ -342,24 +351,28 @@ public class Vector extends Point2D.Double {
|
|||||||
return vec;
|
return vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector sub( Vector vector ) {
|
public Vector sub( Vector vector ) {
|
||||||
x -= vector.x;
|
x -= vector.x;
|
||||||
y -= vector.y;
|
y -= vector.y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector sub( double x, double y ) {
|
public Vector sub( double x, double y ) {
|
||||||
this.x -= x;
|
this.x -= x;
|
||||||
this.y -= y;
|
this.y -= y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector scale( double scalar ) {
|
public Vector scale( double scalar ) {
|
||||||
x *= scalar;
|
x *= scalar;
|
||||||
y *= scalar;
|
y *= scalar;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector div( double scalar ) {
|
public Vector div( double scalar ) {
|
||||||
if( scalar == 0.0 ) {
|
if( scalar == 0.0 ) {
|
||||||
throw new IllegalArgumentException("Can't divide by zero.");
|
throw new IllegalArgumentException("Can't divide by zero.");
|
||||||
@@ -487,6 +500,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
* @see #setLength(double)
|
* @see #setLength(double)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector limit( double max ) {
|
public Vector limit( double max ) {
|
||||||
if( lengthSq() > max * max ) {
|
if( lengthSq() > max * max ) {
|
||||||
setLength(max);
|
setLength(max);
|
||||||
@@ -503,6 +517,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
* @see #setLength(double)
|
* @see #setLength(double)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector limit( double min, double max ) {
|
public Vector limit( double min, double max ) {
|
||||||
if( min > max ) {
|
if( min > max ) {
|
||||||
double t = min;
|
double t = min;
|
||||||
@@ -556,6 +571,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
* @see #rotate(double)
|
* @see #rotate(double)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector rotateRad( double rad ) {
|
public Vector rotateRad( double rad ) {
|
||||||
double temp = x;
|
double temp = x;
|
||||||
x = x * Math.cos(rad) - y * Math.sin(rad);
|
x = x * Math.cos(rad) - y * Math.sin(rad);
|
||||||
@@ -587,6 +603,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param t Ein Wert zwischen 0 und 1.
|
* @param t Ein Wert zwischen 0 und 1.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector morph( Vector vector, double t ) {
|
public Vector morph( Vector vector, double t ) {
|
||||||
double tt = Math.min(Math.max(t, 0.0), 1.0);
|
double tt = Math.min(Math.max(t, 0.0), 1.0);
|
||||||
x = x + (vector.x - x) * tt;
|
x = x + (vector.x - x) * tt;
|
||||||
@@ -631,6 +648,7 @@ public class Vector extends Point2D.Double {
|
|||||||
* @param t Ein Wert zwischen 0 und 1.
|
* @param t Ein Wert zwischen 0 und 1.
|
||||||
* @return Dieser Vektor selbst (method chaining)
|
* @return Dieser Vektor selbst (method chaining)
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Vector interpolate( Vector vector, double t ) {
|
public Vector interpolate( Vector vector, double t ) {
|
||||||
x = x + (vector.x - x) * t;
|
x = x + (vector.x - x) * t;
|
||||||
y = y + (vector.y - y) * t;
|
y = y + (vector.y - y) * t;
|
||||||
|
|||||||
@@ -1,6 +1,269 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
// TODO: Auslagern des Frames in diese Klasse (Trennung Swing-GUI/Canvas, Zeichenmaschine und Drawing-Thread)
|
import schule.ngb.zm.util.Log;
|
||||||
public class Zeichenfenster {
|
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.lang.reflect.Method;
|
||||||
|
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 final KeyListener fullscreenExitListener = new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed( KeyEvent e ) {
|
||||||
|
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
|
||||||
|
// canvas.removeKeyListener(this);
|
||||||
|
setFullscreen(false);
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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.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: new int[]{32, 64, 128, 512} ) {
|
||||||
|
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.setFocusable(true);
|
||||||
|
this.setLocationByPlatform(true);
|
||||||
|
// this.centerFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GraphicsDevice getDisplayDevice() {
|
||||||
|
return displayDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle getScreenBounds() {
|
||||||
|
// return GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
|
||||||
|
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 constrains 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() ) {
|
||||||
|
// Temporarily stop rendering
|
||||||
|
while( canvas.isRendering() ) {
|
||||||
|
try {
|
||||||
|
canvas.suspendRendering();
|
||||||
|
} catch( InterruptedException ex ) {
|
||||||
|
LOG.info(ex, "setFullsceen(true) was interrupted and canceled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( pEnable && !fullscreen ) {
|
||||||
|
// Activate fullscreen
|
||||||
|
dispose();
|
||||||
|
setUndecorated(true);
|
||||||
|
setResizable(false);
|
||||||
|
displayDevice.setFullScreenWindow(this);
|
||||||
|
|
||||||
|
// Register ESC to exit fullscreen
|
||||||
|
canvas.addKeyListener(fullscreenExitListener);
|
||||||
|
|
||||||
|
// Reset canvas size to its new bounds to recreate buffer and drawing surface
|
||||||
|
java.awt.Rectangle canvasBounds = getCanvasBounds();
|
||||||
|
canvas.setSize(canvasBounds.width, canvasBounds.height);
|
||||||
|
//canvas.requestFocus();
|
||||||
|
canvas.requestFocus();
|
||||||
|
|
||||||
|
fullscreen = true;
|
||||||
|
} else if( !pEnable && fullscreen ) {
|
||||||
|
displayDevice.setFullScreenWindow(null);
|
||||||
|
dispose();
|
||||||
|
setUndecorated(false);
|
||||||
|
setResizable(false);
|
||||||
|
|
||||||
|
canvas.removeKeyListener(fullscreenExitListener);
|
||||||
|
canvas.setSize(canvasPreferredWidth, canvasPreferredHeight);
|
||||||
|
|
||||||
|
setVisible(true);
|
||||||
|
pack();
|
||||||
|
|
||||||
|
//canvas.requestFocus();
|
||||||
|
canvas.requestFocus();
|
||||||
|
|
||||||
|
fullscreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume rendering
|
||||||
|
canvas.resumeRendering();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullscreen() {
|
||||||
|
Window win = displayDevice.getFullScreenWindow();
|
||||||
|
return fullscreen && win.equals(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(Zeichenfenster.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import schule.ngb.zm.layers.ColorLayer;
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
import java.awt.Canvas;
|
import java.awt.Canvas;
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.Toolkit;
|
import java.awt.Toolkit;
|
||||||
import java.awt.image.BufferStrategy;
|
import java.awt.image.BufferStrategy;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
|
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
|
||||||
@@ -20,10 +22,14 @@ import java.util.LinkedList;
|
|||||||
*/
|
*/
|
||||||
public class Zeichenleinwand extends Canvas {
|
public class Zeichenleinwand extends Canvas {
|
||||||
|
|
||||||
|
private final Object[] renderLock = new Object[0];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Liste der hinzugefügten Ebenen.
|
* Liste der hinzugefügten Ebenen.
|
||||||
*/
|
*/
|
||||||
private LinkedList<Layer> layers;
|
private final List<Layer> layers;
|
||||||
|
|
||||||
|
private boolean rendering = false, suspended = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
|
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
|
||||||
@@ -33,15 +39,49 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
*/
|
*/
|
||||||
public Zeichenleinwand( int width, int height ) {
|
public Zeichenleinwand( int width, int height ) {
|
||||||
super.setSize(width, height);
|
super.setSize(width, height);
|
||||||
this.setPreferredSize(this.getSize());
|
this.setPreferredSize(getSize());
|
||||||
this.setMinimumSize(this.getSize());
|
this.setMinimumSize(getSize());
|
||||||
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
||||||
|
|
||||||
// Liste der Ebenen initialisieren und die Standardebenen einfügen
|
layers = Collections.synchronizedList(new LinkedList<>());
|
||||||
layers = new LinkedList<>();
|
|
||||||
synchronized( layers ) {
|
|
||||||
layers.add(new ColorLayer(width, height, Constants.DEFAULT_BACKGROUND));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Leinwand ihren Inhalt gerade zeichnet.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Inhalte gerade gezeichnet werden.
|
||||||
|
*/
|
||||||
|
public boolean isRendering() {
|
||||||
|
return rendering;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pausiert das Zeichnen der Leinwand kurzzeitig.
|
||||||
|
* <p>
|
||||||
|
* Falls die Leinwand gerade beim Zeichnen ist
|
||||||
|
* ({@code isRendering() == true}, blockt die Methode den aufrufenden Thread
|
||||||
|
* so lange, bis das Rendern beendet ist. Danach wird die Ebene nicht mehr
|
||||||
|
* neu gezeichnet, bis {@link #resumeRendering()} aufgerufen wird.
|
||||||
|
* <p>
|
||||||
|
* Das Zeichnen sollte nur dann unterbrochen werden, wenn sich der Kontext
|
||||||
|
* der Canvas-Komponente in seinem Elterncontainer ändert, um Fehler bei
|
||||||
|
* einer fehlenden Container-Hierarchie zu vermeiden.
|
||||||
|
*
|
||||||
|
* @throws InterruptedException Falls der Thread beim Warten unterbrochen
|
||||||
|
* wird.
|
||||||
|
*/
|
||||||
|
public void suspendRendering() throws InterruptedException {
|
||||||
|
synchronized( renderLock ) {
|
||||||
|
if( isRendering() ) {
|
||||||
|
renderLock.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
suspended = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resumeRendering() {
|
||||||
|
suspended = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,8 +96,8 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
@Override
|
@Override
|
||||||
public void setSize( int width, int height ) {
|
public void setSize( int width, int height ) {
|
||||||
super.setSize(width, height);
|
super.setSize(width, height);
|
||||||
this.setPreferredSize(this.getSize());
|
this.setPreferredSize(getSize());
|
||||||
this.setMinimumSize(this.getSize());
|
this.setMinimumSize(getSize());
|
||||||
|
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
for( Layer layer : layers ) {
|
for( Layer layer : layers ) {
|
||||||
@@ -74,26 +114,23 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
*/
|
*/
|
||||||
public void addLayer( Layer layer ) {
|
public void addLayer( Layer layer ) {
|
||||||
if( layer != null ) {
|
if( layer != null ) {
|
||||||
synchronized( layers ) {
|
|
||||||
layer.setSize(getWidth(), getHeight());
|
layer.setSize(getWidth(), getHeight());
|
||||||
layers.add(layer);
|
layers.add(layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fügt der Zeichenleinwand eine Ebene an einem bestimmten Index hinzu. Wenn
|
* Fügt der Zeichenleinwand eine Ebene an einem bestimmten Index hinzu. Wenn
|
||||||
* der Index noch nicht existiert (also größer als die
|
* der Index noch nicht existiert (also größer als die
|
||||||
* {@link #getLayerCount() Anzahl der Ebenen} ist, dann wird die neue Ebene
|
* {@link #getLayerCount() Anzahl der Ebenen} ist), dann wird die neue Ebene
|
||||||
* als letzte eingefügt. Die aufrufende Methode kann also nicht sicher sein,
|
* als letzte eingefügt. Die aufrufende Methode kann also nicht sicher sein,
|
||||||
* dass die neue Ebene am Ende wirklich am Index {@code i} steht.
|
* dass die neue Ebene am Ende wirklich am Index {@code i} steht.
|
||||||
*
|
*
|
||||||
* @param i Index der Ebene, beginnend mit 0.
|
* @param i Index der Ebene beginnend mit 0.
|
||||||
* @param layer Die neue Ebene.
|
* @param layer Die neue Ebene.
|
||||||
*/
|
*/
|
||||||
public void addLayer( int i, Layer layer ) {
|
public void addLayer( int i, Layer layer ) {
|
||||||
if( layer != null ) {
|
if( layer != null ) {
|
||||||
synchronized( layers ) {
|
|
||||||
layer.setSize(getWidth(), getHeight());
|
layer.setSize(getWidth(), getHeight());
|
||||||
if( i > layers.size() ) {
|
if( i > layers.size() ) {
|
||||||
layers.add(layer);
|
layers.add(layer);
|
||||||
@@ -102,7 +139,6 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Anzahl der {@link Layer Ebenen} in dieser Leinwand zurück.
|
* Gibt die Anzahl der {@link Layer Ebenen} in dieser Leinwand zurück.
|
||||||
@@ -114,19 +150,19 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Liste der bisher hinzugefügten Ebenen zurück.
|
* Gibt eine Kopie der Liste der bisher hinzugefügten Ebenen zurück.
|
||||||
*
|
*
|
||||||
* @return Liste der Ebenen.
|
* @return Liste der Ebenen.
|
||||||
*/
|
*/
|
||||||
public java.util.List<Layer> getLayers() {
|
public List<Layer> getLayers() {
|
||||||
return layers;
|
return List.copyOf(layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holt die Ebene am Index <var>i</var> (beginnend bei 0).
|
* Holt die Ebene am Index {@code i} (beginnend bei 0).
|
||||||
*
|
*
|
||||||
* @param i Index der Ebene (beginnend bei 0).
|
* @param i Index der Ebene (beginnend bei 0).
|
||||||
* @return Die Ebene am Index <var>i</var> oder {@code null}.
|
* @return Die Ebene am Index {@code i} oder {@code null}.
|
||||||
* @throws IndexOutOfBoundsException Falls der Index nicht existiert.
|
* @throws IndexOutOfBoundsException Falls der Index nicht existiert.
|
||||||
*/
|
*/
|
||||||
public Layer getLayer( int i ) {
|
public Layer getLayer( int i ) {
|
||||||
@@ -141,14 +177,16 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
* Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen.
|
* Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen.
|
||||||
* Existiert keine solche Ebene, wird {@code null} zurückgegeben.
|
* Existiert keine solche Ebene, wird {@code null} zurückgegeben.
|
||||||
*
|
*
|
||||||
* @param clazz Typ der Ebene.
|
* @param type Klasse der Ebenen, die abgefragt werden.
|
||||||
* @param <L>
|
* @param <L> Typ der Ebenen, die abgefragt werden.
|
||||||
* @return Erste Ebene vom angegeben Typ.
|
* @return Erste Ebene vom angegeben Typ.
|
||||||
*/
|
*/
|
||||||
public <L extends Layer> L getLayer( Class<L> clazz ) {
|
public <L extends Layer> L getLayer( Class<L> type ) {
|
||||||
|
synchronized( layers ) {
|
||||||
for( Layer layer : layers ) {
|
for( Layer layer : layers ) {
|
||||||
if( layer.getClass().equals(clazz) ) {
|
if( layer.getClass().equals(type) ) {
|
||||||
return clazz.cast(layer);
|
return type.cast(layer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -159,43 +197,41 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
* gibt diese als Liste zurück. Die Reihenfolge in der Liste entspricht der
|
* gibt diese als Liste zurück. Die Reihenfolge in der Liste entspricht der
|
||||||
* Reihenfolge der Ebenen in der Leinwand (von unten nach oben).
|
* Reihenfolge der Ebenen in der Leinwand (von unten nach oben).
|
||||||
*
|
*
|
||||||
* @param pClazz
|
* @param type Klasse der Ebenen, die abgefragt werden.
|
||||||
* @param <L>
|
* @param <L> Typ der Ebenen, die abgefragt werden.
|
||||||
* @return
|
* @return Eine Liste mit den vorhandenen Ebenen des abgefragten Typs.
|
||||||
*/
|
*/
|
||||||
public <L extends Layer> java.util.List<L> getLayers( Class<L> pClazz ) {
|
public <L extends Layer> List<L> getLayers( Class<L> type ) {
|
||||||
ArrayList<L> result = new ArrayList<>(layers.size());
|
ArrayList<L> result = new ArrayList<>(layers.size());
|
||||||
|
synchronized( layers ) {
|
||||||
for( Layer layer : layers ) {
|
for( Layer layer : layers ) {
|
||||||
if( layer.getClass().equals(pClazz) ) {
|
if( layer.getClass().equals(type) ) {
|
||||||
result.add(pClazz.cast(layer));
|
result.add(type.cast(layer));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeLayer( Layer pLayer ) {
|
public boolean removeLayer( Layer pLayer ) {
|
||||||
synchronized( layers ) {
|
|
||||||
return layers.remove(pLayer);
|
return layers.remove(pLayer);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void removeLayers( Layer... pLayers ) {
|
public void removeLayers( Layer... removeLayers ) {
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
for( Layer layer : pLayers ) {
|
for( Layer layer : removeLayers ) {
|
||||||
layers.remove(layer);
|
layers.remove(layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearLayers() {
|
public void clearLayers() {
|
||||||
synchronized( layers ) {
|
|
||||||
layers.clear();
|
layers.clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void updateLayers( double delta ) {
|
public void updateLayers( double delta ) {
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
for( Layer layer : layers ) {
|
for( Layer layer : List.copyOf(layers) ) {
|
||||||
layer.update(delta);
|
layer.update(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,11 +279,13 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
|
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
|
||||||
*/
|
*/
|
||||||
public void render() {
|
public void render() {
|
||||||
|
if( !suspended && isDisplayable() ) {
|
||||||
if( getBufferStrategy() == null ) {
|
if( getBufferStrategy() == null ) {
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if( isDisplayable() ) {
|
synchronized( renderLock ) {
|
||||||
|
rendering = true;
|
||||||
BufferStrategy strategy = this.getBufferStrategy();
|
BufferStrategy strategy = this.getBufferStrategy();
|
||||||
if( strategy != null ) {
|
if( strategy != null ) {
|
||||||
do {
|
do {
|
||||||
@@ -256,7 +294,7 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
g2d.clearRect(0, 0, getWidth(), getHeight());
|
||||||
|
|
||||||
synchronized( layers ) {
|
synchronized( layers ) {
|
||||||
for( Layer layer : layers ) {
|
for( Layer layer : List.copyOf(layers) ) {
|
||||||
layer.draw(g2d);
|
layer.draw(g2d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,7 +312,12 @@ public class Zeichenleinwand extends Canvas {
|
|||||||
// Repeat the rendering if the drawing buffer was lost
|
// Repeat the rendering if the drawing buffer was lost
|
||||||
} while( strategy.contentsLost() );
|
} while( strategy.contentsLost() );
|
||||||
}
|
}
|
||||||
|
rendering = false;
|
||||||
|
renderLock.notifyAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(Zeichenleinwand.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import schule.ngb.zm.anim.Animation;
|
import schule.ngb.zm.anim.Animation;
|
||||||
import schule.ngb.zm.shapes.ShapesLayer;
|
import schule.ngb.zm.layers.ColorLayer;
|
||||||
import schule.ngb.zm.tasks.TaskRunner;
|
import schule.ngb.zm.layers.DrawingLayer;
|
||||||
import schule.ngb.zm.util.ImageLoader;
|
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.Log;
|
||||||
|
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.MouseInputListener;
|
import javax.swing.event.MouseInputListener;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -15,7 +17,7 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.logging.Level;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hauptklasse der Zeichenmaschine.
|
* Hauptklasse der Zeichenmaschine.
|
||||||
@@ -24,7 +26,7 @@ import java.util.logging.Level;
|
|||||||
* Die Klasse übernimmt die Initialisierung eines Programmfensters und der
|
* Die Klasse übernimmt die Initialisierung eines Programmfensters und der
|
||||||
* nötigen Komponenten.
|
* nötigen Komponenten.
|
||||||
*/
|
*/
|
||||||
// TODO: Refactorings (besonders in Bezug auf Nebenläufigkeit)
|
@SuppressWarnings( "unused" )
|
||||||
public class Zeichenmaschine extends Constants {
|
public class Zeichenmaschine extends Constants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,43 +38,6 @@ public class Zeichenmaschine extends Constants {
|
|||||||
IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
|
IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob die Zeichenmaschine unter macOS gestartet wurde.
|
|
||||||
*/
|
|
||||||
public static final boolean MACOS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob die Zeichenmaschine unter Windows gestartet wurde.
|
|
||||||
*/
|
|
||||||
public static final boolean WINDOWS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob die Zeichenmaschine unter Linux gestartet wurde.
|
|
||||||
*/
|
|
||||||
public static final boolean LINUX;
|
|
||||||
|
|
||||||
static {
|
|
||||||
final String name = System.getProperty("os.name");
|
|
||||||
|
|
||||||
if( name.contains("Mac") ) {
|
|
||||||
MACOS = true;
|
|
||||||
WINDOWS = false;
|
|
||||||
LINUX = false;
|
|
||||||
} else if( name.contains("Windows") ) {
|
|
||||||
MACOS = false;
|
|
||||||
WINDOWS = true;
|
|
||||||
LINUX = false;
|
|
||||||
} else if( name.equals("Linux") ) { // true for the ibm vm
|
|
||||||
MACOS = false;
|
|
||||||
WINDOWS = false;
|
|
||||||
LINUX = true;
|
|
||||||
} else {
|
|
||||||
MACOS = false;
|
|
||||||
WINDOWS = false;
|
|
||||||
LINUX = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Objektvariablen, die von Unterklassen benutzt werden können.
|
* Objektvariablen, die von Unterklassen benutzt werden können.
|
||||||
*/
|
*/
|
||||||
@@ -110,54 +75,14 @@ public class Zeichenmaschine extends Constants {
|
|||||||
/**
|
/**
|
||||||
* Das Zeichenfenster der Zeichenmaschine
|
* Das Zeichenfenster der Zeichenmaschine
|
||||||
*/
|
*/
|
||||||
private JFrame frame;
|
protected Zeichenfenster 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Aktueller Zustand der Zeichenmaschine.
|
// Aktueller Zustand der Zeichenmaschine.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zustand der Zeichenmaschine insgesamt
|
* Zustand der Zeichenmaschine insgesamt
|
||||||
*/
|
*/
|
||||||
private Options.AppState state = Options.AppState.INITIALIZING;
|
private Options.AppState state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zustand des update/draw Threads
|
* Zustand des update/draw Threads
|
||||||
@@ -167,7 +92,9 @@ public class Zeichenmaschine extends Constants {
|
|||||||
/**
|
/**
|
||||||
* Ob der Zeichenthread noch laufen soll, oder beendet.
|
* Ob der Zeichenthread noch laufen soll, oder beendet.
|
||||||
*/
|
*/
|
||||||
private boolean running = false;
|
private boolean running;
|
||||||
|
|
||||||
|
private boolean terminateImediately = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob die ZM nach dem nächsten Frame pausiert werden soll.
|
* Ob die ZM nach dem nächsten Frame pausiert werden soll.
|
||||||
@@ -178,7 +105,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* Ob die ZM bei nicht überschriebener update() Methode stoppen soll,
|
* Ob die ZM bei nicht überschriebener update() Methode stoppen soll,
|
||||||
* oder trotzdem weiterläuft.
|
* oder trotzdem weiterläuft.
|
||||||
*/
|
*/
|
||||||
private boolean run_once = true;
|
private final boolean run_once;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktuelle Frames pro Sekunde der Zeichenmaschine.
|
* Aktuelle Frames pro Sekunde der Zeichenmaschine.
|
||||||
@@ -188,23 +115,23 @@ public class Zeichenmaschine extends Constants {
|
|||||||
/**
|
/**
|
||||||
* Hauptthread der Zeichenmaschine.
|
* Hauptthread der Zeichenmaschine.
|
||||||
*/
|
*/
|
||||||
private Thread mainThread;
|
private final Thread mainThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue für geplante Aufgaben
|
* Queue für geplante Aufgaben
|
||||||
*/
|
*/
|
||||||
private DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
private final DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue für abgefangene InputEvents
|
* Queue für abgefangene InputEvents
|
||||||
*/
|
*/
|
||||||
private BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
private final BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
|
* Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
|
||||||
* oder das Zeichenfenster weiter geöffnet bleibt.
|
* oder das Zeichenfenster weiter geöffnet bleibt.
|
||||||
*/
|
*/
|
||||||
private boolean quitAfterTeardown = false;
|
private boolean quitAfterShutdown = false;
|
||||||
|
|
||||||
// Mauszeiger
|
// Mauszeiger
|
||||||
/**
|
/**
|
||||||
@@ -255,7 +182,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @param title Der Titel, der oben im Fenster steht.
|
* @param title Der Titel, der oben im Fenster steht.
|
||||||
*/
|
*/
|
||||||
public Zeichenmaschine( String title ) {
|
public Zeichenmaschine( String title ) {
|
||||||
this(STD_WIDTH, STD_HEIGHT, title, true);
|
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -270,7 +197,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* Aufruf von {@code draw()}.
|
* Aufruf von {@code draw()}.
|
||||||
*/
|
*/
|
||||||
public Zeichenmaschine( String title, boolean run_once ) {
|
public Zeichenmaschine( String title, boolean run_once ) {
|
||||||
this(STD_WIDTH, STD_HEIGHT, title, run_once);
|
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, run_once);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -311,78 +238,41 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
||||||
* Aufruf von {@code draw()}.
|
* Aufruf von {@code draw()}.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("static-access")
|
||||||
public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
|
public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
|
||||||
LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
|
LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
|
||||||
|
|
||||||
// Setzen des "Look&Feel"
|
// Register Cmd+Q on macOS
|
||||||
try {
|
if( Constants.MACOS ) {
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
|
||||||
} 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
|
// Erstellen der Leinwand
|
||||||
canvas = new Zeichenleinwand(width, height);
|
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();
|
background = getBackgroundLayer();
|
||||||
drawing = getDrawingLayer();
|
drawing = getDrawingLayer();
|
||||||
shapes = getShapesLayer();
|
shapes = getShapesLayer();
|
||||||
|
|
||||||
// FPS setzen
|
// FPS setzen
|
||||||
framesPerSecondInternal = STD_FPS;
|
framesPerSecondInternal = DEFAULT_FPS;
|
||||||
this.run_once = run_once;
|
this.run_once = run_once;
|
||||||
|
|
||||||
// Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen
|
// Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen
|
||||||
// verändert werden soll.
|
// verändert werden soll.
|
||||||
// TODO: When to call settings?
|
// TODO: (ngb) Wann sollte settings() aufgerufen werden?
|
||||||
settings();
|
settings();
|
||||||
|
|
||||||
// Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören.
|
// Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören.
|
||||||
@@ -392,31 +282,28 @@ public class Zeichenmaschine extends Constants {
|
|||||||
canvas.addMouseWheelListener(inputListener);
|
canvas.addMouseWheelListener(inputListener);
|
||||||
canvas.addKeyListener(inputListener);
|
canvas.addKeyListener(inputListener);
|
||||||
|
|
||||||
|
/*KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean dispatchKeyEvent( KeyEvent e ) {
|
||||||
|
enqueueEvent(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
|
||||||
// Programm beenden, wenn Fenster geschlossen wird
|
// Programm beenden, wenn Fenster geschlossen wird
|
||||||
frame.addWindowListener(new WindowAdapter() {
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosing( WindowEvent e ) {
|
public void windowClosing( WindowEvent e ) {
|
||||||
if( running ) {
|
if( isTerminated() ) {
|
||||||
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 {
|
|
||||||
quit(true);
|
quit(true);
|
||||||
|
} else {
|
||||||
|
exitNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fenster zusammenbauen, auf dem Bildschirm zentrieren ...
|
// Fenster anzeigen
|
||||||
frame.pack();
|
frame.centerFrame();
|
||||||
frame.setResizable(false);
|
|
||||||
centerFrame();
|
|
||||||
// ... und anzeigen!
|
|
||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
|
|
||||||
// Nach dem Anzeigen kann die Pufferstrategie erstellt werden.
|
// Nach dem Anzeigen kann die Pufferstrategie erstellt werden.
|
||||||
@@ -440,25 +327,28 @@ public class Zeichenmaschine extends Constants {
|
|||||||
*
|
*
|
||||||
* @param title
|
* @param title
|
||||||
*/
|
*/
|
||||||
// TODO: Implement in conjunction with Zeichenfenster
|
private Zeichenfenster createFrame( Zeichenleinwand c, String title ) {
|
||||||
private final Zeichenfenster createFrame( String title ) {
|
while( frame == null ) {
|
||||||
return null;
|
try {
|
||||||
|
TaskRunner.invokeLater(() -> {
|
||||||
|
Zeichenfenster.setLookAndFeel();
|
||||||
|
frame = new Zeichenfenster(canvas, title);
|
||||||
|
}).get();
|
||||||
|
} catch( InterruptedException e ) {
|
||||||
|
// Keep waiting
|
||||||
|
} 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.
|
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
|
||||||
*/
|
*/
|
||||||
public final void centerFrame() {
|
public final void centerFrame() {
|
||||||
// TODO: Center on current display (not main display by default)
|
frame.centerFrame();
|
||||||
// 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)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -475,37 +365,33 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* ansonsten deaktiviert.
|
* ansonsten deaktiviert.
|
||||||
*/
|
*/
|
||||||
public final void setFullscreen( boolean pEnable ) {
|
public final void setFullscreen( boolean pEnable ) {
|
||||||
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
if( pEnable && !frame.isFullscreen() ) {
|
||||||
if( displayDevice.isFullScreenSupported() ) {
|
frame.setFullscreen(true);
|
||||||
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;
|
canvasWidth = canvas.getWidth();
|
||||||
|
canvasHeight = canvas.getHeight();
|
||||||
|
|
||||||
|
if( frame.isFullscreen() )
|
||||||
fullscreenChanged();
|
fullscreenChanged();
|
||||||
} else if( !pEnable && fullscreen ) {
|
} else if( !pEnable && frame.isFullscreen() ) {
|
||||||
fullscreen = false;
|
frame.setFullscreen(false);
|
||||||
|
|
||||||
canvas.removeKeyListener(fullscreenExitListener);
|
canvasWidth = canvas.getWidth();
|
||||||
displayDevice.setFullScreenWindow(null);
|
canvasHeight = canvas.getHeight();
|
||||||
changeSize(initialWidth, initialHeight);
|
|
||||||
frame.pack();
|
if( !frame.isFullscreen() )
|
||||||
// frame.setUndecorated(false);
|
|
||||||
fullscreenChanged();
|
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() {
|
public boolean isFullscreen() {
|
||||||
Window win = displayDevice.getFullScreenWindow();
|
return frame.isFullscreen();
|
||||||
return fullscreen && win != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -541,9 +427,18 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* im Zeichenfenster an.
|
* im Zeichenfenster an.
|
||||||
*/
|
*/
|
||||||
public final void redraw() {
|
public final void redraw() {
|
||||||
if( state == Options.AppState.PAUSED ) {
|
if( state == Options.AppState.PAUSED
|
||||||
|
|| state == Options.AppState.TERMINATED ) {
|
||||||
draw();
|
draw();
|
||||||
}
|
}
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt den aktuellen Inhalt der {@link Zeichenleinwand}
|
||||||
|
* im Zeichenfenster an, ohne vorher {@link #draw()} aufzurufen.
|
||||||
|
*/
|
||||||
|
public final void render() {
|
||||||
canvas.render();
|
canvas.render();
|
||||||
// canvas.invalidate();
|
// canvas.invalidate();
|
||||||
// frame.repaint();
|
// frame.repaint();
|
||||||
@@ -583,14 +478,26 @@ public class Zeichenmaschine extends Constants {
|
|||||||
return state == Options.AppState.PAUSED;
|
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.
|
* Stoppt die Zeichenmaschine.
|
||||||
* <p>
|
* <p>
|
||||||
* Nachdem der aktuelle Frame gezeichnet wurde wechselt die Zeichenmaschine
|
* Nachdem der aktuelle Frame gezeichnet wurde wechselt die Zeichenmaschine
|
||||||
* in den Zustand {@link Options.AppState#STOPPED} und ruft
|
* 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
|
* wechselt der Zustand zu {@link Options.AppState#TERMINATED}. Das
|
||||||
* Zeichenfenster bleibt weiter geöffnet.
|
* 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() {
|
public final void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
@@ -601,7 +508,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* <p>
|
* <p>
|
||||||
* Wird nach dem {@link #stop() Stopp} der Zeichenmaschine aufgerufen und
|
* Wird nach dem {@link #stop() Stopp} der Zeichenmaschine aufgerufen und
|
||||||
* verbleibende Threads, Tasks, etc. zu stoppen und aufzuräumen. Die
|
* 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.
|
* vor {@code cleanup()} aufgerufen wird.
|
||||||
*/
|
*/
|
||||||
private void cleanup() {
|
private void cleanup() {
|
||||||
@@ -624,7 +531,23 @@ public class Zeichenmaschine extends Constants {
|
|||||||
public final void exit() {
|
public final void exit() {
|
||||||
if( running ) {
|
if( running ) {
|
||||||
running = false;
|
running = false;
|
||||||
this.quitAfterTeardown = true;
|
quitAfterShutdown = true;
|
||||||
|
} else {
|
||||||
|
quit(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void exitNow() {
|
||||||
|
// Do nothing, when already quitting
|
||||||
|
if( state == Options.AppState.QUITING ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( running ) {
|
||||||
|
running = false;
|
||||||
|
terminateImediately = true;
|
||||||
|
quitAfterShutdown = true;
|
||||||
|
mainThread.interrupt();
|
||||||
} else {
|
} else {
|
||||||
quit(true);
|
quit(true);
|
||||||
}
|
}
|
||||||
@@ -632,6 +555,10 @@ public class Zeichenmaschine extends Constants {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Beendet das Programm vollständig.
|
* Beendet das Programm vollständig.
|
||||||
|
* <p>
|
||||||
|
* Enspricht dem Aufruf {@code quit(true)}.
|
||||||
|
*
|
||||||
|
* @see #quit(boolean)
|
||||||
*/
|
*/
|
||||||
public final void quit() {
|
public final void quit() {
|
||||||
//quit(!IN_BLUEJ);
|
//quit(!IN_BLUEJ);
|
||||||
@@ -641,11 +568,21 @@ public class Zeichenmaschine extends Constants {
|
|||||||
/**
|
/**
|
||||||
* Beendet das Programm. Falls {@code exit} gleich {@code true} ist, wird
|
* Beendet das Programm. Falls {@code exit} gleich {@code true} ist, wird
|
||||||
* die komplette VM beendet.
|
* 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.
|
* @param exit Ob die VM beendet werden soll.
|
||||||
* @see System#exit(int)
|
* @see System#exit(int)
|
||||||
*/
|
*/
|
||||||
public final void quit( boolean exit ) {
|
public final void quit( boolean exit ) {
|
||||||
|
state = Options.AppState.QUITING;
|
||||||
|
TaskRunner.invokeLater(() -> {
|
||||||
frame.setVisible(false);
|
frame.setVisible(false);
|
||||||
canvas.dispose();
|
canvas.dispose();
|
||||||
frame.dispose();
|
frame.dispose();
|
||||||
@@ -653,28 +590,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
if( exit ) {
|
if( exit ) {
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -685,16 +601,11 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @see Zeichenleinwand#setSize(int, int)
|
* @see Zeichenleinwand#setSize(int, int)
|
||||||
*/
|
*/
|
||||||
public final void setSize( int width, int height ) {
|
public final void setSize( int width, int height ) {
|
||||||
if( fullscreen ) {
|
frame.setCanvasSize(width, height);
|
||||||
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);
|
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
|
||||||
frame.pack();
|
canvasWidth = canvasBounds.width;
|
||||||
}
|
canvasHeight = canvasBounds.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -952,13 +863,15 @@ public class Zeichenmaschine extends Constants {
|
|||||||
return;
|
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().");
|
LOG.warn("Don't use delay(int) from within settings() or setup().");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long timer = 0L;
|
long timer = 0L;
|
||||||
if( updateState == Options.AppState.DRAWING ) {
|
if( /*updateState == Options.AppState.DRAWING*/
|
||||||
|
isTerminating() ) {
|
||||||
// Falls gerade draw() ausgeführt wird, zeigen wir den aktuellen
|
// Falls gerade draw() ausgeführt wird, zeigen wir den aktuellen
|
||||||
// Stand der Zeichnung auf der Leinwand an. Die Zeit für das
|
// Stand der Zeichnung auf der Leinwand an. Die Zeit für das
|
||||||
// Rendern wird gemessen und von der Wartezeit abgezogen.
|
// Rendern wird gemessen und von der Wartezeit abgezogen.
|
||||||
@@ -967,6 +880,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
timer = System.nanoTime() - timer;
|
timer = System.nanoTime() - timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Options.AppState oldState = updateState;
|
||||||
try {
|
try {
|
||||||
int sub = (int) Math.ceil(timer / 1000000.0);
|
int sub = (int) Math.ceil(timer / 1000000.0);
|
||||||
|
|
||||||
@@ -974,9 +888,12 @@ public class Zeichenmaschine extends Constants {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateState = Options.AppState.DELAYED;
|
||||||
Thread.sleep(ms - sub, (int) (timer % 1000000L));
|
Thread.sleep(ms - sub, (int) (timer % 1000000L));
|
||||||
} catch( InterruptedException ex ) {
|
} catch( InterruptedException ignored ) {
|
||||||
// Nothing
|
// Nothing
|
||||||
|
} finally {
|
||||||
|
updateState = oldState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1140,7 +1057,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* Spiels oder der Abspann einer Animation angezeigt werden, oder mit
|
* Spiels oder der Abspann einer Animation angezeigt werden, oder mit
|
||||||
* {@link #saveImage()} die erstellte Zeichnung abgespeichert werden.
|
* {@link #saveImage()} die erstellte Zeichnung abgespeichert werden.
|
||||||
*/
|
*/
|
||||||
public void teardown() {
|
public void shutdown() {
|
||||||
// Intentionally left blank
|
// Intentionally left blank
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1175,18 +1092,23 @@ public class Zeichenmaschine extends Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mouse handling
|
* Input handling
|
||||||
*/
|
*/
|
||||||
private void enqueueEvent( InputEvent evt ) {
|
private void enqueueEvent( InputEvent evt ) {
|
||||||
|
if( updateState != Options.AppState.DELAYED ) {
|
||||||
eventQueue.add(evt);
|
eventQueue.add(evt);
|
||||||
|
}
|
||||||
|
|
||||||
if( isPaused() ) {
|
if( isPaused() || isTerminated() ) {
|
||||||
|
if( MouseEvent.class.isInstance(evt) ) {
|
||||||
|
saveMousePosition((MouseEvent)evt);
|
||||||
|
}
|
||||||
dispatchEvents();
|
dispatchEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dispatchEvents() {
|
private void dispatchEvents() {
|
||||||
synchronized( eventQueue ) {
|
//synchronized( eventQueue ) {
|
||||||
while( !eventQueue.isEmpty() ) {
|
while( !eventQueue.isEmpty() ) {
|
||||||
InputEvent evt = eventQueue.poll();
|
InputEvent evt = eventQueue.poll();
|
||||||
|
|
||||||
@@ -1207,7 +1129,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleKeyEvent( KeyEvent evt ) {
|
private void handleKeyEvent( KeyEvent evt ) {
|
||||||
@@ -1245,7 +1167,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
case MouseEvent.MOUSE_RELEASED:
|
case MouseEvent.MOUSE_RELEASED:
|
||||||
mousePressed = false;
|
mousePressed = false;
|
||||||
mouseButton = NOMOUSE;
|
mouseButton = NOMOUSE;
|
||||||
mousePressed(evt);
|
mouseReleased(evt);
|
||||||
break;
|
break;
|
||||||
case MouseEvent.MOUSE_DRAGGED:
|
case MouseEvent.MOUSE_DRAGGED:
|
||||||
//saveMousePosition(evt);
|
//saveMousePosition(evt);
|
||||||
@@ -1376,9 +1298,9 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* // Next frame has started
|
* // Next frame has started
|
||||||
* </code></pre>
|
* </code></pre>
|
||||||
* <p>
|
* <p>
|
||||||
* Die {@link schule.ngb.zm.tasks.FrameSynchronizedTask} implementiert eine
|
* Die {@link schule.ngb.zm.util.tasks.FrameSynchronizedTask} implementiert
|
||||||
* {@link schule.ngb.zm.tasks.Task}, die sich automatisch auf diese Wiese
|
* eine {@link schule.ngb.zm.util.tasks.Task}, die sich automatisch auf
|
||||||
* mit dem Zeichenthread synchronisiert.
|
* diese Wiese mit dem Zeichenthread synchronisiert.
|
||||||
*/
|
*/
|
||||||
public static final Object globalSyncLock = new Object[0];
|
public static final Object globalSyncLock = new Object[0];
|
||||||
|
|
||||||
@@ -1393,48 +1315,51 @@ public class Zeichenmaschine extends Constants {
|
|||||||
public final void run() {
|
public final void run() {
|
||||||
// Wait for full initialization before start
|
// Wait for full initialization before start
|
||||||
while( state != Options.AppState.INITIALIZED ) {
|
while( state != Options.AppState.INITIALIZED ) {
|
||||||
delay(1);
|
Thread.yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThreadExecutor for the update/draw Thread
|
// ThreadExecutor for the update/draw Thread
|
||||||
final UpdateThreadExecutor updateThreadExecutor = new UpdateThreadExecutor();
|
final UpdateThreadExecutor updateThreadExecutor = new UpdateThreadExecutor();
|
||||||
|
|
||||||
// start of thread in ms
|
// Start des Thread in ms
|
||||||
final long start = System.currentTimeMillis();
|
final long start = System.currentTimeMillis();
|
||||||
// current time in ns
|
// Aktuelle Zeit in ns
|
||||||
long beforeTime = System.nanoTime();
|
long beforeTime;
|
||||||
long updateBeforeTime = System.nanoTime();
|
long updateBeforeTime = System.nanoTime();
|
||||||
// store for deltas
|
// Speicher für Änderung
|
||||||
long overslept = 0L;
|
long overslept = 0L;
|
||||||
// internal counters for tick and runtime
|
// Interne Zähler für tick und runtime
|
||||||
int _tick = 0;
|
int _tick = 0;
|
||||||
long _runtime = 0;
|
long _runtime;
|
||||||
// public counters for access by subclasses
|
// Öffentliche Zähler für Unterklassen
|
||||||
tick = 0;
|
tick = 0;
|
||||||
runtime = 0;
|
runtime = 0;
|
||||||
|
|
||||||
// call setup of subclass and wait
|
// setup() der Unterklasse aufrufen
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
|
// Alles startklar ...
|
||||||
state = Options.AppState.RUNNING;
|
state = Options.AppState.RUNNING;
|
||||||
while( running ) {
|
while( running ) {
|
||||||
// delta in seconds
|
// Aktuelle Zeit in ns merken
|
||||||
beforeTime = System.nanoTime();
|
beforeTime = System.nanoTime();
|
||||||
|
|
||||||
|
// Mausposition einmal pro Frame merken
|
||||||
saveMousePosition(mouseEvent);
|
saveMousePosition(mouseEvent);
|
||||||
|
|
||||||
if( state != Options.AppState.PAUSED ) {
|
if( state != Options.AppState.PAUSED ) {
|
||||||
//handleUpdate(delta);
|
// update() und draw() der Unterklasse werden in einem
|
||||||
//handleDraw();
|
// eigenen Thread ausgeführt, aber der Zeichenthread
|
||||||
|
// wartet, bis der Thread fertig ist. Außer die Unterklasse
|
||||||
// Update and draw are executed in a new thread,
|
// ruft delay() auf und lässt den Thread eine länger Zeit
|
||||||
// but we wait for them to finish unless the user
|
// schlafen. Dann wird der nächst Frame vorzeitig gerendert,
|
||||||
// did call any blocking method, that would also block
|
// bis der update-Thread wieder bereit ist. Dadurch können
|
||||||
// rendering of new frames.
|
// nebenläufige Aufgaben (z.B. Animationen) weiterlaufen.
|
||||||
if( !updateThreadExecutor.isRunning() ) {
|
if( !updateThreadExecutor.isRunning() ) {
|
||||||
delta = (System.nanoTime() - updateBeforeTime) / 1000000000.0;
|
delta = (System.nanoTime() - updateBeforeTime) / 1000000000.0;
|
||||||
updateBeforeTime = System.nanoTime();
|
updateBeforeTime = System.nanoTime();
|
||||||
|
|
||||||
|
// uddate()/draw() ausführen
|
||||||
updateThreadExecutor.execute(() -> {
|
updateThreadExecutor.execute(() -> {
|
||||||
if( state == Options.AppState.RUNNING
|
if( state == Options.AppState.RUNNING
|
||||||
&& updateState == Options.AppState.IDLE ) {
|
&& updateState == Options.AppState.IDLE ) {
|
||||||
@@ -1446,10 +1371,11 @@ public class Zeichenmaschine extends Constants {
|
|||||||
// Call to draw()
|
// Call to draw()
|
||||||
updateState = Options.AppState.DRAWING;
|
updateState = Options.AppState.DRAWING;
|
||||||
Zeichenmaschine.this.draw();
|
Zeichenmaschine.this.draw();
|
||||||
updateState = Options.AppState.IDLE;
|
updateState = Options.AppState.DISPATCHING;
|
||||||
// Send latest input events after finishing draw
|
// Send latest input events after finishing draw
|
||||||
// since these may also block
|
// since these may also block
|
||||||
dispatchEvents();
|
dispatchEvents();
|
||||||
|
updateState = Options.AppState.IDLE;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1458,16 +1384,21 @@ public class Zeichenmaschine extends Constants {
|
|||||||
while( updateThreadExecutor.isRunning()
|
while( updateThreadExecutor.isRunning()
|
||||||
&& !updateThreadExecutor.isWaiting() ) {
|
&& !updateThreadExecutor.isWaiting() ) {
|
||||||
Thread.yield();
|
Thread.yield();
|
||||||
|
|
||||||
|
if( Thread.interrupted() ) {
|
||||||
|
running = false;
|
||||||
|
terminateImediately = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display the current buffer content
|
// Display the current buffer content
|
||||||
if( canvas != null ) {
|
if( canvas != null && frame.isDisplayable() ) {
|
||||||
canvas.render();
|
canvas.render();
|
||||||
// canvas.invalidate();
|
// canvas.invalidate();
|
||||||
// frame.repaint();
|
// frame.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// dispatchEvents();
|
// dispatchEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1510,41 +1441,27 @@ public class Zeichenmaschine extends Constants {
|
|||||||
pause_pending = false;
|
pause_pending = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Shutdown the updateThreads
|
|
||||||
updateThreadExecutor.shutdownNow();
|
|
||||||
state = Options.AppState.STOPPED;
|
state = Options.AppState.STOPPED;
|
||||||
|
// Shutdown the updateThread
|
||||||
|
while( !terminateImediately && updateThreadExecutor.isRunning() ) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
updateThreadExecutor.shutdownNow();
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
teardown();
|
shutdown();
|
||||||
cleanup();
|
cleanup();
|
||||||
state = Options.AppState.TERMINATED;
|
state = Options.AppState.TERMINATED;
|
||||||
|
|
||||||
if( quitAfterTeardown ) {
|
if( quitAfterShutdown ) {
|
||||||
quit();
|
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
|
// TODO: Remove
|
||||||
class DelayedTask implements Delayed {
|
static class DelayedTask implements Delayed {
|
||||||
|
|
||||||
long startTime; // in ms
|
long startTime; // in ms
|
||||||
|
|
||||||
@@ -1633,25 +1550,42 @@ public class Zeichenmaschine extends Constants {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: (ngb) exception handling when update/draw throws ex
|
||||||
class UpdateThreadExecutor extends ThreadPoolExecutor {
|
class UpdateThreadExecutor extends ThreadPoolExecutor {
|
||||||
|
|
||||||
private Thread updateThread;
|
private Thread updateThread;
|
||||||
|
|
||||||
private boolean running = false, waiting = false;
|
private boolean running = false;
|
||||||
|
|
||||||
public UpdateThreadExecutor() {
|
public UpdateThreadExecutor() {
|
||||||
super(1, 1, 0L,
|
super(1, 1, 0L,
|
||||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||||
|
new ThreadFactory() {
|
||||||
|
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
|
@Override
|
||||||
|
public Thread newThread( Runnable r ) {
|
||||||
|
Thread t = new Thread(mainThread.getThreadGroup(), r,
|
||||||
|
"updateThread-" + threadNumber.getAndIncrement(),
|
||||||
|
0);
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
});
|
||||||
updateState = Options.AppState.IDLE;
|
updateState = Options.AppState.IDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute( Runnable command ) {
|
||||||
|
running = true;
|
||||||
|
super.execute(command);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void beforeExecute( Thread t, Runnable r ) {
|
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
|
// but it might change if a new Thread needed to be spawned
|
||||||
// due to en error.
|
// due to en error.
|
||||||
updateThread = t;
|
updateThread = t;
|
||||||
running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1682,7 +1616,9 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public boolean isWaiting() {
|
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.Constants;
|
||||||
import schule.ngb.zm.Updatable;
|
import schule.ngb.zm.Updatable;
|
||||||
import schule.ngb.zm.events.EventDispatcher;
|
import schule.ngb.zm.util.Validator;
|
||||||
import schule.ngb.zm.tasks.FrameSynchronizedTask;
|
import schule.ngb.zm.util.events.EventDispatcher;
|
||||||
import schule.ngb.zm.tasks.TaskRunner;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
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 runtime;
|
||||||
|
|
||||||
protected int elapsed_time = 0;
|
protected int elapsedTime = 0;
|
||||||
|
|
||||||
protected boolean running = false, finished = false;
|
protected boolean running = false, finished = false;
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ public abstract class Animation<T> implements Updatable {
|
|||||||
|
|
||||||
public Animation( DoubleUnaryOperator easing ) {
|
public Animation( DoubleUnaryOperator easing ) {
|
||||||
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
||||||
this.easing = easing;
|
this.easing = Validator.requireNotNull(easing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Animation( int runtime ) {
|
public Animation( int runtime ) {
|
||||||
@@ -35,7 +34,7 @@ public abstract class Animation<T> implements Updatable {
|
|||||||
|
|
||||||
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
this.easing = easing;
|
this.easing = Validator.requireNotNull(easing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRuntime() {
|
public int getRuntime() {
|
||||||
@@ -58,17 +57,17 @@ public abstract class Animation<T> implements Updatable {
|
|||||||
|
|
||||||
public final void start() {
|
public final void start() {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
elapsed_time = 0;
|
elapsedTime = 0;
|
||||||
running = true;
|
running = true;
|
||||||
finished = false;
|
finished = false;
|
||||||
interpolate(easing.applyAsDouble(0.0));
|
animate(easing.applyAsDouble(0.0));
|
||||||
initializeEventDispatcher().dispatchEvent("start", this);
|
initializeEventDispatcher().dispatchEvent("start", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void stop() {
|
public final void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
// Make sure the last animation frame was interpolated correctly
|
// 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();
|
this.finish();
|
||||||
finished = true;
|
finished = true;
|
||||||
initializeEventDispatcher().dispatchEvent("stop", this);
|
initializeEventDispatcher().dispatchEvent("stop", this);
|
||||||
@@ -84,11 +83,7 @@ public abstract class Animation<T> implements Updatable {
|
|||||||
|
|
||||||
public final void await() {
|
public final void await() {
|
||||||
while( !finished ) {
|
while( !finished ) {
|
||||||
try {
|
Thread.yield();
|
||||||
Thread.sleep(1);
|
|
||||||
} catch( InterruptedException ex ) {
|
|
||||||
// Keep waiting
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,16 +94,16 @@ public abstract class Animation<T> implements Updatable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
elapsed_time += (int) (delta * 1000);
|
elapsedTime += (int) (delta * 1000);
|
||||||
if( elapsed_time > runtime )
|
if( elapsedTime > runtime )
|
||||||
elapsed_time = runtime;
|
elapsedTime = runtime;
|
||||||
|
|
||||||
double t = (double) elapsed_time / (double) runtime;
|
double t = (double) elapsedTime / (double) runtime;
|
||||||
if( t >= 1.0 ) {
|
if( t >= 1.0 ) {
|
||||||
running = false;
|
running = false;
|
||||||
stop();
|
stop();
|
||||||
} else {
|
} 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);
|
* e = Constants.limit(e, 0, 1);
|
||||||
* </code></pre>
|
* </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;
|
EventDispatcher<Animation, AnimationListener> eventDispatcher;
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ public class AnimationFacade<S> extends Animation<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interpolate( double e ) {
|
public void animate( double e ) {
|
||||||
anim.interpolate(e);
|
anim.animate(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,76 +1,116 @@
|
|||||||
package schule.ngb.zm.anim;
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
public class AnimationGroup extends Animation<Shape> {
|
@SuppressWarnings( "unused" )
|
||||||
|
public class AnimationGroup<T> extends Animation<T> {
|
||||||
|
|
||||||
Animation<? extends Shape>[] anims;
|
List<Animation<T>> anims;
|
||||||
|
|
||||||
private boolean overrideRuntime = false;
|
|
||||||
|
|
||||||
|
|
||||||
public AnimationGroup( DoubleUnaryOperator easing, Animation<? extends Shape>... anims ) {
|
private boolean overrideEasing = false;
|
||||||
super(easing);
|
|
||||||
this.anims = anims;
|
|
||||||
|
|
||||||
int maxRuntime = Arrays.stream(this.anims).mapToInt((a) -> a.getRuntime()).reduce(0, Integer::max);
|
private int overrideRuntime = -1;
|
||||||
setRuntime(maxRuntime);
|
|
||||||
|
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 ) {
|
public AnimationGroup( int lag, Collection<Animation<T>> anims ) {
|
||||||
super(runtime, easing);
|
this(lag, -1, null, anims);
|
||||||
this.anims = anims;
|
}
|
||||||
overrideRuntime = true;
|
|
||||||
|
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
|
@Override
|
||||||
public Shape getAnimationTarget() {
|
public T getAnimationTarget() {
|
||||||
return null;
|
for( Animation<T> anim : anims ) {
|
||||||
|
if( anim.isActive() ) {
|
||||||
|
return anim.getAnimationTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return anims.get(anims.size() - 1).getAnimationTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
if( overrideRuntime ) {
|
elapsedTime += (int) (delta * 1000);
|
||||||
synchronized( anims ) {
|
// Animation is done. Stop all Animations.
|
||||||
for( Animation<? extends Shape> anim: anims ) {
|
if( elapsedTime > runtime ) {
|
||||||
if( anim.isActive() ) {
|
for( int i = 0; i < anims.size(); i++ ) {
|
||||||
anim.update(delta);
|
if( anims.get(i).isActive() ) {
|
||||||
|
anims.get(i).elapsedTime = anims.get(i).runtime;
|
||||||
|
anims.get(i).stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
running = false;
|
||||||
|
this.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
super.update(delta);
|
t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
if( t >= 1.0 ) {
|
||||||
public void interpolate( double e ) {
|
anims.get(i).elapsedTime = anims.get(i).runtime;
|
||||||
synchronized( anims ) {
|
anims.get(i).stop();
|
||||||
for( Animation<? extends Shape> anim: anims ) {
|
} else {
|
||||||
anim.interpolate(e);
|
double e = overrideEasing ?
|
||||||
|
easing.applyAsDouble(t) :
|
||||||
|
anims.get(i).easing.applyAsDouble(t);
|
||||||
|
|
||||||
|
anims.get(i).animate(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void animate( double e ) {
|
||||||
synchronized( anims ) {
|
|
||||||
for( Animation<? extends Shape> anim: anims ) {
|
|
||||||
anim.initialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
synchronized( anims ) {
|
|
||||||
for( Animation<? extends Shape> anim: anims ) {
|
|
||||||
anim.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package schule.ngb.zm.anim;
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
import schule.ngb.zm.events.Listener;
|
import schule.ngb.zm.util.events.Listener;
|
||||||
|
|
||||||
public interface AnimationListener extends Listener<Animation> {
|
public interface AnimationListener extends Listener<Animation> {
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ package schule.ngb.zm.anim;
|
|||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.Constants;
|
import schule.ngb.zm.Constants;
|
||||||
import schule.ngb.zm.Vector;
|
import schule.ngb.zm.Vector;
|
||||||
import schule.ngb.zm.tasks.FrameSynchronizedTask;
|
import schule.ngb.zm.util.tasks.FramerateLimitedTask;
|
||||||
import schule.ngb.zm.tasks.FramerateLimitedTask;
|
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||||
import schule.ngb.zm.tasks.TaskRunner;
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
@@ -15,6 +14,7 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.function.*;
|
import java.util.function.*;
|
||||||
|
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class Animations {
|
public class Animations {
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( String propName, T target, double to, int runtime, DoubleUnaryOperator easing ) {
|
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);
|
String getterName = makeMethodName("get", propName);
|
||||||
Method getter = target.getClass().getMethod(getterName);
|
Method getter = target.getClass().getMethod(getterName);
|
||||||
if( getter != null && getter.getReturnType().equals(propType) ) {
|
if( getter.getReturnType().equals(propType) ) {
|
||||||
return (R) getter.invoke(target);
|
return (R) getter.invoke(target);
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchMethodException(String.format("No getter for property <%s> found.", propName));
|
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);
|
String setterName = makeMethodName("set", propName);
|
||||||
Method setter = target.getClass().getMethod(setterName, propType);
|
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;
|
return setter;
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchMethodException(String.format("No setter for property <%s> found.", propName));
|
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 firstChar = propName.substring(0, 1).toUpperCase();
|
||||||
String tail = "";
|
String tail = "";
|
||||||
if( propName.length() > 1 ) {
|
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 ) {
|
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(target);
|
||||||
Validator.requireNotNull(propSetter);
|
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 ) {
|
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 ) {
|
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 ) {
|
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 ) {
|
public static final <T, R> Future<T> play( 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)));
|
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() {
|
return TaskRunner.run(new FramerateLimitedTask() {
|
||||||
double t = 0.0;
|
double t = 0.0;
|
||||||
|
|
||||||
final long starttime = System.currentTimeMillis();
|
final long starttime = System.currentTimeMillis();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
// One animation step for t in [0,1]
|
// One animation step for t in [0,1]
|
||||||
@@ -164,8 +167,8 @@ public class Animations {
|
|||||||
}, target);
|
}, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> T animateAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
public static final <T> T playAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
||||||
Future<T> future = animate(target, runtime, easing, stepper);
|
Future<T> future = play(target, runtime, easing, stepper);
|
||||||
while( !future.isDone() ) {
|
while( !future.isDone() ) {
|
||||||
try {
|
try {
|
||||||
return future.get();
|
return future.get();
|
||||||
@@ -179,16 +182,17 @@ public class Animations {
|
|||||||
return target;
|
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(
|
return animate(
|
||||||
target, runtime,
|
target, runtime,
|
||||||
animator::easing,
|
animator::easing,
|
||||||
animator::interpolator,
|
animator::interpolator,
|
||||||
animator::applicator
|
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() {
|
return TaskRunner.run(new FramerateLimitedTask() {
|
||||||
@Override
|
@Override
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
@@ -203,13 +207,13 @@ public class Animations {
|
|||||||
}, animation);
|
}, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> Animation<T> animateAndWait( Animation<T> animation ) {
|
public static <T> Animation<T> playAndWait( Animation<T> animation ) {
|
||||||
Future<Animation<T>> future = animate(animation);
|
Future<Animation<T>> future = play(animation);
|
||||||
animation.await();
|
animation.await();
|
||||||
return animation;
|
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);
|
final AnimationFacade<T> facade = new AnimationFacade<>(animation, animation.getRuntime(), easing);
|
||||||
return TaskRunner.run(new FramerateLimitedTask() {
|
return TaskRunner.run(new FramerateLimitedTask() {
|
||||||
@Override
|
@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
|
@Override
|
||||||
public void interpolate( double e ) {
|
public void animate( double e ) {
|
||||||
object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e)));
|
object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e)));
|
||||||
object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e)));
|
object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class FillAnimation extends Animation<Shape> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interpolate( double e ) {
|
public void animate( double e ) {
|
||||||
object.setFillColor(Color.interpolate(oFill, tFill, e));
|
object.setFillColor(Color.interpolate(oFill, tFill, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class MorphAnimation extends Animation<Shape> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interpolate( double e ) {
|
public void animate( double e ) {
|
||||||
object.setX(Constants.interpolate(original.getX(), target.getX(), e));
|
object.setX(Constants.interpolate(original.getX(), target.getX(), e));
|
||||||
object.setY(Constants.interpolate(original.getY(), target.getY(), e));
|
object.setY(Constants.interpolate(original.getY(), target.getY(), e));
|
||||||
object.setFillColor(Color.interpolate(original.getFillColor(), target.getFillColor(), e));
|
object.setFillColor(Color.interpolate(original.getFillColor(), target.getFillColor(), e));
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class MoveAnimation extends Animation<Shape> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interpolate( double e ) {
|
public void animate( double e ) {
|
||||||
object.setX(Constants.interpolate(oX, tX, e));
|
object.setX(Constants.interpolate(oX, tX, e));
|
||||||
object.setY(Constants.interpolate(oY, tY, e));
|
object.setY(Constants.interpolate(oY, tY, e));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class RotateAnimation extends Animation<Shape> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interpolate( double e ) {
|
public void animate( double e ) {
|
||||||
object.rotateTo(Constants.interpolate(oA, tA, e));
|
object.rotateTo(Constants.interpolate(oA, tA, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class StrokeAnimation extends Animation<Shape> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interpolate( double e ) {
|
public void animate( double e ) {
|
||||||
object.setStrokeColor(Color.interpolate(oFill, tFill, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
147
src/main/java/schule/ngb/zm/layers/ColorLayer.java
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
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>
|
||||||
|
* Ein {@code ColorLayer} ist eine der drei Standardebenen der
|
||||||
|
* {@link schule.ngb.zm.Zeichenmaschine}.
|
||||||
|
* <p>
|
||||||
|
* Die Farbe der Ebene kann beliebig gesetzt werden und kann gut als
|
||||||
|
* Hintergrundfarbe 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
109
src/main/java/schule/ngb/zm/layers/DrawableLayer.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Layer um {@link Drawable} Objekte zu zeichnen.
|
||||||
|
* <p>
|
||||||
|
* Objekte, die das {@code Drawable} Interface implementieren, können der Ebene
|
||||||
|
* hinzugefügt werden. Die Ebene sorgt dafür, dass alle {@code Drawable}s einmal
|
||||||
|
* pro Frame über ihre {@link Drawable#draw(Graphics2D)} Methode gezeichnet.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public class DrawableLayer extends Layer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste der {@link Drawable}s.
|
||||||
|
*/
|
||||||
|
protected final List<Drawable> drawables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Ebene bei jedem Aufruf von {@link #draw(Graphics2D)} geleert
|
||||||
|
* werden soll.
|
||||||
|
*/
|
||||||
|
protected boolean clearBeforeDraw = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Ebene in der Standardgröße.
|
||||||
|
*/
|
||||||
|
public DrawableLayer() {
|
||||||
|
drawables = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Ebene mit der angegebenen Größe.
|
||||||
|
*
|
||||||
|
* @param width Die Breite der Ebene.
|
||||||
|
* @param height Die Höhe der Ebene.
|
||||||
|
*/
|
||||||
|
public DrawableLayer( int width, int height ) {
|
||||||
|
super(width, height);
|
||||||
|
drawables = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt alle angegebenen {@code Drawable}s der Ebene hinzu.
|
||||||
|
*
|
||||||
|
* @param drawables Die {@code Drawable} Objekte.
|
||||||
|
*/
|
||||||
|
public void add( Drawable... drawables ) {
|
||||||
|
synchronized( this.drawables ) {
|
||||||
|
Collections.addAll(this.drawables, drawables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt eine Liste aller {@code Drawable} Objekte dieser Ebene zurück.
|
||||||
|
*
|
||||||
|
* @return Die Liste der {@code Drawable} Objekte.
|
||||||
|
*/
|
||||||
|
public java.util.List<Drawable> getDrawables() {
|
||||||
|
return drawables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Ebene bei jedem Frame automatisch gelöscht wird.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Ebene vorm Zeichnen gelöscht wird,
|
||||||
|
* {@code false} sonst.
|
||||||
|
*/
|
||||||
|
public boolean isClearBeforeDraw() {
|
||||||
|
return clearBeforeDraw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stellt ein, ob die Ebene vorm Zeichnen gelöscht werden soll.
|
||||||
|
*
|
||||||
|
* @param pClearBeforeDraw Ob die Ebene vorm Zeichnen gelöscht werden
|
||||||
|
* soll.
|
||||||
|
*/
|
||||||
|
public void setClearBeforeDraw( boolean pClearBeforeDraw ) {
|
||||||
|
this.clearBeforeDraw = pClearBeforeDraw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
if( clearBeforeDraw ) {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Drawable> it = List.copyOf(drawables);
|
||||||
|
for( Drawable d : it ) {
|
||||||
|
if( d.isVisible() ) {
|
||||||
|
d.draw(drawing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.draw(graphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
866
src/main/java/schule/ngb/zm/layers/DrawingLayer.java
Normal file
@@ -0,0 +1,866 @@
|
|||||||
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
import schule.ngb.zm.Layer;
|
||||||
|
import schule.ngb.zm.Options;
|
||||||
|
import schule.ngb.zm.Fillable;
|
||||||
|
import schule.ngb.zm.Strokeable;
|
||||||
|
import schule.ngb.zm.shapes.Text;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.*;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Ebene auf der direkt gezeichnet werden kann.
|
||||||
|
* <p>
|
||||||
|
* Ein {@code DrawingLayer} ist eine der drei Standardebenen der
|
||||||
|
* {@link schule.ngb.zm.Zeichenmaschine}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wiederverwendbarer Speicher für eine Linie.
|
||||||
|
*/
|
||||||
|
protected Line2D.Double line = new Line2D.Double();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wiederverwendbarer Speicher für eine Ellipse.
|
||||||
|
*/
|
||||||
|
protected Ellipse2D.Double ellipse = new Ellipse2D.Double();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wiederverwendbarer Speicher für ein Rechteck.
|
||||||
|
*/
|
||||||
|
protected Rectangle2D.Double rect = new Rectangle2D.Double();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wiederverwendbarer Speicher für einen Kreisbogen.
|
||||||
|
*/
|
||||||
|
protected Arc2D.Double arc = new Arc2D.Double();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wiederverwendbarer Speicher für einen Pfad.
|
||||||
|
*/
|
||||||
|
protected Path2D.Double path = new Path2D.Double();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob ein individueller Pfad gestartet wurde.
|
||||||
|
*/
|
||||||
|
private boolean pathStarted = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stapel für zwischengespeicherte Transformationsmatrizen.
|
||||||
|
*/
|
||||||
|
private final Stack<AffineTransform> transformStack;
|
||||||
|
// private FontMetrics fontMetrics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Shape, an die die Methoden für Farben und Konturlinien delegiert
|
||||||
|
* werden.
|
||||||
|
*/
|
||||||
|
private schule.ngb.zm.shapes.Text shapeDelegate;
|
||||||
|
|
||||||
|
private final Stack<schule.ngb.zm.shapes.Text> styleStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Ebene in der Standardgröße.
|
||||||
|
*/
|
||||||
|
public DrawingLayer() {
|
||||||
|
super();
|
||||||
|
transformStack = new Stack<>();
|
||||||
|
transformStack.push(new AffineTransform());
|
||||||
|
// fontMetrics = drawing.getFontMetrics();
|
||||||
|
|
||||||
|
styleStack = new Stack<>();
|
||||||
|
resetStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt eine Ebene mit der angegebenen Größe.
|
||||||
|
*
|
||||||
|
* @param width Die Breite der Ebene.
|
||||||
|
* @param height Die Höhe der Ebene.
|
||||||
|
*/
|
||||||
|
public DrawingLayer( int width, int height ) {
|
||||||
|
super(width, height);
|
||||||
|
transformStack = new Stack<>();
|
||||||
|
transformStack.push(new AffineTransform());
|
||||||
|
// fontMetrics = drawing.getFontMetrics();
|
||||||
|
|
||||||
|
styleStack = new Stack<>();
|
||||||
|
resetStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die aktuelle Füllfarbe zurück.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Füllfarbe.
|
||||||
|
*/
|
||||||
|
public Color getFillColor() {
|
||||||
|
return shapeDelegate.getFillColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf die angegebene Farbe.
|
||||||
|
*
|
||||||
|
* @param color Die neue Füllfarbe oder {@code null}.
|
||||||
|
* @see Color
|
||||||
|
*/
|
||||||
|
public void setFillColor( Color color ) {
|
||||||
|
shapeDelegate.setFillColor(color);
|
||||||
|
drawing.setPaint(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 schule.ngb.zm.Color(red, green, blue, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt die Füllung der Form.
|
||||||
|
*/
|
||||||
|
public void noFill() {
|
||||||
|
shapeDelegate.noFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Füllfarbe auf den Standardwert zurück.
|
||||||
|
*
|
||||||
|
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
||||||
|
*/
|
||||||
|
public void resetFill() {
|
||||||
|
shapeDelegate.resetFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFill( Paint fill ) {
|
||||||
|
shapeDelegate.setFill(fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Paint getFill() {
|
||||||
|
return shapeDelegate.getFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 ) {
|
||||||
|
shapeDelegate.setGradient(fromX, fromY, from, toX, toY, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 ) {
|
||||||
|
shapeDelegate.setGradient(centerX, centerY, radius, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||||
|
Point2D.Double anchorPoint = schule.ngb.zm.shapes.Shape.getAnchorPoint(getWidth(), getHeight(), dir);
|
||||||
|
Point2D.Double inversePoint = schule.ngb.zm.shapes.Shape.getAnchorPoint(getWidth(), getHeight(), dir.inverse());
|
||||||
|
shapeDelegate.setGradient(inversePoint.x, inversePoint.y, from, anchorPoint.x, anchorPoint.y, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( Color from, Color to ) {
|
||||||
|
shapeDelegate.setGradient(getWidth() / 2.0, getHeight() / 2.0, min(getWidth(), getHeight()) / 2.0, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt den Farbverlauf von der Form.
|
||||||
|
*/
|
||||||
|
public void noGradient() {
|
||||||
|
shapeDelegate.noGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStroke( Stroke stroke ) {
|
||||||
|
shapeDelegate.setStroke(stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die aktuelle Farbe der Konturlinie zurück.
|
||||||
|
*
|
||||||
|
* @return Die Konturfarbe oder {@code null}.
|
||||||
|
*/
|
||||||
|
public Color getStrokeColor() {
|
||||||
|
return shapeDelegate.getStrokeColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Farbe der Konturlinie auf die angegebene Farbe.
|
||||||
|
*
|
||||||
|
* @param color Die neue Farbe der Konturlinie.
|
||||||
|
* @see Color
|
||||||
|
*/
|
||||||
|
public void setStrokeColor( Color color ) {
|
||||||
|
shapeDelegate.setStrokeColor(color);
|
||||||
|
drawing.setPaint(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 schule.ngb.zm.Color(red, green, blue, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt die Kontur der Form.
|
||||||
|
*/
|
||||||
|
public void noStroke() {
|
||||||
|
shapeDelegate.noStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
shapeDelegate.resetStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Dicke der Konturlinie zurück.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Dicke der Linie.
|
||||||
|
*/
|
||||||
|
public double getStrokeWeight() {
|
||||||
|
return shapeDelegate.getStrokeWeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 ) {
|
||||||
|
shapeDelegate.setStrokeWeight(weight);
|
||||||
|
drawing.setStroke(shapeDelegate.getStroke());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Art der Konturlinie zurück.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Art der Konturlinie.
|
||||||
|
* @see Options.StrokeType
|
||||||
|
*/
|
||||||
|
public Options.StrokeType getStrokeType() {
|
||||||
|
return shapeDelegate.getStrokeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
||||||
|
* {@link #DOTTED} und {@link #SOLID}.
|
||||||
|
*
|
||||||
|
* @param type Eine der möglichen Konturarten.
|
||||||
|
* @see Options.StrokeType
|
||||||
|
*/
|
||||||
|
public void setStrokeType( Options.StrokeType type ) {
|
||||||
|
shapeDelegate.setStrokeType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stroke getStroke() {
|
||||||
|
return shapeDelegate.getStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnchor( Options.Direction anchor ) {
|
||||||
|
shapeDelegate.setAnchor(anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear( int gray ) {
|
||||||
|
clear(gray, gray, gray, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear( int gray, int alpha ) {
|
||||||
|
clear(gray, gray, gray, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear( int red, int green, int blue ) {
|
||||||
|
clear(red, green, blue, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear( int red, int green, int blue, int alpha ) {
|
||||||
|
clear(new schule.ngb.zm.Color(red, green, blue, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
pushMatrix();
|
||||||
|
resetMatrix();
|
||||||
|
drawing.setColor(pColor.getJavaColor());
|
||||||
|
drawing.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
|
||||||
|
drawing.setColor(currentColor);
|
||||||
|
popMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void line( double x1, double y1, double x2, double y2 ) {
|
||||||
|
line.setLine(x1, y1, x2, y2);
|
||||||
|
drawShape(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pixel( double x, double y ) {
|
||||||
|
buffer.setRGB((int) x, (int) y, shapeDelegate.getFontColor().getRGBA());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void square( double x, double y, double w ) {
|
||||||
|
rect(x, y, w, w, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void square( double x, double y, double w, Options.Direction anchor ) {
|
||||||
|
rect(x, y, w, w, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet ein Rechteck in die Zeichenebene.
|
||||||
|
*
|
||||||
|
* @param x x-Koordinate des Rechtecks.
|
||||||
|
* @param y y-Koordinate des Rechtecks.
|
||||||
|
* @param w Breite des Rechtecks.
|
||||||
|
* @param h Höhe des Rechtecks.
|
||||||
|
*/
|
||||||
|
public void rect( double x, double y, double w, double h ) {
|
||||||
|
rect(x, y, w, h, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet ein Rechteck mit in die Zeichenebene.
|
||||||
|
*
|
||||||
|
* @param x x-Koordinate des Rechtecks.
|
||||||
|
* @param y y-Koordinate des Rechtecks.
|
||||||
|
* @param w Breite des Rechtecks.
|
||||||
|
* @param h Höhe des Rechtecks.
|
||||||
|
* @param anchor Ankerpunkt de Rechtecks.
|
||||||
|
*/
|
||||||
|
public void rect( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||||
|
Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor);
|
||||||
|
rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h);
|
||||||
|
fillShape(rect);
|
||||||
|
drawShape(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet ein Rechteck mit abgerundeten Ecken in die Zeichenebene.
|
||||||
|
* <p>
|
||||||
|
* Jede Ecke wird als Viertelkreis eines Kreises mit dem Radius
|
||||||
|
* {@code radius} gezeichnet.
|
||||||
|
*
|
||||||
|
* @param x x-Koordinate des Rechtecks.
|
||||||
|
* @param y y-Koordinate des Rechtecks.
|
||||||
|
* @param w Breite des Rechtecks.
|
||||||
|
* @param h Höhe des Rechtecks.
|
||||||
|
* @param radius Radius der Eckenkreise.
|
||||||
|
*/
|
||||||
|
public void roundedRect( double x, double y, double w, double h, double radius ) {
|
||||||
|
roundedRect(x, y, w, h, radius, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet ein Rechteck mit abgerundeten Ecken in die Zeichenebene.
|
||||||
|
* <p>
|
||||||
|
* Jede Ecke wird als Viertelkreis eines Kreises mit dem Radius
|
||||||
|
* {@code radius} gezeichnet.
|
||||||
|
*
|
||||||
|
* @param x x-Koordinate des Rechtecks.
|
||||||
|
* @param y y-Koordinate des Rechtecks.
|
||||||
|
* @param w Breite des Rechtecks.
|
||||||
|
* @param h Höhe des Rechtecks.
|
||||||
|
* @param radius Radius der Eckenkreise.
|
||||||
|
* @param anchor Ankerpunkt de Rechtecks.
|
||||||
|
*/
|
||||||
|
public void roundedRect( double x, double y, double w, double h, double radius, Options.Direction anchor ) {
|
||||||
|
Point2D.Double anchorPoint = this.getOriginPoint(x, y, w, h, anchor);
|
||||||
|
Shape rect = new RoundRectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), w, h, radius, radius);
|
||||||
|
fillShape(rect);
|
||||||
|
drawShape(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void point( double x, double y ) {
|
||||||
|
ellipse(x - 1, y - 1, 2, 2, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void circle( double x, double y, double r ) {
|
||||||
|
ellipse(x, y, r + r, r + r, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void circle( double x, double y, double r, Options.Direction anchor ) {
|
||||||
|
ellipse(x, y, r + r, r + r, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ellipse( double x, double y, double w, double h ) {
|
||||||
|
ellipse(x, y, w, h, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ellipse( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||||
|
Point2D.Double anchorPoint = getOriginPoint(x, y, w, h, anchor);
|
||||||
|
ellipse.setFrame(anchorPoint.x, anchorPoint.y, w, h);
|
||||||
|
fillShape(ellipse);
|
||||||
|
drawShape(ellipse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void arc( double x, double y, double r, double angle1, double angle2 ) {
|
||||||
|
arc(x, y, r + r, r + r, angle1, angle2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void arc( double x, double y, double w, double h, double angle1, double angle2 ) {
|
||||||
|
while( angle2 < angle1 ) angle2 += 360.0;
|
||||||
|
|
||||||
|
Point2D.Double anchorPoint = getOriginPoint(x, y, w, h, CENTER);
|
||||||
|
arc.setArc(
|
||||||
|
anchorPoint.x, anchorPoint.y,
|
||||||
|
w, h,
|
||||||
|
//Math.toRadians(angle1), Math.toRadians(angle2 - angle1),
|
||||||
|
angle1, angle2 - angle1,
|
||||||
|
Arc2D.OPEN
|
||||||
|
);
|
||||||
|
|
||||||
|
drawShape(arc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pie( double x, double y, double r, double angle1, double angle2 ) {
|
||||||
|
while( angle2 < angle1 ) angle2 += 360.0;
|
||||||
|
|
||||||
|
double d = r + r;
|
||||||
|
|
||||||
|
Point2D.Double anchorPoint = getOriginPoint(x, y, d, d, CENTER);
|
||||||
|
arc.setArc(
|
||||||
|
anchorPoint.x, anchorPoint.y,
|
||||||
|
d, d,
|
||||||
|
angle1, angle2 - angle1,
|
||||||
|
Arc2D.PIE
|
||||||
|
);
|
||||||
|
|
||||||
|
fillShape(arc);
|
||||||
|
drawShape(arc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void curve( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
||||||
|
QuadCurve2D curve = new QuadCurve2D.Double(x1, y1, x2, y2, x3, y3);
|
||||||
|
drawShape(curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void curve( double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4 ) {
|
||||||
|
CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||||
|
drawShape(curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void triangle( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
||||||
|
Path2D path = new Path2D.Double();
|
||||||
|
path.moveTo(x1, y1);
|
||||||
|
path.lineTo(x2, y2);
|
||||||
|
path.lineTo(x3, y3);
|
||||||
|
path.lineTo(x1, y1);
|
||||||
|
|
||||||
|
fillShape(path);
|
||||||
|
drawShape(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rhombus( double x, double y, double width, double height ) {
|
||||||
|
rhombus(x, y, width, height, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rhombus( double x, double y, double width, double height, Options.Direction anchor ) {
|
||||||
|
double whalf = width / 2.0, hhalf = height / 2.0;
|
||||||
|
Point2D.Double anchorPoint = getOriginPoint(x, y, width, height, anchor);
|
||||||
|
polygon(anchorPoint.x + whalf, anchorPoint.y, anchorPoint.x + width, anchorPoint.y + hhalf, anchorPoint.x + whalf, anchorPoint.y + height, anchorPoint.x, anchorPoint.y + hhalf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void polygon( double... coordinates ) {
|
||||||
|
if( coordinates.length < 4 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path2D path = new Path2D.Double();
|
||||||
|
path.moveTo(coordinates[0], coordinates[1]);
|
||||||
|
for( int i = 2; i < coordinates.length; i += 2 ) {
|
||||||
|
if( i + 1 < coordinates.length ) {
|
||||||
|
path.lineTo(coordinates[i], coordinates[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = coordinates.length;
|
||||||
|
if( coordinates[len - 2] != coordinates[0] || coordinates[len - 1] != coordinates[1] ) {
|
||||||
|
path.lineTo(coordinates[0], coordinates[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fillShape(path);
|
||||||
|
drawShape(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void polygon( Point2D... points ) {
|
||||||
|
if( points.length < 2 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path2D path = new Path2D.Double();
|
||||||
|
path.moveTo(points[0].getX(), points[0].getY());
|
||||||
|
for( int i = 1; i < points.length; i += 1 ) {
|
||||||
|
path.lineTo(points[i].getX(), points[i].getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = points.length;
|
||||||
|
if( points[len - 1].equals(points[0]) ) {
|
||||||
|
path.moveTo(points[0].getX(), points[0].getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
fillShape(path);
|
||||||
|
drawShape(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fillShape( Shape shape ) {
|
||||||
|
if( shapeDelegate.getFill() != null ) {
|
||||||
|
drawing.setPaint(shapeDelegate.getFill());
|
||||||
|
drawing.fill(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void drawShape( Shape shape ) {
|
||||||
|
if( shapeDelegate.hasStroke() ) {
|
||||||
|
drawing.setColor(shapeDelegate.getStrokeColor().getJavaColor());
|
||||||
|
drawing.setStroke(shapeDelegate.getStroke());
|
||||||
|
drawing.draw(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginShape() {
|
||||||
|
path.reset();
|
||||||
|
pathStarted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lineTo( double x, double y ) {
|
||||||
|
if( !pathStarted ) {
|
||||||
|
path.moveTo(x, y);
|
||||||
|
pathStarted = true;
|
||||||
|
} else {
|
||||||
|
path.lineTo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endShape() {
|
||||||
|
path.closePath();
|
||||||
|
path.trimToSize();
|
||||||
|
pathStarted = false;
|
||||||
|
|
||||||
|
fillShape(path);
|
||||||
|
drawShape(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void image( String imageSource, double x, double y ) {
|
||||||
|
image(ImageLoader.loadImage(imageSource), x, y, 1.0, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void image( String imageSource, double x, double y, Options.Direction anchor ) {
|
||||||
|
image(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void image( String imageSource, double x, double y, double scale ) {
|
||||||
|
image(ImageLoader.loadImage(imageSource), x, y, scale, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
|
||||||
|
image(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void image( Image image, double x, double y ) {
|
||||||
|
image(image, x, y, 1.0, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void image( Image image, double x, double y, double scale ) {
|
||||||
|
image(image, x, y, scale, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void image( Image image, double x, double y, double scale, Options.Direction anchor ) {
|
||||||
|
if( image != null ) {
|
||||||
|
double neww = image.getWidth(null) * scale;
|
||||||
|
double newh = image.getHeight(null) * scale;
|
||||||
|
Point2D.Double anchorPoint = getOriginPoint(x, y, neww, newh, anchor);
|
||||||
|
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Font getFont() {
|
||||||
|
return shapeDelegate.getFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFont( Font newFont ) {
|
||||||
|
shapeDelegate.setFont(newFont);
|
||||||
|
// fontMetrics = drawing.getFontMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFont( String fontName ) {
|
||||||
|
shapeDelegate.setFont(fontName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFont( String fontName, int size ) {
|
||||||
|
shapeDelegate.setFont(fontName, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFont( String fontName, int size, int style ) {
|
||||||
|
shapeDelegate.setFont(fontName, size, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getFontSize() {
|
||||||
|
return shapeDelegate.getFontSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontSize( double size ) {
|
||||||
|
shapeDelegate.setFontSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void text( String text, double x, double y ) {
|
||||||
|
text(text, x, y, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void text( String text, double x, double y, Options.Direction anchor ) {
|
||||||
|
drawing.setFont(shapeDelegate.getFont());
|
||||||
|
FontMetrics fm = drawing.getFontMetrics();
|
||||||
|
Point2D.Double anchorPoint = getOriginPoint(x, y + fm.getAscent(), fm.stringWidth(text), fm.getHeight(), anchor);
|
||||||
|
|
||||||
|
drawing.drawString(text, (float) anchorPoint.x, (float) anchorPoint.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Berechnet den Ursprung einer zu zeichnenden Form, wenn der angegebene
|
||||||
|
* Ankerpunkt zugrunde gelegt wird.
|
||||||
|
* <p>
|
||||||
|
* Der Ursprung einer Form liegt immer oben links. Eine Form an den
|
||||||
|
* Koordinaten {@code (300, 300)} und den Abmessungen {@code (100, 50)}
|
||||||
|
* würde für den Anker {@link Options.Direction#NORTHWEST} also den Ursprung
|
||||||
|
* {@code (300, 300)} haben. Für den Anker {@link Options.Direction#CENTER}
|
||||||
|
* wäre der Ursprung zu den Koordinaten um die hälfte der Abmessungen nach
|
||||||
|
* links und oben verschoben bei {@code (250, 275)}. Beim Zeichnen liegen
|
||||||
|
* die Koordinaten {@code (300, 300)} dann in der Mitte der Form.
|
||||||
|
*
|
||||||
|
* @param x x-Koordinate des umgebenden Rechtecks.
|
||||||
|
* @param y y-Koordinate des umgebenden Rechtecks.
|
||||||
|
* @param w Breite des umgebenden Rechtecks.
|
||||||
|
* @param h Höhe des umgebenden Rechtecks.
|
||||||
|
* @param anchor Zu verendender Anker.
|
||||||
|
* @return Der Ursprung des umgebenden Rechtecks.
|
||||||
|
*/
|
||||||
|
protected Point2D.Double getOriginPoint( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||||
|
Point2D.Double ap = schule.ngb.zm.shapes.Shape.getAnchorPoint(w, h, anchor);
|
||||||
|
ap.x = x - ap.x;
|
||||||
|
ap.y = y - ap.y;
|
||||||
|
return ap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushStyle() {
|
||||||
|
styleStack.push(shapeDelegate);
|
||||||
|
shapeDelegate = new Text(shapeDelegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popStyle() {
|
||||||
|
if( styleStack.isEmpty() ) {
|
||||||
|
resetStyle();
|
||||||
|
} else {
|
||||||
|
shapeDelegate = styleStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetStyle() {
|
||||||
|
Text newDelegate = new Text(0, 0, "");
|
||||||
|
newDelegate.setFillColor(DEFAULT_FILLCOLOR);
|
||||||
|
newDelegate.setStrokeColor(DEFAULT_STROKECOLOR);
|
||||||
|
if( shapeDelegate != null ) {
|
||||||
|
newDelegate.setAnchor(shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
shapeDelegate = newDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void translate( double dx, double dy ) {
|
||||||
|
drawing.translate(dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scale( double factor ) {
|
||||||
|
drawing.scale(factor, factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotate( double pAngle ) {
|
||||||
|
drawing.rotate(Math.toRadians(pAngle));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotate( double pAngle, double centerX, double centerY ) {
|
||||||
|
drawing.rotate(Math.toRadians(pAngle), centerX, centerY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shear( double dx, double dy ) {
|
||||||
|
drawing.shear(dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffineTransform getMatrix() {
|
||||||
|
return drawing.getTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushMatrix() {
|
||||||
|
transformStack.push(drawing.getTransform());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popMatrix() {
|
||||||
|
if( transformStack.isEmpty() ) {
|
||||||
|
resetMatrix();
|
||||||
|
} else {
|
||||||
|
drawing.setTransform(transformStack.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetMatrix() {
|
||||||
|
drawing.setTransform(new AffineTransform());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Layer;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
|
|
||||||
import schule.ngb.zm.util.ImageLoader;
|
@SuppressWarnings( "unused" )
|
||||||
|
|
||||||
public class ImageLayer extends Layer {
|
public class ImageLayer extends Layer {
|
||||||
|
|
||||||
protected Image image;
|
protected Image image;
|
||||||
@@ -1,45 +1,53 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
import java.awt.*;
|
import schule.ngb.zm.Color;
|
||||||
|
import schule.ngb.zm.Layer;
|
||||||
|
import schule.ngb.zm.Options;
|
||||||
|
|
||||||
|
import java.awt.BasicStroke;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.Stroke;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public final class Shape2DLayer extends Layer {
|
public final class Shape2DLayer extends Layer {
|
||||||
|
|
||||||
protected Color strokeColor = STD_STROKECOLOR;
|
private schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||||
|
|
||||||
protected Color fillColor = STD_FILLCOLOR;
|
private schule.ngb.zm.Color fillColor = DEFAULT_FILLCOLOR;
|
||||||
|
|
||||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
private double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||||
|
|
||||||
protected Options.StrokeType strokeType = SOLID;
|
private Options.StrokeType strokeType = SOLID;
|
||||||
|
|
||||||
private LinkedList<java.awt.Shape> shapes;
|
private final LinkedList<java.awt.Shape> shapes;
|
||||||
|
|
||||||
private boolean instantDraw = false;
|
private boolean instantDraw = false;
|
||||||
|
|
||||||
public Shape2DLayer() {
|
public Shape2DLayer() {
|
||||||
super();
|
super();
|
||||||
shapes = new LinkedList<java.awt.Shape>();
|
shapes = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shape2DLayer( boolean instantDraw ) {
|
public Shape2DLayer( boolean instantDraw ) {
|
||||||
super();
|
super();
|
||||||
shapes = new LinkedList<java.awt.Shape>();
|
shapes = new LinkedList<>();
|
||||||
this.instantDraw = instantDraw;
|
this.instantDraw = instantDraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shape2DLayer( int width, int height ) {
|
public Shape2DLayer( int width, int height ) {
|
||||||
super(width, height);
|
super(width, height);
|
||||||
shapes = new LinkedList<java.awt.Shape>();
|
shapes = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shape2DLayer( int width, int height, boolean instantDraw ) {
|
public Shape2DLayer( int width, int height, boolean instantDraw ) {
|
||||||
super(width, height);
|
super(width, height);
|
||||||
shapes = new LinkedList<java.awt.Shape>();
|
shapes = new LinkedList<>();
|
||||||
this.instantDraw = instantDraw;
|
this.instantDraw = instantDraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getFillColor() {
|
public schule.ngb.zm.Color getFillColor() {
|
||||||
return fillColor;
|
return fillColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +55,7 @@ public final class Shape2DLayer extends Layer {
|
|||||||
setFillColor(gray, gray, gray, 255);
|
setFillColor(gray, gray, gray, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFillColor( Color pColor ) {
|
public void setFillColor( schule.ngb.zm.Color pColor ) {
|
||||||
fillColor = pColor;
|
fillColor = pColor;
|
||||||
drawing.setColor(pColor.getJavaColor());
|
drawing.setColor(pColor.getJavaColor());
|
||||||
}
|
}
|
||||||
@@ -65,10 +73,10 @@ public final class Shape2DLayer extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
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;
|
return strokeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +84,7 @@ public final class Shape2DLayer extends Layer {
|
|||||||
setStrokeColor(gray, gray, gray, 255);
|
setStrokeColor(gray, gray, gray, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStrokeColor( Color pColor ) {
|
public void setStrokeColor( schule.ngb.zm.Color pColor ) {
|
||||||
strokeColor = pColor;
|
strokeColor = pColor;
|
||||||
drawing.setColor(pColor.getJavaColor());
|
drawing.setColor(pColor.getJavaColor());
|
||||||
}
|
}
|
||||||
@@ -102,7 +110,7 @@ public final class Shape2DLayer extends Layer {
|
|||||||
drawing.setStroke(createStroke());
|
drawing.setStroke(createStroke());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stroke createStroke() {
|
private Stroke createStroke() {
|
||||||
switch( strokeType ) {
|
switch( strokeType ) {
|
||||||
case DOTTED:
|
case DOTTED:
|
||||||
return new BasicStroke(
|
return new BasicStroke(
|
||||||
@@ -149,7 +157,7 @@ public final class Shape2DLayer extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D pGraphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
if( !instantDraw ) {
|
if( !instantDraw ) {
|
||||||
for( Shape shape : shapes ) {
|
for( Shape shape : shapes ) {
|
||||||
drawing.setColor(fillColor.getJavaColor());
|
drawing.setColor(fillColor.getJavaColor());
|
||||||
@@ -160,7 +168,7 @@ public final class Shape2DLayer extends Layer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.draw(pGraphics);
|
super.draw(graphics);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,29 @@
|
|||||||
package schule.ngb.zm.shapes;
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
import schule.ngb.zm.Layer;
|
import schule.ngb.zm.Layer;
|
||||||
import schule.ngb.zm.anim.Animation;
|
import schule.ngb.zm.anim.Animation;
|
||||||
import schule.ngb.zm.anim.AnimationFacade;
|
import schule.ngb.zm.anim.AnimationFacade;
|
||||||
import schule.ngb.zm.anim.Easing;
|
import schule.ngb.zm.anim.Easing;
|
||||||
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Layer um {@link Shape} Objekte zu zeichnen.
|
||||||
|
* <p>
|
||||||
|
* Ein {@code ShapesLayer} ist eine der drei Standardebenen der
|
||||||
|
* {@link schule.ngb.zm.Zeichenmaschine}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class ShapesLayer extends Layer {
|
public class ShapesLayer extends Layer {
|
||||||
|
|
||||||
protected boolean clearBeforeDraw = true;
|
protected boolean clearBeforeDraw = true;
|
||||||
|
|
||||||
private List<Shape> shapes;
|
private final List<Shape> shapes;
|
||||||
|
|
||||||
private List<Animation<? extends Shape>> animations;
|
private final List<Animation<? extends Shape>> animations;
|
||||||
|
|
||||||
public ShapesLayer() {
|
public ShapesLayer() {
|
||||||
super();
|
super();
|
||||||
@@ -39,44 +44,40 @@ public class ShapesLayer extends Layer {
|
|||||||
public <ST extends Shape> ST getShape( Class<ST> shapeClass ) {
|
public <ST extends Shape> ST getShape( Class<ST> shapeClass ) {
|
||||||
for( Shape s : shapes ) {
|
for( Shape s : shapes ) {
|
||||||
if( shapeClass.isInstance(s) ) {
|
if( shapeClass.isInstance(s) ) {
|
||||||
return (ST) s;
|
return shapeClass.cast(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public java.util.List<Shape> getShapes() {
|
public List<Shape> getShapes() {
|
||||||
return shapes;
|
return shapes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <ST extends Shape> java.util.List<ST> getShapes( Class<ST> shapeClass ) {
|
public <ST extends Shape> List<ST> getShapes( Class<ST> shapeClass ) {
|
||||||
java.util.List<ST> result = new LinkedList<>();
|
List<ST> result = new LinkedList<>();
|
||||||
for( Shape s : shapes ) {
|
for( Shape s : shapes ) {
|
||||||
if( shapeClass.isInstance(s) ) {
|
if( shapeClass.isInstance(s) ) {
|
||||||
result.add((ST) s);
|
result.add(shapeClass.cast(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add( Shape... shapes ) {
|
public void add( Shape... shapes ) {
|
||||||
synchronized( shapes ) {
|
synchronized( this.shapes ) {
|
||||||
for( Shape s : shapes ) {
|
Collections.addAll(this.shapes, shapes);
|
||||||
this.shapes.add(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add( Collection<Shape> shapes ) {
|
public void add( Collection<Shape> shapes ) {
|
||||||
synchronized( shapes ) {
|
synchronized( this.shapes ) {
|
||||||
for( Shape s : shapes ) {
|
this.shapes.addAll(shapes);
|
||||||
this.shapes.add(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove( Shape... shapes ) {
|
public void remove( Shape... shapes ) {
|
||||||
synchronized( shapes ) {
|
synchronized( this.shapes ) {
|
||||||
for( Shape s : shapes ) {
|
for( Shape s : shapes ) {
|
||||||
this.shapes.remove(s);
|
this.shapes.remove(s);
|
||||||
}
|
}
|
||||||
@@ -84,10 +85,8 @@ public class ShapesLayer extends Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void remove( Collection<Shape> shapes ) {
|
public void remove( Collection<Shape> shapes ) {
|
||||||
synchronized( shapes ) {
|
synchronized( this.shapes ) {
|
||||||
for( Shape s: shapes ) {
|
this.shapes.removeAll(shapes);
|
||||||
this.shapes.remove(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,16 +98,16 @@ public class ShapesLayer extends Layer {
|
|||||||
|
|
||||||
public void showAll() {
|
public void showAll() {
|
||||||
synchronized( shapes ) {
|
synchronized( shapes ) {
|
||||||
for( Shape pShape : shapes ) {
|
for( Shape s : shapes ) {
|
||||||
pShape.show();
|
s.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideAll() {
|
public void hideAll() {
|
||||||
synchronized( shapes ) {
|
synchronized( shapes ) {
|
||||||
for( Shape pShape : shapes ) {
|
for( Shape s : shapes ) {
|
||||||
pShape.hide();
|
s.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,6 +117,14 @@ public class ShapesLayer extends Layer {
|
|||||||
anim.start();
|
anim.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final 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 ) {
|
public <S extends Shape> void play( Animation<S> anim, int runtime ) {
|
||||||
play(anim, runtime, Easing.DEFAULT_EASING);
|
play(anim, runtime, Easing.DEFAULT_EASING);
|
||||||
}
|
}
|
||||||
@@ -135,26 +142,27 @@ public class ShapesLayer extends Layer {
|
|||||||
anim.update(delta);
|
anim.update(delta);
|
||||||
|
|
||||||
if( !anim.isActive() ) {
|
if( !anim.isActive() ) {
|
||||||
animations.remove(anim);
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D pGraphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
if( clearBeforeDraw ) {
|
if( clearBeforeDraw ) {
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized( shapes ) {
|
synchronized( shapes ) {
|
||||||
for( Shape pShape : shapes ) {
|
List<Shape> it = List.copyOf(shapes);
|
||||||
if( pShape.isVisible() ) {
|
for( Shape s : it ) {
|
||||||
pShape.draw(drawing);
|
if( s.isVisible() ) {
|
||||||
|
s.draw(drawing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.draw(pGraphics);
|
super.draw(graphics);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
package schule.ngb.zm.turtle;
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
|
import schule.ngb.zm.*;
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.Layer;
|
import schule.ngb.zm.Fillable;
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Strokeable;
|
||||||
import schule.ngb.zm.Vector;
|
|
||||||
import schule.ngb.zm.shapes.FilledShape;
|
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
public class TurtleLayer extends Layer {
|
@SuppressWarnings( "unused" )
|
||||||
|
public class TurtleLayer extends Layer implements Strokeable, Fillable {
|
||||||
|
|
||||||
// Rotating by the clock
|
// Rotating by the clock
|
||||||
public static final int H1 = 30;
|
public static final int H1 = 30;
|
||||||
@@ -39,7 +40,7 @@ public class TurtleLayer extends Layer {
|
|||||||
|
|
||||||
public static final int H12 = 360;
|
public static final int H12 = 360;
|
||||||
|
|
||||||
private static Stack<Color> turtleColors;
|
private final static Stack<Color> turtleColors;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
turtleColors = new Stack<>();
|
turtleColors = new Stack<>();
|
||||||
@@ -51,9 +52,9 @@ public class TurtleLayer extends Layer {
|
|||||||
turtleColors.add(Color.BLUE);
|
turtleColors.add(Color.BLUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Turtle mainTurtle = null;
|
private final Turtle mainTurtle;
|
||||||
|
|
||||||
private ArrayList<Turtle> turtles = new ArrayList<Turtle>(6);
|
private final List<Turtle> turtles = new ArrayList<>(6);
|
||||||
|
|
||||||
public TurtleLayer() {
|
public TurtleLayer() {
|
||||||
super();
|
super();
|
||||||
@@ -117,7 +118,20 @@ public class TurtleLayer extends Layer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin of delegate methods (auto-generated)
|
// begin of delegate methods (auto-generated)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVisible() {
|
||||||
|
return mainTurtle.isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginPath() {
|
||||||
|
mainTurtle.beginPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closePath() {
|
||||||
|
mainTurtle.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
public void fill() {
|
public void fill() {
|
||||||
mainTurtle.fill();
|
mainTurtle.fill();
|
||||||
@@ -175,100 +189,196 @@ public class TurtleLayer extends Layer {
|
|||||||
mainTurtle.moveTo(x, y);
|
mainTurtle.moveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getFillColor() {
|
@Override
|
||||||
return mainTurtle.getFillColor();
|
public void setFill( Paint fill ) {
|
||||||
|
mainTurtle.setFill(fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Paint getFill() {
|
||||||
|
return mainTurtle.getFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setFillColor( Color color ) {
|
public void setFillColor( Color color ) {
|
||||||
mainTurtle.setFillColor(color);
|
mainTurtle.setFillColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFillColor( int gray ) {
|
@Override
|
||||||
mainTurtle.setFillColor(gray);
|
public Color getFillColor() {
|
||||||
}
|
return mainTurtle.getFillColor();
|
||||||
|
|
||||||
public void noFill() {
|
|
||||||
mainTurtle.noFill();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int gray, int alpha ) {
|
|
||||||
mainTurtle.setFillColor(gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue ) {
|
|
||||||
mainTurtle.setFillColor(red, green, blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
|
||||||
mainTurtle.setFillColor(red, green, blue, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetFill() {
|
|
||||||
mainTurtle.resetFill();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color getStrokeColor() {
|
|
||||||
return mainTurtle.getStrokeColor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setStrokeColor( Color color ) {
|
public void setStrokeColor( Color color ) {
|
||||||
mainTurtle.setStrokeColor(color);
|
mainTurtle.setStrokeColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStrokeColor( int gray ) {
|
@Override
|
||||||
mainTurtle.setStrokeColor(gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void noStroke() {
|
|
||||||
mainTurtle.noStroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int gray, int alpha ) {
|
|
||||||
mainTurtle.setStrokeColor(gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int red, int green, int blue ) {
|
|
||||||
mainTurtle.setStrokeColor(red, green, blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
|
||||||
mainTurtle.setStrokeColor(red, green, blue, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getStrokeWeight() {
|
|
||||||
return mainTurtle.getStrokeWeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeWeight( double weight ) {
|
public void setStrokeWeight( double weight ) {
|
||||||
mainTurtle.setStrokeWeight(weight);
|
mainTurtle.setStrokeWeight(weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Options.StrokeType getStrokeType() {
|
public Options.StrokeType getStrokeType() {
|
||||||
return mainTurtle.getStrokeType();
|
return mainTurtle.getStrokeType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
public void setStrokeType( Options.StrokeType type ) {
|
||||||
mainTurtle.setStrokeType(type);
|
mainTurtle.setStrokeType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||||
|
mainTurtle.setGradient(from, to, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( Color from, Color to ) {
|
||||||
|
mainTurtle.setGradient(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFill() {
|
||||||
|
return mainTurtle.hasFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFillColor() {
|
||||||
|
return mainTurtle.hasFillColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasGradient() {
|
||||||
|
return mainTurtle.hasGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( Color color, int alpha ) {
|
||||||
|
mainTurtle.setFillColor(color, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( int gray ) {
|
||||||
|
mainTurtle.setFillColor(gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( int gray, int alpha ) {
|
||||||
|
mainTurtle.setFillColor(gray, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( int red, int green, int blue ) {
|
||||||
|
mainTurtle.setFillColor(red, green, blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||||
|
mainTurtle.setFillColor(red, green, blue, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noFill() {
|
||||||
|
mainTurtle.noFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetFill() {
|
||||||
|
mainTurtle.resetFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultipleGradientPaint getGradient() {
|
||||||
|
return mainTurtle.getGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||||
|
mainTurtle.setGradient(fromX, fromY, from, toX, toY, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||||
|
mainTurtle.setGradient(centerX, centerY, radius, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noGradient() {
|
||||||
|
mainTurtle.noGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStroke( Stroke stroke ) {
|
||||||
|
mainTurtle.setStroke(stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stroke getStroke() {
|
||||||
|
return mainTurtle.getStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasStroke() {
|
||||||
|
return mainTurtle.hasStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getStrokeColor() {
|
||||||
|
return mainTurtle.getStrokeColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( Color color, int alpha ) {
|
||||||
|
mainTurtle.setStrokeColor(color, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( int gray ) {
|
||||||
|
mainTurtle.setStrokeColor(gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( int gray, int alpha ) {
|
||||||
|
mainTurtle.setStrokeColor(gray, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( int red, int green, int blue ) {
|
||||||
|
mainTurtle.setStrokeColor(red, green, blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||||
|
mainTurtle.setStrokeColor(red, green, blue, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noStroke() {
|
||||||
|
mainTurtle.noStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void resetStroke() {
|
public void resetStroke() {
|
||||||
mainTurtle.resetStroke();
|
mainTurtle.resetStroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPosToPath() {
|
@Override
|
||||||
mainTurtle.addPosToPath();
|
public double getStrokeWeight() {
|
||||||
|
return mainTurtle.getStrokeWeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closePath() {
|
|
||||||
mainTurtle.closePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of delegate methods (auto-generated)
|
// end of delegate methods (auto-generated)
|
||||||
|
|
||||||
|
|
||||||
public class Turtle extends FilledShape {
|
/**
|
||||||
|
* Die Turtle der Zeichenmaschine.
|
||||||
|
*/
|
||||||
|
public class Turtle extends BasicDrawable {
|
||||||
|
|
||||||
private static final int STD_SIZE = 12;
|
private static final int DEFAULT_SIZE = 12;
|
||||||
|
|
||||||
boolean penDown = true;
|
boolean penDown = true;
|
||||||
|
|
||||||
@@ -282,7 +392,31 @@ public class TurtleLayer extends Layer {
|
|||||||
|
|
||||||
boolean pathOpen = false;
|
boolean pathOpen = false;
|
||||||
|
|
||||||
Turtle() {}
|
/**
|
||||||
|
* Path-Objekt für die Darstellung der Turtle.
|
||||||
|
*/
|
||||||
|
Path2D.Double turtlePath;
|
||||||
|
|
||||||
|
Turtle() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVisible() {
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginPath() {
|
||||||
|
pathOpen = false;
|
||||||
|
addPosToPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closePath() {
|
||||||
|
if( pathOpen ) {
|
||||||
|
addPosToPath();
|
||||||
|
path.closePath();
|
||||||
|
path.trimToSize();
|
||||||
|
pathOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addPosToPath() {
|
private void addPosToPath() {
|
||||||
if( !pathOpen ) {
|
if( !pathOpen ) {
|
||||||
@@ -294,53 +428,39 @@ public class TurtleLayer extends Layer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closePath() {
|
|
||||||
if( pathOpen ) {
|
|
||||||
addPosToPath();
|
|
||||||
path.closePath();
|
|
||||||
path.trimToSize();
|
|
||||||
pathOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fill() {
|
public void fill() {
|
||||||
closePath();
|
closePath();
|
||||||
|
|
||||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
if( hasFill() ) {
|
||||||
drawing.setColor(fillColor.getJavaColor());
|
drawing.setPaint(getFill());
|
||||||
drawing.fill(path);
|
drawing.fill(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVisible() {
|
|
||||||
return visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D graphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
/*Shape shape = new RoundRectangle2D.Double(
|
if( turtlePath == null ) {
|
||||||
-12, -5, 16, 10, 5, 3
|
turtlePath = new Path2D.Double();
|
||||||
);*/
|
path.moveTo(DEFAULT_SIZE, 0);
|
||||||
Path2D path = new Path2D.Double();
|
path.lineTo(-DEFAULT_SIZE, -DEFAULT_SIZE / 2.0);
|
||||||
path.moveTo(STD_SIZE, 0);
|
path.lineTo(-DEFAULT_SIZE, DEFAULT_SIZE / 2.0);
|
||||||
path.lineTo(-STD_SIZE, -STD_SIZE/2);
|
path.lineTo(DEFAULT_SIZE, 0);
|
||||||
path.lineTo(-STD_SIZE, STD_SIZE/2);
|
}
|
||||||
path.lineTo(STD_SIZE, 0);
|
|
||||||
|
|
||||||
AffineTransform verzerrung = new AffineTransform();
|
AffineTransform verzerrung = new AffineTransform();
|
||||||
verzerrung.translate(position.x, position.y);
|
verzerrung.translate(position.x, position.y);
|
||||||
verzerrung.rotate(Math.toRadians(direction.angle()));
|
verzerrung.rotate(Math.toRadians(direction.angle()));
|
||||||
|
|
||||||
Shape shape = verzerrung.createTransformedShape(path);
|
java.awt.Shape shape = verzerrung.createTransformedShape(turtlePath);
|
||||||
|
|
||||||
if( strokeColor != null ) {
|
if( hasStroke() ) {
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
graphics.setColor(strokeColor.getJavaColor());
|
||||||
} else {
|
} else {
|
||||||
graphics.setColor(STD_STROKECOLOR.getJavaColor());
|
graphics.setColor(DEFAULT_STROKECOLOR.getJavaColor());
|
||||||
}
|
}
|
||||||
graphics.fill(shape);
|
graphics.fill(shape);
|
||||||
graphics.setColor(Color.BLACK.getJavaColor());
|
graphics.setColor(Color.BLACK.getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.draw(shape);
|
graphics.draw(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,9 +474,9 @@ public class TurtleLayer extends Layer {
|
|||||||
Vector positionStart = position.copy();
|
Vector positionStart = position.copy();
|
||||||
position.add(Vector.setLength(direction, dist));
|
position.add(Vector.setLength(direction, dist));
|
||||||
|
|
||||||
if( penDown && strokeColor != null ) {
|
if( penDown && hasStroke() ) {
|
||||||
drawing.setColor(strokeColor.getJavaColor());
|
drawing.setColor(strokeColor.getJavaColor());
|
||||||
drawing.setStroke(createStroke());
|
drawing.setStroke(getStroke());
|
||||||
drawing.drawLine((int) positionStart.x, (int) positionStart.y, (int) position.x, (int) position.y);
|
drawing.drawLine((int) positionStart.x, (int) positionStart.y, (int) position.x, (int) position.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,9 +527,9 @@ public class TurtleLayer extends Layer {
|
|||||||
position.x = x;
|
position.x = x;
|
||||||
position.y = y;
|
position.y = y;
|
||||||
|
|
||||||
if( penDown && strokeColor != null ) {
|
if( penDown && hasStroke() ) {
|
||||||
drawing.setColor(strokeColor.getJavaColor());
|
drawing.setColor(strokeColor.getJavaColor());
|
||||||
drawing.setStroke(createStroke());
|
drawing.setStroke(getStroke());
|
||||||
drawing.drawLine((int) x, (int) y, (int) position.x, (int) position.y);
|
drawing.drawLine((int) x, (int) y, (int) position.x, (int) position.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
import schule.ngb.zm.events.Listener;
|
import schule.ngb.zm.util.events.Listener;
|
||||||
|
|
||||||
public interface AudioListener extends Listener<Audio> {
|
public interface AudioListener extends Listener<Audio> {
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
import schule.ngb.zm.anim.Animation;
|
import schule.ngb.zm.util.events.EventDispatcher;
|
||||||
import schule.ngb.zm.anim.AnimationListener;
|
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||||
import schule.ngb.zm.events.EventDispatcher;
|
|
||||||
import schule.ngb.zm.tasks.TaskRunner;
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import javax.sound.sampled.*;
|
import javax.sound.sampled.*;
|
||||||
@@ -168,7 +166,7 @@ public class Music implements Audio {
|
|||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public synchronized void dispose() {
|
||||||
if( audioLine != null ) {
|
if( audioLine != null ) {
|
||||||
if( audioLine.isRunning() ) {
|
if( audioLine.isRunning() ) {
|
||||||
playing = false;
|
playing = false;
|
||||||
@@ -177,7 +175,6 @@ public class Music implements Audio {
|
|||||||
if( audioLine.isOpen() ) {
|
if( audioLine.isOpen() ) {
|
||||||
audioLine.drain();
|
audioLine.drain();
|
||||||
audioLine.close();
|
audioLine.close();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -191,7 +188,7 @@ public class Music implements Audio {
|
|||||||
audioStream = null;
|
audioStream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stream() {
|
private synchronized void stream() {
|
||||||
audioLine.start();
|
audioLine.start();
|
||||||
playing = true;
|
playing = true;
|
||||||
if( eventDispatcher != null ) {
|
if( eventDispatcher != null ) {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||||
|
|
||||||
import javax.sound.sampled.*;
|
import javax.sound.sampled.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,8 +205,9 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt
|
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und
|
||||||
* die Wiedergabe dann.
|
* stoppt die Wiedergabe dann.
|
||||||
|
*
|
||||||
* @param count Anzahl der Wiederholungen.
|
* @param count Anzahl der Wiederholungen.
|
||||||
*/
|
*/
|
||||||
public void loop( int count ) {
|
public void loop( int count ) {
|
||||||
@@ -232,7 +232,7 @@ public class Sound implements Audio {
|
|||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public synchronized void dispose() {
|
||||||
if( audioClip != null ) {
|
if( audioClip != null ) {
|
||||||
if( audioClip.isRunning() ) {
|
if( audioClip.isRunning() ) {
|
||||||
audioClip.stop();
|
audioClip.stop();
|
||||||
@@ -242,7 +242,7 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openClip() {
|
private synchronized boolean openClip() {
|
||||||
if( audioClip != null ) {
|
if( audioClip != null ) {
|
||||||
audioClip.setFramePosition(0);
|
audioClip.setFramePosition(0);
|
||||||
return true;
|
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;
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -23,20 +23,44 @@ public class NeuralNetwork {
|
|||||||
|
|
||||||
for( int i = 0; i < layer.getInputCount(); i++ ) {
|
for( int i = 0; i < layer.getInputCount(); i++ ) {
|
||||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
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.print(' ');
|
||||||
}
|
}
|
||||||
out.println();
|
out.println();
|
||||||
}
|
}
|
||||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||||
out.print(layer.biases[j]);
|
out.print(layer.biases.get(0, j));
|
||||||
out.print(' ');
|
out.print(' ');
|
||||||
}
|
}
|
||||||
out.println();
|
out.println();
|
||||||
}
|
}
|
||||||
out.flush();
|
out.flush();
|
||||||
} catch( IOException ex ) {
|
} 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++ ) {
|
for( int i = 0; i < inputs; i++ ) {
|
||||||
split = in.readLine().split(" ");
|
split = in.readLine().split(" ");
|
||||||
for( int j = 0; j < neurons; j++ ) {
|
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
|
// Load Biases
|
||||||
split = in.readLine().split(" ");
|
split = in.readLine().split(" ");
|
||||||
for( int j = 0; j < neurons; j++ ) {
|
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);
|
layers.add(layer);
|
||||||
@@ -70,29 +94,30 @@ public class NeuralNetwork {
|
|||||||
|
|
||||||
return new NeuralNetwork(layers);
|
return new NeuralNetwork(layers);
|
||||||
} catch( IOException | NoSuchElementException ex ) {
|
} 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public static NeuralNetwork loadFromFile( String source ) {
|
public static NeuralNetwork loadFromDataFile( String source ) {
|
||||||
try(
|
try(
|
||||||
InputStream stream = ResourceStreamProvider.getInputStream(source);
|
InputStream stream = ResourceStreamProvider.getInputStream(source);
|
||||||
Scanner in = new Scanner(stream)
|
DataInputStream in = new DataInputStream(stream)
|
||||||
) {
|
) {
|
||||||
List<NeuronLayer> layers = new LinkedList<>();
|
List<NeuronLayer> layers = new LinkedList<>();
|
||||||
while( in.hasNext() ) {
|
while( in.available() > 0 ) {
|
||||||
int neurons = in.nextInt();
|
int neurons = in.readInt();
|
||||||
int inputs = in.nextInt();
|
int inputs = in.readInt();
|
||||||
|
|
||||||
NeuronLayer layer = new NeuronLayer(neurons, inputs);
|
NeuronLayer layer = new NeuronLayer(neurons, inputs);
|
||||||
for( int i = 0; i < inputs; i++ ) {
|
for( int i = 0; i < inputs; i++ ) {
|
||||||
for( int j = 0; j < neurons; j++ ) {
|
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++ ) {
|
for( int j = 0; j < neurons; j++ ) {
|
||||||
layer.biases[j] = in.nextDouble();
|
layer.biases.set(0, j, in.readDouble());
|
||||||
}
|
}
|
||||||
|
|
||||||
layers.add(layer);
|
layers.add(layer);
|
||||||
@@ -100,14 +125,14 @@ public class NeuralNetwork {
|
|||||||
|
|
||||||
return new NeuralNetwork(layers);
|
return new NeuralNetwork(layers);
|
||||||
} catch( IOException | NoSuchElementException ex ) {
|
} 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;
|
return null;
|
||||||
}*/
|
}
|
||||||
|
|
||||||
private NeuronLayer[] layers;
|
private NeuronLayer[] layers;
|
||||||
|
|
||||||
private double[][] output;
|
private MLMatrix output;
|
||||||
|
|
||||||
private double learningRate = 0.1;
|
private double learningRate = 0.1;
|
||||||
|
|
||||||
@@ -146,6 +171,7 @@ public class NeuralNetwork {
|
|||||||
public int getLayerCount() {
|
public int getLayerCount() {
|
||||||
return layers.length;
|
return layers.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NeuronLayer[] getLayers() {
|
public NeuronLayer[] getLayers() {
|
||||||
return layers;
|
return layers;
|
||||||
}
|
}
|
||||||
@@ -162,17 +188,28 @@ public class NeuralNetwork {
|
|||||||
this.learningRate = pLearningRate;
|
this.learningRate = pLearningRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double[][] getOutput() {
|
public MLMatrix getOutput() {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double[][] predict( double[][] inputs ) {
|
public MLMatrix predict( double[] inputs ) {
|
||||||
//this.output = layers[1].apply(layers[0].apply(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);
|
this.output = layers[0].apply(inputs);
|
||||||
return this.output;
|
return this.output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void learn( double[][] expected ) {
|
public void learn( double[][] expected ) {
|
||||||
|
learn(MatrixFactory.create(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void learn( MLMatrix expected ) {
|
||||||
layers[layers.length - 1].backprop(expected, learningRate);
|
layers[layers.length - 1].backprop(expected, learningRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +1,66 @@
|
|||||||
package schule.ngb.zm.ml;
|
package schule.ngb.zm.ml;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
import java.util.function.Function;
|
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 i = 0; i < weights[0].length; i++ ) {
|
||||||
for( int j = 0; j < weights.length; j++ ) {
|
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;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NeuronLayer fromArray( double[][] weights, double[] biases ) {
|
public static NeuronLayer fromArray( double[][] weights, double[] biases, boolean transpose ) {
|
||||||
NeuronLayer layer = new NeuronLayer(weights[0].length, weights.length);
|
NeuronLayer layer = fromArray(weights, transpose);
|
||||||
for( int i = 0; i < weights[0].length; i++ ) {
|
|
||||||
for( int j = 0; j < weights.length; j++ ) {
|
|
||||||
layer.weights.coefficients[i][j] = weights[i][j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for( int j = 0; j < biases.length; j++ ) {
|
for( int j = 0; j < biases.length; j++ ) {
|
||||||
layer.biases[j] = biases[j];
|
layer.biases.set(0, j, biases[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
Matrix weights;
|
MLMatrix weights;
|
||||||
|
|
||||||
double[] biases;
|
MLMatrix biases;
|
||||||
|
|
||||||
NeuronLayer previous, next;
|
NeuronLayer previous, next;
|
||||||
|
|
||||||
DoubleUnaryOperator activationFunction, activationFunctionDerivative;
|
DoubleUnaryOperator activationFunction, activationFunctionDerivative;
|
||||||
|
|
||||||
double[][] lastOutput, lastInput;
|
MLMatrix lastOutput, lastInput;
|
||||||
|
|
||||||
public NeuronLayer( int neurons, int inputs ) {
|
public NeuronLayer( int neurons, int inputs ) {
|
||||||
weights = new Matrix(inputs, neurons);
|
weights = MatrixFactory
|
||||||
weights.initializeRandom(-1, 1);
|
.create(inputs, neurons)
|
||||||
|
.initializeRandom();
|
||||||
|
|
||||||
biases = new double[neurons];
|
biases = MatrixFactory
|
||||||
Arrays.fill(biases, 0.0); // TODO: Random?
|
.create(1, neurons)
|
||||||
|
.initializeZero();
|
||||||
|
|
||||||
activationFunction = MLMath::sigmoid;
|
activationFunction = MLMath::sigmoid;
|
||||||
activationFunctionDerivative = MLMath::sigmoidDerivative;
|
activationFunctionDerivative = MLMath::sigmoidDerivative;
|
||||||
@@ -85,45 +101,42 @@ public class NeuronLayer implements Function<double[][], double[][]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Matrix getWeights() {
|
public MLMatrix getWeights() {
|
||||||
return weights;
|
return weights;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MLMatrix getBiases() {
|
||||||
|
return biases;
|
||||||
|
}
|
||||||
|
|
||||||
public int getNeuronCount() {
|
public int getNeuronCount() {
|
||||||
return weights.coefficients[0].length;
|
return weights.columns();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getInputCount() {
|
public int getInputCount() {
|
||||||
return weights.coefficients.length;
|
return weights.rows();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double[][] getLastOutput() {
|
public MLMatrix getLastOutput() {
|
||||||
return lastOutput;
|
return lastOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWeights( double[][] newWeights ) {
|
public void setWeights( MLMatrix newWeights ) {
|
||||||
weights.coefficients = MLMath.copyMatrix(newWeights);
|
weights = newWeights.duplicate();
|
||||||
}
|
|
||||||
|
|
||||||
public void adjustWeights( double[][] adjustment ) {
|
|
||||||
weights.coefficients = MLMath.matrixAdd(weights.coefficients, adjustment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return weights.toString() + "\n" + Arrays.toString(biases);
|
return "weights:\n" + weights.toString() + "\nbiases:\n" + biases.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double[][] apply( double[][] inputs ) {
|
public MLMatrix apply( MLMatrix inputs ) {
|
||||||
lastInput = inputs;
|
lastInput = inputs.duplicate();
|
||||||
lastOutput = MLMath.matrixApply(
|
lastOutput = inputs
|
||||||
MLMath.biasAdd(
|
.multiplyAddBias(weights, biases)
|
||||||
MLMath.matrixMultiply(inputs, weights.coefficients),
|
.applyInPlace(activationFunction);
|
||||||
biases
|
|
||||||
),
|
|
||||||
activationFunction
|
|
||||||
);
|
|
||||||
if( next != null ) {
|
if( next != null ) {
|
||||||
return next.apply(lastOutput);
|
return next.apply(lastOutput);
|
||||||
} else {
|
} else {
|
||||||
@@ -132,36 +145,41 @@ public class NeuronLayer implements Function<double[][], double[][]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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));
|
return ( in ) -> apply(before.apply(in));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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));
|
return ( in ) -> after.apply(apply(in));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void backprop( double[][] expected, double learningRate ) {
|
public void backprop( MLMatrix expected, double learningRate ) {
|
||||||
double[][] error, delta, adjustment;
|
MLMatrix error, adjustment;
|
||||||
if( next == null ) {
|
if( next == null ) {
|
||||||
error = MLMath.matrixSub(expected, this.lastOutput);
|
error = expected.sub(lastOutput);
|
||||||
} else {
|
} 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?
|
// Hier schon leraningRate anwenden?
|
||||||
// See https://towardsdatascience.com/understanding-and-implementing-neural-networks-in-java-from-scratch-61421bb6352c
|
// See https://towardsdatascience.com/understanding-and-implementing-neural-networks-in-java-from-scratch-61421bb6352c
|
||||||
//delta = MLMath.matrixApply(delta, ( x ) -> learningRate * x);
|
//delta = MLMath.matrixApply(delta, ( x ) -> learningRate * x);
|
||||||
if( previous != null ) {
|
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 = lastInput.transposedMultiplyAndScale(error, learningRate);
|
||||||
adjustment = MLMath.matrixApply(adjustment, ( x ) -> learningRate * x);
|
weights.addInPlace(adjustment);
|
||||||
this.adjustWeights(adjustment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package schule.ngb.zm.shapes;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
|
|
||||||
public abstract class FilledShape extends StrokedShape {
|
|
||||||
|
|
||||||
protected Color fillColor = STD_FILLCOLOR;
|
|
||||||
|
|
||||||
public Color getFillColor() {
|
|
||||||
return fillColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( Color color ) {
|
|
||||||
fillColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( Color color, int alpha ) {
|
|
||||||
setFillColor(new Color(color, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int gray ) {
|
|
||||||
setFillColor(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void noFill() {
|
|
||||||
setFillColor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int gray, int alpha ) {
|
|
||||||
setFillColor(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue ) {
|
|
||||||
setFillColor(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
|
||||||
setFillColor(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetFill() {
|
|
||||||
setFillColor(STD_FILLCOLOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package schule.ngb.zm.shapes;
|
|||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Options;
|
||||||
import schule.ngb.zm.util.ImageLoader;
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public class Rectangle extends Shape {
|
|||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.anchor = Options.Direction.NORTHWEST;
|
this.anchor = Options.Direction.NORTHWEST;
|
||||||
|
|
||||||
|
//this.cacheEnabled = getClass().equals(Rectangle.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle( Rectangle pRechteck ) {
|
public Rectangle( Rectangle pRechteck ) {
|
||||||
@@ -24,9 +26,25 @@ public class Rectangle extends Shape {
|
|||||||
copyFrom(pRechteck);
|
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 ) {
|
public Rectangle( Shape pShape ) {
|
||||||
|
this(pShape, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle( Shape pShape, boolean transformed ) {
|
||||||
java.awt.Shape s = pShape.getShape();
|
java.awt.Shape s = pShape.getShape();
|
||||||
|
if( transformed ) {
|
||||||
s = pShape.getTransform().createTransformedShape(s);
|
s = pShape.getTransform().createTransformedShape(s);
|
||||||
|
}
|
||||||
Rectangle2D bounds = s.getBounds2D();
|
Rectangle2D bounds = s.getBounds2D();
|
||||||
x = bounds.getX();
|
x = bounds.getX();
|
||||||
y = bounds.getY();
|
y = bounds.getY();
|
||||||
|
|||||||
@@ -1,47 +1,115 @@
|
|||||||
package schule.ngb.zm.shapes;
|
package schule.ngb.zm.shapes;
|
||||||
|
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.*;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
|
||||||
public abstract class Shape extends FilledShape {
|
/**
|
||||||
|
* 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 Werte 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 BasicDrawable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* x-Koordinate der Form.
|
||||||
|
*/
|
||||||
protected double x;
|
protected double x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* y-Koordinate der Form.
|
||||||
|
*/
|
||||||
protected double y;
|
protected double y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotation in Grad um den Punkt (x, y).
|
||||||
|
*/
|
||||||
protected double rotation = 0.0;
|
protected double rotation = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skalierungsfaktor.
|
||||||
|
*/
|
||||||
protected double scale = 1.0;
|
protected double scale = 1.0;
|
||||||
|
|
||||||
protected boolean visible = true;
|
/**
|
||||||
|
* Ankerpunkt der Form.
|
||||||
|
*/
|
||||||
protected Options.Direction anchor = Options.Direction.CENTER;
|
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 ) {
|
public Shape( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shape() {
|
/**
|
||||||
this(0.0, 0.0);
|
* Gibt die x-Koordinate der Form zurück.
|
||||||
}
|
*
|
||||||
|
* @return Die x-Koordinate.
|
||||||
|
*/
|
||||||
public double getX() {
|
public double getX() {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die x-Koordinate der Form.
|
||||||
|
*
|
||||||
|
* @param x Die neue x-Koordinate.
|
||||||
|
*/
|
||||||
public void setX( double x ) {
|
public void setX( double x ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die y-Koordinate der Form zurück.
|
||||||
|
*
|
||||||
|
* @return Die y-Koordinate.
|
||||||
|
*/
|
||||||
public double getY() {
|
public double getY() {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die y-Koordinate der Form.
|
||||||
|
*
|
||||||
|
* @param y Die neue y-Koordinate.
|
||||||
|
*/
|
||||||
public void setY( double y ) {
|
public void setY( double y ) {
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
@@ -49,8 +117,9 @@ public abstract class Shape extends FilledShape {
|
|||||||
/**
|
/**
|
||||||
* Gibt die Breite dieser Form zurück.
|
* Gibt die Breite dieser Form zurück.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung, <b>bevor</b>
|
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
||||||
* Drehungen und andere Transformationen auf sei angewandt wurden.
|
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||||
|
* angewandt wurden.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
||||||
* {@link #getBounds()} abgerufen werden.
|
* {@link #getBounds()} abgerufen werden.
|
||||||
@@ -62,8 +131,9 @@ public abstract class Shape extends FilledShape {
|
|||||||
/**
|
/**
|
||||||
* Gibt die Höhe dieser Form zurück.
|
* Gibt die Höhe dieser Form zurück.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung, <b>bevor</b>
|
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
||||||
* Drehungen und andere Transformationen auf sei angewandt wurden.
|
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||||
|
* angewandt wurden.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
||||||
* {@link #getBounds()} abgerufen werden.
|
* {@link #getBounds()} abgerufen werden.
|
||||||
@@ -72,28 +142,80 @@ public abstract class Shape extends FilledShape {
|
|||||||
*/
|
*/
|
||||||
public abstract double getHeight();
|
public abstract double getHeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Rotation in Grad zurück.
|
||||||
|
*
|
||||||
|
* @return Rotation in Grad.
|
||||||
|
*/
|
||||||
public double getRotation() {
|
public double getRotation() {
|
||||||
return rotation;
|
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() {
|
public double getScale() {
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVisible() {
|
public void scale( double factor ) {
|
||||||
return visible;
|
scale = factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hide() {
|
public void scaleBy( double factor ) {
|
||||||
visible = false;
|
scale(scale * factor);
|
||||||
}
|
|
||||||
|
|
||||||
public void show() {
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toggle() {
|
|
||||||
visible = !visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Options.Direction getAnchor() {
|
public Options.Direction getAnchor() {
|
||||||
@@ -117,16 +239,26 @@ public abstract class Shape extends FilledShape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) {
|
/**
|
||||||
|
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
|
||||||
|
* auf der angegebenen Breite und Höhe des umschließenden Rechtecks.
|
||||||
|
* <p>
|
||||||
|
* Die Koordinaten des Ankerpunktes 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ßenden Rechtecks.
|
||||||
|
* @param height Höhe des umschließenden Rechtecks.
|
||||||
|
* @param anchor Gesuchter Ankerpunkt.
|
||||||
|
* @return Ein {@link Point2D} mit den relativen Koordinaten.
|
||||||
|
*/
|
||||||
|
public static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) {
|
||||||
double wHalf = width * .5, hHalf = height * .5;
|
double wHalf = width * .5, hHalf = height * .5;
|
||||||
|
|
||||||
// anchor == CENTER
|
return new Point2D.Double(
|
||||||
Point2D.Double anchorPoint = new Point2D.Double(
|
|
||||||
wHalf + wHalf * anchor.x,
|
wHalf + wHalf * anchor.x,
|
||||||
hHalf + hHalf * anchor.y
|
hHalf + hHalf * anchor.y
|
||||||
);
|
);
|
||||||
|
|
||||||
return anchorPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,12 +272,10 @@ public abstract class Shape extends FilledShape {
|
|||||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||||
|
|
||||||
// anchor == CENTER
|
// anchor == CENTER
|
||||||
Point2D.Double anchorPoint = new Point2D.Double(
|
return new Point2D.Double(
|
||||||
wHalf * (anchor.x - this.anchor.x),
|
wHalf * (anchor.x - this.anchor.x),
|
||||||
hHalf * (anchor.y - this.anchor.y)
|
hHalf * (anchor.y - this.anchor.y)
|
||||||
);
|
);
|
||||||
|
|
||||||
return anchorPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,14 +294,22 @@ public abstract class Shape extends FilledShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kopiert die Eigenschaften der übergebenen Form in diese.
|
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
||||||
* <p>
|
* <p>
|
||||||
* Unterklassen sollten diese Methode überschreiben, um weitere
|
* Unterklassen sollten diese Methode überschreiben, um weitere
|
||||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). Mit
|
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises).
|
||||||
* dem Aufruf {@code super.copyFrom(shape)} sollten die Basiseigenschaften
|
* Unterklassen sollten immer mit dem Aufruf {@code super.copyFrom(shape)}
|
||||||
* kopiert werden.
|
* 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 ) {
|
public void copyFrom( Shape shape ) {
|
||||||
if( shape != null ) {
|
if( shape != null ) {
|
||||||
@@ -181,16 +319,55 @@ public abstract class Shape extends FilledShape {
|
|||||||
setStrokeWeight(shape.getStrokeWeight());
|
setStrokeWeight(shape.getStrokeWeight());
|
||||||
setStrokeType(shape.getStrokeType());
|
setStrokeType(shape.getStrokeType());
|
||||||
visible = shape.isVisible();
|
visible = shape.isVisible();
|
||||||
rotation = shape.rotation;
|
rotation = shape.getRotation();
|
||||||
scale(shape.scale);
|
scale(shape.getScale());
|
||||||
setAnchor(shape.getAnchor());
|
setAnchor(shape.getAnchor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
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>
|
||||||
|
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, kann die
|
||||||
|
* Methode {@code null} zurückgeben.
|
||||||
|
*
|
||||||
|
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
|
||||||
|
* {@code null}.
|
||||||
|
*/
|
||||||
public abstract java.awt.Shape getShape();
|
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() {
|
public Bounds getBounds() {
|
||||||
return new Bounds(this);
|
return new Bounds(this);
|
||||||
}
|
}
|
||||||
@@ -273,7 +450,7 @@ public abstract class Shape extends FilledShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void nextTo( Shape shape, Options.Direction dir ) {
|
public void nextTo( Shape shape, Options.Direction dir ) {
|
||||||
nextTo(shape, dir, STD_BUFFER);
|
nextTo(shape, dir, DEFAULT_BUFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -295,40 +472,6 @@ public abstract class Shape extends FilledShape {
|
|||||||
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
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 ) {
|
/*public void shear( double dx, double dy ) {
|
||||||
verzerrung.shear(dx, dy);
|
verzerrung.shear(dx, dy);
|
||||||
}*/
|
}*/
|
||||||
@@ -375,17 +518,9 @@ public abstract class Shape extends FilledShape {
|
|||||||
shape = transform.createTransformedShape(shape);
|
shape = transform.createTransformedShape(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color currentColor = graphics.getColor();
|
java.awt.Color currentColor = graphics.getColor();
|
||||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
fillShape(shape, graphics);
|
||||||
graphics.setColor(fillColor.getJavaColor());
|
strokeShape(shape, graphics);
|
||||||
graphics.fill(shape);
|
|
||||||
}
|
|
||||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
|
||||||
&& strokeWeight > 0.0 ) {
|
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
|
||||||
graphics.setStroke(createStroke());
|
|
||||||
graphics.draw(shape);
|
|
||||||
}
|
|
||||||
graphics.setColor(currentColor);
|
graphics.setColor(currentColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,8 +531,8 @@ public abstract class Shape extends FilledShape {
|
|||||||
* verglichen. Unterklassen überschreiben die Methode, um weitere
|
* verglichen. Unterklassen überschreiben die Methode, um weitere
|
||||||
* Eigenschaften zu berücksichtigen.
|
* Eigenschaften zu berücksichtigen.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Eigenschaften von {@link FilledShape} und {@link StrokedShape} werden
|
* Die Eigenschaften, die durch {@link Fillable} und {@link Strokeable}
|
||||||
* nicht verglichen.
|
* impliziert werden, werden nicht verglichen.
|
||||||
*
|
*
|
||||||
* @param o Ein anderes Objekt.
|
* @param o Ein anderes Objekt.
|
||||||
* @return
|
* @return
|
||||||
@@ -413,4 +548,43 @@ public abstract class Shape extends FilledShape {
|
|||||||
Double.compare(pShape.scale, scale) == 0;
|
Double.compare(pShape.scale, scale) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(getStroke());
|
||||||
|
graphics.draw(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.awt.geom.AffineTransform;
|
|||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ import java.util.List;
|
|||||||
* {@link #arrangeInGrid(int, Options.Direction, double, int)} und
|
* {@link #arrangeInGrid(int, Options.Direction, double, int)} und
|
||||||
* {@link #align(Options.Direction)} verwendet werden, die jeweils die Position
|
* {@link #align(Options.Direction)} verwendet werden, die jeweils die Position
|
||||||
* der Formen in der Gruppe ändern und nicht die Position der Gruppe selbst (so
|
* der Formen in der Gruppe ändern und nicht die Position der Gruppe selbst (so
|
||||||
* wie z.B. {@link #alignTo(Shape, Options.Direction)}.
|
* wie z.B. {@link #alignTo(Shape, Options.Direction)}).
|
||||||
*/
|
*/
|
||||||
public class ShapeGroup extends Shape {
|
public class ShapeGroup extends Shape {
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ public class ShapeGroup extends Shape {
|
|||||||
|
|
||||||
public static final int ARRANGE_COLS = 1;
|
public static final int ARRANGE_COLS = 1;
|
||||||
|
|
||||||
private List<Shape> shapes;
|
private final List<Shape> shapes;
|
||||||
|
|
||||||
private double groupWidth = -1.0;
|
private double groupWidth = -1.0;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public class ShapeGroup extends Shape {
|
|||||||
|
|
||||||
public ShapeGroup() {
|
public ShapeGroup() {
|
||||||
super();
|
super();
|
||||||
shapes = new ArrayList<>(10);
|
shapes = Collections.synchronizedList(new ArrayList<>(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapeGroup( double x, double y ) {
|
public ShapeGroup( double x, double y ) {
|
||||||
@@ -62,9 +63,7 @@ public class ShapeGroup extends Shape {
|
|||||||
public ShapeGroup( double x, double y, Shape... shapes ) {
|
public ShapeGroup( double x, double y, Shape... shapes ) {
|
||||||
super(x, y);
|
super(x, y);
|
||||||
this.shapes = new ArrayList<>(shapes.length);
|
this.shapes = new ArrayList<>(shapes.length);
|
||||||
for( Shape pShape : shapes ) {
|
Collections.addAll(this.shapes, shapes);
|
||||||
this.shapes.add(pShape);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shape copy() {
|
public Shape copy() {
|
||||||
@@ -99,9 +98,11 @@ public class ShapeGroup extends Shape {
|
|||||||
|
|
||||||
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
||||||
LinkedList<ShapeType> list = new LinkedList<>();
|
LinkedList<ShapeType> list = new LinkedList<>();
|
||||||
for( Shape s : shapes ) {
|
synchronized( shapes ) {
|
||||||
if( typeClass.getClass().isInstance(s) ) {
|
for( Shape shape : shapes ) {
|
||||||
list.add((ShapeType) s);
|
if( typeClass.isInstance(shape) ) {
|
||||||
|
list.add(typeClass.cast(shape));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
@@ -170,19 +171,15 @@ public class ShapeGroup extends Shape {
|
|||||||
int rows, cols;
|
int rows, cols;
|
||||||
if( mode == ARRANGE_ROWS ) {
|
if( mode == ARRANGE_ROWS ) {
|
||||||
rows = n;
|
rows = n;
|
||||||
cols = (int) ceil(shapes.size() / n);
|
cols = (int) ceil(shapes.size() / (double)n);
|
||||||
} else {
|
} else {
|
||||||
cols = n;
|
cols = n;
|
||||||
rows = (int) ceil(shapes.size() / n);
|
rows = (int) ceil(shapes.size() / (double)n);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate grid cell size
|
// Calculate grid cell size
|
||||||
double maxHeight = shapes.stream().mapToDouble(
|
double maxHeight = shapes.stream().mapToDouble(Shape::getHeight).reduce(0.0, Double::max);
|
||||||
( s ) -> s.getHeight()
|
double maxWidth = shapes.stream().mapToDouble(Shape::getWidth).reduce(0.0, Double::max);
|
||||||
).reduce(0.0, Double::max);
|
|
||||||
double maxWidth = shapes.stream().mapToDouble(
|
|
||||||
( s ) -> s.getWidth()
|
|
||||||
).reduce(0.0, Double::max);
|
|
||||||
double halfHeight = maxHeight * .5;
|
double halfHeight = maxHeight * .5;
|
||||||
double halfWidth = maxWidth * .5;
|
double halfWidth = maxWidth * .5;
|
||||||
|
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
package schule.ngb.zm.shapes;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class StrokedShape extends Constants implements Drawable {
|
|
||||||
|
|
||||||
protected Color strokeColor = STD_STROKECOLOR;
|
|
||||||
|
|
||||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
|
||||||
|
|
||||||
protected Options.StrokeType strokeType = SOLID;
|
|
||||||
|
|
||||||
protected Stroke stroke = null;
|
|
||||||
|
|
||||||
public Color getStrokeColor() {
|
|
||||||
return strokeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( Color color ) {
|
|
||||||
this.strokeColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( Color color, int alpha ) {
|
|
||||||
setStrokeColor(new Color(color, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int gray ) {
|
|
||||||
setStrokeColor(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void noStroke() {
|
|
||||||
setStrokeColor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int gray, int alpha ) {
|
|
||||||
setStrokeColor(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int red, int green, int blue ) {
|
|
||||||
setStrokeColor(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
|
||||||
setStrokeColor(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getStrokeWeight() {
|
|
||||||
return strokeWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrokeWeight( double weight ) {
|
|
||||||
this.strokeWeight = weight;
|
|
||||||
this.stroke = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Options.StrokeType getStrokeType() {
|
|
||||||
return strokeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
|
||||||
* {@link #DOTTED} und {@link #SOLID}.
|
|
||||||
*
|
|
||||||
* @param type
|
|
||||||
*/
|
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
|
||||||
this.strokeType = type;
|
|
||||||
this.stroke = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract void draw( Graphics2D graphics );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt ein {@link Stroke} Objekt für den Konturtyp.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Stroke createStroke() {
|
|
||||||
// TODO: Used global cached Stroke Objects?
|
|
||||||
if( stroke == null ) {
|
|
||||||
switch( strokeType ) {
|
|
||||||
case DOTTED:
|
|
||||||
stroke = new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
BasicStroke.JOIN_ROUND,
|
|
||||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
|
||||||
break;
|
|
||||||
case DASHED:
|
|
||||||
stroke = new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
BasicStroke.JOIN_ROUND,
|
|
||||||
10.0f, new float[]{5.0f}, 0.0f);
|
|
||||||
break;
|
|
||||||
case SOLID:
|
|
||||||
default:
|
|
||||||
stroke = new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
BasicStroke.JOIN_ROUND);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stroke;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetStroke() {
|
|
||||||
setStrokeColor(STD_STROKECOLOR);
|
|
||||||
setStrokeWeight(STD_STROKEWEIGHT);
|
|
||||||
setStrokeType(SOLID);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ package schule.ngb.zm.shapes;
|
|||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.Options;
|
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.Canvas;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
@@ -22,15 +22,16 @@ public class Text extends Shape {
|
|||||||
protected int width = 0, height = 0, ascent = 0;
|
protected int width = 0, height = 0, ascent = 0;
|
||||||
|
|
||||||
public Text( double x, double y, String text ) {
|
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 ) {
|
public Text( double x, double y, String text, String fontname ) {
|
||||||
super(x, y);
|
super(x, y);
|
||||||
Font userfont = FontLoader.loadFont(fontname);
|
Font userfont = FontLoader.loadFont(fontname);
|
||||||
if( userfont != null ) {
|
if( userfont != null ) {
|
||||||
font = userfont;
|
font = userfont;
|
||||||
} else {
|
} else {
|
||||||
font = new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE);
|
font = new Font(Font.SANS_SERIF, Font.PLAIN, DEFAULT_FONTSIZE);
|
||||||
}
|
}
|
||||||
setText(text);
|
setText(text);
|
||||||
fillColor = null;
|
fillColor = null;
|
||||||
@@ -60,29 +61,58 @@ public class Text extends Shape {
|
|||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFont( String fontname ) {
|
public Font getFont() {
|
||||||
Font newFont = FontLoader.loadFont(fontname);
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFont( Font newFont ) {
|
||||||
|
//font = newFont.deriveFont(font.getStyle(), font.getSize2D());
|
||||||
|
font = new Font(newFont.getFontName(), newFont.getStyle(), newFont.getSize());
|
||||||
|
//font = newFont;
|
||||||
|
calculateBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFont( String fontName ) {
|
||||||
|
Font newFont = FontLoader.loadFont(fontName);
|
||||||
if( newFont != null ) {
|
if( newFont != null ) {
|
||||||
setFont(newFont);
|
setFont(newFont);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFont( Font newFont ) {
|
public void setFont( String fontName, double fontSize ) {
|
||||||
font = newFont.deriveFont(font.getSize2D());
|
Font newFont = FontLoader.loadFont(fontName);
|
||||||
|
if( newFont != null ) {
|
||||||
|
setFont(newFont);
|
||||||
|
}
|
||||||
|
font = newFont.deriveFont((float) fontSize);
|
||||||
calculateBounds();
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Font getFont() {
|
public void setFont( String fontName, double fontSize, int style ) {
|
||||||
return font;
|
Font newFont = FontLoader.loadFont(fontName);
|
||||||
|
if( newFont != null ) {
|
||||||
|
setFont(newFont);
|
||||||
|
}
|
||||||
|
font = newFont.deriveFont(style, (float) fontSize);
|
||||||
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFontsize( double size ) {
|
public double getFontSize() {
|
||||||
|
return font.getSize2D();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontSize( double size ) {
|
||||||
font = font.deriveFont((float) size);
|
font = font.deriveFont((float) size);
|
||||||
calculateBounds();
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getFontsize() {
|
public int getFontStyle() {
|
||||||
return font.getSize2D();
|
return font.getStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontStyle( int fontStyle ) {
|
||||||
|
font = font.deriveFont(fontStyle);
|
||||||
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getText() {
|
public String getText() {
|
||||||
@@ -151,19 +181,19 @@ public class Text extends Shape {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void copyFrom( Shape shape ) {
|
public void copyFrom( Shape shape ) {
|
||||||
super.copyFrom(shape);
|
|
||||||
if( shape instanceof Text ) {
|
if( shape instanceof Text ) {
|
||||||
Text pText = (Text) shape;
|
Text pText = (Text) shape;
|
||||||
this.text = pText.getText();
|
this.text = pText.getText();
|
||||||
this.font = pText.getFont();
|
this.font = pText.getFont();
|
||||||
calculateBounds();
|
|
||||||
}
|
}
|
||||||
|
super.copyFrom(shape);
|
||||||
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scale( double factor ) {
|
public void scale( double factor ) {
|
||||||
super.scale(factor);
|
super.scale(factor);
|
||||||
setFontsize(font.getSize2D() * factor);
|
setFontSize(font.getSize2D() * factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -191,7 +221,7 @@ public class Text extends Shape {
|
|||||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||||
&& strokeWeight > 0.0 ) {
|
&& strokeWeight > 0.0 ) {
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
graphics.setColor(strokeColor.getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.drawRect(0, 0, width, height);
|
graphics.drawRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package schule.ngb.zm.charts;
|
package schule.ngb.zm.shapes.charts;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.shapes.Rectangle;
|
import schule.ngb.zm.shapes.Rectangle;
|
||||||
@@ -288,7 +288,7 @@ public class BarChart extends Rectangle {
|
|||||||
barY += barHeight + gap;
|
barY += barHeight + gap;
|
||||||
}
|
}
|
||||||
graphics.setColor(getStrokeColor().getJavaColor());
|
graphics.setColor(getStrokeColor().getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
if( inverted ) {
|
if( inverted ) {
|
||||||
graphics.drawLine((int) width, 0, (int) width, (int) height);
|
graphics.drawLine((int) width, 0, (int) width, (int) height);
|
||||||
} else {
|
} else {
|
||||||
@@ -360,7 +360,7 @@ public class BarChart extends Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
graphics.setColor(getStrokeColor().getJavaColor());
|
graphics.setColor(getStrokeColor().getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
if( inverted ) {
|
if( inverted ) {
|
||||||
graphics.drawLine(0, 0, (int) width, 0);
|
graphics.drawLine(0, 0, (int) width, 0);
|
||||||
} else {
|
} else {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package schule.ngb.zm.charts;
|
package schule.ngb.zm.shapes.charts;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
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.Color;
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Options;
|
||||||
import schule.ngb.zm.shapes.Rectangle;
|
import schule.ngb.zm.shapes.Rectangle;
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ public class ChartAxes extends Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
graphics.setColor(strokeColor.getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.drawLine(0, (int)(height), (int)((xMax-xMin) * xUnit), (int)(height));
|
graphics.drawLine(0, (int)(height), (int)((xMax-xMin) * xUnit), (int)(height));
|
||||||
graphics.drawLine(0, (int)(height), 0, (int)(height - (yMax-yMin) * yUnit));
|
graphics.drawLine(0, (int)(height), 0, (int)(height - (yMax-yMin) * yUnit));
|
||||||
if( showArrows ) {
|
if( showArrows ) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package schule.ngb.zm.charts;
|
package schule.ngb.zm.shapes.charts;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
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.shapes.Rectangle;
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
import java.awt.BasicStroke;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
@@ -92,7 +91,7 @@ public class LineChart extends Rectangle {
|
|||||||
for( ChartValue lcv : val ) {
|
for( ChartValue lcv : val ) {
|
||||||
if( drawLines && lastLcv != null ) {
|
if( drawLines && lastLcv != null ) {
|
||||||
graphics.setColor(getStrokeColor().getJavaColor());
|
graphics.setColor(getStrokeColor().getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.drawLine((int)(lastLcv.getX()*xUnit), (int)(height - lastLcv.getValue()*yUnit), (int)(lcv.getX()*xUnit), (int)(height - lcv.getValue()*yUnit));
|
graphics.drawLine((int)(lastLcv.getX()*xUnit), (int)(height - lastLcv.getValue()*yUnit), (int)(lcv.getX()*xUnit), (int)(height - lcv.getValue()*yUnit));
|
||||||
drawDot(graphics, lastLcv, xUnit, yUnit);
|
drawDot(graphics, lastLcv, xUnit, yUnit);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package schule.ngb.zm.charts;
|
package schule.ngb.zm.shapes.charts;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.shapes.Circle;
|
import schule.ngb.zm.shapes.Circle;
|
||||||
@@ -326,7 +326,7 @@ public class PieChart extends Circle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
graphics.setColor(getStrokeColor().getJavaColor());
|
graphics.setColor(getStrokeColor().getJavaColor());
|
||||||
graphics.setStroke(createStroke());
|
graphics.setStroke(getStroke());
|
||||||
graphics.drawOval(0, 0, (int) (radius * 2), (int) (radius * 2));
|
graphics.drawOval(0, 0, (int) (radius * 2), (int) (radius * 2));
|
||||||
|
|
||||||
graphics.setTransform(originalTransform);
|
graphics.setTransform(originalTransform);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package schule.ngb.zm.charts;
|
package schule.ngb.zm.shapes.charts;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.shapes.Circle;
|
import schule.ngb.zm.shapes.Circle;
|
||||||
@@ -4,33 +4,101 @@ import java.util.Collection;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Hilfsklasse um Dinge zu zählen.
|
||||||
|
* <p>
|
||||||
|
* Im einfachsten Fall kann der Zähler als geteilte Zählvariable genutzt werden,
|
||||||
|
* die mit {@link #inc()} und {@link #dec()} aus verschiedenen Objekten oder
|
||||||
|
* Methoden verändert werden kann.
|
||||||
|
* <p>
|
||||||
|
* Der Zähler kann aber auch Objekte zählen, indem die Instanzen an
|
||||||
|
* {@link #count(Object)} übergeben werden. Am Ende kann mit {@link #getCount()}
|
||||||
|
* die Anzahl der Objkete abgerufen werden.
|
||||||
|
* <p>
|
||||||
|
* Handelt es sich bei den Objekten um Zahlen, dann merkt sich ein Zähler auch
|
||||||
|
* das Maximum, das Minimum, die Summe und den Durchschnitt der gezählten
|
||||||
|
* Werte.
|
||||||
|
* <p>
|
||||||
|
* Ein Zähler kann auch komplette Arrays oder Listen von Zahlen zählen und die
|
||||||
|
* obigen Statistiken auswerten.
|
||||||
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public final class Counter {
|
public final class Counter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Counter}, der alle Integer im angegebenen
|
||||||
|
* Array gezählt hat.
|
||||||
|
*
|
||||||
|
* @param values Die zu zählenden Werte.
|
||||||
|
* @return Ein neuer {@code Counter}.
|
||||||
|
*/
|
||||||
|
public static Counter fromArray( int[] values ) {
|
||||||
|
return new Counter().countAll(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Counter}, der alle Doubles im angegebenen
|
||||||
|
* Array gezählt hat.
|
||||||
|
*
|
||||||
|
* @param values Die zu zählenden Werte.
|
||||||
|
* @return Ein neuer {@code Counter}.
|
||||||
|
*/
|
||||||
public static Counter fromArray( double[] values ) {
|
public static Counter fromArray( double[] values ) {
|
||||||
return new Counter().countAll(values);
|
return new Counter().countAll(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Counter}, der alle Zahlen im angegebenen
|
||||||
|
* Array gezählt hat.
|
||||||
|
*
|
||||||
|
* @param values Die zu zählenden Werte.
|
||||||
|
* @return Ein neuer {@code Counter}.
|
||||||
|
*/
|
||||||
public static Counter fromArray( Number[] values ) {
|
public static Counter fromArray( Number[] values ) {
|
||||||
return new Counter().countAll(values);
|
return new Counter().countAll(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Counter}, der alle Zahlen in der angegebenen
|
||||||
|
* Liste gezählt hat.
|
||||||
|
*
|
||||||
|
* @param values Die zu zählenden Werte.
|
||||||
|
* @return Ein neuer {@code Counter}.
|
||||||
|
*/
|
||||||
public static Counter fromList( List<Number> values ) {
|
public static Counter fromList( List<Number> values ) {
|
||||||
return new Counter().countAll(values);
|
return new Counter().countAll(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AtomicInteger count = new AtomicInteger(0);
|
/**
|
||||||
|
* Aktuelle Anzahl gezählter Werte.
|
||||||
|
*/
|
||||||
|
private final AtomicInteger count = new AtomicInteger(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistiken zu den gezählten Werten.
|
||||||
|
*/
|
||||||
private double min = Double.NaN, max = Double.NaN, sum = Double.NaN;
|
private double min = Double.NaN, max = Double.NaN, sum = Double.NaN;
|
||||||
|
|
||||||
public void Counter() {
|
/**
|
||||||
|
* Erstellt einen neuen, leeren {@code Counter}.
|
||||||
|
*/
|
||||||
|
public Counter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Counter( int initial ) {
|
/**
|
||||||
|
* Ertstellt einen neuen {@code Counter}, der mit dem angegebenen Wert
|
||||||
|
* initialisiert ist.
|
||||||
|
*
|
||||||
|
* @param initial Wert des Zählers zu Beginn.
|
||||||
|
*/
|
||||||
|
public Counter( int initial ) {
|
||||||
count.set(initial);
|
count.set(initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die aktuelle Anzahl zurück.
|
||||||
|
* @return Die aktuelle Anzahl.
|
||||||
|
*/
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return count.get();
|
return count.get();
|
||||||
}
|
}
|
||||||
@@ -61,6 +129,8 @@ public final class Counter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Counter setCount( int count ) {
|
public Counter setCount( int count ) {
|
||||||
this.count.set(count);
|
this.count.set(count);
|
||||||
return this;
|
return this;
|
||||||
@@ -126,6 +196,14 @@ public final class Counter {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
|
public synchronized Counter countAll( int[] values ) {
|
||||||
|
for( double value : values ) {
|
||||||
|
count(value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public synchronized Counter countAll( Number[] values ) {
|
public synchronized Counter countAll( Number[] values ) {
|
||||||
for( Number value : values ) {
|
for( Number value : values ) {
|
||||||
|
|||||||
190
src/main/java/schule/ngb/zm/util/Faker.java
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
import schule.ngb.zm.Constants;
|
||||||
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
import schule.ngb.zm.util.io.FileLoader;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.font.LineMetrics;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hilfsklasse, um zufällige Beispieldaten zu erzeugen.
|
||||||
|
* <p>
|
||||||
|
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen,
|
||||||
|
* unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalterbilder.
|
||||||
|
*/
|
||||||
|
public final class Faker {
|
||||||
|
|
||||||
|
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
||||||
|
*
|
||||||
|
* @param n Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[][] fakeUsers( int n ) {
|
||||||
|
String[][] data = loadMockfile("users");
|
||||||
|
String[][] result = new String[n][];
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
result[i] = Constants.choice(data);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Vornamen.
|
||||||
|
*
|
||||||
|
* @param n Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeNames( int n ) {
|
||||||
|
String[][] data = loadMockfile("users");
|
||||||
|
String[] result = new String[n];
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
String[] row = Constants.choice(data);
|
||||||
|
result[i] = row[0];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen (Vor- und
|
||||||
|
* Nachname).
|
||||||
|
*
|
||||||
|
* @param n Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeFullnames( int n ) {
|
||||||
|
String[][] data = loadMockfile("users");
|
||||||
|
String[] result = new String[n];
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
String[] row = Constants.choice(data);
|
||||||
|
result[i] = row[0] + " " + row[1];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
||||||
|
*
|
||||||
|
* @param n Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeUsernames( int n ) {
|
||||||
|
String[][] data = loadMockfile("users");
|
||||||
|
String[] result = new String[n];
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
String[] row = Constants.choice(data);
|
||||||
|
result[i] = row[3];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Passwörter.
|
||||||
|
*
|
||||||
|
* @param n Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakePasswords( int n ) {
|
||||||
|
String[][] data = loadMockfile("users");
|
||||||
|
String[] result = new String[n];
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
String[] row = Constants.choice(data);
|
||||||
|
result[i] = row[4];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger E-Mail-Adressen.
|
||||||
|
*
|
||||||
|
* @param n Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeEmails( int n ) {
|
||||||
|
String[][] data = loadMockfile("users");
|
||||||
|
String[] result = new String[n];
|
||||||
|
for( int i = 0; i < n; i++ ) {
|
||||||
|
String[] row = Constants.choice(data);
|
||||||
|
result[i] = row[5];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Platzhalter-Bild in der angegebenen Größe.
|
||||||
|
* <p>
|
||||||
|
* Das Bild ist ein aus dem Internet geladenes, zufälliges Motiv, dass unter
|
||||||
|
* einer freien Lizenz (Creative Commons) steht.
|
||||||
|
*
|
||||||
|
* @param width Breite des Bildes.
|
||||||
|
* @param height Höhe des Bildes.
|
||||||
|
* @return Ein zufälliges Bild in der angegebenen Größe.
|
||||||
|
*/
|
||||||
|
public static BufferedImage fakeImage( int width, int height ) {
|
||||||
|
return fakeImage(width, height, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Platzhalter-Bild in der angegebenen Größe.
|
||||||
|
* <p>
|
||||||
|
* Wenn {@code fromWeb} auf {@code true} gesetzt ist, wird ein zufälliges
|
||||||
|
* Motiv, das unter einer freien Lizenz (Creative Commons) steht, geladen.
|
||||||
|
* Bei {@code false} wird das Bild lokal generiert.
|
||||||
|
*
|
||||||
|
* @param width Breite des Bildes.
|
||||||
|
* @param height Höhe des Bildes.
|
||||||
|
* @param fromWeb Bei {@code true} wird das Bild aus dem Internet geladen,
|
||||||
|
* bei {@code false} wird das Bild lokal erzeugt.
|
||||||
|
* @return Ein zufälliges Bild in der angegebenen Größe.
|
||||||
|
*/
|
||||||
|
public static BufferedImage fakeImage( int width, int height, boolean fromWeb ) {
|
||||||
|
if( !fromWeb ) {
|
||||||
|
BufferedImage img = ImageLoader.createImage(width, height);
|
||||||
|
Graphics2D graphics = (Graphics2D) img.getGraphics().create();
|
||||||
|
|
||||||
|
String text = width + " x " + height;
|
||||||
|
|
||||||
|
Color clr = Constants.randomNiceColor();
|
||||||
|
|
||||||
|
graphics.setBackground(clr.getJavaColor());
|
||||||
|
graphics.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
graphics.setColor(clr.textcolor().getJavaColor());
|
||||||
|
graphics.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, (int) ((width + height) * 0.05)));
|
||||||
|
FontMetrics fontMerics = graphics.getFontMetrics();
|
||||||
|
LineMetrics lineMetrics = fontMerics.getLineMetrics(text, graphics);
|
||||||
|
graphics.drawString(text,
|
||||||
|
(int) ((width - fontMerics.stringWidth(text)) / 2),
|
||||||
|
(int) (height / 2 - lineMetrics.getDescent() + lineMetrics.getAscent() / 2)
|
||||||
|
);
|
||||||
|
graphics.dispose();
|
||||||
|
|
||||||
|
return img;
|
||||||
|
} else {
|
||||||
|
return ImageLoader.loadImage(String.format(FAKE_IMG_URL, width, height), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String fakeText( int words, int paragraphs ) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[][] loadMockfile( String name ) {
|
||||||
|
return FileLoader.loadCsv("schule/ngb/zm/util/mock-" + name + ".csv", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Faker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package schule.ngb.zm.util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public final class FileLoader {
|
|
||||||
|
|
||||||
public static final Charset ASCII = StandardCharsets.US_ASCII;
|
|
||||||
|
|
||||||
public static final Charset UTF8 = StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
public static final Charset UTF16 = StandardCharsets.UTF_16;
|
|
||||||
|
|
||||||
public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
|
|
||||||
|
|
||||||
|
|
||||||
public static List<String> loadLines( String source ) {
|
|
||||||
return loadLines(source, UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> loadLines( String source, Charset charset ) {
|
|
||||||
try {
|
|
||||||
return Files.readAllLines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset);
|
|
||||||
} catch( IOException | URISyntaxException ex ) {
|
|
||||||
LOG.error(ex, "Error while loading lines from source <%s>", source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Collections.EMPTY_LIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String loadText( String source ) {
|
|
||||||
return loadText(source, UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String loadText( String source, Charset charset ) {
|
|
||||||
try {
|
|
||||||
return Files.readString(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset);
|
|
||||||
} catch( IOException | URISyntaxException ex ) {
|
|
||||||
LOG.error(ex, "Error while loading text from source <%s>", source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String[][] loadCsv( String source, boolean skipFirst ) {
|
|
||||||
return loadCsv(source, ',', skipFirst, UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String[][] loadCsv( String source, char separator, boolean skipFirst, Charset charset ) {
|
|
||||||
try {
|
|
||||||
int n = skipFirst ? 1 : 0;
|
|
||||||
return Files
|
|
||||||
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
|
||||||
.skip(n)
|
|
||||||
.map(
|
|
||||||
( line ) -> line.split(Character.toString(separator))
|
|
||||||
).toArray(String[][]::new);
|
|
||||||
} catch( IOException | URISyntaxException ex ) {
|
|
||||||
LOG.error(ex, "Error while loading csv source <%s>", source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String[0][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] loadValues( String source, char separator, boolean skipFirst ) {
|
|
||||||
return loadValues(source, separator, skipFirst, UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] loadValues( String source, char separator, boolean skipFirst, Charset charset ) {
|
|
||||||
try {
|
|
||||||
int n = skipFirst ? 1 : 0;
|
|
||||||
return Files
|
|
||||||
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
|
||||||
.skip(n)
|
|
||||||
.map(
|
|
||||||
( line ) -> Arrays
|
|
||||||
.stream(line.split(Character.toString(separator)))
|
|
||||||
.mapToDouble(
|
|
||||||
( value ) -> {
|
|
||||||
try {
|
|
||||||
return Double.parseDouble(value);
|
|
||||||
} catch( NumberFormatException nfe ) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new);
|
|
||||||
} catch( IOException | URISyntaxException ex ) {
|
|
||||||
LOG.error(ex, "Error while loading double values from csv source <%s>", source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new double[0][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(FileLoader.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package schule.ngb.zm.util;
|
|
||||||
|
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.FontFormatException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class FontLoader {
|
|
||||||
|
|
||||||
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public static Font loadFont( 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for System fonts
|
|
||||||
Font font = Font.decode(source);
|
|
||||||
if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
|
||||||
fontCache.put(source, font);
|
|
||||||
LOG.debug("Loaded system font for <%s>.", source);
|
|
||||||
return font;
|
|
||||||
} else {
|
|
||||||
font = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load userfonts
|
|
||||||
try( InputStream in = ResourceStreamProvider.getInputStream(source) ) {
|
|
||||||
font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN);
|
|
||||||
|
|
||||||
if( font != null ) {
|
|
||||||
fontCache.put(source, font);
|
|
||||||
//ge.registerFont(font);
|
|
||||||
}
|
|
||||||
LOG.debug("Loaded custom font from <%s>.", source);
|
|
||||||
} catch( IOException ioex ) {
|
|
||||||
LOG.error(ioex, "Error loading custom font file from source <%s>.", source);
|
|
||||||
} catch( FontFormatException ffex ) {
|
|
||||||
LOG.error(ffex, "Error creating custom font from source <%s>.", source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return font;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FontLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(FontLoader.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
package schule.ngb.zm.util;
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
@@ -29,12 +26,13 @@ import static java.util.logging.Level.*;
|
|||||||
* Klasse nur genau ein {@code Log}-Objekt erstellt. Mehrere {@code Log}s nutzen
|
* Klasse nur genau ein {@code Log}-Objekt erstellt. Mehrere {@code Log}s nutzen
|
||||||
* dann aber denselben {@code Logger}.
|
* dann aber denselben {@code Logger}.
|
||||||
* <p>
|
* <p>
|
||||||
* Die API orientiert sich lose an <a href="#">Log4j</a> und vereinfacht die
|
* Die API orientiert sich lose an <a
|
||||||
|
* href="https://logging.apache.org/log4j/2.x/">Log4j</a> und vereinfacht die
|
||||||
* Nutzung der Java logging API für die häufigsten Anwendungsfälle.
|
* Nutzung der Java logging API für die häufigsten Anwendungsfälle.
|
||||||
*/
|
*/
|
||||||
public final class Log {
|
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";
|
private static final String DEFAULT_LOG_FORMAT = "[%1$tT] [%4$11s] %5$s %6$s%n";
|
||||||
|
|
||||||
@@ -80,7 +78,7 @@ public final class Log {
|
|||||||
*/
|
*/
|
||||||
public static void enableGlobalLevel( Level level ) {
|
public static void enableGlobalLevel( Level level ) {
|
||||||
int lvl = Validator.requireNotNull(level).intValue();
|
int lvl = Validator.requireNotNull(level).intValue();
|
||||||
ensureRootLoggerIntialized();
|
ensureRootLoggerInitialized();
|
||||||
|
|
||||||
// Decrease level of root level ConsoleHandlers for output
|
// Decrease level of root level ConsoleHandlers for output
|
||||||
Logger rootLogger = Logger.getLogger("");
|
Logger rootLogger = Logger.getLogger("");
|
||||||
@@ -119,31 +117,30 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Log getLogger( Class<?> clazz ) {
|
public static Log getLogger( Class<?> clazz ) {
|
||||||
ensureRootLoggerIntialized();
|
ensureRootLoggerInitialized();
|
||||||
return new Log(clazz);
|
return new Log(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ensureRootLoggerIntialized() {
|
private static void ensureRootLoggerInitialized() {
|
||||||
if( LOGGING_INIT ) {
|
if( LOGGING_INIT ) {
|
||||||
return;
|
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);
|
Logger rootLogger = Logger.getLogger(ROOT_LOGGER);
|
||||||
rootLogger.setLevel(Level.INFO);
|
rootLogger.setLevel(Level.INFO);
|
||||||
|
|
||||||
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null
|
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null
|
||||||
&& LogManager.getLogManager().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.addHandler(new StreamHandler(System.err, new LogFormatter()));
|
@Override
|
||||||
rootLogger.setUseParentHandlers(false);
|
public synchronized void publish( final LogRecord record ) {
|
||||||
|
super.publish(record);
|
||||||
|
flush();
|
||||||
}
|
}
|
||||||
if( rootLogger.getUseParentHandlers() ) {
|
});
|
||||||
// This logger was not configured somewhere else
|
|
||||||
// Add a Handler and Formatter
|
|
||||||
|
|
||||||
//StreamHandler rootHandler = new StreamHandler(System.out, new SimpleFormatter());
|
|
||||||
//rootLogger.addHandler(rootHandler);
|
|
||||||
//rootLogger.setUseParentHandlers(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGING_INIT = true;
|
LOGGING_INIT = true;
|
||||||
@@ -202,26 +199,15 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String inferCallerName() {
|
|
||||||
StackTraceElement[] trace = new Throwable().getStackTrace();
|
|
||||||
for( int i = 0; i < trace.length; i++ ) {
|
|
||||||
/// if( trace[i].getClassName().equals(sourceClass.getName()) ) {
|
|
||||||
if( !trace[i].getClassName().equals(Log.class.getName()) ) {
|
|
||||||
return trace[i].getMethodName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doLog( Level level, final Throwable throwable, final Supplier<String> msgSupplier ) {
|
private void doLog( Level level, final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||||
String clazz = sourceClass.getName();
|
String clazz = sourceClass.getName();
|
||||||
String method = "unknown";
|
String method = "unknown";
|
||||||
|
|
||||||
StackTraceElement[] trace = new Throwable().getStackTrace();
|
StackTraceElement[] trace = new Throwable().getStackTrace();
|
||||||
for( int i = 0; i < trace.length; i++ ) {
|
for( StackTraceElement stackTraceElement : trace ) {
|
||||||
if( !trace[i].getClassName().equals(Log.class.getName()) ) {
|
if( !stackTraceElement.getClassName().equals(Log.class.getName()) ) {
|
||||||
clazz = trace[i].getClassName();
|
clazz = stackTraceElement.getClassName();
|
||||||
method = trace[i].getMethodName();
|
method = stackTraceElement.getMethodName();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,7 +348,7 @@ public final class Log {
|
|||||||
pw.close();
|
pw.close();
|
||||||
throwable = sw.toString();
|
throwable = sw.toString();
|
||||||
}
|
}
|
||||||
return String.format(DEFAULT_LOG_FORMAT,
|
return String.format(DEFAULT_DEBUG_FORMAT,
|
||||||
zdt,
|
zdt,
|
||||||
source,
|
source,
|
||||||
record.getLoggerName(),
|
record.getLoggerName(),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.Random;
|
|||||||
* href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
|
* href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
|
||||||
* FLAFLA2</a> implementiert.
|
* FLAFLA2</a> implementiert.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class Noise {
|
public class Noise {
|
||||||
|
|
||||||
private static final int N = 256;
|
private static final int N = 256;
|
||||||
@@ -42,7 +43,7 @@ public class Noise {
|
|||||||
/**
|
/**
|
||||||
* Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
|
* Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
|
||||||
*
|
*
|
||||||
* @param rand
|
* @param rand Ein Zufallsgenerator-Objekt.
|
||||||
*/
|
*/
|
||||||
public Noise( Random rand ) {
|
public Noise( Random rand ) {
|
||||||
init(rand);
|
init(rand);
|
||||||
|
|||||||
@@ -2,77 +2,189 @@ package schule.ngb.zm.util;
|
|||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Hilfsklasse zur Zeitmessung im Nanosekundenbereich.
|
||||||
|
* <p>
|
||||||
|
* Die Klasse kann zum Beispiel genutzt werden, um die Laufzeit eines
|
||||||
|
* Quelltextes zu messen. Wie eine echte Stoppuhr läuft der {@code Timer}
|
||||||
|
* weiter, wenn nach einem {@link #stop()} wieder {@link #start()} aufgerufen
|
||||||
|
* wird. Soll die Zeitmessung wieder bei null beginnen, muss vorher
|
||||||
|
* {@link #reset()} genutzt werden.
|
||||||
|
* <p>
|
||||||
|
* Die gemessene Zeit kann in {@link #getMillis() Millisekunden} oder
|
||||||
|
* {@link #getSeconds() Sekunden} abgerufen werden. Wird eine noch größere
|
||||||
|
* Genauigkeit benötigt, können mit {@link #getTime(TimeUnit)} beliebige
|
||||||
|
* Zeiteinheiten (zum Beispiel {@link TimeUnit#NANOSECONDS Nanosekunden})
|
||||||
|
* abgerufen werden.
|
||||||
|
* <p>
|
||||||
|
* Die Zeit kann auch bei laufender Uhr abgefragt werden. In dem Fall wird die
|
||||||
|
* bis zu diesem Zeitpunkt gemessene Zeit zurückgegeben.
|
||||||
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public final class Timer {
|
public final class Timer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Basiseinheit für die Zeitmessung.
|
||||||
|
*/
|
||||||
private final TimeUnit baseUnit;
|
private final TimeUnit baseUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Zeitmessung gerade läuft.
|
||||||
|
*/
|
||||||
private boolean running = false;
|
private boolean running = false;
|
||||||
|
|
||||||
private long starttime = -1;
|
/**
|
||||||
|
* Startzeit der Zeitmessung. -1, wenn noch keine Messung gestartet wurde.
|
||||||
|
*/
|
||||||
|
private long start = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeit, die bisher bei allen Zeitmessungen, die gestoppt wurden, insgesamt
|
||||||
|
* vergangen ist.
|
||||||
|
*/
|
||||||
private long elapsed = 0;
|
private long elapsed = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen neuen {@code Timer} mit Millisekunden als Basiseinheit.
|
||||||
|
*/
|
||||||
public Timer() {
|
public Timer() {
|
||||||
this(TimeUnit.MILLISECONDS);
|
this(TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen {@code Timer}, der die angegebene Einheit als Basiseinheit
|
||||||
|
* für {@link #getTime()} benutzt.
|
||||||
|
* <p>
|
||||||
|
* Um eine Zeitmessung in Nanosekunden durchzuführen, kann der {@code Timer}
|
||||||
|
* beispielsweise so instanziiert werden:
|
||||||
|
* <pre><code>
|
||||||
|
* Timer clock = new Timer(TimeUnit.NANOSECONDS);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param baseUnit Die Basiseinheit für die Zeitmessung.
|
||||||
|
*/
|
||||||
public Timer( TimeUnit baseUnit ) {
|
public Timer( TimeUnit baseUnit ) {
|
||||||
this.baseUnit = baseUnit;
|
this.baseUnit = baseUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ob die Zeitmessung gerade läuft.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Zeitmessung mit {@link #start()} gestartet
|
||||||
|
* wurde.
|
||||||
|
*/
|
||||||
public boolean isRunning() {
|
public boolean isRunning() {
|
||||||
return running;
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Startet die Zeitmessung.
|
||||||
|
* <p>
|
||||||
|
* Wenn zuvor schon eine Zeitmessung gestartet wurde, wird die neue Messung
|
||||||
|
* zur Summe aller Messungen hinzuaddiert. Soll die Messung bei null
|
||||||
|
* starten, muss vorher {@link #reset()} verwendet werden:
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* // Timer auf null stellen und sofort starten
|
||||||
|
* timer.reset().start();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @return Dieser {@code Timer} selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Timer start() {
|
public Timer start() {
|
||||||
starttime = System.nanoTime();
|
start = System.nanoTime();
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stoppt den {@code Timer}, wenn er derzeit läuft. Die gemessene Zeit wird
|
||||||
|
* zur Summe aller gemessenen Zeiten hinzuaddiert.
|
||||||
|
*
|
||||||
|
* @return Dieser {@code Timer} selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Timer stop() {
|
public Timer stop() {
|
||||||
|
if( running ) {
|
||||||
running = false;
|
running = false;
|
||||||
elapsed += System.nanoTime() - starttime;
|
elapsed += System.nanoTime() - start;
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den {@code Timer} auf den Startzustand und löscht alle bisher
|
||||||
|
* gemessenen Zeiten. Falls die Zeitmessung gerade läuft, wird sie nicht
|
||||||
|
* gestoppt, sondern läuft vom Zeitpunkt des Aufrufs weiter.
|
||||||
|
*
|
||||||
|
* @return Dieser {@code Timer} selbst (method chaining).
|
||||||
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public Timer reset() {
|
public Timer reset() {
|
||||||
running = false;
|
|
||||||
starttime = -1;
|
|
||||||
elapsed = 0;
|
elapsed = 0;
|
||||||
|
start = -1;
|
||||||
|
|
||||||
|
if( running ) {
|
||||||
|
start = System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Zeit in der Basiseinheit zurück.
|
||||||
|
*
|
||||||
|
* @return Die bisher insgesamt gemessene Zeit.
|
||||||
|
*/
|
||||||
public long getTime() {
|
public long getTime() {
|
||||||
return getTime(baseUnit);
|
return getTime(baseUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Zeit in der angegebenen Einheit zurück.
|
||||||
|
* <p>
|
||||||
|
* Größere Zeiteinheiten werden gerundet und verlieren daher an Genauigkeit.
|
||||||
|
* Eine Zeitmessung von 999 Millisekunden wird als 0 Sekunden
|
||||||
|
* zurückgegeben.
|
||||||
|
* <p>
|
||||||
|
* Um genauere Ergebnisse zu erhalten, kann mit {@link #getMillis()} und
|
||||||
|
* {@link #getSeconds()} die gemessene Zeit in Minuten beziehungsweise
|
||||||
|
* Sekunden als Kommazahl abgefragt werden.
|
||||||
|
*
|
||||||
|
* @param unit Zeiteinheit
|
||||||
|
* @return Die bisher insgesamt gemessene Zeit in der gewählten Zeiteinheit.
|
||||||
|
*/
|
||||||
public long getTime( TimeUnit unit ) {
|
public long getTime( TimeUnit unit ) {
|
||||||
if( running ) {
|
if( running ) {
|
||||||
return unit.convert(System.nanoTime() - starttime + elapsed, TimeUnit.NANOSECONDS);
|
return unit.convert(System.nanoTime() - start + elapsed, TimeUnit.NANOSECONDS);
|
||||||
} else {
|
} else {
|
||||||
return unit.convert(elapsed, TimeUnit.NANOSECONDS);
|
return unit.convert(elapsed, TimeUnit.NANOSECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die gemessene Zeit in Millisekunden (gerundet) zurück.
|
||||||
|
*
|
||||||
|
* @return Die gemessene Zeit in ms.
|
||||||
|
*/
|
||||||
public int getMillis() {
|
public int getMillis() {
|
||||||
if( running ) {
|
if( running ) {
|
||||||
return (int) ((System.nanoTime() - starttime + elapsed) / 1000000);
|
return (int) ((System.nanoTime() - start + elapsed) / 1000000);
|
||||||
} else {
|
} else {
|
||||||
return (int) (elapsed / 1000000);
|
return (int) (elapsed / 1000000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die gemessene Zeit in Sekunden zurück.
|
||||||
|
*
|
||||||
|
* @return Die gemessene Zeit in s.
|
||||||
|
*/
|
||||||
public double getSeconds() {
|
public double getSeconds() {
|
||||||
if( running ) {
|
if( running ) {
|
||||||
return (System.nanoTime() - starttime + elapsed) / 1000000000.0;
|
return (System.nanoTime() - start + elapsed) / 1000000000.0;
|
||||||
} else {
|
} else {
|
||||||
return elapsed / 1000000000.0;
|
return elapsed / 1000000000.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package schule.ngb.zm.util;
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@SuppressWarnings( "UnusedReturnValue" )
|
||||||
public class Validator {
|
public class Validator {
|
||||||
|
|
||||||
public static final <T> T requireNotNull( T obj ) {
|
public static final <T> T requireNotNull( T obj ) {
|
||||||
@@ -147,7 +147,7 @@ public class Validator {
|
|||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
||||||
if( arr.length == 0 )
|
if( arr.length == 0 )
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be empty") : msg.get());
|
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ public class Validator {
|
|||||||
|
|
||||||
public static final <T> T[] requireSize( T[] arr, int size, Supplier<String> msg ) {
|
public static final <T> T[] requireSize( T[] arr, int size, Supplier<String> msg ) {
|
||||||
if( arr.length != size )
|
if( arr.length != size )
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size) : msg.get());
|
throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size, arr.length) : msg.get());
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ public class Validator {
|
|||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
|
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
|
||||||
if( arr == null || arr.length > 0 )
|
if( arr == null || arr.length > 0 )
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be null or empty") : msg.get());
|
throw new IllegalArgumentException(msg == null ? "Parameter array may not be null or empty" : msg.get());
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package schule.ngb.zm.events;
|
package schule.ngb.zm.util.events;
|
||||||
|
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
@@ -9,9 +9,9 @@ import java.util.function.BiConsumer;
|
|||||||
|
|
||||||
public class EventDispatcher<E, L extends Listener<E>> {
|
public class EventDispatcher<E, L extends Listener<E>> {
|
||||||
|
|
||||||
private CopyOnWriteArraySet<L> listeners;
|
private final CopyOnWriteArraySet<L> listeners;
|
||||||
|
|
||||||
private ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
private final ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
||||||
|
|
||||||
public EventDispatcher() {
|
public EventDispatcher() {
|
||||||
listeners = new CopyOnWriteArraySet<>();
|
listeners = new CopyOnWriteArraySet<>();
|
||||||
@@ -49,9 +49,7 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
|
|
||||||
if( eventRegistered(eventKey) ) {
|
if( eventRegistered(eventKey) ) {
|
||||||
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
||||||
listeners.forEach(( listener ) -> {
|
listeners.forEach(( listener ) -> dispatcher.accept(event, listener));
|
||||||
dispatcher.accept(event, listener);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package schule.ngb.zm.events;
|
package schule.ngb.zm.util.events;
|
||||||
|
|
||||||
public interface Listener<E> {
|
public interface Listener<E> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
169
src/main/java/schule/ngb/zm/util/io/FileLoader.java
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package schule.ngb.zm.util.io;
|
||||||
|
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public final class FileLoader {
|
||||||
|
|
||||||
|
public static final Charset ASCII = StandardCharsets.US_ASCII;
|
||||||
|
|
||||||
|
public static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
public static final Charset UTF16 = StandardCharsets.UTF_16;
|
||||||
|
|
||||||
|
public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
|
||||||
|
|
||||||
|
public static List<String> loadLines( String source ) {
|
||||||
|
return loadLines(source, UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt die angegebene Datei Zeilenweise in ein Array.
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
* @param charset
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<String> loadLines( String source, Charset charset ) {
|
||||||
|
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
|
||||||
|
String line;
|
||||||
|
while( (line = reader.readLine()) != null ) {
|
||||||
|
result.add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LOG.error(ex, "Error while loading lines from source <%s>", source);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String loadText( String source ) {
|
||||||
|
return loadText(source, UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String loadText( String source, Charset charset ) {
|
||||||
|
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
String line;
|
||||||
|
while( (line = reader.readLine()) != null ) {
|
||||||
|
result.append(line).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LOG.error(ex, "Error while loading string from source <%s>", source);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt die Daten aus einer CSV Datei in ein zweidimensionales
|
||||||
|
* String-Array.
|
||||||
|
* <p>
|
||||||
|
* Der Aufruf entspricht dem Aufruf von
|
||||||
|
* <pre><code>
|
||||||
|
* FileLoader.loadCsv(source, ',', skipFirst, UTF8);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param source Die Quelle der CSV-Daten.
|
||||||
|
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||||
|
* @return Ein Array mit den Daten als {@code String}s.
|
||||||
|
* @see #loadCsv(String, char, boolean, Charset)
|
||||||
|
*/
|
||||||
|
public static String[][] loadCsv( String source, boolean skipFirst ) {
|
||||||
|
return loadCsv(source, ',', skipFirst, UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt die Daten aus einer CSV Datei in ein zweidimensionales
|
||||||
|
* String-Array.
|
||||||
|
* <p>
|
||||||
|
* Die Methode ist nicht in der Lage, komplexe CSV-Dateien zu verarbeiten.
|
||||||
|
* Insbesondere können Inhalte, die das Trennzeichen {@code separator}
|
||||||
|
* enthalten, nicht korrekt erkannt werden. Das Trennzeichen wird unabhängig
|
||||||
|
* vom Kontext immer als Zelltrenner erkannt. (Im Normalfall kann das
|
||||||
|
* Trennzeichen durch die Verwendung doppelter Anführungszeichen in der Art
|
||||||
|
* {@code Inhalt,"Inhalt, der Komma enthält",Inhalt} maskiert werden.)
|
||||||
|
* <p>
|
||||||
|
* Es wird auch keine erweiterte Inhaltserkennung ausgeführt, sondern alle
|
||||||
|
* Inhalte als {@code String} gelesen. Die weitere Verarbeitung mit den
|
||||||
|
* passenden Parser-Methoden (beispielsweise
|
||||||
|
* {@link Double#parseDouble(String)}) obligt dem Nutzer.
|
||||||
|
*
|
||||||
|
* @param source Die Quelle der CSV-Daten.
|
||||||
|
* @param separator Das verwendete Trennzeichen.
|
||||||
|
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||||
|
* @param charset Die zu verwendende Zeichenkodierung.
|
||||||
|
* @return Ein Array mit den Daten als {@code String}s.
|
||||||
|
*/
|
||||||
|
public static String[][] loadCsv( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||||
|
int n = skipFirst ? 1 : 0;
|
||||||
|
List<String> lines = loadLines(source, charset);
|
||||||
|
return lines.stream().skip(n).map(
|
||||||
|
//( line ) -> line.split(Character.toString(separator))
|
||||||
|
( line ) -> line.split("\\s*" + separator + "\\s*")
|
||||||
|
).toArray(String[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[][] loadValues( String source, char separator, boolean skipFirst ) {
|
||||||
|
return loadValues(source, separator, skipFirst, UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt Double-Werte aus einer CSV Datei in ein zweidimensionales Array.
|
||||||
|
* <p>
|
||||||
|
* Die gelesenen Strings werden mit {@link Double#parseDouble(String)} in
|
||||||
|
* {@code double} umgeformt. Es leigt in der Verantwortung des Nutzers
|
||||||
|
* sicherzustellen, dass die CSV-Datei auch nur Zahlen enthält, die korrekt
|
||||||
|
* in {@code double} umgewandelt werden können. Zellen für die die
|
||||||
|
* Umwandlung fehlschlägt werden mit 0.0 befüllt.
|
||||||
|
* <p>
|
||||||
|
* Die Methode unterliegt denselben Einschränkungen wie
|
||||||
|
* {@link #loadCsv(String, char, boolean, Charset)}.
|
||||||
|
*
|
||||||
|
* @param source Die Quelle der CSV-Daten.
|
||||||
|
* @param separator Das verwendete Trennzeichen.
|
||||||
|
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||||
|
* @param charset Die zu verwendende Zeichenkodierung.
|
||||||
|
* @return Ein Array mit den Daten als {@code String}s.
|
||||||
|
*/
|
||||||
|
public static double[][] loadValues( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||||
|
int n = skipFirst ? 1 : 0;
|
||||||
|
List<String> lines = loadLines(source, charset);
|
||||||
|
return lines.stream().skip(n).map(
|
||||||
|
( line ) -> Arrays
|
||||||
|
.stream(line.split(Character.toString(separator)))
|
||||||
|
.mapToDouble(
|
||||||
|
( value ) -> {
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(value);
|
||||||
|
} catch( NumberFormatException nfe ) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).toArray()
|
||||||
|
).toArray(double[][]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileLoader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(FileLoader.class);
|
||||||
|
|
||||||
|
}
|
||||||
131
src/main/java/schule/ngb/zm/util/io/FontLoader.java
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package schule.ngb.zm.util.io;
|
||||||
|
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class FontLoader {
|
||||||
|
|
||||||
|
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt eine Schrift aus einer Datei.
|
||||||
|
* <p>
|
||||||
|
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
||||||
|
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
||||||
|
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das
|
||||||
|
* Ergebnis {@code null}.
|
||||||
|
* <p>
|
||||||
|
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher
|
||||||
|
* geladen und kann danach in der Zeichenmaschine benutzt werden.
|
||||||
|
* <p>
|
||||||
|
* Eine 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 Die erste geladene Schrift oder {@code null}.
|
||||||
|
* @see #loadFont(String, String)
|
||||||
|
*/
|
||||||
|
public static Font loadFont( String source ) {
|
||||||
|
String name = source;
|
||||||
|
// Dateipfad entfernen
|
||||||
|
int lastIndex = source.lastIndexOf(File.separatorChar);
|
||||||
|
if( lastIndex > -1 ) {
|
||||||
|
source = 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 ) {
|
||||||
|
Validator.requireNotNull(source,"Font source may not be null");
|
||||||
|
Validator.requireNotEmpty(source, "Font source may not be empty.");
|
||||||
|
|
||||||
|
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( source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
||||||
|
fontCache.put(name, font);
|
||||||
|
fontCache.put(source, font);
|
||||||
|
LOG.debug("Loaded system font for <%s>.", source);
|
||||||
|
return font;
|
||||||
|
} else {
|
||||||
|
font = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load userfonts
|
||||||
|
try( InputStream in = ResourceStreamProvider.getInputStream(source) ) {
|
||||||
|
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 source <%s>.", source);
|
||||||
|
} catch( IOException ioex ) {
|
||||||
|
LOG.error(ioex, "Error loading custom font file from source <%s>.", source);
|
||||||
|
} catch( FontFormatException ffex ) {
|
||||||
|
LOG.error(ffex, "Error creating custom font from source <%s>.", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt eine Schrift aus einer Datei.
|
||||||
|
* <p>
|
||||||
|
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
||||||
|
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
||||||
|
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das
|
||||||
|
* Ergebnis {@code null}.
|
||||||
|
* <p>
|
||||||
|
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher
|
||||||
|
* geladen und kann danach in der Zeichenmaschine benutzt werden.
|
||||||
|
* <p>
|
||||||
|
* Eine 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 name
|
||||||
|
* @param sources
|
||||||
|
* @return Die erste geladene Schrift oder {@code null}.
|
||||||
|
* @see #loadFont(String, String)
|
||||||
|
*/
|
||||||
|
public static Font loadFonts( String name, String... sources ) {
|
||||||
|
for( String fontSource: sources ) {
|
||||||
|
// TODO: Ignore exceptions in this case and throw own at end?
|
||||||
|
Font font = loadFont(name, fontSource);
|
||||||
|
if( font != null ) {
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FontLoader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(FontLoader.class);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 javax.imageio.ImageIO;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
@@ -19,9 +22,9 @@ public final class ImageLoader {
|
|||||||
|
|
||||||
public static boolean caching = true;
|
public static boolean caching = true;
|
||||||
|
|
||||||
private static Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
private static final Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
private static final SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
||||||
@@ -45,12 +48,12 @@ public final class ImageLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
||||||
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden
|
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden konnte.
|
||||||
* konnte. Ist ein Bild mit der angegebenen Quelle im Cache, wird das
|
* Ist ein Bild mit der angegebenen Quelle im Cache, wird das gespeicherte
|
||||||
* gespeicherte Bild zurückgegeben. Dies kann mit {@code caching = false}
|
* Bild zurückgegeben. Dies kann mit {@code caching = false} verhindert
|
||||||
* verhindert werden.
|
* werden.
|
||||||
* <p>
|
* <p>
|
||||||
* Wurde das Chaching global deaktiviert, kann mit <code>caching =
|
* Wurde das Caching global deaktiviert, kann mit <code>caching =
|
||||||
* true</code> das Bild trotzdem aus dem Cache geladen werden, wenn es
|
* true</code> das Bild trotzdem aus dem Cache geladen werden, wenn es
|
||||||
* vorhanden ist.
|
* vorhanden ist.
|
||||||
*
|
*
|
||||||
@@ -110,9 +113,9 @@ public final class ImageLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speiechert das angegebene Bild unter dem angegebenen Namen im Cache.
|
* Speichert das angegebene Bild unter dem angegebenen Namen im Cache.
|
||||||
* <p>
|
* <p>
|
||||||
* Exisitert zu {@code name} schon ein Bild im Zwischenspeicher
|
* Existiert zu {@code name} schon ein Bild im Zwischenspeicher
|
||||||
* ({@code ImageLoader.isCached(name) == true}), dann wird dieses
|
* ({@code ImageLoader.isCached(name) == true}), dann wird dieses
|
||||||
* überschrieben.
|
* überschrieben.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -146,10 +149,8 @@ public final class ImageLoader {
|
|||||||
* @param name Name des Bildes im Cache.
|
* @param name Name des Bildes im Cache.
|
||||||
*/
|
*/
|
||||||
public static void invalidateCache( final String name ) {
|
public static void invalidateCache( final String name ) {
|
||||||
if( imageCache.containsKey(name) ) {
|
|
||||||
imageCache.remove(name);
|
imageCache.remove(name);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speichert ein Bild als {@link SoftReference} im Cache.
|
* Speichert ein Bild als {@link SoftReference} im Cache.
|
||||||
@@ -208,7 +209,7 @@ public final class ImageLoader {
|
|||||||
* Bild zurückgegeben und nicht komplett neu geladen.
|
* Bild zurückgegeben und nicht komplett neu geladen.
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Wichtig:</b> Bildreferenzen, die aus dem Cache geladen werden,
|
* <b>Wichtig:</b> Bildreferenzen, die aus dem Cache geladen werden,
|
||||||
* verweisen alle auf dasselbe Objekt. Änderungen schalgen sich daher in
|
* verweisen alle auf dasselbe Objekt. Änderungen schlagen sich daher in
|
||||||
* allen anderen Versionen des Bildes nieder (inklusive dem Bild im
|
* allen anderen Versionen des Bildes nieder (inklusive dem Bild im
|
||||||
* Zwischenspeicher). Für Änderungen sollte daher immer
|
* Zwischenspeicher). Für Änderungen sollte daher immer
|
||||||
* {@link #copyImage(BufferedImage) eine Kopie} des Bildes erstellt werden:
|
* {@link #copyImage(BufferedImage) eine Kopie} des Bildes erstellt werden:
|
||||||
@@ -299,7 +300,7 @@ public final class ImageLoader {
|
|||||||
* @param file Die Zieldatei.
|
* @param file Die Zieldatei.
|
||||||
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
||||||
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
||||||
* wenn die Datei schon exisitiert.
|
* wenn die Datei schon existiert.
|
||||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||||
* {@code null} ist.
|
* {@code null} ist.
|
||||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||||
@@ -332,7 +333,7 @@ public final class ImageLoader {
|
|||||||
* @param file Die Zieldatei.
|
* @param file Die Zieldatei.
|
||||||
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
||||||
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
||||||
* wenn die Datei schon exisitiert.
|
* wenn die Datei schon existiert.
|
||||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||||
* {@code null} ist.
|
* {@code null} ist.
|
||||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||||
@@ -1,18 +1,70 @@
|
|||||||
package schule.ngb.zm.util;
|
package schule.ngb.zm.util.io;
|
||||||
|
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.stream.StreamSupport;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helferklasse, um {@link InputStream}s für Resourcen zu erhalten.
|
* Helferklasse, um {@link InputStream}s für Resourcen zu erhalten.
|
||||||
*/
|
*/
|
||||||
public class ResourceStreamProvider {
|
public class ResourceStreamProvider {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
|
||||||
|
* Resource Locator</em>). Eine passende Datei-Resource wird wie folgt
|
||||||
|
* ermittelt:
|
||||||
|
* <ol>
|
||||||
|
* <li>Ist {@code source} eine existierende Datei
|
||||||
|
* ({@code new File(source}.isFile() == true})?</li>
|
||||||
|
* <li>Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?.</li>
|
||||||
|
* <li>Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?</li>
|
||||||
|
* <li>Ansonten erstellt ein {@link URL}-Objekt.</li>
|
||||||
|
* </ol>
|
||||||
|
* <p>
|
||||||
|
* Ein {@code URL} für die erste gefundene Resource wird zurückgegeben.
|
||||||
|
* Auftretende Exceptions
|
||||||
|
* werden als {@link IOException} geworfen.
|
||||||
|
* <p>
|
||||||
|
* Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt.
|
||||||
|
* Eine {@link java.net.MalformedURLException} beim Konstruieren des {@code URL}
|
||||||
|
* zu einer Datei verhindert daher, dass noch im Classpath gesucht wird.
|
||||||
|
*
|
||||||
|
* @param source Eine Quelle für die Resource (Absoluter Dateipfad,
|
||||||
|
* Dateipfad im Classpath oder Netzwerkresource)
|
||||||
|
* @return Ein {@code InputStream} für die Resource
|
||||||
|
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
||||||
|
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
||||||
|
* ist.
|
||||||
|
* @throws IOException Geworfen beim Erzeugen einer URL zu
|
||||||
|
* einer bestehenden Resource.
|
||||||
|
*/
|
||||||
|
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||||
|
Validator.requireNotNull(source, "Resource source may not be null");
|
||||||
|
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
||||||
|
|
||||||
|
// Ist source ein valider Dateipfad?
|
||||||
|
File file = new File(source);
|
||||||
|
if( file.isFile() ) {
|
||||||
|
return file.toURI().toURL();
|
||||||
|
}
|
||||||
|
// Ist source im Classpath vorhanden?
|
||||||
|
URL url = Zeichenmaschine.class.getClassLoader().getResource(source);
|
||||||
|
if( url != null ) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
// Dann versuchen aus source direkt eine URL zu machen.
|
||||||
|
return new URL(source);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sucht eine zur angegebenen Quelle passende Resource und öffnet einen
|
* Sucht eine zur angegebenen Quelle passende Resource und öffnet einen
|
||||||
* passenden {@link InputStream}. Die konkrete Art des Streams hängt davon
|
* passenden {@link InputStream}. Die konkrete Art des Streams hängt davon
|
||||||
@@ -46,134 +98,41 @@ public class ResourceStreamProvider {
|
|||||||
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
||||||
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
||||||
* ist.
|
* ist.
|
||||||
* @throws IOException Geworfen beim öffnen des Streams zu
|
* @throws IOException Geworfen beim Öffnen des Streams zu
|
||||||
* einer bestehenden Resource oder falls
|
* einer bestehenden Resource oder falls
|
||||||
* keine passende Resource gefunden wurde.
|
* keine passende Resource gefunden wurde.
|
||||||
*/
|
*/
|
||||||
public static InputStream getInputStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
public static InputStream getInputStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||||
Validator.requireNotNull(source, "Resource source may not be null");
|
return getResourceURL(source).openStream();
|
||||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
|
||||||
|
|
||||||
InputStream in = null;
|
|
||||||
|
|
||||||
// See if source is a readable file
|
|
||||||
File file = new File(source);
|
|
||||||
try {
|
|
||||||
if( file.isFile() ) {
|
|
||||||
in = new FileInputStream(file);
|
|
||||||
}
|
|
||||||
} catch( FileNotFoundException fnfex ) {
|
|
||||||
// Somehow an exception occurred, but we still try other sources
|
|
||||||
in = null;
|
|
||||||
}
|
|
||||||
// File does not exist, try other means
|
|
||||||
// load ressource relative to .class-file
|
|
||||||
if( in == null ) {
|
|
||||||
in = Zeichenmaschine.class.getResourceAsStream(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
// relative to ClassLoader
|
|
||||||
if( in == null ) {
|
|
||||||
in = Zeichenmaschine.class.getClassLoader().getResourceAsStream(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load form web or jar-file
|
|
||||||
if( in == null ) {
|
|
||||||
in = new URL(source).openStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
// One of the above got a valid Stream,
|
|
||||||
// otherwise an Exception was thrown
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InputStream getInputStream( File file ) throws IOException {
|
|
||||||
Validator.requireNotNull(file, "Provided file can't be null.");
|
|
||||||
return new FileInputStream(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InputStream getInputStream( URL url ) throws IOException {
|
|
||||||
Validator.requireNotNull(url, "Provided URL can't be null.");
|
|
||||||
return url.openStream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
|
|
||||||
* Resource Locator</em>). Eine passende Datei-Resource wird wie folgt
|
|
||||||
* ermittelt:
|
|
||||||
* <ol>
|
|
||||||
* <li>Ist {@code source} eine existierende Datei
|
|
||||||
* ({@code new File(source}.isFile() == true})?</li>
|
|
||||||
* <li>Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?.</li>
|
|
||||||
* <li>Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?</li>
|
|
||||||
* <li>Ansonten erstellt ein {@link URL}-Objekt.</li>
|
|
||||||
* </ol>
|
|
||||||
* <p>
|
|
||||||
* Ein {@code URL} für die erste gefundene Resource wird zurückgegeben.
|
|
||||||
* Auftretende Exceptions
|
|
||||||
* werden als {@link IOException} geworfen.
|
|
||||||
* <p>
|
|
||||||
* Bei einer Exception werden die folgenden Quellen nicht mehr abgefragt.
|
|
||||||
* Eine {@link java.net.MalformedURLException} beim Konstruieren des {@code URL}
|
|
||||||
* zu einer Datei verhindert daher, dass noch im Classpath gesucht wird.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Resource (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkresource)
|
|
||||||
* @return Ein {@code InputStream} für die Resource
|
|
||||||
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
|
||||||
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
|
||||||
* ist.
|
|
||||||
* @throws IOException Geworfen beim erzeugen eines URL zu
|
|
||||||
* einer bestehenden Resource.
|
|
||||||
*/
|
|
||||||
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
|
||||||
Validator.requireNotNull(source, "Resource source may not be null");
|
|
||||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
|
||||||
|
|
||||||
File file = new File(source);
|
|
||||||
if( file.isFile() ) {
|
|
||||||
return file.toURI().toURL();
|
|
||||||
}
|
|
||||||
|
|
||||||
URL url;
|
|
||||||
|
|
||||||
url = Zeichenmaschine.class.getResource(source);
|
|
||||||
if( url != null ) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
url = Zeichenmaschine.class.getClassLoader().getResource(source);
|
|
||||||
if( url != null ) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new URL(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ver
|
|
||||||
* @param source
|
* @param source
|
||||||
* @return
|
* @return
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public static OutputStream getOutputStream( String source ) throws IOException {
|
public static OutputStream getOutputStream( String source ) throws IOException {
|
||||||
Validator.requireNotNull(source, "Resource source may not be null");
|
try {
|
||||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
return Files.newOutputStream(Path.of(getResourceURL(source).toURI()));
|
||||||
URL url = getResourceURL(source);
|
} catch( URISyntaxException ex ) {
|
||||||
return getOutputStream(new File(url.getPath()));
|
throw new IOException(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OutputStream getOutputStream( File file ) throws IOException {
|
public static BufferedReader getReader( String source ) throws IOException {
|
||||||
Validator.requireNotNull(file, "Provided file can't be null.");
|
return getReader(source, StandardCharsets.UTF_8);
|
||||||
return new FileOutputStream(file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Reader getReader( String source ) throws IOException {
|
public static BufferedReader getReader( String source, Charset charset ) throws IOException {
|
||||||
return new InputStreamReader(getInputStream(source));
|
return new BufferedReader(new InputStreamReader(getInputStream(source), charset.newDecoder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Writer getWriter( String source ) throws IOException {
|
public static BufferedWriter getWriter( String source ) throws IOException {
|
||||||
return new OutputStreamWriter(getOutputStream(source));
|
return getWriter(source, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufferedWriter getWriter( String source, Charset charset ) throws IOException {
|
||||||
|
return new BufferedWriter(new OutputStreamWriter(getOutputStream(source), charset.newEncoder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceStreamProvider() {
|
private ResourceStreamProvider() {
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package schule.ngb.zm.tasks;
|
package schule.ngb.zm.util.tasks;
|
||||||
|
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
|
||||||
|
|
||||||
import java.util.concurrent.Delayed;
|
import java.util.concurrent.Delayed;
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public abstract class DelayedTask extends Task implements Delayed {
|
public abstract class DelayedTask extends Task implements Delayed {
|
||||||
@@ -1,39 +1,22 @@
|
|||||||
package schule.ngb.zm.tasks;
|
package schule.ngb.zm.util.tasks;
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
import schule.ngb.zm.Constants;
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
|
||||||
public abstract class FrameSynchronizedTask extends Task {
|
public abstract class FrameSynchronizedTask extends Task {
|
||||||
|
|
||||||
private static Thread mainThread;
|
|
||||||
|
|
||||||
private static final Thread getMainThread() {
|
|
||||||
if( mainThread == null ) {
|
|
||||||
mainThread = Thread.currentThread();
|
|
||||||
if( !mainThread.getName().equals(Constants.APP_NAME) ) {
|
|
||||||
// Need to search for main Zeichenthread ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mainThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
initialize();
|
initialize();
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
Object lock = Zeichenmaschine.globalSyncLock;
|
final Object lock = Zeichenmaschine.globalSyncLock;
|
||||||
|
|
||||||
// start of thread in ms
|
|
||||||
final long start = System.currentTimeMillis();
|
|
||||||
// current time in ns
|
// current time in ns
|
||||||
long beforeTime = System.nanoTime();
|
long beforeTime = System.nanoTime();
|
||||||
// store for deltas
|
// internal counters for tick and delta time
|
||||||
long overslept = 0L;
|
int _tick;
|
||||||
// internal counters for tick and runtime
|
double delta;
|
||||||
int _tick = 0;
|
|
||||||
|
|
||||||
double delta = 0.0;
|
|
||||||
|
|
||||||
while( running ) {
|
while( running ) {
|
||||||
// delta in seconds
|
// delta in seconds
|
||||||
@@ -53,10 +36,7 @@ public abstract class FrameSynchronizedTask extends Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
running = false;
|
|
||||||
done = true;
|
done = true;
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package schule.ngb.zm.tasks;
|
package schule.ngb.zm.util.tasks;
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
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 {
|
public abstract class RateLimitedTask extends Task {
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ public abstract class RateLimitedTask extends Task {
|
|||||||
// store for deltas
|
// store for deltas
|
||||||
long overslept = 0L;
|
long overslept = 0L;
|
||||||
// delta in ms
|
// delta in ms
|
||||||
double delta = 0;
|
double delta;
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
while( running ) {
|
while( running ) {
|
||||||
@@ -47,10 +47,7 @@ public abstract class RateLimitedTask extends Task {
|
|||||||
// Did we sleep to long?
|
// Did we sleep to long?
|
||||||
overslept = (System.nanoTime() - afterTime) - sleep;
|
overslept = (System.nanoTime() - afterTime) - sleep;
|
||||||
}
|
}
|
||||||
|
|
||||||
running = false;
|
|
||||||
done = true;
|
done = true;
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package schule.ngb.zm.tasks;
|
package schule.ngb.zm.util.tasks;
|
||||||
|
|
||||||
import schule.ngb.zm.Updatable;
|
import schule.ngb.zm.Updatable;
|
||||||
|
|
||||||
@@ -1,21 +1,15 @@
|
|||||||
package schule.ngb.zm.tasks;
|
package schule.ngb.zm.util.tasks;
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Führt Aufgaben (Tasks) parallel zum Hauptprogramm aus.
|
* Führt Aufgaben (Tasks) parallel zum Hauptprogramm aus.
|
||||||
*/
|
*/
|
||||||
public class TaskRunner {
|
public class TaskRunner {
|
||||||
|
|
||||||
private static final int POOL_SIZE = 4;
|
|
||||||
|
|
||||||
private static final int SHUTDOWN_TIME = 100;
|
|
||||||
|
|
||||||
|
|
||||||
private static TaskRunner runner;
|
private static TaskRunner runner;
|
||||||
|
|
||||||
public static TaskRunner getTaskRunner() {
|
public static TaskRunner getTaskRunner() {
|
||||||
|
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 |
3001
src/main/resources/schule/ngb/zm/util/mock-users.csv
Normal file
@@ -2,6 +2,9 @@ package schule.ngb.zm;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class ConstantsTest {
|
class ConstantsTest {
|
||||||
@@ -169,4 +172,30 @@ class ConstantsTest {
|
|||||||
assertTrue(0.0 <= d && 1.0 >= d, "Noise should be in Range 0 to 1. Was <" + d + ">.");
|
assertTrue(0.0 <= d && 1.0 >= d, "Noise should be in Range 0 to 1. Was <" + d + ">.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void choice() {
|
||||||
|
int[] values = IntStream.range(0,1000).toArray();
|
||||||
|
int[] choosen = Constants.choice(values, 1, false);
|
||||||
|
assertEquals(1, choosen.length);
|
||||||
|
assertTrue(Arrays.stream(values).anyMatch((i) -> i == choosen[0]));
|
||||||
|
|
||||||
|
int[] choosen4 = Constants.choice(values, 2000, false);
|
||||||
|
assertEquals(2000, choosen4.length);
|
||||||
|
|
||||||
|
int[] choosen2 = Constants.choice(values, 400, false);
|
||||||
|
assertEquals(400, choosen2.length);
|
||||||
|
assertTrue(Arrays.stream(choosen2).allMatch((i) -> Arrays.binarySearch(values, i)>=0 ));
|
||||||
|
|
||||||
|
int[] choosen3 = Constants.choice(values, 999, true);
|
||||||
|
assertEquals(999, choosen3.length);
|
||||||
|
assertTrue(Arrays.stream(choosen3).allMatch((i) -> Arrays.binarySearch(values, i)>=0 ));
|
||||||
|
for( int i = 0; i < choosen3.length; i++ ) {
|
||||||
|
for( int j = i+1; j < choosen3.length; j++ ) {
|
||||||
|
assertNotEquals(choosen3[i], choosen3[j], "Item "+i+" also found at index "+j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> Constants.choice(values, 2000, true));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import schule.ngb.zm.layers.Shape2DLayer;
|
||||||
import schule.ngb.zm.shapes.Rectangle;
|
import schule.ngb.zm.shapes.Rectangle;
|
||||||
|
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package schule.ngb.zm;
|
|||||||
|
|
||||||
import schule.ngb.zm.shapes.*;
|
import schule.ngb.zm.shapes.*;
|
||||||
|
|
||||||
import java.awt.geom.Point2D;
|
|
||||||
|
|
||||||
public class TestInput extends Zeichenmaschine {
|
public class TestInput extends Zeichenmaschine {
|
||||||
|
|
||||||
public static void main( String[] args ) {
|
public static void main( String[] args ) {
|
||||||
@@ -23,7 +21,7 @@ public class TestInput extends Zeichenmaschine {
|
|||||||
shapes.getShape(1).setFillColor(PINK);
|
shapes.getShape(1).setFillColor(PINK);
|
||||||
|
|
||||||
text = new Text(10, 42, "");
|
text = new Text(10, 42, "");
|
||||||
text.setFontsize(32);
|
text.setFontSize(32);
|
||||||
text.setAnchor(NORTHWEST);
|
text.setAnchor(NORTHWEST);
|
||||||
shapes.add(text);
|
shapes.add(text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import schule.ngb.zm.turtle.TurtleLayer;
|
import schule.ngb.zm.layers.TurtleLayer;
|
||||||
import schule.ngb.zm.turtle.TurtleLayer.Turtle;
|
import schule.ngb.zm.layers.TurtleLayer.Turtle;
|
||||||
|
|
||||||
public class TestTurtle extends Zeichenmaschine {
|
public class TestTurtle extends Zeichenmaschine {
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import schule.ngb.zm.Color;
|
|||||||
import schule.ngb.zm.Constants;
|
import schule.ngb.zm.Constants;
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Options;
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
import schule.ngb.zm.layers.ShapesLayer;
|
||||||
import schule.ngb.zm.shapes.*;
|
import schule.ngb.zm.shapes.*;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@@ -60,7 +61,7 @@ class AnimationsTest {
|
|||||||
|
|
||||||
private void _animateMove( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
private void _animateMove( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
||||||
s.moveTo(0, 0);
|
s.moveTo(0, 0);
|
||||||
Future<Shape> future = Animations.animate(
|
Future<Shape> future = Animations.play(
|
||||||
s, runtime,
|
s, runtime,
|
||||||
easing,
|
easing,
|
||||||
( e ) -> Constants.interpolate(0, zm.getWidth(), e),
|
( e ) -> Constants.interpolate(0, zm.getWidth(), e),
|
||||||
@@ -89,25 +90,11 @@ class AnimationsTest {
|
|||||||
final int midY = (int) (zm.getHeight() * .5);
|
final int midY = (int) (zm.getHeight() * .5);
|
||||||
final int radius = (int) (zm.getWidth() * .25);
|
final int radius = (int) (zm.getWidth() * .25);
|
||||||
|
|
||||||
Animator<Shape, Double> ani = new Animator<Shape, Double>() {
|
Future<Shape> future = Animations.play(
|
||||||
@Override
|
s, runtime, easing, (e) -> {
|
||||||
public double easing( double t ) {
|
double rad = Math.toRadians(Constants.interpolate(0, 360, e));
|
||||||
return easing.applyAsDouble(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double interpolator( double e ) {
|
|
||||||
return Constants.interpolate(0, 360, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void applicator( Shape s, Double angle ) {
|
|
||||||
double rad = Math.toRadians(angle);
|
|
||||||
s.moveTo(midX + radius * Math.cos(rad), midY + radius * Math.sin(rad));
|
s.moveTo(midX + radius * Math.cos(rad), midY + radius * Math.sin(rad));
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
Future<Shape> future = Animations.animate(s, runtime, ani);
|
|
||||||
assertNotNull(future);
|
assertNotNull(future);
|
||||||
try {
|
try {
|
||||||
assertEquals(s, future.get());
|
assertEquals(s, future.get());
|
||||||
@@ -146,7 +133,7 @@ class AnimationsTest {
|
|||||||
private void _animateRotate( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
private void _animateRotate( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
||||||
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
||||||
s.rotateTo(0);
|
s.rotateTo(0);
|
||||||
Future<Shape> future = Animations.animate(
|
Future<Shape> future = Animations.play(
|
||||||
s, runtime,
|
s, runtime,
|
||||||
easing,
|
easing,
|
||||||
( e ) -> s.rotateTo(Constants.interpolate(0, 720, e))
|
( 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 ) {
|
private void _animateColor( Shape s, Color to, int runtime, DoubleUnaryOperator easing ) {
|
||||||
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
||||||
final Color from = s.getFillColor();
|
final Color from = s.getFillColor();
|
||||||
Future<Shape> future = Animations.animate(
|
Future<Shape> future = Animations.play(
|
||||||
s, runtime,
|
s, runtime,
|
||||||
easing,
|
easing,
|
||||||
( e ) -> Color.interpolate(from, to, e),
|
( 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||