diff --git a/.gitignore b/.gitignore
index 1140a67..2317eb1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@ hs_err_pid*
Thumbs.db
.gradle
+local.properties
**/build/
!src/**/build/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0d5f66..b7291dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,9 +10,13 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Dokumentation erweitert.
- Caching-Mechanismen in Klasse `util.Cache` ausgelagert.
- `util.io.ImageLoader` und `util.io.FontLoader` verwenden `Cache`.
+- `mouseWheelMoved` Eventhandler für Mausrad.
+- `DrawingLayer.imageRotate(...)` Methoden, um Bilder um ihr Zentrum gedreht zu zeichnen.
## Changed
- Die Methoden in `Validator` erwarten nun als zweiten Parameter den Namen des Parameters, der geprüft wird.
+- `DrawingLayer.image(...)` mit Größenänderung umbenannt zu `imageScale(...)`.
+- Klassen in `schule.ngb.zm.util.io` werfen nun nur eine Warnung ohne Stack-Trace, wenn die Ressource nicht gefunden werden konnte.
## Fixed
- `Constants.choice(int...)` und `Constants.choice(double...)` wiederhergestellt.
diff --git a/build.gradle b/build.gradle
index 1d18b7b..9fbcbfb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,11 @@
plugins {
id 'idea'
id 'java-library'
+ id 'org.hidetake.ssh' version '2.10.1'
}
group 'schule.ngb'
-version '0.0.34-SNAPSHOT'
+version '0.0.35-SNAPSHOT'
java {
withSourcesJar()
@@ -19,6 +20,15 @@ repositories {
mavenCentral()
}
+remotes {
+ uberspace {
+ host = 'westphal.uberspace.de'
+ user = 'ngb'
+ identity = file("${System.properties['user.home']}/.ssh/uberspace_rsa")
+ knownHosts = allowAnyHosts
+ }
+}
+
dependencies {
runtimeOnly 'com.googlecode.soundlibs:jlayer:1.0.1.4'
runtimeOnly 'com.googlecode.soundlibs:tritonus-share:0.3.7.4'
@@ -92,6 +102,52 @@ task buildDocs {
dependsOn 'mkdocs'
}
+task zipSite(type: Zip) {
+ group "documentation"
+ description "Create zip archives for documentations"
+
+ dependsOn 'mkdocs'
+
+ from fileTree("${buildDir}/docs/site")
+ exclude '*.py'
+ exclude '__pycache__'
+ archiveName 'site.zip'
+ destinationDir(file("${buildDir}/docs"))
+}
+
+task zipJavadoc(type: Zip) {
+ group "documentation"
+ description "Create zip archives for javadoc"
+
+ dependsOn 'javadoc'
+
+ from fileTree("${buildDir}/docs/javadoc")
+ archiveName 'javadoc.zip'
+ destinationDir(file("${buildDir}/docs"))
+}
+
+task uploadDocs {
+ group "documentation"
+ description "Run all documentation tasks and upload artifacts to zeichenmaschine.xyz"
+
+ dependsOn 'zipSite'
+ dependsOn 'zipJavadoc'
+
+ doLast {
+ ssh.run {
+ session(remotes.uberspace) {
+ execute 'rm -rf /var/www/virtual/ngb/zeichenmaschine.xyz/*', ignoreError: true
+
+ put from: "${buildDir}/docs/site.zip", into: '/var/www/virtual/ngb/zeichenmaschine.xyz', ignoreError: true
+ execute 'unzip -o -q /var/www/virtual/ngb/zeichenmaschine.xyz/site.zip -d /var/www/virtual/ngb/zeichenmaschine.xyz'
+
+ put from: "${buildDir}/docs/javadoc.zip", into: '/var/www/virtual/ngb/zeichenmaschine.xyz', ignoreError: true
+ execute 'unzip -o -q /var/www/virtual/ngb/zeichenmaschine.xyz/javadoc.zip -d /var/www/virtual/ngb/zeichenmaschine.xyz/docs'
+ }
+ }
+ }
+}
+
test {
useJUnitPlatform()
}
diff --git a/docs/einfuehrung.md b/docs/index.md
similarity index 97%
rename from docs/einfuehrung.md
rename to docs/index.md
index 3bb4985..3618386 100644
--- a/docs/einfuehrung.md
+++ b/docs/index.md
@@ -30,9 +30,9 @@ leichter nutzbar machen.
## Dokumentation
-* [Schnellstart](quickstart.md)
+* [Schnellstart](schnellstart.md)
* [Installation](installation.md)
-* {{ javadoc_link() }}
+* [Javadoc]({{ javadoc() }})
## Über die Zeichenmaschine
@@ -68,3 +68,4 @@ Alternativen, von deren Nutzung gar nicht abgeraten werden soll.
Klassen, Methoden und Variablen verwendet. Ausnahme sind einzelne Klassen,
die im Zusammnehang mit dem Namen der Bibliothek stehen, wie die
Hauptklasse `Zeichenmaschine`.
+
diff --git a/docs/installation.md b/docs/installation.md
index e1e6afd..e947273 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -2,10 +2,9 @@
Um ein einfaches Projekt mit der **Zeichenmaschine** aufzusetzen ist nicht mehr
nötig, als
-die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/release/latest)
+die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/releases/latest)
herunterzuladen und dem *Classpath* des Projekts hinzuzufügen. Beschreibungen
-für
-verschiedene Entwicklungsumgebungen sind hier aufgelistet.
+für verschiedene Entwicklungsumgebungen sind hier aufgelistet.
## Integration in Entwicklungsumgebungen
diff --git a/docs/quickstart.md b/docs/schnellstart.md
similarity index 92%
rename from docs/quickstart.md
rename to docs/schnellstart.md
index 3c6ec33..7281d04 100644
--- a/docs/quickstart.md
+++ b/docs/schnellstart.md
@@ -23,7 +23,7 @@ werden.
!!! note "main Methode"
- Bei einigen Entwicklungsumgebungen muss noch eine `main` Methode erstellt
+ Bei einigen Entwicklungsumgebungen muss noch eine `main` Methode erstellt
werden, um die Zeichenmaschine zu starten:
```java
@@ -40,11 +40,11 @@ an.
```java
public class Shapes extends Zeichenmaschine {
-
+
public Shapes() {
super(800, 800, "Shapes");
}
-
+
}
```
@@ -57,7 +57,7 @@ erstellt und in einem Fenster mit dem Titel „Shapes“ angezeigt.
### Formen zeichnen
-Eine Zeichenmaschine hat verschiedene Möglichkeiten, Inhalte in das
+Eine Zeichenmaschine hat verschiedene Möglichkeiten Inhalte in das
Zeichenfenster zu zeichnen. Um ein einfaches statisches Bild zu erzeugen,
überschreiben wir die {{ jdl("schule.ngb.zm.Zeichenmaschine", "draw()",
c=False) }} Methode.
@@ -66,20 +66,20 @@ c=False) }} Methode.
```java
public class Shapes extends Zeichenmaschine {
-
+
public Shapes() {
super(800, 800, "Shapes");
}
-
+
@Override
public void draw() {
background.setColor(BLUE);
-
+
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
drawing.circle(400, 400, 100);
}
-
+
}
```
@@ -101,19 +101,19 @@ die Zeichenfläche vor dem ersten Zeichnen vor.
```java
public class Shapes extends Zeichenmaschine {
-
+
public Shapes() {
super(800, 800, "Shapes");
}
-
+
@Override
public void setup() {
background.setColor(BLUE);
-
+
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
}
-
+
@Override
public void draw() {
for( int i = 0; i < 10; i++ ) {
@@ -124,7 +124,7 @@ die Zeichenfläche vor dem ersten Zeichnen vor.
);
}
}
-
+
}
```
@@ -137,16 +137,16 @@ Im Beispiel setzen wir nun die Grundeinstellungen in der `setup()` Methode. In
!!! tip ""
- Mit {{ jdm("Constants", "canvasWidth") }} und
- {{ jdm("Constants", "canvasHeight") }} kannst du in der Zeichenmaschine
+ Mit {{ jdm("Constants", "canvasWidth") }} und
+ {{ jdm("Constants", "canvasHeight") }} kannst du in der Zeichenmaschine
auf die aktuelle Größe der Zeichenfläche zugreifen.
- {{ jdm("Constants", "random(int,int)") }} erzeugt eine Zufallszahl
+ {{ jdm("Constants", "random(int,int)") }} erzeugt eine Zufallszahl
innerhalb der angegebenen Grenzen.
## Interaktionen mit der Maus: Whack-a-mole
Mit der Zeichenmaschine lassen sich Interaktionen mit der Maus leicht umsetzen.
-Wor wollen das Beispielprogramm zu einem
+Wir wollen das Beispielprogramm zu einem
[Whac-A-Mole](https://de.wikipedia.org/wiki/Whac-A-Mole) Spiel erweitern.
Auf der Zeichenfläche wird nur noch ein gelber Kreis an einer zufälligen Stelle
@@ -169,28 +169,28 @@ zufälligen Werte initialisiert werden. Diese benutzen wir, um die `draw
private int moleX;
private int moleY;
-
+
public Shapes() {
super(800, 800, "Shapes");
}
-
+
@Override
public void setup() {
background.setColor(BLUE);
-
+
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
moleX = random(50, canvasWidth - 50);
moleY = random(50, canvasHeight - 50);
}
-
+
@Override
public void draw() {
drawing.clear();
drawing.circle(moleX, moleY, moleRadius);
}
-
+
}
```
@@ -216,10 +216,10 @@ Pythagoras leicht selber berechnen. Die Zeichenmaschine kann uns diese Arbeit
aber auch abnehmen und stellt eine Methode dafür bereit
({{ jdm("Constants", "distance(double,double,double,double)") }}).
-Um auf einen Mausklick zu reagieren, ergänzen wir die
+Um auf einen Mausklick zu reagieren, ergänzen wir die
{{ jdm("Zeichenmaschine", "mouseClicked()") }} Methode:
-```
+```java
@Override
public void mouseClicked() {
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
@@ -240,22 +240,22 @@ public void mouseClicked() {
private int moleX;
private int moleY;
-
+
public Shapes() {
super(800, 800, "Shapes");
}
-
+
@Override
public void setup() {
background.setColor(BLUE);
-
+
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
moleX = random(50, canvasWidth - 50);
moleY = random(50, canvasHeight - 50);
}
-
+
@Override
public void draw() {
drawing.clear();
@@ -270,13 +270,13 @@ public void mouseClicked() {
redraw();
}
}
-
+
}
```
!!! warning ""
- Der Aufruf von {{ jdm("Zeichenmaschine", "redraw()") }} zeichnet
+ Der Aufruf von {{ jdm("Zeichenmaschine", "redraw()") }} zeichnet
die Zeichenfläche neu, indem die `draw()` Methode erneut aufgerufen wird.
Du solltest `draw()` niemals direkt aufrufen.
@@ -290,8 +290,8 @@ angeklickt wird.
## Ein paar Details zur Zeichenmaschine
Die _Zeichenmaschine_ wurde stark von der kreativen Programmierumgebung
-[Processing](https://processing.org) inspiriert. Wenn Du Processing schon
-kennst, dann werden Dir einige der Konzepte der _Zeichenmaschine_ schon bekannt
+[Processing](https://processing.org) inspiriert. Wenn du Processing schon
+kennst, dann werden dir einige der Konzepte der _Zeichenmaschine_ schon bekannt
vorkommen.
### Farben
@@ -300,7 +300,7 @@ Farben können auf verschiedene Weisen angegeben werden. Unser Beispiel nutzt
bisher zwei Arten:
1. Die einfachste Möglichkeit sind die _Farbkonstanten_
- wie {{ jdm('Constants', 'BLUE') }} oder {{ jdm('Constants', 'RED') }}. Im
+ wie {{ jdm('Constants', 'BLUE') }} oder {{ jdm('Constants', 'RED') }}. Im
Beispiel setzen wir den Hintergrund auf die Farbe `BLUE`.
2. Farben werden häufig im RGB-Farbraum definiert. Dazu wird jeweils der Rot-,
Grün- und Blauanteil der Farbe als Wert zwischen 0 und 255 angegeben. Im
@@ -341,15 +341,15 @@ auf.
Erstellst Du eine _Zeichenmaschine_ (beziehungsweise ein Objekt einer
-Unterklasse), dann wird zuerst die {{ jdm('Zeichenmaschine', 'setup()') }}
-Methode ausgeführt. Danach folgt einmalig die
+Unterklasse), dann wird zuerst die {{ jdm('Zeichenmaschine', 'setup()') }}
+Methode ausgeführt. Danach folgt einmalig die
{{ jdm('Zeichenmaschine', 'draw()') }} Methode und dann endet das Hauptprogramm.
Die Eingaben der Maus werden in einem parallelen Ablauf (einem _Thread_)
-abgefangen und daraufhin die {{ jdm('Zeichenmaschine', 'mouseClicked()') }}
+abgefangen und daraufhin die {{ jdm('Zeichenmaschine', 'mouseClicked()') }}
Methode aufgerufen. In unserem Programm prüfen wir, ob mit der Maus
-innerhalb des Kreises geklickt wurde und rufen dann
-{{ jdm('Zeichenmaschine', 'redraw()') }} auf, woraufhin ein weiteres Mal
+innerhalb des Kreises geklickt wurde und rufen dann
+{{ jdm('Zeichenmaschine', 'redraw()') }} auf, woraufhin ein weiteres Mal
`draw()` ausgeführt wird.
In _Processing_ wird dies der "statische" Modus genannt, weil das Programm nur
@@ -392,14 +392,14 @@ Sekunde aufgerufen wird. Normalerweise passiert dies genau 60-mal pro Sekunde.
### Lebenszeit eines Kreises
Jeder Kreis soll drei Sekunden zu sehen sein. Daher fügen wir eine neue
-Objektvariable namens `moleTime` ein, die zunächst auf drei steht. Da wir auch
-Bruchteile von Skeunden abziehen wollen, wählen wir als Datentyp `double`:
+Objektvariable namens `moleTime` ein, die zunächst auf Drei steht. Da wir auch
+Bruchteile von Sekunden abziehen wollen, wählen wir als Datentyp `double`:
```Java
private double moleTime=3.0;
```
-Der Parameter `delta`, der `update()` Methode ist der Zeitraum in Sekunden, seit
+Der Parameter `delta` der `update()` Methode ist der Zeitraum in Sekunden seit
dem letzten Frame. Subtrahieren wir diesen Wert bei jedem Frame von `moleTime`,
wird der Wert immer kleiner. Dann müssen wir nur noch prüfen, ob er kleiner Null
ist und in dem Fall den Kreis auf eine neue Position springen lassen.
@@ -415,52 +415,52 @@ drawing.circle(moleX,moleY,moleRadius*(moleTime/3.0));
```Java
import schule.ngb.zm.Zeichenmaschine;
-
+
public class Shapes extends Zeichenmaschine {
-
+
private int moleRadius = 20;
-
+
private int moleX;
-
+
private int moleY;
-
+
private double moleTime;
-
+
public Shapes() {
super(800, 800, "Shapes");
}
-
+
@Override
public void setup() {
background.setColor(BLUE);
-
+
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
-
+
randomizeMole();
}
-
+
private void randomizeMole() {
moleX = random(moleRadius*2, canvasWidth - moleRadius*2);
moleY = random(moleRadius*2, canvasHeight - moleRadius*2);
moleTime = 3.0;
}
-
+
@Override
public void update( double delta ) {
moleTime -= delta;
-
+
if( moleTime <= 0 ) {
randomizeMole();
}
}
-
+
@Override
public void draw() {
drawing.clear();
drawing.circle(moleX, moleY, moleRadius * (moleTime / 3.0));
}
-
+
@Override
public void mouseClicked() {
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
@@ -468,11 +468,11 @@ drawing.circle(moleX,moleY,moleRadius*(moleTime/3.0));
redraw();
}
}
-
+
public static void main( String[] args ) {
new Shapes();
}
-
+
}
```
@@ -483,21 +483,21 @@ drawing.circle(moleX,moleY,moleRadius*(moleTime/3.0));
!!! tip ""
Der Maulwurf muss mittlerweile an drei verschiedenen Stellen im Programm
- auf eine zufällige Position gesetzt werden (am Anfang, wenn er angeklickt
+ auf eine zufällige Position gesetzt werden (am Anfang, wenn er angeklickt
wurde und wenn die drei Sekunden abgelaufen sind). Daher wurde das Versetzen
- in eine eigene Methode `randomizeMole()` ausgelagert.
+ in eine eigene Methode `randomizeMole()` ausgelagert.
### Punktezähler
-Zum Schluss wollen wir noch bei jedem Treffer mit der Maus die Punkte Zählen und
+Zum Schluss wollen wir noch bei jedem Treffer mit der Maus die Punkte zählen und
als Text auf die Zeichenfläche schreiben.
Dazu ergänzen wir eine weitere Objektvariable `score`, die in `mouseClicked()`
erhöht wird, falls der Kreis getroffen wurde.
-Um den Punktestand anzuzeigen ergänzen wir in `draw()` einen Aufruf von
-{{ jdl('layers.DrawingLayer', 'text(java.lang.String,double,double,schule.ngb.zm.Options.Direction)') }}
-mit dem Inhalt von `score` und den Koordinaten, an denen der Text gezeigt
+Um den Punktestand anzuzeigen ergänzen wir in `draw()` einen Aufruf von
+{{ jdl('layers.DrawingLayer', 'text(java.lang.String,double,double,schule.ngb.zm.Options.Direction)') }}
+mit dem Inhalt von `score` und den Koordinaten, an denen der Text gezeigt
werden soll.
Die Zeichenebene zeichnet im Moment alle Formen und Text ausgehend vom Zentrum
der Form. Damit der Text 10 Pixel vom Rand entfernt links oben angezeigt wird,
@@ -518,59 +518,59 @@ drawing.setFillColor(BLACK);
```Java
import schule.ngb.zm.Zeichenmaschine;
-
+
public class Shapes extends Zeichenmaschine {
-
+
private int moleRadius = 20;
-
+
private int moleX;
-
+
private int moleY;
-
+
private double moleTime;
-
+
private int score = 0;
-
+
public Shapes() {
super(800, 800, "Shapes");
}
-
+
@Override
public void setup() {
background.setColor(BLUE);
-
+
drawing.noStroke();
drawing.setFontSize(24);
-
+
randomizeMole();
}
-
+
private void randomizeMole() {
moleX = random(moleRadius*2, canvasWidth - moleRadius*2);
moleY = random(moleRadius*2, canvasHeight - moleRadius*2);
moleTime = 3.0;
}
-
+
@Override
public void update( double delta ) {
moleTime -= delta;
-
+
if( moleTime <= 0 ) {
randomizeMole();
}
}
-
+
@Override
public void draw() {
drawing.clear();
-
+
drawing.setFillColor(255, 223, 34);
drawing.circle(moleX, moleY, moleRadius * (moleTime / 3.0));
-
+
drawing.setFillColor(BLACK);
drawing.text("Punkte: " + score, 10, 10, NORTHWEST);
}
-
+
@Override
public void mouseClicked() {
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
@@ -579,28 +579,28 @@ drawing.setFillColor(BLACK);
redraw();
}
}
-
+
public static void main( String[] args ) {
new Shapes();
}
-
+
}
```
## Wie es weitergehen kann
-In diesem Schnellstart-Tutorial hast Du die Grundlagen der _Zeichenmaschine_
-gelernt. Um weiterzumachen, kannst Du versuchen, das Whack-a-mole Spiel um diese
+In diesem Schnellstart-Tutorial hast du die Grundlagen der _Zeichenmaschine_
+gelernt. Um weiterzumachen, kannst du versuchen, das Whack-a-mole Spiel um diese
Funktionen zu erweitern:
- Mehrere "Maulwürfe" gleichzeitig.
- Unterschiedliche Zeiten pro Maulwurf.
-Wenn Du mehr über die Möglichkeiten lernen möchtest, die Dir die
-_Zeichenmaschine_ bereitstellt, kannst Du Dir die weiteren Tutorials in dieser
+Wenn du mehr über die Möglichkeiten lernen möchtest, die dir die
+_Zeichenmaschine_ bereitstellt, kannst du dir die weiteren Tutorials in dieser
Dokumentation ansehen. Ein guter Startpunkt ist das
[Aquarium](tutorials/aquarium/aquarium1.md).
-Viele verschiedene Beispiele, ohne detailliertes Tutorial, findest Du in der
+Viele verschiedene Beispiele, ohne detailliertes Tutorial, findest du in der
Kategorie Beispiele und auf GitHub im Repository
[jneug/zeichenmaschine-examples](https://github.com/jneug/zeichenmaschine-examples).
diff --git a/mkdocs.yml b/mkdocs.yml
index 95d001e..ba698f1 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -8,7 +8,7 @@ site_dir: build/docs/site
theme:
name: material
- custom_dir: docs/home_override/
+ # custom_dir: docs/home_override/
language: de
logo: assets/icon_64.png
favicon: assets/icon_32.png
@@ -37,7 +37,7 @@ extra_css:
- assets/zmstyles.css
nav:
- - Einführung: einfuehrung.md
+ - Einführung: index.md
- Schnellstart: schnellstart.md
- Installation: installation.md
- Tutorials:
diff --git a/src/main/java/schule/ngb/zm/BasicDrawable.java b/src/main/java/schule/ngb/zm/BasicDrawable.java
index 120bfc4..3d403cd 100644
--- a/src/main/java/schule/ngb/zm/BasicDrawable.java
+++ b/src/main/java/schule/ngb/zm/BasicDrawable.java
@@ -34,6 +34,11 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
*/
protected Options.StrokeType strokeType = SOLID;
+ /**
+ * Die Art der Kantenverbindungen von Linien.
+ */
+ protected Options.StrokeJoin strokeJoin = MITER;
+
/**
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
@@ -53,6 +58,7 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
*/
protected MultipleGradientPaint fill = null;
+ // TODO: Add TexturePaint fill (https://docs.oracle.com/javase/8/docs//api/java/awt/TexturePaint.html)
// Implementierung Drawable Interface
@@ -154,7 +160,7 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
@Override
public Stroke getStroke() {
if( stroke == null ) {
- stroke = Strokeable.createStroke(strokeType, strokeWeight);
+ stroke = Strokeable.createStroke(strokeType, strokeWeight, strokeJoin);
}
return stroke;
}
@@ -191,4 +197,15 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
this.stroke = null;
}
+ @Override
+ public Options.StrokeJoin getStrokeJoin() {
+ return strokeJoin;
+ }
+
+ @Override
+ public void setStrokeJoin( Options.StrokeJoin join ) {
+ strokeJoin = join;
+ this.stroke = null;
+ }
+
}
diff --git a/src/main/java/schule/ngb/zm/Color.java b/src/main/java/schule/ngb/zm/Color.java
index 4c4217a..68a9880 100644
--- a/src/main/java/schule/ngb/zm/Color.java
+++ b/src/main/java/schule/ngb/zm/Color.java
@@ -576,6 +576,18 @@ public class Color implements Paint {
}
}
+ public double compare( Color color ) {
+ double maxDist = 764.8333151739665;
+
+ // see: https://www.compuphase.com/cmetric.htm
+ long rmean = (getRed() + color.getRed()) / 2;
+ long r = getRed() - color.getRed();
+ long g = getGreen() - color.getGreen();
+ long b = getBlue() - color.getBlue();
+
+ return 1.0 - (Math.sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8)) / maxDist);
+ }
+
/**
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
*
diff --git a/src/main/java/schule/ngb/zm/Constants.java b/src/main/java/schule/ngb/zm/Constants.java
index 8a9cc54..6aef3c4 100644
--- a/src/main/java/schule/ngb/zm/Constants.java
+++ b/src/main/java/schule/ngb/zm/Constants.java
@@ -11,6 +11,7 @@ import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Random;
import java.util.function.DoubleUnaryOperator;
@@ -67,7 +68,7 @@ public class Constants {
/**
* Patchversion der Zeichenmaschine.
*/
- public static final int APP_VERSION_REV = 34;
+ public static final int APP_VERSION_REV = 35;
/**
* Version der Zeichenmaschine als Text-String.
@@ -170,6 +171,21 @@ public class Constants {
*/
public static final Options.StrokeType DOTTED = Options.StrokeType.DOTTED;
+ /**
+ * Option für abgerundete Kantenverbindungen von Konturen und Linien.
+ */
+ public static final Options.StrokeJoin ROUND = Options.StrokeJoin.ROUND;
+
+ /**
+ * Option für abgeschnittene Kantenverbindungen von Konturen und Linien.
+ */
+ public static final Options.StrokeJoin BEVEL = Options.StrokeJoin.BEVEL;
+
+ /**
+ * Option für eckige Kantenverbindungen von Konturen und Linien.
+ */
+ public static final Options.StrokeJoin MITER = Options.StrokeJoin.MITER;
+
/**
* Option für Pfeile mit Strichen als Kopf.
*/
@@ -1268,7 +1284,7 @@ public class Constants {
*
* @return Die {@code Random}-Instanz.
*/
- private static Random getRandom() {
+ public static Random getRandom() {
if( random == null ) {
random = new Random();
}
@@ -1389,26 +1405,6 @@ public class Constants {
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.
*
@@ -1571,6 +1567,18 @@ public class Constants {
return valueList.toArray(values);
}
+ /**
+ * Bringt die Werte im Array in eine zufällige Reihenfolge.
+ *
+ * @param values Ein Array mit Werte, die gemischt werden sollen.
+ * @param Datentyp der Elemente.
+ * @return Das Array in zufälliger Reihenfolge.
+ */
+ public static final List shuffle( List values ) {
+ Collections.shuffle(values, random);
+ return values;
+ }
+
/**
* Geteilte {@code Noise}-Instanz zur Erzeugung von Perlin-Noise.
*/
diff --git a/src/main/java/schule/ngb/zm/Options.java b/src/main/java/schule/ngb/zm/Options.java
index d170a87..ac719d8 100644
--- a/src/main/java/schule/ngb/zm/Options.java
+++ b/src/main/java/schule/ngb/zm/Options.java
@@ -1,5 +1,6 @@
package schule.ngb.zm;
+import java.awt.BasicStroke;
import java.awt.geom.Arc2D;
/**
@@ -31,6 +32,36 @@ public final class Options {
DOTTED
}
+ /**
+ * Linienstile für Konturlinien.
+ */
+ public enum StrokeJoin {
+
+ /**
+ * Abgerundete Verbindungen.
+ */
+ ROUND(BasicStroke.JOIN_ROUND),
+
+ /**
+ * Abgeschnittene Verbindungen.
+ */
+ BEVEL(BasicStroke.JOIN_BEVEL),
+
+ /**
+ * Eckige Verbindungen.
+ */
+ MITER(BasicStroke.JOIN_MITER);
+
+ /**
+ * Der entsprechende Wert der Konstanten in {@link java.awt}
+ */
+ public final int awt_type;
+
+ StrokeJoin( int type ) {
+ awt_type = type;
+ }
+ }
+
/**
* Stile für Pfeilspitzen.
*/
diff --git a/src/main/java/schule/ngb/zm/Strokeable.java b/src/main/java/schule/ngb/zm/Strokeable.java
index 22b4921..f9b1cc8 100644
--- a/src/main/java/schule/ngb/zm/Strokeable.java
+++ b/src/main/java/schule/ngb/zm/Strokeable.java
@@ -174,7 +174,7 @@ public interface Strokeable extends Drawable {
* @param weight Die Dicke der Konturlinie.
*/
default void setStrokeWeight( double weight ) {
- setStroke(createStroke(getStrokeType(), weight));
+ setStroke(createStroke(getStrokeType(), weight, getStrokeJoin()));
}
/**
@@ -193,7 +193,26 @@ public interface Strokeable extends Drawable {
* @see Options.StrokeType
*/
default void setStrokeType( Options.StrokeType type ) {
- setStroke(createStroke(type, getStrokeWeight()));
+ setStroke(createStroke(type, getStrokeWeight(), getStrokeJoin()));
+ }
+
+ /**
+ * Gibt die Art der Konturverbindungen zurück.
+ *
+ * @return Die aktuelle Art der Konturverbindungen.
+ * @see Options.StrokeJoin
+ */
+ Options.StrokeJoin getStrokeJoin();
+
+ /**
+ * Setzt den Typ der Konturverbindungen. Erlaubte Werte sind {@link Constants#ROUND},
+ * {@link Constants#MITER} und {@link Constants#BEVEL}.
+ *
+ * @param join Eine der möglichen Konturverbindungen.
+ * @see Options.StrokeJoin
+ */
+ default void setStrokeJoin( Options.StrokeJoin join ) {
+ setStroke(createStroke(getStrokeType(), getStrokeWeight(), join));
}
/**
@@ -205,26 +224,26 @@ public interface Strokeable extends Drawable {
* @param strokeWeight
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
*/
- static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight ) {
+ static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight, Options.StrokeJoin strokeJoin ) {
switch( strokeType ) {
case DOTTED:
return new BasicStroke(
(float) strokeWeight,
BasicStroke.CAP_ROUND,
- BasicStroke.JOIN_ROUND,
+ strokeJoin.awt_type,
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
case DASHED:
return new BasicStroke(
(float) strokeWeight,
BasicStroke.CAP_ROUND,
- BasicStroke.JOIN_ROUND,
+ strokeJoin.awt_type,
10.0f, new float[]{5.0f}, 0.0f);
case SOLID:
default:
return new BasicStroke(
(float) strokeWeight,
BasicStroke.CAP_ROUND,
- BasicStroke.JOIN_ROUND);
+ strokeJoin.awt_type);
}
}
diff --git a/src/main/java/schule/ngb/zm/Zeichenfenster.java b/src/main/java/schule/ngb/zm/Zeichenfenster.java
index 74109e2..c12f681 100644
--- a/src/main/java/schule/ngb/zm/Zeichenfenster.java
+++ b/src/main/java/schule/ngb/zm/Zeichenfenster.java
@@ -28,7 +28,7 @@ public class Zeichenfenster extends JFrame {
/**
* Setzt das Look and Feel auf den Standard des Systems.
*
- * Sollte einmalig vor erstellen des erstyen Programmfensters aufgerufen
+ * Sollte einmalig vor Erstellen des ersten Programmfensters aufgerufen
* werden.
*/
public static final void setLookAndFeel() {
diff --git a/src/main/java/schule/ngb/zm/Zeichenmaschine.java b/src/main/java/schule/ngb/zm/Zeichenmaschine.java
index 312dcc5..e550697 100644
--- a/src/main/java/schule/ngb/zm/Zeichenmaschine.java
+++ b/src/main/java/schule/ngb/zm/Zeichenmaschine.java
@@ -94,7 +94,7 @@ public class Zeichenmaschine extends Constants {
*/
private boolean running;
- private boolean terminateImediately = false;
+ private boolean terminateImmediately = false;
/**
* Ob die ZM nach dem nächsten Frame pausiert werden soll.
@@ -545,7 +545,7 @@ public class Zeichenmaschine extends Constants {
if( running ) {
running = false;
- terminateImediately = true;
+ terminateImmediately = true;
quitAfterShutdown = true;
mainThread.interrupt();
} else {
@@ -769,6 +769,23 @@ public class Zeichenmaschine extends Constants {
framesPerSecond = framesPerSecondInternal;
}
+ /**
+ * Erstellt aus dem aktuellen Inhalt der {@link Zeichenleinwand} ein neues
+ * {@link BufferedImage}.
+ */
+ public final BufferedImage getImage() {
+ BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight());
+
+ Graphics2D g = img.createGraphics();
+ // TODO: Transparente Hintergründe beim Speichern von png erlauben
+ g.setColor(DEFAULT_BACKGROUND.getJavaColor());
+ g.fillRect(0, 0, img.getWidth(), img.getHeight());
+ canvas.draw(g);
+ g.dispose();
+
+ return img;
+ }
+
/**
* Speichert den aktuellen Inhalt der {@link Zeichenleinwand} in einer
* Bilddatei auf der Festplatte. Zur Auswahl der Zieldatei wird dem Nutzer
@@ -794,24 +811,15 @@ public class Zeichenmaschine extends Constants {
* Bilddatei im angegebenen Dateipfad auf der Festplatte.
*/
public final void saveImage( String filepath ) {
- BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight());
-
- Graphics2D g = img.createGraphics();
- // TODO: Transparente Hintergründe beim Speichern von png erlauben
- g.setColor(DEFAULT_BACKGROUND.getJavaColor());
- g.fillRect(0, 0, img.getWidth(), img.getHeight());
- canvas.draw(g);
- g.dispose();
-
try {
- ImageLoader.saveImage(img, new File(filepath), true);
+ ImageLoader.saveImage(getImage(), new File(filepath), true);
} catch( IOException ex ) {
ex.printStackTrace();
}
}
/**
- * Erstellt eine Momentanaufnahme des aktuellen Inhalts der
+ * Erstellt eine Momentaufnahme des aktuellen Inhalts der
* {@link Zeichenleinwand} und erstellt daraus eine
* {@link ImageLayer Bildebene}. Die Ebene wird automatisch der
* {@link Zeichenleinwand} vor dem {@link #background} hinzugefügt.
@@ -1225,7 +1233,7 @@ public class Zeichenmaschine extends Constants {
}
public void mouseWheelMoved( MouseEvent e ) {
- mouseWheelMoved();
+ mouseMoved();
}
public void mouseWheelMoved() {
@@ -1399,7 +1407,7 @@ public class Zeichenmaschine extends Constants {
if( Thread.interrupted() ) {
running = false;
- terminateImediately = true;
+ terminateImmediately = true;
break;
}
}
@@ -1455,7 +1463,7 @@ public class Zeichenmaschine extends Constants {
}
state = Options.AppState.STOPPED;
// Shutdown the updateThread
- while( !terminateImediately && updateThreadExecutor.isRunning() ) {
+ while( !terminateImmediately && updateThreadExecutor.isRunning() ) {
Thread.yield();
}
updateThreadExecutor.shutdownNow();
@@ -1562,7 +1570,7 @@ public class Zeichenmaschine extends Constants {
@Override
public void mouseWheelMoved( MouseWheelEvent e ) {
- enqueueEvent(e);
+ // enqueueEvent(e);
}
}
diff --git a/src/main/java/schule/ngb/zm/anim/Animation.java b/src/main/java/schule/ngb/zm/anim/Animation.java
index 8a01557..5b80915 100644
--- a/src/main/java/schule/ngb/zm/anim/Animation.java
+++ b/src/main/java/schule/ngb/zm/anim/Animation.java
@@ -50,7 +50,7 @@ public abstract class Animation extends Constants implements Updatable {
}
public void setEasing( DoubleUnaryOperator pEasing ) {
- this.easing = pEasing;
+ this.easing = Validator.requireNotNull(pEasing, "easing");
}
public abstract T getAnimationTarget();
@@ -61,7 +61,7 @@ public abstract class Animation extends Constants implements Updatable {
running = true;
finished = false;
animate(easing.applyAsDouble(0.0));
- initializeEventDispatcher().dispatchEvent("start", this);
+ dispatchEvent("start");
}
public final void stop() {
@@ -70,7 +70,7 @@ public abstract class Animation extends Constants implements Updatable {
animate(easing.applyAsDouble((double) elapsedTime / (double) runtime));
this.finish();
finished = true;
- initializeEventDispatcher().dispatchEvent("stop", this);
+ dispatchEvent("stop");
}
public void initialize() {
@@ -100,10 +100,9 @@ public abstract class Animation extends Constants implements Updatable {
double t = (double) elapsedTime / (double) runtime;
if( t >= 1.0 ) {
- running = false;
stop();
} else {
- animate(easing.applyAsDouble(t));
+ animate(getEasing().applyAsDouble(t));
}
}
@@ -118,7 +117,7 @@ public abstract class Animation extends Constants implements Updatable {
* e = Constants.limit(e, 0, 1);
*
*
- * @param e Fortschritt der Animation nachdem die Easingfunktion angewandt
+ * @param e Fortschritt der Animation, nachdem die Easing-Funktion angewandt
* wurde.
*/
public abstract void animate( double e );
@@ -134,6 +133,12 @@ public abstract class Animation extends Constants implements Updatable {
return eventDispatcher;
}
+ private void dispatchEvent( String type ) {
+ if( eventDispatcher != null ) {
+ eventDispatcher.dispatchEvent(type, this);
+ }
+ }
+
public void addListener( AnimationListener listener ) {
initializeEventDispatcher().addListener(listener);
}
diff --git a/src/main/java/schule/ngb/zm/anim/AnimationFacade.java b/src/main/java/schule/ngb/zm/anim/AnimationFacade.java
index 3bc8aa7..4b499a9 100644
--- a/src/main/java/schule/ngb/zm/anim/AnimationFacade.java
+++ b/src/main/java/schule/ngb/zm/anim/AnimationFacade.java
@@ -4,9 +4,15 @@ import schule.ngb.zm.util.Validator;
import java.util.function.DoubleUnaryOperator;
+/**
+ * Eine Wrapper Animation, um die Werte einer anderen Animation (Laufzeit, Easing) zu überschrieben,
+ * ohne die Werte der Originalanimation zu verändern.
+ *
+ * @param Art des Animierten Objektes.
+ */
public class AnimationFacade extends Animation {
- private Animation anim;
+ private final Animation anim;
public AnimationFacade( Animation anim, int runtime, DoubleUnaryOperator easing ) {
super(runtime, easing);
diff --git a/src/main/java/schule/ngb/zm/anim/AnimationGroup.java b/src/main/java/schule/ngb/zm/anim/AnimationGroup.java
index c1de074..41beb2e 100644
--- a/src/main/java/schule/ngb/zm/anim/AnimationGroup.java
+++ b/src/main/java/schule/ngb/zm/anim/AnimationGroup.java
@@ -1,22 +1,28 @@
package schule.ngb.zm.anim;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.DoubleUnaryOperator;
+// TODO: (ngb) Maybe use AnimationFacade to override runtime?
@SuppressWarnings( "unused" )
public class AnimationGroup extends Animation {
- List> anims;
+ private final List> anims;
- private boolean overrideEasing = false;
+ private final boolean overrideEasing;
private int overrideRuntime = -1;
- private int lag = 0;
+ private final int lag;
private int active = 0;
+ public AnimationGroup( Animation... anims ) {
+ this(0, -1, null, Arrays.asList(anims));
+ }
+
public AnimationGroup( Collection> anims ) {
this(0, -1, null, anims);
}
@@ -42,6 +48,8 @@ public class AnimationGroup extends Animation {
if( easing != null ) {
this.easing = easing;
overrideEasing = true;
+ } else {
+ overrideEasing = false;
}
if( runtime > 0 ) {
@@ -64,52 +72,110 @@ public class AnimationGroup extends Animation {
return anim.getAnimationTarget();
}
}
- return anims.get(anims.size() - 1).getAnimationTarget();
+ if( this.finished ) {
+ return anims.get(anims.size() - 1).getAnimationTarget();
+ } else {
+ return anims.get(0).getAnimationTarget();
+ }
}
@Override
- public void update( double delta ) {
- elapsedTime += (int) (delta * 1000);
- // Animation is done. Stop all Animations.
- if( elapsedTime > runtime ) {
- for( int i = 0; i < anims.size(); i++ ) {
- if( anims.get(i).isActive() ) {
- anims.get(i).elapsedTime = anims.get(i).runtime;
- anims.get(i).stop();
- }
+ public DoubleUnaryOperator getEasing() {
+ for( Animation anim : anims ) {
+ if( anim.isActive() ) {
+ return anim.getEasing();
}
- running = false;
- this.stop();
}
-
- while( active < anims.size() && elapsedTime >= active * lag ) {
- anims.get(active).start();
- active += 1;
+ if( this.finished ) {
+ return anims.get(anims.size() - 1).getEasing();
+ } else {
+ return anims.get(0).getEasing();
}
+ }
- for( int i = 0; i < active; i++ ) {
- double t = 0.0;
- if( overrideRuntime > 0 ) {
- t = (double) (elapsedTime - i*lag) / (double) overrideRuntime;
- } else {
- t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
- }
+// @Override
+// public void update( double delta ) {
+// elapsedTime += (int) (delta * 1000);
+//
+// // Animation is done. Stop all Animations.
+// if( elapsedTime > runtime ) {
+// for( int i = 0; i < anims.size(); i++ ) {
+// if( anims.get(i).isActive() ) {
+// anims.get(i).elapsedTime = anims.get(i).runtime;
+// anims.get(i).stop();
+// }
+// }
+// elapsedTime = runtime;
+// 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 {
+// t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
+// }
+//
+// if( t >= 1.0 ) {
+// anims.get(i).elapsedTime = anims.get(i).runtime;
+// anims.get(i).stop();
+// } else {
+// double e = overrideEasing ?
+// easing.applyAsDouble(t) :
+// anims.get(i).easing.applyAsDouble(t);
+//
+// anims.get(i).animate(e);
+// }
+// }
+// }
- if( t >= 1.0 ) {
- anims.get(i).elapsedTime = anims.get(i).runtime;
- anims.get(i).stop();
- } else {
- double e = overrideEasing ?
- easing.applyAsDouble(t) :
- anims.get(i).easing.applyAsDouble(t);
- anims.get(i).animate(e);
+ @Override
+ public void finish() {
+ for( Animation anim : anims ) {
+ if( anim.isActive() ) {
+ anim.elapsedTime = anim.runtime;
+ anim.stop();
}
}
}
@Override
public void animate( double e ) {
+ while( active < anims.size() && elapsedTime >= active * lag ) {
+ anims.get(active).start();
+ active += 1;
+ }
+
+ for( int i = 0; i < active; i++ ) {
+ Animation curAnim = anims.get(i);
+
+ double curRuntime = curAnim.getRuntime();
+ if( overrideRuntime > 0 ) {
+ curRuntime = overrideRuntime;
+ }
+
+ double t = (double) (elapsedTime - i * lag) / (double) curRuntime;
+ if( t >= 1.0 ) {
+ curAnim.elapsedTime = curAnim.getRuntime();
+ curAnim.stop();
+ } else {
+ e = overrideEasing ?
+ easing.applyAsDouble(t) :
+ curAnim.easing.applyAsDouble(t);
+
+ curAnim.elapsedTime = (elapsedTime - i * lag);
+ curAnim.animate(e);
+ }
+ }
+
}
}
diff --git a/src/main/java/schule/ngb/zm/anim/AnimationSequence.java b/src/main/java/schule/ngb/zm/anim/AnimationSequence.java
new file mode 100644
index 0000000..66949d8
--- /dev/null
+++ b/src/main/java/schule/ngb/zm/anim/AnimationSequence.java
@@ -0,0 +1,144 @@
+package schule.ngb.zm.anim;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.DoubleUnaryOperator;
+
+/**
+ * Führt eine Liste von Animationen nacheinander aus. Jede Animation startet direkt nachdem die
+ * davor geendet ist. Optional kann zwischen dem Ende einer und dem Start der nächsten Animation
+ * ein
+ * lag eingefügt werden.
+ *
+ * @param Die Art des animierten Objektes.
+ */
+@SuppressWarnings( "unused" )
+public class AnimationSequence extends Animation {
+
+ private final List> anims;
+
+ private final int lag;
+
+ private int currentAnimationIndex = -1, currentStart = -1, nextStart = -1;
+
+ @SafeVarargs
+ public AnimationSequence( Animation... anims ) {
+ this(0, Arrays.asList(anims));
+ }
+
+ public AnimationSequence( Collection> anims ) {
+ this(0, anims);
+ }
+
+ public AnimationSequence( int lag, Collection> anims ) {
+ super(Easing::linear);
+
+ this.anims = List.copyOf(anims);
+ this.lag = lag;
+
+ this.runtime = (anims.size() - 1) * lag + anims.stream().mapToInt(Animation::getRuntime).sum();
+ }
+
+ @Override
+ public T getAnimationTarget() {
+ for( Animation anim : anims ) {
+ if( anim.isActive() ) {
+ return anim.getAnimationTarget();
+ }
+ }
+ if( this.finished ) {
+ return anims.get(anims.size() - 1).getAnimationTarget();
+ } else {
+ return anims.get(0).getAnimationTarget();
+ }
+ }
+
+ @Override
+ public DoubleUnaryOperator getEasing() {
+ for( Animation anim : anims ) {
+ if( anim.isActive() ) {
+ return anim.getEasing();
+ }
+ }
+ if( this.finished ) {
+ return anims.get(anims.size() - 1).getEasing();
+ } else {
+ return anims.get(0).getEasing();
+ }
+ }
+
+// @Override
+// public void update( double delta ) {
+// elapsedTime += (int) (delta * 1000);
+//
+// // Animation is done. Stop all Animations.
+// if( elapsedTime > runtime ) {
+// for( int i = 0; i < anims.size(); i++ ) {
+// if( anims.get(i).isActive() ) {
+// anims.get(i).elapsedTime = anims.get(i).runtime;
+// anims.get(i).stop();
+// }
+// }
+// elapsedTime = runtime;
+// running = false;
+// this.stop();
+// }
+//
+// Animation curAnim = null;
+// if( elapsedTime > nextStart ) {
+// currentAnimation += 1;
+// curAnim = anims.get(currentAnimation);
+// currentStart = nextStart;
+// nextStart += lag + curAnim.getRuntime();
+// curAnim.start();
+// } else {
+// curAnim = anims.get(currentAnimation);
+// }
+//
+// // Calculate delta for current animation
+// double t = (double) (elapsedTime - currentStart) / (double) curAnim.getRuntime();
+// if( t >= 1.0 ) {
+// curAnim.elapsedTime = curAnim.runtime;
+// curAnim.stop();
+// } else {
+// curAnim.animate(curAnim.easing.applyAsDouble(t));
+// }
+// }
+
+
+ @Override
+ public void finish() {
+ for( Animation anim : anims ) {
+ if( anim.isActive() ) {
+ anim.elapsedTime = anim.runtime;
+ anim.stop();
+ }
+ }
+ }
+
+ @Override
+ public void animate( double e ) {
+ Animation curAnim = null;
+ if( running && elapsedTime > nextStart ) {
+ currentAnimationIndex += 1;
+ curAnim = anims.get(currentAnimationIndex);
+ currentStart = nextStart;
+ nextStart += lag + curAnim.getRuntime();
+ curAnim.start();
+ } else {
+ curAnim = anims.get(currentAnimationIndex);
+ }
+
+ // Calculate delta for current animation
+ double t = (double) (elapsedTime - currentStart) / (double) curAnim.getRuntime();
+ if( t >= 1.0 ) {
+ curAnim.elapsedTime = curAnim.runtime;
+ curAnim.stop();
+ } else {
+ curAnim.elapsedTime = (elapsedTime - currentStart);
+ curAnim.animate(curAnim.easing.applyAsDouble(t));
+ }
+ }
+
+}
diff --git a/src/main/java/schule/ngb/zm/anim/CircleAnimation.java b/src/main/java/schule/ngb/zm/anim/CircleAnimation.java
index 4c0cdb8..320a0ed 100644
--- a/src/main/java/schule/ngb/zm/anim/CircleAnimation.java
+++ b/src/main/java/schule/ngb/zm/anim/CircleAnimation.java
@@ -7,33 +7,87 @@ import schule.ngb.zm.shapes.Shape;
import java.util.function.DoubleUnaryOperator;
+/**
+ * Animates the {@code target} in a circular motion centered at (cx, cy).
+ */
public class CircleAnimation extends Animation {
- private Shape object;
+ private final Shape target;
- private double centerx, centery, radius, startangle;
+ private final double centerX, centerY, rotateTo;
- public CircleAnimation( Shape target, double cx, double cy, int runtime, DoubleUnaryOperator easing ) {
+ private double rotationRadius, startAngle;
+
+ private final boolean rotateRight;
+
+ public CircleAnimation( Shape target, double cx, double cy ) {
+ this(target, cx, cy, 360, true, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, double rotateTo ) {
+ this(target, cx, cy, rotateTo, true, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight ) {
+ this(target, cx, cy, 360, rotateRight, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, double rotateTo, boolean rotateRight ) {
+ this(target, cx, cy, rotateTo, rotateRight, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, int runtime ) {
+ this(target, cx, cy, 360, true, runtime, DEFAULT_EASING);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight, int runtime ) {
+ this(target, cx, cy, 360, rotateRight, runtime, DEFAULT_EASING);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, DoubleUnaryOperator easing ) {
+ this(target, cx, cy, 360, true, DEFAULT_ANIM_RUNTIME, easing);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight, DoubleUnaryOperator easing ) {
+ this(target, cx, cy, 360, rotateRight, DEFAULT_ANIM_RUNTIME, easing);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, double rotateTo, int runtime, DoubleUnaryOperator easing ) {
+ this(target, cx, cy, rotateTo, true, runtime, easing);
+ }
+
+ public CircleAnimation( Shape target, double cx, double cy, double rotateTo, boolean rotateRight, 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();
+ this.target = target;
+ this.centerX = cx;
+ this.centerY = cy;
+ this.rotateTo = Constants.radians(Constants.limit(rotateTo, 0, 360));
+ this.rotateRight = rotateRight;
+ }
+
+ @Override
+ public void initialize() {
+ Vector vec = new Vector(target.getX(), target.getY()).sub(centerX, centerY);
+ startAngle = vec.heading();
+ rotationRadius = vec.length();
}
@Override
public Shape getAnimationTarget() {
- return object;
+ return target;
}
@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);
+ double angle = startAngle;
+ if( rotateRight ) {
+ angle += Constants.interpolate(0, rotateTo, e);
+ } else {
+ angle -= Constants.interpolate(0, rotateTo, e);
+ }
+ double x = centerX + rotationRadius * Constants.cos(angle);
+ double y = centerY + rotationRadius * Constants.sin(angle);
+ target.moveTo(x, y);
}
}
diff --git a/src/main/java/schule/ngb/zm/anim/ContinousAnimation.java b/src/main/java/schule/ngb/zm/anim/ContinousAnimation.java
index 924adbe..b96b430 100644
--- a/src/main/java/schule/ngb/zm/anim/ContinousAnimation.java
+++ b/src/main/java/schule/ngb/zm/anim/ContinousAnimation.java
@@ -8,9 +8,9 @@ public class ContinousAnimation extends Animation {
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.
+ * 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;
@@ -29,7 +29,7 @@ public class ContinousAnimation extends Animation {
}
private ContinousAnimation( Animation baseAnimation, int lag, boolean easeInOnly ) {
- super(baseAnimation.getRuntime(), baseAnimation.getEasing());
+ super(baseAnimation.getRuntime() + lag, baseAnimation.getEasing());
this.baseAnimation = baseAnimation;
this.lag = lag;
this.easeInOnly = easeInOnly;
@@ -40,35 +40,80 @@ public class ContinousAnimation extends Animation {
return baseAnimation.getAnimationTarget();
}
+ @Override
+ public int getRuntime() {
+ return Integer.MAX_VALUE;
+ }
+
+ // @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 finish() {
+ baseAnimation.elapsedTime = baseAnimation.getRuntime();
+ baseAnimation.stop();
+ }
+
+ @Override
+ public void initialize() {
+ baseAnimation.start();
+ }
+
+ @Override
+ public void setRuntime( int pRuntime ) {
+ baseAnimation.setRuntime(pRuntime);
+ runtime = pRuntime + lag;
+ }
+
@Override
public void update( double delta ) {
- elapsedTime += (int) (delta * 1000);
- if( elapsedTime >= runtime + lag ) {
- elapsedTime %= (runtime + lag);
+ int currentRuntime = elapsedTime + (int) (delta * 1000);
+ if( currentRuntime >= runtime + lag ) {
+ elapsedTime = currentRuntime % (runtime + lag);
if( easeInOnly && easing != null ) {
- easing = null;
+ easing = Easing.linear();
// 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);
- }
+ super.update(delta);
}
@Override
public void animate( double e ) {
+// double t = (double) elapsedTime / (double) runtime;
+// if( t >= 1.0 ) {
+// t = 1.0;
+// }
+ baseAnimation.elapsedTime = elapsedTime;
baseAnimation.animate(e);
+ m = (e - lastEase) / (delta * 1000 / (asDouble(runtime)));
+ lastEase = e;
}
}
diff --git a/src/main/java/schule/ngb/zm/anim/FadeAnimation.java b/src/main/java/schule/ngb/zm/anim/FadeAnimation.java
index 4895eda..31a6132 100644
--- a/src/main/java/schule/ngb/zm/anim/FadeAnimation.java
+++ b/src/main/java/schule/ngb/zm/anim/FadeAnimation.java
@@ -13,32 +13,51 @@ public class FadeAnimation extends Animation {
public static final int FADE_OUT = 0;
- private Shape object;
+ private final Shape target;
+
+ private final int targetAlpha;
private Color fill, stroke;
- private int fillAlpha, strokeAlpha, tAlpha;
+ private int fillAlpha, strokeAlpha;
- public FadeAnimation( Shape object, int alpha, int runtime, DoubleUnaryOperator easing ) {
+ public FadeAnimation( Shape target, int targetAlpha ) {
+ this(target, targetAlpha, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
+ }
+
+ public FadeAnimation( Shape target, int targetAlpha, int runtime ) {
+ this(target, targetAlpha, runtime, DEFAULT_EASING);
+ }
+
+ public FadeAnimation( Shape target, int runtime, DoubleUnaryOperator easing ) {
+ this(target, 0, runtime, easing);
+ }
+
+ public FadeAnimation( Shape target, int targetAlpha, int runtime, DoubleUnaryOperator easing ) {
super(runtime, easing);
- this.object = object;
- fill = object.getFillColor();
+ this.target = target;
+ this.targetAlpha = targetAlpha;
+ }
+
+ @Override
+ public void initialize() {
+ fill = target.getFillColor();
fillAlpha = fill.getAlpha();
- stroke = object.getStrokeColor();
+ stroke = target.getStrokeColor();
strokeAlpha = stroke.getAlpha();
- tAlpha = alpha;
+
}
@Override
public Shape getAnimationTarget() {
- return object;
+ return target;
}
@Override
public void animate( double e ) {
- object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e)));
- object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e)));
+ target.setFillColor(fill, (int) Constants.interpolate(fillAlpha, targetAlpha, e));
+ target.setStrokeColor(stroke, (int) Constants.interpolate(strokeAlpha, targetAlpha, e));
}
}
diff --git a/src/main/java/schule/ngb/zm/anim/FillAnimation.java b/src/main/java/schule/ngb/zm/anim/FillAnimation.java
index 92c51d5..c87b125 100644
--- a/src/main/java/schule/ngb/zm/anim/FillAnimation.java
+++ b/src/main/java/schule/ngb/zm/anim/FillAnimation.java
@@ -1,23 +1,28 @@
package schule.ngb.zm.anim;
import schule.ngb.zm.Color;
-import schule.ngb.zm.Constants;
import schule.ngb.zm.shapes.Shape;
import java.util.function.DoubleUnaryOperator;
public class FillAnimation extends Animation {
- private Shape object;
+ private final Shape object;
- private Color oFill, tFill;
+ private Color originFill;
- public FillAnimation( Shape object, Color newFill, int runtime, DoubleUnaryOperator easing ) {
+ private final Color targetFill;
+
+ public FillAnimation( Shape target, Color newFill, int runtime, DoubleUnaryOperator easing ) {
super(runtime, easing);
- this.object = object;
- oFill = object.getFillColor();
- tFill = newFill;
+ this.object = target;
+ targetFill = newFill;
+ }
+
+ @Override
+ public void initialize() {
+ originFill = object.getFillColor();
}
@Override
@@ -27,7 +32,7 @@ public class FillAnimation extends Animation {
@Override
public void animate( double e ) {
- object.setFillColor(Color.interpolate(oFill, tFill, e));
+ object.setFillColor(Color.interpolate(originFill, targetFill, e));
}
}
diff --git a/src/main/java/schule/ngb/zm/anim/MoveAnimation.java b/src/main/java/schule/ngb/zm/anim/MoveAnimation.java
index 3a826ce..bc90c5c 100644
--- a/src/main/java/schule/ngb/zm/anim/MoveAnimation.java
+++ b/src/main/java/schule/ngb/zm/anim/MoveAnimation.java
@@ -1,28 +1,31 @@
package schule.ngb.zm.anim;
-import schule.ngb.zm.Color;
import schule.ngb.zm.Constants;
-import schule.ngb.zm.shapes.Circle;
-import schule.ngb.zm.shapes.Ellipse;
-import schule.ngb.zm.shapes.Rectangle;
import schule.ngb.zm.shapes.Shape;
import java.util.function.DoubleUnaryOperator;
public class MoveAnimation extends Animation {
- private Shape object;
+ private final Shape object;
- private double oX, oY, tX, tY;
+ private final double targetX, targetY;
- public MoveAnimation( Shape object, double x, double y, int runtime, DoubleUnaryOperator easing ) {
+ private double originX, originY;
+
+
+ public MoveAnimation( Shape target, double targetX, double targetY, int runtime, DoubleUnaryOperator easing ) {
super(runtime, easing);
- this.object = object;
- oX = object.getX();
- oY = object.getY();
- tX = x;
- tY = y;
+ this.object = target;
+ this.targetX = targetX;
+ this.targetY = targetY;
+ }
+
+ @Override
+ public void initialize() {
+ originX = object.getX();
+ originY = object.getY();
}
@Override
@@ -32,8 +35,8 @@ public class MoveAnimation extends Animation {
@Override
public void animate( double e ) {
- object.setX(Constants.interpolate(oX, tX, e));
- object.setY(Constants.interpolate(oY, tY, e));
+ object.setX(Constants.interpolate(originX, targetX, e));
+ object.setY(Constants.interpolate(originY, targetY, e));
}
}
diff --git a/src/main/java/schule/ngb/zm/layers/DrawingLayer.java b/src/main/java/schule/ngb/zm/layers/DrawingLayer.java
index 9c0f4fe..085d8f7 100644
--- a/src/main/java/schule/ngb/zm/layers/DrawingLayer.java
+++ b/src/main/java/schule/ngb/zm/layers/DrawingLayer.java
@@ -404,6 +404,11 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
return shapeDelegate.getStrokeType();
}
+ @Override
+ public Options.StrokeJoin getStrokeJoin() {
+ return shapeDelegate.getStrokeJoin();
+ }
+
/**
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
* {@link #DOTTED} und {@link #SOLID}.
@@ -980,7 +985,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @see ImageLoader#loadImage(String)
*/
public void image( String imageSource, double x, double y ) {
- image(ImageLoader.loadImage(imageSource), x, y, 1.0, shapeDelegate.getAnchor());
+ imageScale(ImageLoader.loadImage(imageSource), x, y, 1.0, shapeDelegate.getAnchor());
}
/**
@@ -997,7 +1002,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @see ImageLoader#loadImage(String)
*/
public void image( String imageSource, double x, double y, Options.Direction anchor ) {
- image(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
+ imageScale(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
}
/**
@@ -1005,8 +1010,9 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
* skaliert.
*
- * Siehe {@link #image(Image, double, double, double, Options.Direction)}
- * für mehr Details.
+ * Siehe
+ * {@link #imageScale(Image, double, double, double, Options.Direction)} für
+ * mehr Details.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
@@ -1014,8 +1020,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @param scale Der Skalierungsfaktor des Bildes.
* @see ImageLoader#loadImage(String)
*/
- public void image( String imageSource, double x, double y, double scale ) {
- image(ImageLoader.loadImage(imageSource), x, y, scale, shapeDelegate.getAnchor());
+ public void imageScale( String imageSource, double x, double y, double scale ) {
+ imageScale(ImageLoader.loadImage(imageSource), x, y, scale, shapeDelegate.getAnchor());
}
/**
@@ -1023,8 +1029,9 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
* skaliert und der angegebene Ankerpunkt verwendet.
*
- * Siehe {@link #image(Image, double, double, double, Options.Direction)}
- * für mehr Details.
+ * Siehe
+ * {@link #imageScale(Image, double, double, double, Options.Direction)} für
+ * mehr Details.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
@@ -1033,8 +1040,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @param anchor Der Ankerpunkt.
* @see ImageLoader#loadImage(String)
*/
- public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
- image(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
+ public void imageScale( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
+ imageScale(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
}
/**
@@ -1046,23 +1053,24 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @param y y-Koordinate des Ankerpunktes.
*/
public void image( Image image, double x, double y ) {
- image(image, x, y, 1.0, shapeDelegate.getAnchor());
+ imageScale(image, x, y, 1.0, shapeDelegate.getAnchor());
}
/**
* Zeichnet das angegebene Bild an den angegebenen Koordinaten auf die
* Zeichenebene. Das Bild wird um den angegebenen Faktor skaliert.
*
- * Siehe {@link #image(Image, double, double, double, Options.Direction)}
- * für mehr Details.
+ * Siehe
+ * {@link #imageScale(Image, double, double, double, Options.Direction)} für
+ * mehr Details.
*
* @param image Das vorher geladene Bild.
* @param x x-Koordinate des Ankerpunktes.
* @param y y-Koordinate des Ankerpunktes.
* @param scale Der Skalierungsfaktor des Bildes.
*/
- public void image( Image image, double x, double y, double scale ) {
- image(image, x, y, scale, shapeDelegate.getAnchor());
+ public void imageScale( Image image, double x, double y, double scale ) {
+ imageScale(image, x, y, scale, shapeDelegate.getAnchor());
}
/**
@@ -1077,8 +1085,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* Das Seitenverhältnis wird immer beibehalten.
*
* Soll das Bild innerhalb eines vorgegebenen Rechtecks liegen, sollte
- * {@link #image(Image, double, double, double, double, Options.Direction)}
- * verwendet werden.
+ * {@link #imageScale(Image, double, double, double, double,
+ * Options.Direction)} verwendet werden.
*
* @param image Das vorher geladene Bild.
* @param x x-Koordinate des Ankerpunktes.
@@ -1086,7 +1094,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @param scale Der Skalierungsfaktor des Bildes.
* @param anchor Der Ankerpunkt.
*/
- public void image( Image image, double x, double y, double scale, Options.Direction anchor ) {
+ public void imageScale( 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;
@@ -1095,7 +1103,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
}*/
double neww = image.getWidth(null) * scale;
double newh = image.getHeight(null) * scale;
- image(image, x, y, neww, newh, anchor);
+ imageScale(image, x, y, neww, newh, anchor);
}
/**
@@ -1103,8 +1111,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* Koordinaten in der angegebenen Größe auf die Zeichenebene.
*
* Siehe
- * {@link #image(Image, double, double, double, double, Options.Direction)}
- * für mehr Details.
+ * {@link #imageScale(Image, double, double, double, double,
+ * Options.Direction)} für mehr Details.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
@@ -1113,8 +1121,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
* @see ImageLoader#loadImage(String)
*/
- public void image( String imageSource, double x, double y, double width, double height ) {
- image(ImageLoader.loadImage(imageSource), x, y, width, height, shapeDelegate.getAnchor());
+ public void imageScale( String imageSource, double x, double y, double width, double height ) {
+ imageScale(ImageLoader.loadImage(imageSource), x, y, width, height, shapeDelegate.getAnchor());
}
/**
@@ -1123,8 +1131,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* angegebene Ankerpunkt verwendet.
*
* Siehe
- * {@link #image(Image, double, double, double, double, Options.Direction)}
- * für mehr Details.
+ * {@link #imageScale(Image, double, double, double, double,
+ * Options.Direction)} für mehr Details.
*
* @param imageSource Die Bildquelle.
* @param x x-Koordinate des Ankerpunktes.
@@ -1134,8 +1142,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @param anchor Der Ankerpunkt.
* @see ImageLoader#loadImage(String)
*/
- public void image( String imageSource, double x, double y, double width, double height, Options.Direction anchor ) {
- image(ImageLoader.loadImage(imageSource), x, y, width, height, anchor);
+ public void imageScale( String imageSource, double x, double y, double width, double height, Options.Direction anchor ) {
+ imageScale(ImageLoader.loadImage(imageSource), x, y, width, height, anchor);
}
/**
@@ -1143,8 +1151,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* angegebenen Größe auf die Zeichenebene.
*
* Siehe
- * {@link #image(Image, double, double, double, double, Options.Direction)}
- * für mehr Details.
+ * {@link #imageScale(Image, double, double, double, double,
+ * Options.Direction)} für mehr Details.
*
* @param image Ein Bild-Objekt.
* @param x x-Koordinate des Ankerpunktes.
@@ -1152,8 +1160,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @param width Breite des Bildes auf der Zeichenebene oder 0.
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
*/
- public void image( Image image, double x, double y, double width, double height ) {
- image(image, x, y, width, height, shapeDelegate.getAnchor());
+ public void imageScale( Image image, double x, double y, double width, double height ) {
+ imageScale(image, x, y, width, height, shapeDelegate.getAnchor());
}
/**
@@ -1171,7 +1179,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
*
* Soll die Bildgröße unter Beachtung der Abmessungen um einen Faktor
* verändert werden, sollte
- * {@link #image(Image, double, double, double, Options.Direction)}
+ * {@link #imageScale(Image, double, double, double, Options.Direction)}
* verwendet werden.
*
* @param image Ein Bild-Objekt.
@@ -1181,17 +1189,163 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
* @param anchor Der Ankerpunkt.
*/
- public void image( Image image, double x, double y, double width, double height, Options.Direction anchor ) {
+ public void imageScale( Image image, double x, double y, double width, double height, Options.Direction anchor ) {
+ imageRotateAndScale(image, x, y, 0, width, height, anchor);
+ }
+
+ /**
+ * Zeichnet das Bild von der angegebenen Bildquelle an den angegebenen
+ * Koordinaten mit der angegebenen Drehung auf die Zeichenebene.
+ *
+ * Das Bild wird um seinen Mittelpunkt als Rotationszentrum gedreht.
+ *
+ * @param imageSource Die Bildquelle.
+ * @param x x-Koordinate des Ankerpunktes.
+ * @param y y-Koordinate des Ankerpunktes.
+ * @param angle Winkel in Grad.
+ */
+ public void imageRotate( String imageSource, double x, double y, double angle ) {
+ imageRotate(ImageLoader.loadImage(imageSource), x, y, angle, shapeDelegate.getAnchor());
+ }
+
+ /**
+ * Zeichnet das Bild von der angegebenen Bildquelle an den angegebenen
+ * Koordinaten mit der angegebenen Drehung auf die Zeichenebene. Der
+ * angegebene Ankerpunkt wird verwendet.
+ *
+ * Das Bild wird um seinen Mittelpunkt als Rotationszentrum gedreht.
+ *
+ * @param imageSource Die Bildquelle.
+ * @param x x-Koordinate des Ankerpunktes.
+ * @param y y-Koordinate des Ankerpunktes.
+ * @param angle Winkel in Grad.
+ * @param anchor Der Ankerpunkt.
+ */
+ public void imageRotate( String imageSource, double x, double y, double angle, Options.Direction anchor ) {
+ imageRotate(ImageLoader.loadImage(imageSource), x, y, angle, anchor);
+ }
+
+ /**
+ * Zeichnet das angegebene Bild an den angegebenen Koordinaten mit der
+ * angegebenen Drehung auf die Zeichenebene.
+ *
+ * Das Bild wird um seinen Mittelpunkt als Rotationszentrum gedreht.
+ *
+ * @param image Ein Bild-Objekt.
+ * @param x x-Koordinate des Ankerpunktes.
+ * @param y y-Koordinate des Ankerpunktes.
+ * @param angle Winkel in Grad.
+ */
+ public void imageRotate( Image image, double x, double y, double angle ) {
+ imageRotateAndScale(image, x, y, angle, image.getWidth(null), image.getHeight(null), shapeDelegate.getAnchor());
+ }
+
+ /**
+ * Zeichnet das angegebene Bild an den angegebenen Koordinaten mit der
+ * angegebenen Drehung auf die Zeichenebene. Der angegebene Ankerpunkt wird
+ * verwendet.
+ *
+ * Das Bild wird um seinen Mittelpunkt als Rotationszentrum gedreht.
+ *
+ * @param image Ein Bild-Objekt.
+ * @param x x-Koordinate des Ankerpunktes.
+ * @param y y-Koordinate des Ankerpunktes.
+ * @param angle Winkel in Grad.
+ * @param anchor Der Ankerpunkt.
+ */
+ public void imageRotate( Image image, double x, double y, double angle, Options.Direction anchor ) {
+ imageRotateAndScale(image, x, y, angle, image.getWidth(null), image.getHeight(null), anchor);
+ }
+
+ /**
+ * Zeichnet das Bild von der angegebenen Bildquelle an den angegebenen
+ * Koordinaten mit der angegebenen Drehung in der angegebenen Größe auf die
+ * Zeichenebene.
+ *
+ * @param imageSource Die Bildquelle.
+ * @param x x-Koordinate des Ankerpunktes.
+ * @param y y-Koordinate des Ankerpunktes.
+ * @param angle Winkel in Grad.
+ * @param width Breite des Bildes auf der Zeichenebene oder 0.
+ * @param height Höhe des Bildes auf der Zeichenebene oder 0.
+ * @see #imageRotate(String, double, double, double)
+ * @see #imageScale(Image, double, double, double)
+ */
+ public void imageRotateAndScale( String imageSource, double x, double y, double angle, double width, double height ) {
+ imageRotateAndScale(ImageLoader.loadImage(imageSource), x, y, angle, width, height, shapeDelegate.getAnchor());
+ }
+
+ /**
+ * Zeichnet das Bild von der angegebenen Bildquelle an den angegebenen
+ * Koordinaten mit der angegebenen Drehung in der angegebenen Größe auf die
+ * Zeichenebene. Der angegebene Ankerpunkt wird verwendet.
+ *
+ * @param imageSource Die Bildquelle.
+ * @param x x-Koordinate des Ankerpunktes.
+ * @param y y-Koordinate des Ankerpunktes.
+ * @param angle Winkel in Grad.
+ * @param width Breite des Bildes auf der Zeichenebene oder 0.
+ * @param height Höhe des Bildes auf der Zeichenebene oder 0.
+ * @param anchor Der Ankerpunkt.
+ * @see #imageRotate(String, double, double, double)
+ * @see #imageScale(Image, double, double, double)
+ */
+ public void imageRotateAndScale( String imageSource, double x, double y, double angle, double width, double height, Options.Direction anchor ) {
+ imageRotateAndScale(ImageLoader.loadImage(imageSource), x, y, angle, width, height, anchor);
+ }
+
+ /**
+ * Zeichnet das angegebene Bild an den angegebenen Koordinaten mit der
+ * angegebenen Drehung in der angegebenen Größe auf die Zeichenebene. Der
+ * angegebene Ankerpunkt wird verwendet.
+ *
+ * @param image Ein Bild-Objekt.
+ * @param x x-Koordinate des Ankerpunktes.
+ * @param y y-Koordinate des Ankerpunktes.
+ * @param angle Winkel in Grad.
+ * @param width Breite des Bildes auf der Zeichenebene oder 0.
+ * @param height Höhe des Bildes auf der Zeichenebene oder 0.
+ * @see #imageRotate(String, double, double, double)
+ * @see #imageScale(Image, double, double, double)
+ */
+ public void imageRotateAndScale( Image image, double x, double y, double angle, double width, double height ) {
+ imageRotateAndScale(image, x, y, angle, width, height, shapeDelegate.getAnchor());
+ }
+
+ /**
+ * Zeichnet das angegebene Bild an den angegebenen Koordinaten mit der
+ * angegebenen Drehung in der angegebenen Größe auf die Zeichenebene. Der
+ * angegebene Ankerpunkt wird verwendet.
+ *
+ * @param image Ein Bild-Objekt.
+ * @param x x-Koordinate des Ankerpunktes.
+ * @param y y-Koordinate des Ankerpunktes.
+ * @param angle Winkel in Grad.
+ * @param width Breite des Bildes auf der Zeichenebene oder 0.
+ * @param height Höhe des Bildes auf der Zeichenebene oder 0.
+ * @param anchor Der Ankerpunkt.
+ * @see #imageRotate(String, double, double, double)
+ * @see #imageScale(Image, double, double, double)
+ */
+ public void imageRotateAndScale( Image image, double x, double y, double angle, double width, double height, Options.Direction anchor ) {
// TODO: Use Validator or at least LOG a message if image == null?
if( image != null ) {
+ AffineTransform orig = drawing.getTransform();
+
+ int imgWidth = image.getWidth(null);
+ int imgHeight = image.getHeight(null);
+
if( width == 0 ) {
- width = (height / image.getHeight(null)) * image.getWidth(null);
+ width = (height / imgHeight) * imgWidth;
} else if( height == 0 ) {
- height = (width / image.getWidth(null)) * image.getHeight(null);
+ height = (width / imgWidth) * imgHeight;
}
Point2D.Double anchorPoint = getOriginPoint(x, y, width, height, anchor);
+ drawing.rotate(Math.toRadians(angle), anchorPoint.x + width / 2, anchorPoint.y + height / 2);
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) width, (int) height, null);
+
+ drawing.setTransform(orig);
}
}
diff --git a/src/main/java/schule/ngb/zm/layers/ShapesLayer.java b/src/main/java/schule/ngb/zm/layers/ShapesLayer.java
index 5791edc..78d170d 100644
--- a/src/main/java/schule/ngb/zm/layers/ShapesLayer.java
+++ b/src/main/java/schule/ngb/zm/layers/ShapesLayer.java
@@ -1,6 +1,7 @@
package schule.ngb.zm.layers;
import schule.ngb.zm.Layer;
+import schule.ngb.zm.Updatable;
import schule.ngb.zm.anim.Animation;
import schule.ngb.zm.anim.AnimationFacade;
import schule.ngb.zm.anim.Easing;
@@ -24,20 +25,26 @@ public class ShapesLayer extends Layer {
*/
protected boolean clearBeforeDraw = true;
- private final List shapes;
+ protected boolean updateShapes = true;
+
+ protected final List shapes;
private final List> animations;
+ private final List updatables;
+
public ShapesLayer() {
super();
shapes = new LinkedList<>();
animations = new LinkedList<>();
+ updatables = new LinkedList<>();
}
public ShapesLayer( int width, int height ) {
super(width, height);
shapes = new LinkedList<>();
animations = new LinkedList<>();
+ updatables = new LinkedList<>();
}
public Shape getShape( int index ) {
@@ -70,12 +77,24 @@ public class ShapesLayer extends Layer {
public void add( Shape... shapes ) {
synchronized( this.shapes ) {
Collections.addAll(this.shapes, shapes);
+
+ for( Shape s : shapes ) {
+ if( Updatable.class.isInstance(s) ) {
+ updatables.add((Updatable) s);
+ }
+ }
}
}
public void add( Collection shapes ) {
synchronized( this.shapes ) {
this.shapes.addAll(shapes);
+
+ for( Shape s : shapes ) {
+ if( Updatable.class.isInstance(s) ) {
+ updatables.add((Updatable) s);
+ }
+ }
}
}
@@ -139,6 +158,27 @@ public class ShapesLayer extends Layer {
@Override
public void update( double delta ) {
+ if( updateShapes ) {
+ synchronized( shapes ) {
+ List uit = List.copyOf(updatables);
+ for( Updatable u : uit ) {
+ if( u.isActive() ) {
+ u.update(delta);
+ }
+ }
+ }
+
+ /*
+ Iterator uit = updatables.iterator();
+ while( uit.hasNext() ) {
+ Updatable u = uit.next();
+ if( u.isActive() ) {
+ u.update(delta);
+ }
+ }
+ */
+ }
+
Iterator> it = animations.iterator();
while( it.hasNext() ) {
Animation extends Shape> anim = it.next();
diff --git a/src/main/java/schule/ngb/zm/layers/TurtleLayer.java b/src/main/java/schule/ngb/zm/layers/TurtleLayer.java
index 21cc366..cdb4851 100644
--- a/src/main/java/schule/ngb/zm/layers/TurtleLayer.java
+++ b/src/main/java/schule/ngb/zm/layers/TurtleLayer.java
@@ -230,6 +230,11 @@ public class TurtleLayer extends Layer implements Strokeable, Fillable {
return mainTurtle.getStrokeType();
}
+ @Override
+ public Options.StrokeJoin getStrokeJoin() {
+ return mainTurtle.getStrokeJoin();
+ }
+
@Override
public void setStrokeType( Options.StrokeType type ) {
mainTurtle.setStrokeType(type);
diff --git a/src/main/java/schule/ngb/zm/shapes/Curve.java b/src/main/java/schule/ngb/zm/shapes/Curve.java
index b7357f4..54d3762 100644
--- a/src/main/java/schule/ngb/zm/shapes/Curve.java
+++ b/src/main/java/schule/ngb/zm/shapes/Curve.java
@@ -1,5 +1,9 @@
package schule.ngb.zm.shapes;
+import schule.ngb.zm.Options;
+
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
@@ -170,6 +174,30 @@ public class Curve extends Shape {
move(dx, dy);
}
+ @Override
+ public void draw( Graphics2D graphics, AffineTransform transform ) {
+ if( !visible ) {
+ return;
+ }
+
+ AffineTransform orig = graphics.getTransform();
+ if( transform != null ) {
+ //graphics.transform(transform);
+ }
+
+ graphics.translate(x, y);
+ graphics.rotate(Math.toRadians(rotation));
+
+ java.awt.Shape shape = getShape();
+
+ java.awt.Color currentColor = graphics.getColor();
+ fillShape(shape, graphics);
+ strokeShape(shape, graphics);
+ graphics.setColor(currentColor);
+
+ graphics.setTransform(orig);
+ }
+
@Override
public boolean equals( Object o ) {
if( this == o ) return true;
diff --git a/src/main/java/schule/ngb/zm/shapes/CustomShape.java b/src/main/java/schule/ngb/zm/shapes/CustomShape.java
index 4758d67..2b189d5 100644
--- a/src/main/java/schule/ngb/zm/shapes/CustomShape.java
+++ b/src/main/java/schule/ngb/zm/shapes/CustomShape.java
@@ -11,6 +11,7 @@ public class CustomShape extends Shape {
public CustomShape( double x, double y ) {
super(x, y);
path = new Path2D.Double();
+ path.moveTo(x, y);
}
public CustomShape( CustomShape custom ) {
@@ -36,7 +37,7 @@ public class CustomShape extends Shape {
}
public void lineTo( double x, double y ) {
- path.lineTo(x - x, y - y);
+ path.lineTo(x - this.x, y - this.y);
calculateBounds();
}
diff --git a/src/main/java/schule/ngb/zm/shapes/Shape.java b/src/main/java/schule/ngb/zm/shapes/Shape.java
index 33a8be2..6cdf583 100644
--- a/src/main/java/schule/ngb/zm/shapes/Shape.java
+++ b/src/main/java/schule/ngb/zm/shapes/Shape.java
@@ -404,6 +404,7 @@ public abstract class Shape extends BasicDrawable {
setStrokeColor(shape.getStrokeColor());
setStrokeWeight(shape.getStrokeWeight());
setStrokeType(shape.getStrokeType());
+ setStrokeJoin(shape.getStrokeJoin());
visible = shape.isVisible();
rotation = shape.getRotation();
scale(shape.getScale());
diff --git a/src/main/java/schule/ngb/zm/util/Validator.java b/src/main/java/schule/ngb/zm/util/Validator.java
index 5c6d159..74796cc 100644
--- a/src/main/java/schule/ngb/zm/util/Validator.java
+++ b/src/main/java/schule/ngb/zm/util/Validator.java
@@ -19,7 +19,7 @@ public class Validator {
public static final T requireNotNull( T obj, Supplier msg ) {
if( obj == null ) {
- throw new NullPointerException(msg == null ? "Parameter may nut be null." : msg.get());
+ throw new NullPointerException(msg == null ? "Parameter may not be null." : msg.get());
}
return obj;
}
diff --git a/src/main/java/schule/ngb/zm/util/io/FileLoader.java b/src/main/java/schule/ngb/zm/util/io/FileLoader.java
index 375e5d3..8ecb6f3 100644
--- a/src/main/java/schule/ngb/zm/util/io/FileLoader.java
+++ b/src/main/java/schule/ngb/zm/util/io/FileLoader.java
@@ -1,9 +1,11 @@
package schule.ngb.zm.util.io;
import schule.ngb.zm.util.Log;
+import schule.ngb.zm.util.Validator;
import java.io.BufferedReader;
import java.io.IOException;
+import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -11,6 +13,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
+import java.util.logging.Level;
/**
* Hilfsklasse, um Textdateien in verschiedenen Formaten einzulesen.
@@ -63,6 +66,9 @@ public final class FileLoader {
* @return Eine Liste mit den Zeilen der Textdatei.
*/
public static List loadLines( String source, Charset charset ) {
+ Validator.requireNotNull(source, "source");
+ Validator.requireNotNull(charset, "charset");
+
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
List result = new ArrayList<>();
@@ -72,8 +78,11 @@ public final class FileLoader {
}
return result;
+ } catch( MalformedURLException muex ) {
+ LOG.warn("Could not find resource for <%s>", source);
+ return Collections.emptyList();
} catch( IOException ex ) {
- LOG.error(ex, "Error while loading lines from source <%s>", source);
+ LOG.warn(ex, "Error while loading lines from source <%s>", source);
return Collections.emptyList();
}
}
@@ -101,6 +110,9 @@ public final class FileLoader {
* @return Der Inhalt der Textdatei.
*/
public static String loadText( String source, Charset charset ) {
+ Validator.requireNotNull(source, "source");
+ Validator.requireNotNull(charset, "charset");
+
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
StringBuilder result = new StringBuilder();
@@ -110,8 +122,11 @@ public final class FileLoader {
}
return result.toString();
+ } catch( MalformedURLException muex ) {
+ LOG.warn("Could not find resource for <%s>", source);
+ return "";
} catch( IOException ex ) {
- LOG.error(ex, "Error while loading string from source <%s>", source);
+ LOG.warn(ex, "Error while loading string from source <%s>", source);
return "";
}
}
@@ -166,34 +181,39 @@ public final class FileLoader {
).toArray(String[][]::new);
}
- public static double[][] loadValues( String source, char separator, boolean skipFirst ) {
+ public static double[][] loadValues( String source, String separator, boolean skipFirst ) {
return loadValues(source, separator, skipFirst, UTF8);
}
/**
- * Lädt Double-Werte aus einer CSV Datei in ein zweidimensionales Array.
+ * Lädt Double-Werte aus einer Text-Datei in ein zweidimensionales Array.
*
- * 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.
+ * Die Zeilen der Eingabedatei werden anhand der Zeichenkette {@code separator}
+ * in einzelne Teile aufgetrennt. {@code separator} wird als regulärer Ausdruck
+ * interpretiert (siehe {@link String#split(String)}).
+ *
+ * Jeder Teilstring wird mit {@link Double#parseDouble(String)} in
+ * {@code double} umgeformt. Es liegt in der Verantwortung des Nutzers,
+ * sicherzustellen, dass die Eingabedatei 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.
*
* 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 separator Ein Trennzeichen oder ein regulärer Ausdruck.
* @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 ) {
+ public static double[][] loadValues( String source, String separator, boolean skipFirst, Charset charset ) {
int n = skipFirst ? 1 : 0;
List lines = loadLines(source, charset);
return lines.stream().skip(n).map(
( line ) -> Arrays
- .stream(line.split(Character.toString(separator)))
+ //.stream(line.split(Character.toString(separator)))
+ .stream(line.split(separator))
.mapToDouble(
( value ) -> {
try {
diff --git a/src/main/java/schule/ngb/zm/util/io/FontLoader.java b/src/main/java/schule/ngb/zm/util/io/FontLoader.java
index 9a2075c..682b5b3 100644
--- a/src/main/java/schule/ngb/zm/util/io/FontLoader.java
+++ b/src/main/java/schule/ngb/zm/util/io/FontLoader.java
@@ -10,6 +10,7 @@ import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.net.MalformedURLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -62,8 +63,8 @@ public class FontLoader {
}
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.");
+ Validator.requireNotNull(source, "source");
+ Validator.requireNotEmpty(source, "source");
if( fontCache.containsKey(name) ) {
LOG.trace("Retrieved font <%s> from font cache.", name);
@@ -91,10 +92,12 @@ public class FontLoader {
//ge.registerFont(font);
}
LOG.debug("Loaded custom font from source <%s>.", source);
+ } catch( MalformedURLException muex ) {
+ LOG.warn("Could not find font resource for <%s>", source);
} catch( IOException ioex ) {
- LOG.error(ioex, "Error loading custom font file from source <%s>.", source);
+ LOG.warn(ioex, "Error loading custom font file from source <%s>.", source);
} catch( FontFormatException ffex ) {
- LOG.error(ffex, "Error creating custom font from source <%s>.", source);
+ LOG.warn(ffex, "Error creating custom font from source <%s>.", source);
}
return font;
diff --git a/src/main/java/schule/ngb/zm/util/io/ImageLoader.java b/src/main/java/schule/ngb/zm/util/io/ImageLoader.java
index 7ed5528..6043830 100644
--- a/src/main/java/schule/ngb/zm/util/io/ImageLoader.java
+++ b/src/main/java/schule/ngb/zm/util/io/ImageLoader.java
@@ -16,6 +16,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
+import java.net.MalformedURLException;
/**
* Eine Hilfsklasse mit Klassenmethoden, um Bilder zu laden.
@@ -67,8 +68,8 @@ public final class ImageLoader {
* @return
*/
public static BufferedImage loadImage( String source, boolean caching ) {
- Validator.requireNotNull(source, "Image source may not be null");
- Validator.requireNotEmpty(source, "Image source may not be empty.");
+ Validator.requireNotNull(source, "source");
+ Validator.requireNotEmpty(source, "source");
if( caching && imageCache.containsKey(source) ) {
return imageCache.get(source);
@@ -84,8 +85,10 @@ public final class ImageLoader {
if( caching && img != null ) {
imageCache.put(source, img);
}
+ } catch( MalformedURLException muex ) {
+ LOG.warn("Could not find image resource for <%s>", source);
} catch( IOException ioex ) {
- LOG.error(ioex, "Error loading image file from source <%s>.", source);
+ LOG.warn(ioex, "Error loading image file from source <%s>.", source);
}
return img;
}
@@ -318,12 +321,8 @@ public final class ImageLoader {
* @throws IOException Falls es einen Fehler beim Speichern gab.
*/
public static void saveImage( BufferedImage image, File file, boolean overwriteIfExists ) throws IOException {
- if( image == null ) {
- throw new NullPointerException("Image may not be .");
- }
- if( file == null ) {
- throw new NullPointerException("File may not be .");
- }
+ Validator.requireNotNull(image, "image");
+ Validator.requireNotNull(file, "file");
if( file.isFile() ) {
// Datei existiert schon
diff --git a/src/main/java/schule/ngb/zm/util/io/ResourceStreamProvider.java b/src/main/java/schule/ngb/zm/util/io/ResourceStreamProvider.java
index ed921c3..fb900b1 100644
--- a/src/main/java/schule/ngb/zm/util/io/ResourceStreamProvider.java
+++ b/src/main/java/schule/ngb/zm/util/io/ResourceStreamProvider.java
@@ -47,8 +47,8 @@ public class ResourceStreamProvider {
* einer bestehenden Ressource.
*/
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.");
+ Validator.requireNotNull(source, "source");
+ Validator.requireNotEmpty(source, "source");
// Ist source ein valider Dateipfad?
File file = new File(source);
diff --git a/src/test/java/schule/ngb/zm/ColorTest.java b/src/test/java/schule/ngb/zm/ColorTest.java
index a5c2642..ca68a30 100644
--- a/src/test/java/schule/ngb/zm/ColorTest.java
+++ b/src/test/java/schule/ngb/zm/ColorTest.java
@@ -252,4 +252,18 @@ class ColorTest {
void darker() {
}
+ @Test
+ void compare() {
+ assertEquals(1.0, Color.RED.compare(Color.RED), 0.0001);
+ assertEquals(1.0, Color.BLUE.compare(Color.BLUE), 0.0001);
+ assertEquals(1.0, Color.WHITE.compare(Color.WHITE), 0.0001);
+ assertEquals(1.0, Color.BLACK.compare(Color.BLACK), 0.0001);
+
+
+ assertEquals(0.0, Color.BLACK.compare(Color.WHITE), 0.0001);
+ assertEquals(0.0, Color.WHITE.compare(Color.BLACK), 0.0001);
+
+ assertEquals(0.5, Color.GRAY.compare(Color.BLACK), 0.01);
+ }
+
}
diff --git a/src/test/java/schule/ngb/zm/Testmaschine.java b/src/test/java/schule/ngb/zm/Testmaschine.java
new file mode 100644
index 0000000..dd22370
--- /dev/null
+++ b/src/test/java/schule/ngb/zm/Testmaschine.java
@@ -0,0 +1,51 @@
+package schule.ngb.zm;
+
+
+import schule.ngb.zm.layers.DrawingLayer;
+import schule.ngb.zm.util.Log;
+
+public class Testmaschine extends Zeichenmaschine {
+
+ static {
+ Log.enableGlobalDebugging();
+ }
+
+ private DrawingLayer gridLayer;
+
+ public Testmaschine() {
+ this(400, 400);
+ }
+
+ public Testmaschine( int width, int height ) {
+ super(width, height, "Testmaschine", false);
+ }
+
+ @Override
+ public void settings() {
+ gridLayer = new DrawingLayer(getWidth(), getHeight());
+ this.getCanvas().addLayer(1, gridLayer);
+ setGrid(50, 10);
+ }
+
+ public void setGrid( int majorGrid, int minorGrid ) {
+ gridLayer.clear();
+
+ gridLayer.clear(LIGHTGRAY);
+ gridLayer.setStrokeColor(LIGHTGRAY.darker(20));
+ for( int i = 0; i < getWidth(); i += minorGrid ) {
+ gridLayer.line(i, 0, i, gridLayer.getHeight());
+ }
+ for( int i = 0; i < getHeight(); i += minorGrid ) {
+ gridLayer.line(0, i, gridLayer.getWidth(), i);
+ }
+
+ gridLayer.setStrokeColor(LIGHTGRAY.darker(50));
+ for( int i = 0; i < getWidth(); i += majorGrid ) {
+ gridLayer.line(i, 0, i, gridLayer.getHeight());
+ }
+ for( int i = 0; i < getHeight(); i += majorGrid ) {
+ gridLayer.line(0, i, gridLayer.getWidth(), i);
+ }
+ }
+
+}
diff --git a/src/test/java/schule/ngb/zm/TestmaschineTest.java b/src/test/java/schule/ngb/zm/TestmaschineTest.java
new file mode 100644
index 0000000..4065676
--- /dev/null
+++ b/src/test/java/schule/ngb/zm/TestmaschineTest.java
@@ -0,0 +1,71 @@
+package schule.ngb.zm;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import schule.ngb.zm.layers.DrawingLayer;
+import schule.ngb.zm.util.io.ImageLoader;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static schule.ngb.zm.util.test.ImageAssertions.assertEquals;
+import static schule.ngb.zm.util.test.ImageAssertions.setSaveDiffImageOnFail;
+
+public class TestmaschineTest {
+
+ private static Testmaschine tm;
+
+ private static DrawingLayer drawing;
+
+ @BeforeAll
+ static void beforeAll() {
+ setSaveDiffImageOnFail(true);
+
+ tm = new Testmaschine();
+ drawing = tm.getDrawingLayer();
+ assertNotNull(drawing);
+ }
+
+ @AfterAll
+ static void afterAll() {
+ tm.exit();
+ }
+
+ @BeforeEach
+ void setUp() {
+ drawing.clear();
+ }
+
+ @Test
+ void testSaveDiffImage() {
+ drawing.noStroke();
+ drawing.setAnchor(Constants.NORTHWEST);
+ drawing.setFillColor(Constants.BLUE);
+ drawing.rect(0, 0, 400, 400);
+ drawing.setFillColor(Constants.RED);
+ drawing.rect(100, 100, 200, 200);
+
+ BufferedImage img1 = ImageLoader.createImage(400, 400);
+ Graphics2D graphics = img1.createGraphics();
+
+ graphics.setColor(Constants.BLUE.getJavaColor());
+ graphics.fillRect(0, 0, 400, 400);
+ graphics.setColor(Constants.RED.getJavaColor());
+ graphics.fillRect(100, 100, 200, 200);
+
+ assertEquals(drawing.buffer, drawing.buffer);
+ assertEquals(ImageLoader.copyImage(drawing.buffer), drawing.buffer);
+ assertEquals(img1, drawing.buffer);
+ assertEquals(img1, tm.getImage());
+ }
+
+ @Test
+ void testGrid() {
+// tm.setGrid(50, 10);
+ tm.delay(2000);
+ }
+
+}
diff --git a/src/test/java/schule/ngb/zm/anim/AnimationGroupsTest.java b/src/test/java/schule/ngb/zm/anim/AnimationGroupsTest.java
new file mode 100644
index 0000000..3d54b4d
--- /dev/null
+++ b/src/test/java/schule/ngb/zm/anim/AnimationGroupsTest.java
@@ -0,0 +1,96 @@
+package schule.ngb.zm.anim;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import schule.ngb.zm.Color;
+import schule.ngb.zm.Testmaschine;
+import schule.ngb.zm.Zeichenmaschine;
+import schule.ngb.zm.layers.ShapesLayer;
+import schule.ngb.zm.shapes.Circle;
+import schule.ngb.zm.shapes.Shape;
+
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class AnimationGroupsTest {
+
+ private static Testmaschine zm;
+
+ private static ShapesLayer shapes;
+
+ @BeforeAll
+ static void beforeAll() {
+ zm = new Testmaschine();
+ shapes = zm.getShapesLayer();
+ assertNotNull(shapes);
+ }
+
+ @AfterAll
+ static void afterAll() {
+ zm.exit();
+ }
+
+ @BeforeEach
+ void setUp() {
+ shapes.removeAll();
+ }
+
+ @Test
+ void animationGroup() {
+ Shape s = new Circle(0, 0, 10);
+ shapes.add(s);
+
+ Animation anims = new AnimationGroup<>(
+ 500,
+ Arrays.asList(
+ new MoveAnimation(s, 200, 200, 2000, Easing.DEFAULT_EASING),
+ new FillAnimation(s, Color.GREEN, 1000, Easing.sineIn())
+ )
+ );
+ Animations.playAndWait(anims);
+ assertEquals(200, s.getX());
+ assertEquals(200, s.getY());
+ assertEquals(Color.GREEN, s.getFillColor());
+ }
+
+ @Test
+ void animationSequence() {
+ Shape s = new Circle(0, 0, 10);
+ shapes.add(s);
+
+ Animation anims = new AnimationSequence<>(
+ Arrays.asList(
+ new CircleAnimation(s, 200, 0, 90, false, 1000, Easing::rushIn),
+ new CircleAnimation(s, 200, 400, 90, 1000, Easing::rushOut),
+ new CircleAnimation(s, 200, 400, 90, false, 1000, Easing::rushIn),
+ new CircleAnimation(s, 200, 0, 90, 1000, Easing::rushOut)
+ )
+ );
+ Animations.playAndWait(anims);
+ assertEquals(0, s.getX());
+ assertEquals(0, s.getY());
+ }
+
+ @Test
+ void animationSequenceContinous() {
+ Shape s = new Circle(0, 0, 10);
+ shapes.add(s);
+
+ Animation anims = new ContinousAnimation<>(new AnimationSequence<>(
+ Arrays.asList(
+ new CircleAnimation(s, 200, 0, 90, false, 1000, Easing::rushIn),
+ new CircleAnimation(s, 200, 400, 90, 1000, Easing::rushOut),
+ new CircleAnimation(s, 200, 400, 90, false, 1000, Easing::rushIn),
+ new CircleAnimation(s, 200, 0, 90, 1000, Easing::rushOut)
+ )
+ ), false);
+ Animations.playAndWait(anims);
+ zm.delay(8000);
+ anims.stop();
+ }
+
+}
diff --git a/src/test/java/schule/ngb/zm/anim/AnimationTest.java b/src/test/java/schule/ngb/zm/anim/AnimationTest.java
new file mode 100644
index 0000000..98b8e75
--- /dev/null
+++ b/src/test/java/schule/ngb/zm/anim/AnimationTest.java
@@ -0,0 +1,75 @@
+package schule.ngb.zm.anim;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import schule.ngb.zm.Testmaschine;
+import schule.ngb.zm.layers.ShapesLayer;
+import schule.ngb.zm.shapes.Circle;
+import schule.ngb.zm.shapes.Shape;
+import schule.ngb.zm.util.test.TestEnv;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AnimationTest {
+
+ private static Testmaschine zm;
+
+ private static ShapesLayer shapes;
+
+ @BeforeAll
+ static void beforeAll() {
+ zm = new Testmaschine();
+ shapes = zm.getShapesLayer();
+ assertNotNull(shapes);
+ }
+
+ @AfterAll
+ static void afterAll() {
+ zm.exit();
+ }
+
+ @BeforeEach
+ void setUp() {
+ shapes.removeAll();
+ }
+
+ @Test
+ void circleAnimation() {
+ Shape s = new Circle(zm.getWidth()/4.0, zm.getHeight()/2.0, 10);
+ shapes.add(s);
+
+ CircleAnimation anim = new CircleAnimation(s, zm.getWidth()/2.0, zm.getHeight()/2.0, 360, true, 3000, Easing::linear);
+ Animations.playAndWait(anim);
+ assertEquals(zm.getWidth()/4.0, s.getX());
+ assertEquals(zm.getHeight()/2.0, s.getY());
+ }
+
+ @Test
+ void fadeAnimation() {
+ Shape s = new Circle(zm.getWidth()/4.0, zm.getHeight()/2.0, 10);
+ s.setFillColor(s.getFillColor(), 0);
+ s.setStrokeColor(s.getStrokeColor(), 0);
+ shapes.add(s);
+
+ Animation anim = new FadeAnimation(s, 255, 1000);
+ Animations.playAndWait(anim);
+ assertEquals(s.getFillColor().getAlpha(), 255);
+ }
+
+ @Test
+ void continousAnimation() {
+ Shape s = new Circle(zm.getWidth()/4, zm.getHeight()/2, 10);
+ shapes.add(s);
+
+ ContinousAnimation anim = new ContinousAnimation(
+ new CircleAnimation(s, zm.getWidth()/2, zm.getHeight()/2, 360, true, 1000, Easing::linear)
+ );
+ Animations.play(anim);
+ zm.delay(3000);
+ anim.stop();
+ }
+
+}
diff --git a/src/test/java/schule/ngb/zm/layers/DrawingLayerTest.java b/src/test/java/schule/ngb/zm/layers/DrawingLayerTest.java
new file mode 100644
index 0000000..9aebe58
--- /dev/null
+++ b/src/test/java/schule/ngb/zm/layers/DrawingLayerTest.java
@@ -0,0 +1,30 @@
+package schule.ngb.zm.layers;
+
+import org.junit.jupiter.api.Test;
+import schule.ngb.zm.Constants;
+import schule.ngb.zm.Zeichenmaschine;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DrawingLayerTest {
+
+ @Test
+ void imageRotateAndScale() {
+ Zeichenmaschine zm = new Zeichenmaschine();
+ zm.getDrawingLayer().imageRotateAndScale(
+ "WitchCraftIcons_122_t.PNG",
+ 50, 100,
+ 90,
+ 300, 200,
+ Constants.NORTHWEST
+ );
+ zm.redraw();
+
+ try {
+ Thread.sleep(4000);
+ } catch( InterruptedException e ) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/test/java/schule/ngb/zm/util/FileLoaderTest.java b/src/test/java/schule/ngb/zm/util/FileLoaderTest.java
index f1b45c1..f622a05 100644
--- a/src/test/java/schule/ngb/zm/util/FileLoaderTest.java
+++ b/src/test/java/schule/ngb/zm/util/FileLoaderTest.java
@@ -72,10 +72,10 @@ class FileLoaderTest {
{2.1,2.2,2.3},
{3.1,3.2,3.3}
};
- csv = FileLoader.loadValues("data_comma.csv", ',', true);
+ csv = FileLoader.loadValues("data_comma.csv", ",", true);
assertArrayEquals(data, csv);
- csv = FileLoader.loadValues("data_semicolon_latin.csv", ';', true, FileLoader.ISO_8859_1);
+ csv = FileLoader.loadValues("data_semicolon_latin.csv", ";", true, FileLoader.ISO_8859_1);
assertArrayEquals(data, csv);
}
diff --git a/src/test/java/schule/ngb/zm/util/test/ImageAssertions.java b/src/test/java/schule/ngb/zm/util/test/ImageAssertions.java
new file mode 100644
index 0000000..27e1d53
--- /dev/null
+++ b/src/test/java/schule/ngb/zm/util/test/ImageAssertions.java
@@ -0,0 +1,165 @@
+package schule.ngb.zm.util.test;
+
+import org.junit.jupiter.api.Assertions;
+import org.opentest4j.AssertionFailedError;
+import schule.ngb.zm.util.io.ImageLoader;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.function.Supplier;
+
+public final class ImageAssertions {
+
+ private static boolean SAVE_DIFF_IMAGE_ON_FAIL = false;
+
+ private static File DIFF_IMAGE_PATH = new File("build/test-results/diff");
+
+ private static AssertionFailedError ASSERTION_FAILED_ERROR = null;
+
+
+ public static boolean isSaveDiffImageOnFail() {
+ return SAVE_DIFF_IMAGE_ON_FAIL;
+ }
+
+ public static final void setSaveDiffImageOnFail( boolean saveOnFail ) {
+ SAVE_DIFF_IMAGE_ON_FAIL = saveOnFail;
+ }
+
+ public static File getDiffImagePath() {
+ return DIFF_IMAGE_PATH;
+ }
+
+ public static void assertEquals( BufferedImage expected, BufferedImage actual ) {
+ assertEquals(expected, actual, () -> "Actual image differs from expected buffer.");
+ }
+
+ public static void assertEquals( BufferedImage expected, BufferedImage actual, String message ) {
+ assertEquals(expected, actual, () -> message);
+ }
+
+ public static void assertEquals( BufferedImage expected, BufferedImage actual, Supplier messageSupplier ) {
+ // Compare image dimensions
+ int expectedHeight = expected.getHeight(), expectedWidth = expected.getWidth();
+ int actualHeight = actual.getHeight(), actualWidth = actual.getWidth();
+ try {
+ Assertions.assertEquals(expectedHeight, actualHeight);
+ Assertions.assertEquals(expectedWidth, actualWidth);
+ } catch( AssertionFailedError afe ) {
+ ASSERTION_FAILED_ERROR = afe;
+ fail(expected, actual, messageSupplier);
+ }
+
+ // TODO: Fix comparison of transparent pixels
+ for( int x = 0; x < actualWidth; x++ ) {
+ for( int y = 0; y < actualHeight; y++ ) {
+ try {
+ Assertions.assertTrue(comparePixels(expected.getRGB(x, y), actual.getRGB(x, y)));
+ } catch( AssertionFailedError afe ) {
+ ASSERTION_FAILED_ERROR = afe;
+ fail(expected, actual, messageSupplier);
+ }
+ }
+ }
+ }
+
+ public static void assertNotEquals( BufferedImage expected, BufferedImage actual ) {
+ assertNotEquals(expected, actual, () -> "Actual image is the same as expected buffer.");
+ }
+
+ public static void assertNotEquals( BufferedImage expected, BufferedImage actual, String message ) {
+ assertNotEquals(expected, actual, () -> message);
+ }
+
+ public static void assertNotEquals( BufferedImage expected, BufferedImage actual, Supplier messageSupplier ) {
+ // Compare image dimensions
+ int expectedHeight = expected.getHeight(), expectedWidth = expected.getWidth();
+ int actualHeight = actual.getHeight(), actualWidth = actual.getWidth();
+ if( expectedHeight != actualHeight || expectedWidth != actualWidth ) {
+ // Image dimensions differ, assertion is true
+ return;
+ }
+
+ for( int x = 0; x < actualWidth; x++ ) {
+ for( int y = 0; y < actualHeight; y++ ) {
+ if( !comparePixels(expected.getRGB(x, y), actual.getRGB(x, y)) ) {
+ // Found different pixels, assertion is true
+ return;
+ }
+ }
+ }
+
+ // Images are the same, fail without diff
+ fail(expected, actual, messageSupplier, false);
+ }
+
+ private static void fail( BufferedImage expected, BufferedImage actual, Supplier messageSupplier ) {
+ fail(expected, actual, messageSupplier, SAVE_DIFF_IMAGE_ON_FAIL);
+ }
+
+ private static void fail( BufferedImage expected, BufferedImage actual, Supplier messageSupplier, boolean saveDiffImage ) {
+ if( saveDiffImage ) {
+ saveDiffImage(expected, actual);
+ }
+ throw new AssertionFailedError(
+ messageSupplier != null ? messageSupplier.get() : null,
+ ASSERTION_FAILED_ERROR
+ );
+ }
+
+ private static boolean comparePixels( int a, int b ) {
+ // TODO: Fix comparison of transparent pixels
+ return a == b || ((0xFF000000 & a) == 0 && (0xFF000000 & b) == 0);
+ }
+
+ public static BufferedImage createDiffImage( BufferedImage expected, BufferedImage actual ) {
+ // Error color (white)
+ int errorColor = 0xFF00FF;
+
+ int expectedHeight = expected.getHeight(), expectedWidth = expected.getWidth();
+ int actualHeight = actual.getHeight(), actualWidth = actual.getWidth();
+ int maxHeight = Math.max(expectedHeight, actualHeight), maxWidth = Math.max(expectedWidth, actualWidth);
+
+ BufferedImage diff = ImageLoader.createImage(maxWidth, maxHeight);
+ for( int x = 0; x < maxWidth; x++ ) {
+ for( int y = 0; y < maxHeight; y++ ) {
+ diff.setRGB(x, y, 0);
+ if( x > actualWidth || y > actualHeight || x > expectedWidth || y > expectedHeight ) {
+ // Set overflow pixels to error color
+ diff.setRGB(x, y, errorColor);
+ } else if( !comparePixels(actual.getRGB(x, y), expected.getRGB(x, y)) ) {
+ // Set differences to error color
+ // If both pixels are transparent, the color dows not matter ...
+ // TODO: saturate error color based on how different the colors are?
+ diff.setRGB(x, y, errorColor);
+ }
+ }
+ }
+
+ return diff;
+ }
+
+ public static boolean saveDiffImage( BufferedImage expected, BufferedImage actual ) {
+ BufferedImage diff = createDiffImage(expected, actual);
+ try {
+ File diffFile = new File(DIFF_IMAGE_PATH, makeDiffName());
+ if( !diffFile.getParentFile().exists() ) {
+ diffFile.mkdirs();
+ }
+ ImageLoader.saveImage(diff, diffFile);
+ } catch( IOException ioe ) {
+ // We fail anyways at this point
+ // TODO: Log something?
+ return false;
+ }
+ return true;
+ }
+
+ private static String makeDiffName() {
+ return System.currentTimeMillis() + ".png";
+ }
+
+ private ImageAssertions() {
+ }
+
+}
diff --git a/src/test/java/schule/ngb/zm/util/test/TestEnv.java b/src/test/java/schule/ngb/zm/util/test/TestEnv.java
new file mode 100644
index 0000000..252a342
--- /dev/null
+++ b/src/test/java/schule/ngb/zm/util/test/TestEnv.java
@@ -0,0 +1,25 @@
+package schule.ngb.zm.util.test;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import schule.ngb.zm.Testmaschine;
+import schule.ngb.zm.Zeichenmaschine;
+
+public class TestEnv implements ParameterResolver {
+
+ @Override
+ public boolean supportsParameter( ParameterContext parameterContext, ExtensionContext extensionContext ) throws ParameterResolutionException {
+ return (
+ parameterContext.getParameter().getType() == Zeichenmaschine.class ||
+ parameterContext.getParameter().getType() == Testmaschine.class
+ );
+ }
+
+ @Override
+ public Object resolveParameter( ParameterContext parameterContext, ExtensionContext extensionContext ) throws ParameterResolutionException {
+ return new Testmaschine();
+ }
+
+}