diff --git a/src/main/java/schule/ngb/zm/Color.java b/src/main/java/schule/ngb/zm/Color.java index 33c2033..e7774fd 100644 --- a/src/main/java/schule/ngb/zm/Color.java +++ b/src/main/java/schule/ngb/zm/Color.java @@ -3,17 +3,15 @@ package schule.ngb.zm; /** * Repräsentiert eine Farbe in der Zeichenmaschine. *

- * Farben bestehen entweder aus einem Grauwert (zwischen 0 und - * 255) oder einem Rot-, Grün- und Blauanteil (jeweils zwischen - * 0 und 255). + * Farben bestehen entweder aus einem Grauwert (zwischen 0 und 255) oder einem + * Rot-, Grün- und Blauanteil (jeweils zwischen 0 und 255). *

- * Eine Farbe hat außerdem einen Transparenzwert zwischen 0 - * (unsichtbar) und 255 (deckend). + * Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255 + * (deckend). */ public class Color { - //@formatter:off /** * Die Farbe Schwarz (Grauwert 0). @@ -124,12 +122,12 @@ public class Color { /** * Erstellt eine Farbe. Die Parameter red, green und - * blue geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte - * liegen zwischen 0 und 255. + * blue geben die Rot-, Grün- und Blauanteile der Farbe. Die + * Werte liegen zwischen 0 und 255. * - * @param red Rotwert zwischen 0 und 255. + * @param red Rotwert zwischen 0 und 255. * @param green Grünwert zwischen 0 und 255. - * @param blue Blauwert zwischen 0 und 255. + * @param blue Blauwert zwischen 0 und 255. */ public Color( int red, int green, int blue ) { this(red, green, blue, 255); @@ -137,20 +135,18 @@ public class Color { /** * Erstellt eine Farbe. Die Parameter red, green und - * blue geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte - * liegen zwischen 0 und 255. + * blue geben die Rot-, Grün- und Blauanteile der Farbe. Die + * Werte liegen zwischen 0 und 255. * alpha gibt den den Transparentwert an (auch zwischen - * 0 und 255), wobei - * 0 komplett durchsichtig ist und 255 komplett - * deckend. + * 0 und 255), wobei 0 komplett durchsichtig ist und 255 komplett deckend. * - * @param red Rotwert zwischen 0 und 255. + * @param red Rotwert zwischen 0 und 255. * @param green Grünwert zwischen 0 und 255. - * @param blue Blauwert zwischen 0 und 255. + * @param blue Blauwert zwischen 0 und 255. * @param alpha Transparentwert zwischen 0 und 255. */ public Color( int red, int green, int blue, int alpha ) { - rgba = (alpha << 24) | (red << 16) | (green << 8) | blue; + rgba = ((alpha&0xFF) << 24) | ((red&0xFF) << 16) | ((green&0xFF) << 8) | ((blue&0xFF) << 0); } /** @@ -184,10 +180,11 @@ public class Color { /** * Interner Konstruktor für die Initialisierung einer Farbe mit einem * RGBA-Wert. - * + *

* Da der Konstruktor {@link #Color(int)} schon besetzt ist, muss hier der - * Parameter {@code isRGBA} auf {@code true} gesetzt werden, damit {@code rgba} - * korrekt interpretiert wird. + * Parameter {@code isRGBA} auf {@code true} gesetzt werden, damit + * {@code rgba} korrekt interpretiert wird. + * * @param rgba RGBA-wert der Farbe. * @param isRGBA Sollte immer {@code true} sein. */ @@ -210,12 +207,12 @@ public class Color { return c; } - public static Color getHSBColor(double h, double s, double b) { - return new Color(java.awt.Color.getHSBColor((float)h, (float)s, (float)b)); + public static Color getHSBColor( double h, double s, double b ) { + return new Color(java.awt.Color.getHSBColor((float) h, (float) s, (float) b)); } - public static Color getHSLColor(double h, double s, double l) { - int rgb = Color.HSLtoRGB(new float[]{(float)h, (float)s, (float)l}); + public static Color getHSLColor( double h, double s, double l ) { + int rgb = Color.HSLtoRGB(new float[]{(float) h, (float) s, (float) l}); return Color.getRGBColor(rgb); } @@ -235,9 +232,9 @@ public class Color { } /** - * Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann - * sechs- oder achtstellig sein (wenn ein Transparentwert vorhanden ist). - * Dem Code kann ein {@code #} Zeichen vorangestellt sein. + * Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann sechs- + * oder achtstellig sein (wenn ein Transparentwert vorhanden ist). Dem Code + * kann ein {@code #} Zeichen vorangestellt sein. * * @param hexcode * @return @@ -336,10 +333,11 @@ public class Color { /** * Konvertiert eine Farbe mit Komponenten im HSL-Farbraum in den * RGB-Farbraum. - * + *

* Die Farbkomponenten werden als Float-Array übergeben. Im Index 0 steht * der h-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den s- und * l-Wert im Bereich von 0 bis 1. + * * @param hsl Die Farbkomponenten im HSL-Farbraum. * @param alpha Ein Transparenzwert im Bereich 0 bis 255. * @return Der RGBA-Wert der Farbe. @@ -392,6 +390,7 @@ public class Color { /** * Erzeugt eine Kopie dieser Farbe. + * * @return Ein neues Farbobjekt. */ public Color copy() { @@ -400,10 +399,11 @@ public class Color { /** * Gibt den RGBA-Wert dieser Farbe zurück. - * - * Eine Farbe wird als 32-Bit Integer gespeichert. Bits 24-31 enthalten - * den Transparenzwert, 16-23 den Rotwert, 8-15 den Grünwert und 0-7 den + *

+ * Eine Farbe wird als 32-Bit Integer gespeichert. Bits 24-31 enthalten den + * Transparenzwert, 16-23 den Rotwert, 8-15 den Grünwert und 0-7 den * Blauwert der Farbe. + * * @return Der RGBA-Wert der Farbe. * @see #getRed() * @see #getGreen() @@ -416,6 +416,7 @@ public class Color { /** * Gibt den Rotwert dieser Farbe zurück. + * * @return Der Rotwert der Farbe zwischen 0 und 255. */ public int getRed() { @@ -424,6 +425,7 @@ public class Color { /** * Gibt den Grünwert dieser Farbe zurück. + * * @return Der Grünwert der Farbe zwischen 0 und 255. */ public int getGreen() { @@ -432,6 +434,7 @@ public class Color { /** * Gibt den Blauwert dieser Farbe zurück. + * * @return Der Blauwert der Farbe zwischen 0 und 255. */ public int getBlue() { @@ -440,6 +443,7 @@ public class Color { /** * Gibt den Transparenzwert dieser Farbe zurück. + * * @return Der Transparenzwert der Farbe zwischen 0 und 255. */ public int getAlpha() { @@ -448,9 +452,10 @@ public class Color { /** * Erzeugt ein {@link java.awt.Color}-Objekt aus dieser Farbe. + *

+ * Das erzeugte Farbobjekt hat dieselben Rot-, Grün-, Blau- und + * Transparenzwerte wie diese Farbe. * - * Das erzeugte Farbobjekt hat dieselben Rot-, Grün-, Blau- - * und Transparenzwerte wie diese Farbe. * @return Ein Java-Farbobjekt. */ public java.awt.Color getJavaColor() { @@ -468,11 +473,18 @@ public class Color { * @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}. */ public boolean equals( Object obj ) { - return obj instanceof Color && ((Color)obj).getRGBA() == this.rgba; + if( obj == null ) { return false; } + if( obj instanceof Color ) { + return ((Color) obj).getRGBA() == this.rgba; + } else if( obj instanceof java.awt.Color ) { + return ((java.awt.Color) obj).getRGB() == this.rgba; + } + return false; } /** * Erzeugt einen Text-String, der diese Farbe beschreibt. + * * @return Eine Textversion der Farbe. */ @Override @@ -482,6 +494,7 @@ public class Color { /** * Berechnet einen Hashcode für dieses Farbobjekt. + * * @return Ein Hashcode für diese Rabe. */ @Override @@ -490,7 +503,8 @@ public class Color { } /** - * Erzeugt eine um 30% hellere Version dieser Farbe. + * Erzeugt eine um 30% hellere Version dieser Farbe. + * * @return Ein Farbobjekt mit einer helleren Farbe. */ public Color brighter() { @@ -499,6 +513,7 @@ public class Color { /** * Erzeugt eine um {@code percent} hellere Version dieser Farbe. + * * @param percent Eine Prozentzahl zwischen 0 und 100. * @return Ein Farbobjekt mit einer helleren Farbe. */ @@ -510,6 +525,7 @@ public class Color { /** * Erzeugt eine um 30% dunklere Version dieser Farbe. + * * @return Ein Farbobjekt mit einer dunkleren Farbe. */ public Color darker() { @@ -518,6 +534,7 @@ public class Color { /** * Erzeugt eine um {@code percent} dunklere Version dieser Farbe. + * * @param percent Eine Prozentzahl zwischen 0 und 100. * @return Ein Farbobjekt mit einer dunkleren Farbe. */ @@ -528,11 +545,12 @@ public class Color { } public Color greyscale() { - return new Color((int)(getRed()*.299 + getGreen()*.587 + getBlue()*0.114)); + return new Color((int) (getRed() * .299 + getGreen() * .587 + getBlue() * 0.114)); } /** * Erzeugt eine zu dieser invertierte Farbe. + * * @return Ein Farbobjekt mit der invertierten Farbe. */ public Color inverted() { @@ -542,6 +560,7 @@ public class Color { /** * Erzeugt die Komplementärfarbe zu dieser. + * * @return Ein Farbobjekt mit der Komplementärfarbe. */ public Color complement() { @@ -554,11 +573,12 @@ public class Color { * Wählt entweder {@link #WHITE weiß} oder {@link #BLACK schwarz} aus, je * nachdem, welche der Farben besser als Textfarbe mit dieser Farbe als * Hintergrund funktioniert (besser lesbar ist). + * * @return Schwarz oder weiß. */ public Color textcolor() { // Basiert auf https://stackoverflow.com/questions/946544/good-text-foreground-color-for-a-given-background-color - if( (getRed()*.299 + getGreen()*.587 + getBlue()*0.114) < 186 ) { + if( (getRed() * .299 + getGreen() * .587 + getBlue() * 0.114) < 186 ) { return WHITE; } else { return BLACK; diff --git a/src/main/java/schule/ngb/zm/Constants.java b/src/main/java/schule/ngb/zm/Constants.java index 561b3aa..b9436f0 100644 --- a/src/main/java/schule/ngb/zm/Constants.java +++ b/src/main/java/schule/ngb/zm/Constants.java @@ -1,6 +1,7 @@ package schule.ngb.zm; import schule.ngb.zm.util.ImageLoader; +import schule.ngb.zm.util.Noise; import java.awt.Cursor; import java.awt.event.KeyEvent; @@ -8,6 +9,39 @@ import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.Random; +/** + * Basisklasse für die meisten Objekte der Zeichemaschine, die von Nutzern + * erweitert werden können. + *

+ * Die Konstanten stellen viele Funktionen zur einfachen Programmierung bereit + * und enthält auch einige dynamische Werte, die von der Zeichenmaschine laufend + * aktuell gehalten werden (beispielsweise {@link #runtime}. + *

+ * Für die Implementierung eigener Klassen ist es oft hilfreich von + * {@code Constants} zu erben, um die Methoden und Konstanten einfach im + * Programm nutzen zu können. + *

+ * class MyClass extends Constants {
+ *     public int summe( int a, int b ) {
+ *         // sum ist durch Vererbung verfügbar,
+ *         return sum(a, b);
+ *     }
+ * }
+ * 
+ *

+ * Alternativ können die statischen Klassenmethoden auch direkt genutzt werden: + *

+ * Constants.sum(1,2,3,4); // 10
+ * 
+ *

+ * Oder die Methoden statisch importiert werden: + *

+ * import static Constants.*;
+ *
+ * sum(1, 2, 3, 4); // 10
+ * 
+ */ +@SuppressWarnings( "unused" ) public class Constants { /** @@ -76,162 +110,287 @@ public class Constants { */ public static final int STD_BUFFER = 10; + /** + * Option für durchgezogene Konturen und Linien. + */ public static final Options.StrokeType SOLID = Options.StrokeType.SOLID; + /** + * Option für gestrichelte Konturen und Linien. + */ public static final Options.StrokeType DASHED = Options.StrokeType.DASHED; + /** + * Option für gepunktete Konturen und Linien. + */ public static final Options.StrokeType DOTTED = Options.StrokeType.DOTTED; + /** + * Option für Pfeile mit Strichen als Kopf. + */ public static final Options.ArrowHead LINES = Options.ArrowHead.LINES; + /** + * Option für Pfeile mit gefüllten Köpfen. + */ public static final Options.ArrowHead FILLED = Options.ArrowHead.FILLED; + /** + * Option für den Abschluss eines Pfades oder Bogens, ohne die Enden zu + * verbinden. + */ public static final Options.PathType OPEN = Options.PathType.OPEN; + /** + * Option für den Abschluss eines Pfades oder Bogens durch Verbindung der + * Enden des Bogens mit einer Linie. + */ public static final Options.PathType CLOSED = Options.PathType.CLOSED; + /** + * Option für den Abschluss eines Bogens durch Verbindung der Enden des + * Bogens mit dem Mittelpunkt der zugrundeliegenden Ellipse. Dadurch + * entsteht ein Kreisausschnitt. + */ public static final Options.PathType PIE = Options.PathType.PIE; + /** + * Richtung: Mitte (bzw. keine Richtung) + */ public static final Options.Direction CENTER = Options.Direction.CENTER; + /** + * Richtung: Norden + * + * @see #UP + */ public static final Options.Direction NORTH = Options.Direction.NORTH; + /** + * Richtung: Osten + * + * @see #RIGHT + */ public static final Options.Direction EAST = Options.Direction.EAST; + /** + * Richtung: Süden + * + * @see #DOWN + */ public static final Options.Direction SOUTH = Options.Direction.SOUTH; + /** + * Richtung: Westen + * + * @see #LEFT + */ public static final Options.Direction WEST = Options.Direction.WEST; + /** + * Richtung: Nordosten + * + * @see #UPLEFT + */ public static final Options.Direction NORTHEAST = Options.Direction.NORTHEAST; + /** + * Richtung: Südosten + * + * @see #DOWNLEFT + */ public static final Options.Direction SOUTHEAST = Options.Direction.SOUTHEAST; + /** + * Richtung: Nordwesten + * + * @see #UPRIGHT + */ public static final Options.Direction NORTHWEST = Options.Direction.NORTHWEST; + /** + * Richtung: Südwesten + * + * @see #DOWNRIGHT + */ public static final Options.Direction SOUTHWEST = Options.Direction.SOUTHWEST; + /** + * Richtung: Mitte + * + * @see #CENTER + */ public static final Options.Direction MIDDLE = Options.Direction.MIDDLE; + /** + * Richtung: Oben + * + * @see #NORTH + */ public static final Options.Direction UP = Options.Direction.UP; + /** + * Richtung: Rechts + * + * @see #EAST + */ public static final Options.Direction RIGHT = Options.Direction.RIGHT; + /** + * Richtung: Unten + * + * @see #SOUTH + */ public static final Options.Direction DOWN = Options.Direction.DOWN; + /** + * Richtung: Links + * + * @see #WEST + */ public static final Options.Direction LEFT = Options.Direction.LEFT; /** - * Schwarz + * Richtung: Oben links + * + * @see #NORTHWEST + */ + public static final Options.Direction UPLEFT = Options.Direction.UPLEFT; + + /** + * Richtung: Unten links + * + * @see #SOUTHWEST + */ + public static final Options.Direction DOWNLEFT = Options.Direction.DOWNLEFT; + + /** + * Richtung: Oben rechts + * + * @see #UPRIGHT + */ + public static final Options.Direction UPRIGHT = Options.Direction.UPRIGHT; + + /** + * Richtung: Unten rechts + * + * @see #SOUTHEAST + */ + public static final Options.Direction DOWNRIGHT = Options.Direction.DOWNRIGHT; + + + /** + * Farbe: Schwarz */ public static final Color BLACK = Color.BLACK; /** - * Weiß + * Farbe: Weiß */ public static final Color WHITE = Color.WHITE; /** - * Grau + * Farbe: Grau */ public static final Color GRAY = Color.GRAY; /** - * Dunkelgrau + * Farbe: Dunkelgrau */ public static final Color DARKGRAY = Color.DARKGRAY; /** - * Hellgrau + * Farbe: Hellgrau */ public static final Color LIGHTGRAY = Color.LIGHTGRAY; /** - * Rot + * Farbe: Rot */ public static final Color RED = Color.RED; /** - * Blau + * Farbe: Blau */ public static final Color BLUE = Color.BLUE; /** - * Grün + * Farbe: Grün */ public static final Color GREEN = Color.GREEN; /** - * Gelb + * Farbe: Gelb */ public static final Color YELLOW = Color.YELLOW; /** - * Orange + * Farbe: Orange */ public static final Color ORANGE = Color.ORANGE; /** - * Türkis + * Farbe: Türkis */ public static final Color CYAN = Color.CYAN; /** - * Magenta + * Farbe: Magenta */ public static final Color MAGENTA = Color.MAGENTA; /** - * Pink + * Farbe: Pink */ public static final Color PINK = Color.PINK; /** - * Lila + * Farbe: Lila */ public static final Color PURPLE = Color.PURPLE; /** - * Braun + * Farbe: Braun */ public static final Color BROWN = Color.BROWN; /** * Standardfarbe für den Hintergrund. */ - public static final Color STD_BACKGROUND = new Color(200, 200, 200); + public static Color DEFAULT_BACKGROUND = new Color(200, 200, 200); /** - * Konstante zur Prüfung, ob ein Mausknopf gedrückt wurde. + * Konstante zur Prüfung, ob kein Mausknopf gedrückt wurde. */ - public static final int NOBUTTON = MouseEvent.NOBUTTON; + public static final int NOMOUSE = MouseEvent.NOBUTTON; /** * Konstante zur Prüfung, ob Mausknopf 1 (links) gedrückt wurde. */ - public static final int BUTTON1 = MouseEvent.BUTTON1; + public static final int MOUSE1 = MouseEvent.BUTTON1; /** * Konstante zur Prüfung, ob Mausknopf 2 (rechts) gedrückt wurde. */ - public static final int BUTTON2 = MouseEvent.BUTTON2; + public static final int MOUSE2 = MouseEvent.BUTTON2; /** * Konstante zur Prüfung, ob Mausknopf 3 (mittig) gedrückt wurde. */ - public static final int BUTTON3 = MouseEvent.BUTTON3; + public static final int MOUSE3 = MouseEvent.BUTTON3; /** - * Konstante für die Kreiszahl Pi. + * Konstante für die Kreiszahl Pi (entspricht 180 Grad). */ public static final double PI = Math.PI; /** - * Konstante für die Hälfte der Kreiszahl Pi. + * Konstante für die Hälfte der Kreiszahl Pi (entspricht 90 Grad). */ public static final double HALF_PI = Math.PI / 2.0; /** - * Konstante für ein Viertel der Kreiszahl Pi. + * Konstante für ein Viertel der Kreiszahl Pi (entspricht 45 Grad). */ public static final double QUARTER_PI = Math.PI / 4.0; @@ -240,8 +399,7 @@ public class Constants { */ public static final double TWO_PI = Math.PI * 2.0; - - /** + /* * Globale Variablen, die von allen Klassen genutzt werden dürfen. Änderungen * wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der * Zeichenmaschine selbst vorgenommen werden. @@ -268,70 +426,86 @@ public class Constants { public static double delta = 0.0; /** - * Die aktuelle {@code x}-Koordinate der Maus. + * Die aktuelle {@code x}-Koordinate der Maus. (Wird einmal pro Frame + * aktualisiert.) */ public static double mouseX = 0.0; /** - * Die aktuelle {@code y}-Koordinate der Maus. + * Die aktuelle {@code y}-Koordinate der Maus. (Wird einmal pro Frame + * aktualisiert.) */ public static double mouseY = 0.0; /** - * Die letzte {@code x}-Koordinate der Maus (wird einmal pro Frame + * Die letzte {@code x}-Koordinate der Maus. (Wird einmal pro Frame * aktualisiert). */ public static double pmouseX = 0.0; /** - * Die letzte {@code y}-Koordinate der Maus (wird einmal pro Frame - * aktualisiert). + * Die letzte {@code y}-Koordinate der Maus. (Wird einmal pro Frame + * aktualisiert.) */ public static double pmouseY = 0.0; /** - * Die aktuelle (current) {@code x}-Koordinate der Maus - * (wird bei jeder Mausbewegung aktualisiert). + * Die aktuelle (current) {@code x}-Koordinate der Maus. (Wird bei + * jeder Mausbewegung aktualisiert). */ public static double cmouseX = 0.0; /** - * Die aktuelle (current) {@code y}-Koordinate der Maus - * (wird bei jeder Mausbewegung aktualisiert). + * Die aktuelle (current) {@code y}-Koordinate der Maus. (Wird bei + * jeder Mausbewegung aktualisiert). */ public static double cmouseY = 0.0; /** - * Gibt an, ob ein Mausknopf derzeit gedrückt ist. + * Gibt an, ob derzeit ein Mausknopf gedrückt ist. */ public static boolean mousePressed = false; /** * Der aktuell gedrückte Mausknopf. Die Mausknöpfe werden durch die - * Konstanten {@link #NOBUTTON}, {@link #BUTTON1}, {@link #BUTTON2} und - * {@link #BUTTON3} angegeben. (Sie stimmen mit den Konstanten in - * {@link MouseEvent} überein. - * - * @see MouseEvent + * Konstanten {@link #NOMOUSE}, {@link #MOUSE1}, {@link #MOUSE2} und + * {@link #MOUSE3} angegeben. */ - public static int mouseButton = NOBUTTON; + public static int mouseButton = NOMOUSE; /** * Das zuletzt ausgelöste {@code MouseEvent}. */ public static MouseEvent mouseEvent; - // Mauszeiger + /** + * Mauszeiger: Pfeil + */ public static final int ARROW = Cursor.DEFAULT_CURSOR; + /** + * Mauszeiger: Fadenkreuz + */ public static final int CROSS = Cursor.CROSSHAIR_CURSOR; + /** + * Mauszeiger: Hand + */ public static final int HAND = Cursor.HAND_CURSOR; + /** + * Mauszeiger: Bewegungspfeile + */ public static final int MOVE = Cursor.MOVE_CURSOR; + /** + * Mauszeiger: Textzeiger + */ public static final int TEXT = Cursor.TEXT_CURSOR; + /** + * Mauszeiger: Ladezeiger + */ public static final int WAIT = Cursor.WAIT_CURSOR; /** @@ -340,14 +514,16 @@ public class Constants { public static boolean keyPressed = false; /** - * Das Zeichen der zuletzt gedrückten Taste. + * Das Text-Zeichen der zuletzt gedrückten Taste. Für Tasten ohne + * zugeordnetes Zeichen ist das Zeichen leer. */ public static char key = ' '; /** * Der Tastencode der zuletzt gedrückten Taste. Die Keycodes können in der - * Klasse {@link KeyEvent} nachgesehen werden. (Zum Beispiel - * {@link KeyEvent#VK_ENTER}.) + * Klasse {@link KeyEvent} nachgesehen werden. Die Keycodes für die + * wichtigsten Tasten sind als Konstanten mit dem Prefix {@code KEY_} + * vorhanden. (Zum Beispiel {@link #KEY_A}.) */ public static int keyCode = 0; @@ -359,12 +535,12 @@ public class Constants { /** * Die Höhe der Zeichenleinwand. */ - public static int width; + public static int canvasWidth; /** * Die Breite der Zeichenleinwand. */ - public static int height; + public static int canvasHeight; /** * Die Breite des Bildschirms, auf dem das Zeichenfenster geöffnet wurde. @@ -387,8 +563,11 @@ public class Constants { /** * Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen Grauwert - * zwischen 0 und 255 an, wobei - * 0 schwarz und 255 weiß ist. + * zwischen 0 und 255 an, wobei 0 schwarz und 255 weiß ist. + * + *

+	 * Color iron_grey = color(94);
+	 * 
* * @param gray Grauwert zwischen 0 und 255. * @return Ein passendes Farbobjekt. @@ -399,16 +578,16 @@ public class Constants { /** * Erstellt eine graue Farbe. Der Parameter {@code gray} gibt einen Grauwert - * zwischen 0 und 255 an, wobei - * 0 schwarz und 255 weiß ist. - * {@code alpha} gibt den den Transparentwert an (auch zwischen - * 0 und 255), wobei - * 0 komplett durchsichtig ist und 255 komplett - * deckend. + * zwischen 0 und 255 an, wobei 0 schwarz und 255 weiß ist. {@code alpha} + * gibt den Transparenzwert an (auch zwischen 0 und 255), wobei 0 komplett + * durchsichtig ist und 255 komplett deckend. + * + *

+	 * Color iron_grey_50 = color(94, 50);
+	 * 
* * @param gray Grauwert zwischen 0 und 255. - * @param alpha Transparentwert zwischen 0 und - * 255. + * @param alpha Transparenzwert zwischen 0 und 255. * @return Ein passendes Farbobjekt. */ public static final Color color( int gray, int alpha ) { @@ -420,6 +599,10 @@ public class Constants { * {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte * liegen zwischen 0 und 255. * + *

+	 * Color arctic_blue = color(149, 214, 220);
+	 * 
+ * * @param red Rotwert zwischen 0 und 255. * @param green Grünwert zwischen 0 und 255. * @param blue Blauwert zwischen 0 und 255. @@ -432,17 +615,18 @@ public class Constants { /** * Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und * {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte - * liegen zwischen 0 und 255. {@code alpha} gibt - * den den Transparentwert an (auch zwischen 0 und - * 255), wobei - * 0 komplett durchsichtig ist und 255 komplett - * deckend. + * liegen zwischen 0 und 255. {@code alpha} gibt den Transparenzwert an + * (auch zwischen 0 und 255), wobei 0 komplett durchsichtig ist und 255 + * komplett deckend. + * + *

+	 * Color arctic_blue_50 = color(149, 214, 220, 50);
+	 * 
* * @param red Rotwert zwischen 0 und 255. * @param green Grünwert zwischen 0 und 255. * @param blue Blauwert zwischen 0 und 255. - * @param alpha Transparenzwert zwischen 0 und - * 255. + * @param alpha Transparenzwert zwischen 0 und 255. * @return Ein passendes Farbobjekt. */ public static final Color color( int red, int green, int blue, int alpha ) { @@ -455,6 +639,10 @@ public class Constants { * Alle Farbkomponenten (Rot, Grün und Blau) werden zufällig im Bereich 0 * bis 255 gewählt. * + *

+	 * Color fillColor = randomColor();
+	 * 
+ * * @return Ein zufälliges Farbobjekt. */ public static final Color randomColor() { @@ -465,6 +653,10 @@ public class Constants { * Erzeugt eine "hübsche" zufällige Farbe. Die Farbe wird so gewählt, dass * die Farben nicht zu verwaschen oder dunkel wirken. * + *

+	 * Color fillColor = randomNiceColor();
+	 * 
+ * * @return Ein zufälliges Farbobjekt. */ public static final Color randomNiceColor() { @@ -480,6 +672,10 @@ public class Constants { * und die Sättigung (saturation) und Hellwert (brightness) im Bereich 0 bis * 100. * + *

+	 * Color arctic_blue = colorHsl(185, 32, 86);
+	 * 
+ * * @param h Farbton im Bereich 0 bis 360. * @param s Sättigung im Bereich 0 bis 100. * @param b Hellwert im Bereich 0 bis 100. @@ -498,6 +694,10 @@ public class Constants { * und die Sättigung (saturation) und Helligkeit (lightness) im Bereich 0 * bis 100. * + *

+	 * Color arctic_blue = colorHsl(185, 50, 72);
+	 * 
+ * * @param h Farbton im Bereich 0 bis 360. * @param s Sättigung im Bereich 0 bis 100. * @param l Helligkeit im Bereich 0 bis 100. @@ -509,14 +709,27 @@ public class Constants { return Color.getHSLColor(h % 360.0, s / 100.0, l / 100.0); } - public static final BufferedImage loadImage( String name ) { - return ImageLoader.loadImage(name); + // Ressourcen + + /** + * Lädt ein Bild aus einer Datei oder von einer Webadresse. + * + * @param source Ein Dateipfad oder eine Webadresse. + * @return Das geladene Bild. + * @see ImageLoader#loadImage(String) + */ + public static final BufferedImage loadImage( String source ) { + return ImageLoader.loadImage(source); } // Mathematische Funktionen /** - * Berechnet das Minimum aller übergebenen Werte. + * Berechnet das Minimum aller angegebenen Werte. + * + *

+	 * double minimum = min(1.0, 5.1, 3.2); // 1.0
+	 * 
* * @param numbers Die Werte, aus denen das Minimum ermittelt werden soll. * @return Das Minimum der Werte. @@ -536,7 +749,11 @@ public class Constants { } /** - * Berechnet das Maximum aller übergebenen Werte. + * Berechnet das Maximum aller angegebenen Werte. + * + *

+	 * double maximum = max(1.0, 5.1, 3.2); // 5.1
+	 * 
* * @param numbers Die Werte, aus denen das Maximum ermittelt werden soll. * @return Das Maximum der Werte. @@ -555,18 +772,39 @@ public class Constants { return max; } + /** + * Berechnet die Summe alle angegebenen Werte. + * + *

+	 * double summe = sum(1.0, 2.0, 3.2); // 6.2
+	 * 
+ * + * @param numbers Die Werte, aus denen die Summe berechnet werden soll. + * @return Die Summe der Werte. + */ public static final double sum( double... numbers ) { if( numbers == null ) { throw new IllegalArgumentException("Array may not be or empty."); } double sum = 0; - for( int i = 0; i < numbers.length; i++ ) { - sum += numbers[i]; + for( double number : numbers ) { + sum += number; } return sum; } + /** + * Berechnet das arithmetische Mittel der angegebenen Werte. + * + *

+	 * double summe = sum(1.0, 2.2, 3.1); // 2.1
+	 * 
+ * + * @param numbers Die Werte, aus denen der MIttelwert berechnet werden + * soll. + * @return Der Mittelwert der Werte. + */ public static final double avg( double... numbers ) { if( numbers == null || numbers.length == 0 ) { throw new IllegalArgumentException("Array may not be or empty."); @@ -576,7 +814,11 @@ public class Constants { } /** - * Ermittelt den Absolutbetrag der Zahl {@code x}. + * Ermittelt den Absolutbetrag der angegebenen Zahl. + * + *

+	 * double positiv = abs(-3.2); // 3.2
+	 * 
* * @param x Eine Zahl. * @return Der Absolutbetrag. @@ -586,7 +828,11 @@ public class Constants { } /** - * Ermittelt das Vorzeichen der Zahl {@code x}. + * Ermittelt das Vorzeichen der angegebenen Zahl. + * + *

+	 * double vorzeichen = sign(-3.2); // -1.0
+	 * 
* * @param x Eine Zahl. * @return -1, 1 oder 0. @@ -596,7 +842,11 @@ public class Constants { } /** - * Rundet die Zahl {@code x}. + * Rundet die angegebene Zahl auf die nächste ganze Zahl. + * + *

+	 * double gerundet = sign(3.2); // 3.0
+	 * 
* * @param x Eine Zahl. * @return Die gerundete Zahl. @@ -606,7 +856,11 @@ public class Constants { } /** - * Rundet die Zahl {@code x} ab. + * Rundet die angegebene Zahl ab. + * + *

+	 * double abgerundet = sign(3.2); // 3.0
+	 * 
* * @param x Eine Zahl. * @return Die abgerundete Zahl. @@ -616,7 +870,11 @@ public class Constants { } /** - * Rundet die Zahl {@code x} auf. + * Rundet die angegebene Zahl auf. + * + *

+	 * double aufgerundet = sign(3.2); // 4.0
+	 * 
* * @param x Eine Zahl. * @return Die aufgerundete Zahl. @@ -626,7 +884,11 @@ public class Constants { } /** - * Ermittelt die Quadratwurzel der Zahl {@code x}. + * Ermittelt die Quadratwurzel der angegebenen Zahl. + * + *

+	 * double wurzel = sqrt(16); // 4.0
+	 * 
* * @param x Eine Zahl. * @return Die Quadratwurzel. @@ -636,19 +898,27 @@ public class Constants { } /** - * Ermittelt die Potenz der Zahl {@code x} zum Exponenten {@code p}. + * Ermittelt die Potenz der angegebenen Zahl zum angegebenen Exponenten. + * + *

+	 * double hoch4 = sqrt(8, 4); // 4096.0
+	 * 
* * @param x Eine Zahl. - * @param p Der Exponent. - * @return {@code x} hoch {@code p}. + * @param e Der Exponent. + * @return {@code x} hoch {@code e}. */ - public static final double pow( double x, double p ) { - return Math.pow(x, p); + public static final double pow( double x, double e ) { + return Math.pow(x, e); } /** * Rechnet von Grad in Radian um. * + *

+	 * double radian = radians(360); // 6.28318530717959
+	 * 
+ * * @param angle Ein Winkel in Grad. * @return Der Winkel in Radian. */ @@ -657,7 +927,11 @@ public class Constants { } /** - * Rechent von Radian in Grad um. + * Rechnet von Radian in Grad um. + * + *

+	 * double grad = radians(HALF_PI); // 90.0
+	 * 
* * @param radians Der Winkel in Radian. * @return Der Winkel in Grad. @@ -667,7 +941,11 @@ public class Constants { } /** - * Ermittelt den Sinus der Zahl {@code x}. + * Ermittelt den Sinus der angegebenen Zahl. + * + *

+	 * double sinus = sin(0.0); // 0.0
+	 * 
* * @param x Eine Zahl. * @return {@code sin(x)}. @@ -677,7 +955,11 @@ public class Constants { } /** - * Ermittelt den Kosinus der Zahl {@code x}. + * Ermittelt den Kosinus der angegebenen Zahl. + * + *

+	 * double kosinus = cos(0.0); // 1.0
+	 * 
* * @param x Eine Zahl. * @return {@code cos(x)}. @@ -687,7 +969,11 @@ public class Constants { } /** - * Ermittelt den Tangens der Zahl {@code x}. + * Ermittelt den Tangens der angegebenen Zahl. + * + *

+	 * double sinus = tan(1.0); // 1.5574077246549
+	 * 
* * @param x Eine Zahl. * @return {@code tan(x)}. @@ -697,7 +983,7 @@ public class Constants { } /** - * Ermittelt den Arkussinus der Zahl {@code x}. + * Ermittelt den Arkussinus der angegebenen Zahl. * * @param x Eine Zahl. * @return {@code asin(x)}. @@ -707,7 +993,7 @@ public class Constants { } /** - * Ermittelt den Arkuskosinus der Zahl {@code x}. + * Ermittelt den Arkuskosinus der angegebenen Zahl. * * @param x Eine Zahl. * @return {@code acos(x)}. @@ -717,7 +1003,7 @@ public class Constants { } /** - * Ermittelt den Arkusktangens der Zahl {@code x}. + * Ermittelt den Arkusktangens der angegebenen Zahl. * * @param x Eine Zahl. * @return {@code atan(x)}. @@ -727,11 +1013,17 @@ public class Constants { } /** - * Beschränkt die Zahl {@code x} auf das Intervall {@code [min, max]}. + * Beschränkt die angegebene Zahl auf das Intervall {@code [min, max]}. * Liegt {@code x} außerhalb des Intervalls, wird eine der Grenzen * zurückgegeben. * + *

+	 * double beschraenkt1 = limit(2.1, 0.0, 3.0); // 2.1
+	 * double beschraenkt2 = limit(4.1, 0.0, 3.0); // 1.0
+	 * 
+ * * @param x Eine Zahl. + * @param min Das Minimum des Intervalls. * @param max Das Maximum des Intervalls. * @return Eine Zahl im Intervall {@code [min, max]}. */ @@ -746,22 +1038,28 @@ public class Constants { } /** - * Beschränkt die Zahl {@code x} auf das Intervall {@code [min, max]}. - * Liegt {@code x} außerhalb des Intervalls, wird eine der Grenzen + * Beschränkt die angegebene Zahl auf das Intervall {@code [min, max]}. + * Liegt {@code i} außerhalb des Intervalls, wird eine der Grenzen * zurückgegeben. * - * @param x Eine Zahl. + *

+	 * double beschraenkt1 = limit(2, 0, 3); // 2
+	 * double beschraenkt2 = limit(4, 0, 3); // 3
+	 * 
+ * + * @param i Eine Zahl. + * @param min Das Minimum des Intervalls. * @param max Das Maximum des Intervalls. * @return Eine Zahl im Intervall {@code [min, max]}. */ - public static final int limit( int x, int min, int max ) { - if( x > max ) { + public static final int limit( int i, int min, int max ) { + if( i > max ) { return max; } - if( x < min ) { + if( i < min ) { return min; } - return x; + return i; } /** @@ -771,30 +1069,91 @@ public class Constants { *
 	 * from - t * (from + to)
 	 * 
+ *

+ * In der Regel liegt {@code t} im Intervall {@code [0, 1]}. Für + * {@code t = 0} ist das Ergebnis {@code from} und für {@code t = 1} ist das + * Ergebnis {@code to}. {@code t} kann aber auch Werte außerhalb des + * Intervalls annehmen. Für {@code t = 2} ist das Ergebnis beispielsweise + * {@code 2*to}; + * + *


+	 * double interpoliert = interpolate(100.0, 500.0, 0.5); // 300.0
+	 * double interpoliert = interpolate(100.0, 500.0, 1.0); // 500.0
+	 * double interpoliert = interpolate(100.0, 500.0, 1.5); // 750.0
+	 * 
* * @param from Startwert * @param to Zielwert - * @param t Wert zwischen 0 und 1. + * @param t Anteil des Ergebnisses auf der Strecke zwischen {@code from} + * und {@code to}. * @return Das Ergebnis der linearen Interpolation. */ public static final double interpolate( double from, double to, double t ) { return from + t * (to - from); } + /** + * Interpoliert einen Wert zwischen {@code from} und {@code to}, aber + * beschränkt {@code t} auf das Intervall {@code [0, 1]}. + * + *

+	 * double interpoliert = interpolate(100.0, 500.0, 0.5); // 300.0
+	 * double interpoliert = interpolate(100.0, 500.0, 1.0); // 500.0
+	 * double interpoliert = interpolate(100.0, 500.0, 1.5); // 500.0
+	 * 
+ * + * @param from Startwert + * @param to Zielwert + * @param t Wert zwischen 0 und 1. + * @return Das Ergebnis der linearen Interpolation. + * @see #interpolate(double, double, double) + */ public static final double morph( double from, double to, double t ) { return interpolate(from, to, limit(t, 0.0, 1.0)); } + /** + * Bestimmt für den angegebenen Wert aus dem Intervall + * {@code [fromMin, fromMax]} einen zugehörigen Wert aus dem Intervall + * {@code [toMin, toMax]}. + *

+ * Für {@code value = fromMin} wird {@code toMin} zurückgegeben. Für + * {@code value = fromMax} ist das Ergebnis {@code toMax}. Befindet sich + * {@code value} genau mittig zwischen {@code fromMin} und {@code fromMax}, + * dann ist das Ergebnis genau {@code interpolate(toMin, toMax, 0.5)}. + *

+ * Mit {@code map()} lassen sich Werte eines Originalintervalls auf ein + * Zielintervalls transponieren. Dies ist beispielsweise hilfreich, um aus + * der Mausbewegung die relative Position auf der Zeichenfläche zu + * bestimmen: + * + *


+	 * double relativeMouseX = map(mouseX, 0.0, canvasWidth, 0.0, 1.0);
+	 * 
+ * + * @param value Der Wert aus dem original Intervall. + * @param fromMin Untere Grenze des original Intervalls. + * @param fromMax Obere Grenze des original Intervalls. + * @param toMin Untere Grenze des Zielintervalls. + * @param toMax Obere Grenze des Zielintervalls. + * @return Der zugehörige Wert aus dem Zielintervall. + * @see #interpolate(double, double, double) + */ public static final double map( double value, double fromMin, double fromMax, double toMin, double toMax ) { return interpolate(toMin, toMax, (value - fromMin) / (fromMax - fromMin)); } /** - * Shared Random instance + * Geteilte {@code Random}-Instanz für einheitliche Zufallszahlen. */ private static Random random = null; - private static final Random getRandom() { + /** + * Gibt die geteilte {@code Random}-Instanz zurück. + * + * @return Die {@code Random}-Instanz. + */ + private static Random getRandom() { if( random == null ) { random = new Random(); } @@ -885,8 +1244,7 @@ public class Constants { * Erzeugt einen zufälligen Wahrheitswert. {@code true} wird mit der * Wahrscheinlichkeit {@code percent} Prozent erzeugt. * - * @param percent Eine Prozentzahl zwischen 0 und - * 100. + * @param percent Eine Prozentzahl zwischen 0 und 100. * @return Ein Wahrheitswert. */ public static final boolean randomBool( int percent ) { @@ -897,6 +1255,7 @@ public class Constants { * Erzeugt einen zufälligen Wahrheitswert. {@code true} wird mit der * Wahrscheinlichkeit {@code weight} erzeugt. * + * @param weight Wahrscheinlichkeit für {@code true}. * @return Ein Wahrheitswert. */ public static final boolean randomBool( double weight ) { @@ -917,6 +1276,7 @@ public class Constants { * Wählt ein zufälliges Element aus dem Array aus. * * @param values Ein Array mit Werten, die zur Auswahl stehen. + * @param Datentyp des Elements. * @return Ein zufälliges Element aus dem Array. */ public static final T choice( T[] values ) { @@ -924,9 +1284,11 @@ public class Constants { } /** - * Wählt ein zufälliges Element aus dem Array aus. + * Wählt die angegebene Anzahl Elemente aus dem Array aus. * * @param values Ein Array mit Werten, die zur Auswahl stehen. + * @param n Anzahl der auszuwählenden Elemente. + * @param Datentyp der Elemente. * @return Ein zufälliges Element aus dem Array. */ public static final T[] choice( T[] values, int n ) { @@ -937,50 +1299,165 @@ public class Constants { return (T[]) result; } + /** + * Geteilte {@code Noise}-Instanz zur Erzeugung von Perlin-Noise. + */ + private static Noise noise = null; + + /** + * Zähler für den letzten generierten Noise-Wert. + */ + private static int N = 0; + + /** + * Gibt die geteilte {@code Random}-Instanz zurück. + * + * @return Die {@code Random}-Instanz. + */ + private static Noise getNoise() { + if( noise == null ) { + noise = new Noise(getRandom()); + } + return noise; + } + /** * Erzeugt den nächsten Wert eines Perlin-Noise. * - * @return + * @return Ein zufälliger Wert. */ public static final double noise() { - // TODO: Implementieren - return 0.0; + return getNoise().noise(0.005 * N++); + } + + /** + * Erzeugt den nächsten Wert eines Perlin-Noise. + * + * @param x x-Wert für den Noise. + * @return Ein zufälliger Wert. + */ + public static final double noise( double x ) { + return getNoise().noise(x); + } + + /** + * Erzeugt den nächsten Wert eines zweidimensionalen Perlin-Noise. + * + * @param x x-Wert für den Noise. + * @param y y-Wert für den Noise. + * @return Ein zufälliger Wert. + */ + public static final double noise( double x, double y ) { + return getNoise().noise(x, y); + } + + /** + * Erzeugt den nächsten Wert eines dreidimensionalen Perlin-Noise. + * + * @param x x-Wert für den Noise. + * @param y y-Wert für den Noise. + * @param z z-Wert für den Noise. + * @return Ein zufälliger Wert. + */ + public static final double noise( double x, double y, double z ) { + return getNoise().noise(x, y, z); } // Typecasting - public static final int getInt( char value ) { + + /** + * Konvertiert das angegebenen Zeichen in eine ganze Zahl. Das Zeichen wird + * jeweils in seinen ASCII-Codepoint transformiert. + * + * @param value Der Wert. + * @return Eine ganze Zahl. + */ + public static final int asInt( char value ) { + return value; + } + + /** + * Konvertiert den angegebenen Wert in eine ganze Zahl. + * + * @param value Der Wert. + * @return Der Wert. + */ + public static final int asInt( byte value ) { + return value; + } + + /** + * Konvertiert den angegebenen Wert in eine ganze Zahl. + * + * @param value Der Wert. + * @return Der Wert. + */ + public static final int asInt( short value ) { + return value; + } + + /** + * Konvertiert den angegebenen Wert in eine ganze Zahl. Zahlen größer als + * {@link Integer#MAX_VALUE} werden auf {@code MAX_VALUE} reduziert. + * Entsprechendes gilt für Werte kleiner {@link Integer#MIN_VALUE}. + * + * @param value Der Wert. + * @return Eine ganze Zahl. + */ + public static final int asInt( long value ) { return (int) value; } - public static final int getInt( byte value ) { + /** + * Konvertiert den angegebenen Wert in eine ganze Zahl. + * + * @param value Der Wert. + * @return Der abgerundete Wert. + */ + public static final int asInt( double value ) { return (int) value; } - public static final int getInt( short value ) { + /** + * Konvertiert den angegebenen Wert in eine ganze Zahl. + * + * @param value Der Wert. + * @return Der abgerundete Wert. + */ + public static final int asInt( float value ) { return (int) value; } - public static final int getInt( long value ) { - return (int) value; + /** + * Konvertiert den angegebenen Wert in eine ganze Zahl. + * + * @param value Der Wert. + * @return Der Wert. + */ + public static final int asInt( int value ) { + return value; } - public static final int getInt( double value ) { - return (int) value; + /** + * Konvertiert den angegebenen Wahrheitswert in eine ganze Zahl. + * {@code true} entspricht 1, {@code false} wird zu 0. + * + * @param value Der Wert. + * @return 1 oder 0. + */ + public static final int asInt( boolean value ) { + return value ? 1 : 0; } - public static final int getInt( float value ) { - return (int) value; - } - - public static final int getInt( int value ) { - return (int) value; - } - - public static final int getInt( boolean value ) { - return value ? 0 : 1; - } - - public static final int getInt( String value ) { + /** + * Konvertiert den angegebenen Text in eine ganze Zahl. Kann der Text nicht + * umgewandelt werden, dann wird 0 zurückgegeben. + * + * @param value Der Wert. + * @return Eine ganze Zahl- + * @see Integer#parseInt(String) + */ + public static final int asInt( String value ) { try { return Integer.parseInt(value); } catch( NumberFormatException ex ) { @@ -988,39 +1465,39 @@ public class Constants { } } - public static final double getDouble( char value ) { + public static final double asDouble( char value ) { return (double) value; } - public static final double getDouble( byte value ) { - return (double) value; - } - - public static final double getDouble( short value ) { - return (double) value; - } - - public static final double getDouble( long value ) { - return (double) value; - } - - public static final double getDouble( double value ) { + public static final double asDouble( byte value ) { return value; } - public static final double getDouble( float value ) { + public static final double asDouble( short value ) { + return value; + } + + public static final double asDouble( long value ) { return (double) value; } - public static final double getDouble( int value ) { - return (double) value; + public static final double asDouble( double value ) { + return value; } - public static final double getDouble( boolean value ) { - return value ? 0.0 : 1.0; + public static final double asDouble( float value ) { + return value; } - public static final double getDouble( String value ) { + public static final double asDouble( int value ) { + return value; + } + + public static final double asDouble( boolean value ) { + return value ? 1.0 : 0.0; + } + + public static final double asDouble( String value ) { try { return Double.parseDouble(value); } catch( NumberFormatException ex ) { @@ -1028,43 +1505,99 @@ public class Constants { } } - public static final boolean getBool( char value ) { + public static final boolean asBool( char value ) { return value != 0; } - public static final boolean getBool( byte value ) { + public static final boolean asBool( byte value ) { return value != 0; } - public static final boolean getBool( short value ) { + public static final boolean asBool( short value ) { return value != 0; } - public static final boolean getBool( int value ) { + public static final boolean asBool( int value ) { return value != 0; } - public static final boolean getBool( long value ) { + public static final boolean asBool( long value ) { return value != 0L; } - public static final boolean getBool( double value ) { + public static final boolean asBool( double value ) { return value != 0.0; } - public static final boolean getBool( float value ) { + public static final boolean asBool( float value ) { return value != 0.0f; } - public static final boolean getBool( boolean value ) { + public static final boolean asBool( boolean value ) { return value; } - public static final boolean getBool( String value ) { + public static final boolean asBool( String value ) { return Boolean.parseBoolean(value); } - // Konstants for Key events (Copied from KeyEvent) + /** + * Formt die angegebene Zahl in ihre Binärdarstellung um. + * + *

+	 * int bin = binary(10); // "1010"
+	 * 
+ * + * @param i Eine Zahl. + * @return Die binäre Darstellung der Zahl als Text. + */ + public static final String binary( int i ) { + return Integer.toBinaryString(i); + } + + /** + * Formt die angegebene Binärzahl in eine Dezimalzahl um. + * + *

+	 * int dezimal = romBinary("1010"); // 10
+	 * 
+ * + * @param binary Ein Text nur aus "0" und "1". + * @return Der Wert der Binärzahl. + */ + public static final int fromBinary( String binary ) { + return Integer.valueOf(binary, 2); + } + + /** + * Formt die angegebene Zahl in ihre hexadezimal Darstellung um. + * + *

+	 * int hexa = hex(255); // "FF"
+	 * 
+ * + * @param i Eine Zahl. + * @return Die hexadezimal Darstellung der Zahl als Text. + */ + public static final String hex( int i ) { + return Integer.toHexString(i); + } + + /** + * Formt die angegebene Binärzahl in eine Dezimalzahl um. + * + *

+	 * int dezimal = romHex("FF"); // 255
+	 * 
+ * + * @param binary Ein Text nur aus den Zeichen "0" bis "9" und "A" bis "F". + * @return Der Wert der Binärzahl. + */ + public static final int fromHex( String binary ) { + return Integer.valueOf(binary, 16); + } + + // Konstants für Key events (Copied from KeyEvent) /** * Constant for the ENTER virtual key. @@ -1248,7 +1781,7 @@ public class Constants { */ public static final int KEY_EQUALS = KeyEvent.VK_EQUALS; - /** VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */ + /* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */ /** * Constant for the "A" key. diff --git a/src/main/java/schule/ngb/zm/Drawable.java b/src/main/java/schule/ngb/zm/Drawable.java index 3511f73..0b93f88 100644 --- a/src/main/java/schule/ngb/zm/Drawable.java +++ b/src/main/java/schule/ngb/zm/Drawable.java @@ -12,7 +12,7 @@ public interface Drawable { * Gibt an, ob das Objekt derzeit sichtbar ist (also gezeichnet werden * muss). * - * @return true, wenn das Objekt sichtbar ist. + * @return {@code true}, wenn das Objekt sichtbar ist. */ boolean isVisible(); diff --git a/src/main/java/schule/ngb/zm/DrawingLayer.java b/src/main/java/schule/ngb/zm/DrawingLayer.java index 4694527..4289aaf 100644 --- a/src/main/java/schule/ngb/zm/DrawingLayer.java +++ b/src/main/java/schule/ngb/zm/DrawingLayer.java @@ -191,7 +191,8 @@ public class DrawingLayer extends Layer { } public void pixel( double x, double y ) { - square(x, y, 1); + // square(x, y, 1); + buffer.setRGB((int)x, (int)y, fillColor.getRGBA()); } public void square( double x, double y, double w ) { diff --git a/src/main/java/schule/ngb/zm/Options.java b/src/main/java/schule/ngb/zm/Options.java index 70cf071..d903367 100644 --- a/src/main/java/schule/ngb/zm/Options.java +++ b/src/main/java/schule/ngb/zm/Options.java @@ -42,6 +42,7 @@ public final class Options { public enum Direction { CENTER(0, 0), + NORTH(0, -1), EAST(1, 0), SOUTH(0, 1), @@ -49,14 +50,19 @@ public final class Options { NORTHEAST(1, -1), SOUTHEAST(1, 1), - NORTHWEST(-1, -1), SOUTHWEST(-1, 1), + NORTHWEST(-1, -1), MIDDLE(CENTER), UP(NORTH), + RIGHT(EAST), DOWN(SOUTH), LEFT(WEST), - RIGHT(EAST); + + UPLEFT(NORTHWEST), + DOWNLEFT(SOUTHWEST), + DOWNRIGHT(SOUTHEAST), + UPRIGHT(NORTHEAST); public final byte x, y; diff --git a/src/main/java/schule/ngb/zm/Updatable.java b/src/main/java/schule/ngb/zm/Updatable.java index 0a60447..0998e1c 100644 --- a/src/main/java/schule/ngb/zm/Updatable.java +++ b/src/main/java/schule/ngb/zm/Updatable.java @@ -9,7 +9,7 @@ public interface Updatable { /** * Gibt an, ob das Objekt gerade auf Aktualisierungen reagiert. - * @return true, wenn das Objekt aktiv ist. + * @return {@code true}, wenn das Objekt aktiv ist. */ public boolean isActive(); diff --git a/src/main/java/schule/ngb/zm/Zeichenleinwand.java b/src/main/java/schule/ngb/zm/Zeichenleinwand.java index 73b9bae..d59fdaf 100644 --- a/src/main/java/schule/ngb/zm/Zeichenleinwand.java +++ b/src/main/java/schule/ngb/zm/Zeichenleinwand.java @@ -1,7 +1,5 @@ package schule.ngb.zm; -import schule.ngb.zm.shapes.ShapesLayer; - import java.awt.Canvas; import java.awt.Graphics; import java.awt.Graphics2D; @@ -37,12 +35,12 @@ public class Zeichenleinwand extends Canvas { super.setSize(width, height); this.setPreferredSize(this.getSize()); this.setMinimumSize(this.getSize()); - this.setBackground(Constants.STD_BACKGROUND.getJavaColor()); + this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor()); // Liste der Ebenen initialisieren und die Standardebenen einfügen layers = new LinkedList<>(); synchronized( layers ) { - layers.add(new ColorLayer(width, height, Constants.STD_BACKGROUND)); + layers.add(new ColorLayer(width, height, Constants.DEFAULT_BACKGROUND)); } } @@ -90,7 +88,7 @@ public class Zeichenleinwand extends Canvas { * als letzte eingefügt. Die aufrufende Methode kann also nicht sicher sein, * dass die neue Ebene am Ende wirklich am Index {@code i} steht. * - * @param i Index der Ebene, beginnend mit 0. + * @param i Index der Ebene, beginnend mit 0. * @param layer Die neue Ebene. */ public void addLayer( int i, Layer layer ) { @@ -125,10 +123,10 @@ public class Zeichenleinwand extends Canvas { } /** - * Holt die Ebene am Index i (beginnend bei 0). + * Holt die Ebene am Index i (beginnend bei 0). * - * @param i Index der Ebene (beginnend bei 0). - * @return Die Ebene am Index i oder null. + * @param i Index der Ebene (beginnend bei 0). + * @return Die Ebene am Index i oder {@code null}. * @throws IndexOutOfBoundsException Falls der Index nicht existiert. */ public Layer getLayer( int i ) { @@ -141,7 +139,7 @@ public class Zeichenleinwand extends Canvas { /** * Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen. - * Existiert keine solche Ebene, wird null zurückgegeben. + * Existiert keine solche Ebene, wird {@code null} zurückgegeben. * * @param clazz Typ der Ebene. * @param diff --git a/src/main/java/schule/ngb/zm/Zeichenmaschine.java b/src/main/java/schule/ngb/zm/Zeichenmaschine.java index f5849cb..0979b6a 100644 --- a/src/main/java/schule/ngb/zm/Zeichenmaschine.java +++ b/src/main/java/schule/ngb/zm/Zeichenmaschine.java @@ -277,8 +277,8 @@ public class Zeichenmaschine extends Constants { } // Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen. - this.width = width; - this.height = height; + this.canvasWidth = width; + this.canvasHeight = height; java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds(); this.screenWidth = (int) displayBounds.getWidth(); this.screenHeight = (int) displayBounds.getHeight(); @@ -291,6 +291,7 @@ public class Zeichenmaschine extends Constants { // Das Icon des Fensters ändern try { + // TODO: Add image sizes ImageIcon icon = new ImageIcon(ImageIO.read(new File("res/icon_64.png"))); if( MACOS ) { @@ -334,7 +335,10 @@ public class Zeichenmaschine extends Constants { frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing( WindowEvent e ) { - exit(); + //exit(); + teardown(); + cleanup(); + quit(true); } }); @@ -408,8 +412,8 @@ public class Zeichenmaschine extends Constants { frame.setResizable(false); // Should be set anyway displayDevice.setFullScreenWindow(frame); // Update width / height - initialWidth = width; - initialHeight = height; + initialWidth = canvasWidth; + initialHeight = canvasHeight; changeSize(screenWidth, screenHeight); // Register ESC as exit fullscreen canvas.addKeyListener(fullscreenExitListener); @@ -595,11 +599,11 @@ public class Zeichenmaschine extends Constants { * @see #setFullscreen(boolean) */ private void changeSize( int newWidth, int newHeight ) { - width = Math.min(Math.max(newWidth, 100), screenWidth); - height = Math.min(Math.max(newHeight, 100), screenHeight); + canvasWidth = Math.min(Math.max(newWidth, 100), screenWidth); + canvasHeight = Math.min(Math.max(newHeight, 100), screenHeight); if( canvas != null ) { - canvas.setSize(width, height); + canvas.setSize(canvasWidth, canvasHeight); } } @@ -629,7 +633,7 @@ public class Zeichenmaschine extends Constants { * @return Die Breite der {@link Zeichenleinwand}. */ public final int getWidth() { - return width; + return canvasWidth; } /** @@ -638,7 +642,7 @@ public class Zeichenmaschine extends Constants { * @return Die Höhe der {@link Zeichenleinwand}. */ public final int getHeight() { - return height; + return canvasHeight; } /** @@ -718,7 +722,7 @@ public class Zeichenmaschine extends Constants { public final ColorLayer getBackgroundLayer() { ColorLayer layer = canvas.getLayer(ColorLayer.class); if( layer == null ) { - layer = new ColorLayer(STD_BACKGROUND); + layer = new ColorLayer(DEFAULT_BACKGROUND); canvas.addLayer(0, layer); } return layer; @@ -812,7 +816,7 @@ public class Zeichenmaschine extends Constants { BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight()); Graphics2D g = img.createGraphics(); - g.setColor(STD_BACKGROUND.getJavaColor()); + g.setColor(DEFAULT_BACKGROUND.getJavaColor()); g.fillRect(0, 0, img.getWidth(), img.getHeight()); canvas.draw(g); g.dispose(); @@ -836,7 +840,7 @@ public class Zeichenmaschine extends Constants { BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight()); Graphics2D g = img.createGraphics(); - g.setColor(STD_BACKGROUND.getJavaColor()); + g.setColor(DEFAULT_BACKGROUND.getJavaColor()); g.fillRect(0, 0, img.getWidth(), img.getHeight()); canvas.draw(g); g.dispose(); @@ -1033,7 +1037,7 @@ public class Zeichenmaschine extends Constants { * @param delta */ public void update( double delta ) { - //running = !run_once; + running = !run_once; stop_after_update = run_once; } @@ -1048,7 +1052,7 @@ public class Zeichenmaschine extends Constants { * dar, da hier die Zeichnung des Programms erstellt wird. */ public void draw() { - running = !stop_after_update; + //running = !stop_after_update; } /** @@ -1166,7 +1170,7 @@ public class Zeichenmaschine extends Constants { break; case MouseEvent.MOUSE_RELEASED: mousePressed = false; - mouseButton = NOBUTTON; + mouseButton = NOMOUSE; mousePressed(evt); break; case MouseEvent.MOUSE_DRAGGED: diff --git a/src/main/java/schule/ngb/zm/media/Audio.java b/src/main/java/schule/ngb/zm/media/Audio.java index 1922946..fa64162 100644 --- a/src/main/java/schule/ngb/zm/media/Audio.java +++ b/src/main/java/schule/ngb/zm/media/Audio.java @@ -18,8 +18,8 @@ public interface Audio { * {@code isLooping() == true}, dann muss auch immer * {@code isPlaying() == true} gelten. * - * @return {@code true}, wenn das Medium in einer Schleife - * abgespielt wird, {@code false} sonst. + * @return {@code true}, wenn das Medium in einer Schleife abgespielt wird, + * {@code false} sonst. */ boolean isLooping(); @@ -36,17 +36,28 @@ public interface Audio { */ void setVolume( double volume ); + /** + * Gibt die aktuelle Lautstärkeeinstellung dieses Mediums zurück. + *

+ * Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton + * und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des + * Mediums. + * + * @return Die Lautstärke als linear skalierter Wert. + */ + double getVolume(); + /** * Startet die Wiedergabe des Mediums und beendet die Methode. Das * Audio-Medium wird einmal abgespielt und stoppt dann. */ - void playOnce(); + void play(); /** * Startet die Wiedergabe des Mediums und blockiert das Programm, bis die * Wiedergabe beendet ist. */ - void playOnceAndWait(); + void playAndWait(); /** * Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode diff --git a/src/main/java/schule/ngb/zm/media/Mixer.java b/src/main/java/schule/ngb/zm/media/Mixer.java index 91b2064..f94ec4f 100644 --- a/src/main/java/schule/ngb/zm/media/Mixer.java +++ b/src/main/java/schule/ngb/zm/media/Mixer.java @@ -50,30 +50,53 @@ public class Mixer implements Audio { pAudio.setVolume(pVolumeFactor * volume); } + /** + * {@inheritDoc} + */ @Override public boolean isPlaying() { return audios.stream().anyMatch(aw -> aw.audio.isPlaying()); } + /** + * {@inheritDoc} + */ @Override public boolean isLooping() { return audios.stream().anyMatch(aw -> aw.audio.isLooping()); } + /** + * {@inheritDoc} + */ @Override public void setVolume( double pVolume ) { volume = (float) pVolume; audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume)); } + /** + * {@inheritDoc} + */ @Override - public void playOnce() { - audios.stream().forEach(aw -> aw.audio.playOnce()); + public double getVolume() { + return volume; } + /** + * {@inheritDoc} + */ @Override - public void playOnceAndWait() { - audios.stream().forEach(aw -> aw.audio.playOnce()); + public void play() { + audios.stream().forEach(aw -> aw.audio.play()); + } + + /** + * {@inheritDoc} + */ + @Override + public void playAndWait() { + audios.stream().forEach(aw -> aw.audio.play()); while( audios.stream().anyMatch(aw -> aw.audio.isPlaying()) ) { try { Thread.sleep(10); @@ -83,16 +106,25 @@ public class Mixer implements Audio { } } + /** + * {@inheritDoc} + */ @Override public void loop() { audios.stream().forEach(aw -> aw.audio.loop()); } + /** + * {@inheritDoc} + */ @Override public void stop() { audios.stream().forEach(aw -> aw.audio.stop()); } + /** + * {@inheritDoc} + */ @Override public void dispose() { if( isPlaying() ) { @@ -101,6 +133,18 @@ public class Mixer implements Audio { audios.stream().forEach(aw -> aw.audio.dispose()); } + /** + * Ändert die Lautstärke aller hinzugefügten Audiomedien in der angegebenen + * Zeit schrittweise, bis die angegebene Lautstärke erreicht ist. + *

+ * Zu beachten ist, dass die Lautstärke des Mixers angepasst wird. Das + * bedeutet, dass die Lautstärke der hinzugefügten Medien mit ihrem + * Lautstärkefaktor multipliziert werden. Die Medien haben am Ende also + * nicht unbedingt die Lautstärke {@code to}. + * + * @param to Der Zielwert für die Lautstärke. + * @param time Die Zeit, nach der die Änderung abgeschlossen sein soll. + */ public void fade( final double to, final int time ) { TaskRunner.run(new Runnable() { @Override @@ -108,9 +152,6 @@ public class Mixer implements Audio { final long start = System.currentTimeMillis(); double t = 0.0; double from = volume; - if( !isPlaying() ) { - playOnce(); - } do { setVolume(Constants.interpolate(from, to, t)); t = (double) (System.currentTimeMillis() - start) / (double) time; diff --git a/src/main/java/schule/ngb/zm/media/Music.java b/src/main/java/schule/ngb/zm/media/Music.java index fa03cf3..72ad34c 100644 --- a/src/main/java/schule/ngb/zm/media/Music.java +++ b/src/main/java/schule/ngb/zm/media/Music.java @@ -1,43 +1,82 @@ package schule.ngb.zm.media; import schule.ngb.zm.tasks.TaskRunner; +import schule.ngb.zm.util.Log; import schule.ngb.zm.util.ResourceStreamProvider; +import schule.ngb.zm.util.Validator; import javax.sound.sampled.*; -import java.io.BufferedInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URL; -import java.util.logging.Level; -import java.util.logging.Logger; +/** + * Ein Musikstück, dass im Projekt abgespielt werden soll. + *

+ * Im gegensatz zu einem {@link Sound} sind Musikstücke längere Audiodateien, + * die zum Beispiel als Hintergrundmusik ablaufen sollen. Die Musik wird daher + * nicht komplett in den Speicher geladen, sondern direkt aus der Audioquelle + * gestreamt und wiedergegeben. + */ public class Music implements Audio { // size of the byte buffer used to read/write the audio stream private static final int BUFFER_SIZE = 4096; + /** + * Ob der Sound gerade abgespielt wird. + */ private boolean playing = false; + /** + * Ob der Sound gerade in einer Schleife abgespielt wird. + */ private boolean looping = false; + /** + * Die Quelle des Musikstücks. + */ private String audioSource; + /** + * Der AudioStream, um die AUdiosdaten zulsen, falls dieser schon geöffnet + * wurde. Sonst {@code null}. + */ private AudioInputStream audioStream; + /** + * Die Line für die Ausgabe, falls diese schon geöffnet wurde. Sonst + * {@code null}. + */ private SourceDataLine audioLine; + /** + * Die Lautstärke der Musik. + */ private float volume = 0.8f; + /** + * Erstellt eine Musik aus der angegebenen Datei oder Webadresse. + * + * @param source Ein Dateipfad oder eine Webadresse. + * @throws NullPointerException Falls die Quelle {@code null} ist. + */ public Music( String source ) { + Validator.requireNotNull(source); this.audioSource = source; } + /** + * {@inheritDoc} + */ @Override public boolean isPlaying() { return playing; } + /** + * {@inheritDoc} + */ @Override public boolean isLooping() { if( !playing ) { @@ -46,6 +85,9 @@ public class Music implements Audio { return looping; } + /** + * {@inheritDoc} + */ @Override public void setVolume( double volume ) { this.volume = (float) volume; @@ -54,6 +96,18 @@ public class Music implements Audio { } } + /** + * {@inheritDoc} + */ + @Override + public double getVolume() { + return volume; + } + + /** + * Interne Methode, um die gesetzte Lautstärke vor dem Abspielen + * anzuwenden. + */ private void applyVolume() { FloatControl gainControl = (FloatControl) audioLine.getControl(FloatControl.Type.MASTER_GAIN); @@ -63,8 +117,11 @@ public class Music implements Audio { gainControl.setValue(vol); } + /** + * {@inheritDoc} + */ @Override - public void playOnce() { + public void play() { if( openLine() ) { TaskRunner.run(new Runnable() { @Override @@ -75,19 +132,28 @@ public class Music implements Audio { } } + /** + * {@inheritDoc} + */ @Override - public void playOnceAndWait() { + public void playAndWait() { if( openLine() ) { stream(); } } + /** + * {@inheritDoc} + */ @Override public void loop() { looping = true; - playOnce(); + play(); } + /** + * {@inheritDoc} + */ @Override public void stop() { playing = false; @@ -95,6 +161,9 @@ public class Music implements Audio { dispose(); } + /** + * {@inheritDoc} + */ @Override public void dispose() { if( audioLine != null ) { @@ -112,7 +181,8 @@ public class Music implements Audio { if( audioStream != null ) { audioStream.close(); } - } catch( IOException ex ) {} + } catch( IOException ex ) { + } audioLine = null; audioStream = null; @@ -126,15 +196,14 @@ public class Music implements Audio { int bytesRead = -1; try { - while (playing && (bytesRead = audioStream.read(bytesBuffer)) != -1) { + while( playing && (bytesRead = audioStream.read(bytesBuffer)) != -1 ) { audioLine.write(bytesBuffer, 0, bytesRead); } audioLine.drain(); audioLine.stop(); } catch( IOException ex ) { - LOGGER.warning("Error while playing Music source <" + audioSource + ">"); - LOGGER.throwing("Music", "stream", ex); + LOG.warn(ex, "Error while playing Music source <%s>", audioSource); } // Wait for the remaining audio to play @@ -163,7 +232,7 @@ public class Music implements Audio { final int ch = format.getChannels(); final float rate = format.getSampleRate(); - AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch*2, rate, false); + AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch * 2, rate, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat); @@ -174,14 +243,14 @@ public class Music implements Audio { audioStream = AudioSystem.getAudioInputStream(outFormat, inStream); return true; } else { - LOGGER.warning("Sound source <" + audioSource + "> could not be played: No audio source found."); + LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource); } } catch( UnsupportedAudioFileException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: The specified audio file is not supported.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource); } catch( LineUnavailableException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Audio line for playing back is unavailable.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource); } catch( IOException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Error playing the audio file.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource); } return false; } @@ -201,7 +270,6 @@ public class Music implements Audio { } } - //private static final Logger LOGGER = Logger.getLogger("schule.ngb.zm.media.Music"); - private static final Logger LOGGER = Logger.getLogger(Music.class.getName()); + private static final Log LOG = Log.getLogger(Music.class); } diff --git a/src/main/java/schule/ngb/zm/media/Sound.java b/src/main/java/schule/ngb/zm/media/Sound.java index ceda917..ece650d 100644 --- a/src/main/java/schule/ngb/zm/media/Sound.java +++ b/src/main/java/schule/ngb/zm/media/Sound.java @@ -1,35 +1,84 @@ package schule.ngb.zm.media; +import schule.ngb.zm.util.Log; import schule.ngb.zm.util.ResourceStreamProvider; +import schule.ngb.zm.util.Validator; import javax.sound.sampled.*; import java.io.IOException; import java.io.InputStream; import java.util.logging.Logger; +/** + * Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden. + *

+ * In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer Sounds, + * die zusammen mit bestimmten Aktionen wiedergegeben werden (zum Beispiel, wenn + * die Spielfigur springt, wenn zwei Objekte kollidieren, usw.). Sounds werden + * komplett in den Speicher geladen und können dadurch immer wieder, als + * Schleife oder auch nur Abschnittsweise abgespielt werden. + *

+ * Für längre Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher die + * KLasse {@link Music} an. + */ +@SuppressWarnings( "unused" ) public class Sound implements Audio { + /** + * Ob der Sound gerade abgespielt wird. + */ private boolean playing = false; + /** + * Ob der Sound gerade in einer Schleife abgespielt wird. + */ private boolean looping = false; + /** + * Die Quelle des Musikstücks. + */ private String audioSource; + /** + * Der Clip, falls er schon geladen wurde, sonst {@code null}. + */ private Clip audioClip; + /** + * Ob die Resourcen des Clips im Speicher nach dem nächsten Abspielen + * freigegeben werden sollen. + */ private boolean disposeAfterPlay = false; + /** + * Die Lautstärke des Clips. + */ private float volume = 0.8f; + /** + * Erstellt einen Sound aus der angegebene Quelle. + * + * @param source Ein Dateipfad oder eine Webadresse. + * @throws NullPointerException Falls die Quelle {@code null} ist. + */ public Sound( String source ) { + Validator.requireNotNull(source); this.audioSource = source; } + /** + * {@inheritDoc} + */ + @Override public boolean isPlaying() { // return audioClip != null && audioClip.isRunning(); return playing; } + /** + * {@inheritDoc} + */ + @Override public boolean isLooping() { if( !playing ) { looping = false; @@ -37,6 +86,10 @@ public class Sound implements Audio { return looping; } + /** + * {@inheritDoc} + */ + @Override public void setVolume( double volume ) { this.volume = (float) volume; if( audioClip != null ) { @@ -44,6 +97,18 @@ public class Sound implements Audio { } } + /** + * {@inheritDoc} + */ + @Override + public double getVolume() { + return volume; + } + + /** + * Interne Methode, um die gesetzte Lautstärke vor dem Abspielen + * anzuwenden. + */ private void applyVolume() { FloatControl gainControl = (FloatControl) audioClip.getControl(FloatControl.Type.MASTER_GAIN); @@ -53,6 +118,10 @@ public class Sound implements Audio { gainControl.setValue(vol); } + /** + * {@inheritDoc} + */ + @Override public void stop() { looping = false; if( audioClip.isRunning() ) { @@ -61,6 +130,10 @@ public class Sound implements Audio { playing = false; } + /** + * {@inheritDoc} + */ + @Override public void play() { if( this.openClip() ) { audioClip.start(); @@ -68,16 +141,10 @@ public class Sound implements Audio { } } - public void playOnce() { - disposeAfterPlay = true; - play(); - } - - public void playOnceAndWait() { - disposeAfterPlay = true; - playAndWait(); - } - + /** + * {@inheritDoc} + */ + @Override public void playAndWait() { this.play(); @@ -94,26 +161,73 @@ public class Sound implements Audio { audioClip.close(); } + /** + * Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips + * frei. + *

+ * Der Aufruf ist effektiv gleich zu + *


+	 * clip.playAndWait();
+	 * clip.dispose();
+	 * 
+ * allerdings wird der aufrufende Thread nicht blockiert und + * {@link #dispose()} automatisch am Ende aufgerufen. + */ + public void playOnce() { + disposeAfterPlay = true; + play(); + } + + /** + * Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips + * frei. + *

+ * Der Aufruf entspricht + *


+	 * clip.playAndWait();
+	 * clip.dispose();
+	 * 
+ */ + public void playOnceAndWait() { + disposeAfterPlay = true; + playAndWait(); + } + + /** + * {@inheritDoc} + */ + @Override public void loop() { loop(Clip.LOOP_CONTINUOUSLY); } + /** + * Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt + * die Wiedergabe dann. + * @param count Anzahl der Wiederholungen. + */ public void loop( int count ) { - int loopCount = count; - if( loopCount != Clip.LOOP_CONTINUOUSLY ) { - if( loopCount <= 0 ) { - return; + if( count > 0 ) { + int loopCount = count; + if( loopCount != Clip.LOOP_CONTINUOUSLY ) { + if( loopCount <= 0 ) { + return; + } + // Adjust Number of loops + loopCount -= 1; + } + if( openClip() ) { + looping = true; + audioClip.loop(loopCount); + playing = true; } - // Adjust Number of loops - loopCount -= 1; - } - if( openClip() ) { - looping = true; - audioClip.loop(loopCount); - playing = true; } } + /** + * {@inheritDoc} + */ + @Override public void dispose() { if( audioClip != null ) { if( audioClip.isRunning() ) { @@ -150,17 +264,14 @@ public class Sound implements Audio { applyVolume(); return true; } else { - LOGGER.warning("Sound source " + audioSource + " could not be played: No audio source found."); + LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource); } } catch( UnsupportedAudioFileException ex ) { - LOGGER.warning("Sound source " + audioSource + " could not be played: The specified audio file is not supported."); - LOGGER.throwing("Sound", "openClip", ex); + LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource); } catch( LineUnavailableException ex ) { - LOGGER.warning("Sound source " + audioSource + " could not be played: Audio line for playing back is unavailable."); - LOGGER.throwing("Sound", "openClip", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource); } catch( IOException ex ) { - LOGGER.warning("Sound source " + audioSource + " could not be played: Error playing the audio file."); - LOGGER.throwing("Sound", "openClip", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource); } return false; } @@ -180,6 +291,11 @@ public class Sound implements Audio { } }*/ + /** + * Interne Methode, die aufgerufen wird, wenn die Wiedergabe gestoppt wird. + * Entweder durch einen Aufruf von {@link #stop()} oder, weil die Wiedergabe + * nach {@link #playOnce()} beendet wurde. + */ private void playbackStopped() { playing = false; if( disposeAfterPlay ) { @@ -188,17 +304,6 @@ public class Sound implements Audio { } } - /* - public void addLineListener( LineListener listener ) { - if( audioClip == null ) { - openClip(); - } - if( audioClip != null ) { - audioClip.addLineListener(listener); - } - } - */ - - private static final Logger LOGGER = Logger.getLogger(Sound.class.getName()); + private static final Log LOG = Log.getLogger(Sound.class); } diff --git a/src/main/java/schule/ngb/zm/shapes/Shape.java b/src/main/java/schule/ngb/zm/shapes/Shape.java index 119e107..d8219f3 100644 --- a/src/main/java/schule/ngb/zm/shapes/Shape.java +++ b/src/main/java/schule/ngb/zm/shapes/Shape.java @@ -168,8 +168,7 @@ public abstract class Shape extends FilledShape { *

* Unterklassen sollten diese Methode überschreiben, um weitere * Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). Mit - * dem Aufruf - * super.copyFrom(shape) sollten die Basiseigenschaften + * dem Aufruf {@code super.copyFrom(shape)} sollten die Basiseigenschaften * kopiert werden. * * @param shape @@ -243,7 +242,7 @@ public abstract class Shape extends FilledShape { } public void alignTo( Options.Direction dir, double buff ) { - Point2D anchorShape = Shape.getAnchorPoint(width, height, dir); + Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir); Point2D anchorThis = this.getAbsAnchorPoint(dir); this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff; diff --git a/src/main/java/schule/ngb/zm/shapes/ShapeGroup.java b/src/main/java/schule/ngb/zm/shapes/ShapeGroup.java index 46a56d5..b8548da 100644 --- a/src/main/java/schule/ngb/zm/shapes/ShapeGroup.java +++ b/src/main/java/schule/ngb/zm/shapes/ShapeGroup.java @@ -5,6 +5,7 @@ import schule.ngb.zm.Options; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; +import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -28,9 +29,20 @@ import java.util.List; * benötigen, erst nach Hinzufügen der Gruppenelemente ausgeführt werden. * Nachdem sich die Zusammensetzung der Gruppe geändert hat, muss die Gruppe * ggf. neu ausgerichtet werden. + *

+ * Für die Ausrichtung der Elemente in einer Gruppe können + * {@link #arrange(Options.Direction, double)}, + * {@link #arrangeInGrid(int, Options.Direction, double, int)} und + * {@link #align(Options.Direction)} verwendet werden, die jeweils die Position + * der Formen in der Gruppe ändern und nicht die Position der Gruppe selbst (so + * wie z.B. {@link #alignTo(Shape, Options.Direction)}. */ public class ShapeGroup extends Shape { + public static final int ARRANGE_ROWS = 0; + + public static final int ARRANGE_COLS = 1; + private List shapes; private double groupWidth = -1.0; @@ -132,6 +144,111 @@ public class ShapeGroup extends Shape { return groupHeight; } + public void arrange( Options.Direction dir, double buffer ) { + Shape last = null; + for( Shape s : shapes ) { + if( last != null ) { + s.nextTo(last, dir, buffer); + } else { + s.moveTo(0, 0); + } + last = s; + } + invalidateBounds(); + } + + public void arrangeInRows( int n, Options.Direction dir, double buffer ) { + arrangeInGrid(n, dir, buffer, ARRANGE_ROWS); + } + + public void arrangeInColumns( int n, Options.Direction dir, double buffer ) { + arrangeInGrid(n, dir, buffer, ARRANGE_COLS); + } + + public void arrangeInGrid( int n, Options.Direction dir, double buffer, int mode ) { + // Calculate grid size + int rows, cols; + if( mode == ARRANGE_ROWS ) { + rows = n; + cols = (int) ceil(shapes.size() / n); + } else { + cols = n; + rows = (int) ceil(shapes.size() / n); + } + + // Calculate grid cell size + double maxHeight = shapes.stream().mapToDouble( + ( s ) -> s.getHeight() + ).reduce(0.0, Double::max); + double maxWidth = shapes.stream().mapToDouble( + ( s ) -> s.getWidth() + ).reduce(0.0, Double::max); + double halfHeight = maxHeight * .5; + double halfWidth = maxWidth * .5; + + // Layout shapes + for( int i = 0; i < shapes.size(); i++ ) { + // Calculate center of grid cell + int row, col; + switch( dir ) { + case UP: + case NORTH: + row = rows - i % rows; + col = cols - (i / rows); + break; + case LEFT: + case WEST: + row = rows - (i / cols); + col = cols - i % cols; + break; + case RIGHT: + case EAST: + row = i / cols; + col = i % cols; + break; + case DOWN: + case SOUTH: + default: + row = i % rows; + col = i / rows; + break; + } + + double centerX = halfWidth + col * (maxWidth + buffer); + double centerY = halfHeight + row * (maxHeight + buffer); + + // Move shape to proper anchor location in cell + Shape s = shapes.get(i); + Point2D ap = Shape.getAnchorPoint(maxWidth, maxHeight, s.getAnchor()); + s.moveTo(centerX + ap.getX(), centerY + ap.getY()); + } + + invalidateBounds(); + } + + public void align( Options.Direction dir ) { + Shape target = shapes.stream().reduce(null, + ( t, s ) -> { + if( t == null ) return s; + Point2D apt = t.getAbsAnchorPoint(dir); + Point2D aps = s.getAbsAnchorPoint(dir); + if( apt.getX() * dir.x >= aps.getX() * dir.x && apt.getY() * dir.y >= aps.getY() * dir.y ) { + return t; + } else { + return s; + } + } + ); + + for( Shape s : shapes ) { + if( s != target ) { + s.alignTo(target, dir); + } + } + + invalidateBounds(); + } + private void invalidateBounds() { groupWidth = -1.0; groupHeight = -1.0; @@ -148,6 +265,25 @@ public class ShapeGroup extends Shape { maxy = Math.max(maxy, bounds.y + bounds.height); } + //groupWidth = maxx - minx; + //groupHeight = maxy - miny; + groupWidth = maxx; + groupHeight = maxy; + } + + public void pack() { + double minx = Double.MAX_VALUE, miny = Double.MAX_VALUE, + maxx = Double.MIN_VALUE, maxy = Double.MIN_VALUE; + for( Shape pShape : shapes ) { + Bounds bounds = pShape.getBounds(); + minx = Math.min(minx, bounds.x); + maxx = Math.max(maxx, bounds.x + bounds.width); + miny = Math.min(miny, bounds.y); + maxy = Math.max(maxy, bounds.y + bounds.height); + } + + x = minx; + y = miny; groupWidth = maxx - minx; groupHeight = maxy - miny; } diff --git a/src/main/java/schule/ngb/zm/shapes/StrokedShape.java b/src/main/java/schule/ngb/zm/shapes/StrokedShape.java index 1dc1290..5f98976 100644 --- a/src/main/java/schule/ngb/zm/shapes/StrokedShape.java +++ b/src/main/java/schule/ngb/zm/shapes/StrokedShape.java @@ -4,8 +4,14 @@ import schule.ngb.zm.Color; import schule.ngb.zm.Constants; import schule.ngb.zm.Drawable; import schule.ngb.zm.Options; +import schule.ngb.zm.util.Noise; import java.awt.*; +import java.awt.Shape; +import java.awt.geom.FlatteningPathIterator; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.util.Arrays; public abstract class StrokedShape extends Constants implements Drawable { @@ -26,7 +32,7 @@ public abstract class StrokedShape extends Constants implements Drawable { this.strokeColor = color; } - public void setStrokeColor( Color color , int alpha ) { + public void setStrokeColor( Color color, int alpha ) { setStrokeColor(new Color(color, alpha)); } @@ -66,10 +72,11 @@ public abstract class StrokedShape extends Constants implements Drawable { /** * Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED}, * {@link #DOTTED} und {@link #SOLID}. + * * @param type */ public void setStrokeType( Options.StrokeType type ) { - this.strokeType = DASHED; + this.strokeType = type; this.stroke = null; } @@ -78,9 +85,11 @@ public abstract class StrokedShape extends Constants implements Drawable { /** * Erstellt ein {@link Stroke} Objekt für den Konturtyp. + * * @return */ protected Stroke createStroke() { + // TODO: Used global cached Stroke Objects? if( stroke == null ) { switch( strokeType ) { case DOTTED: diff --git a/src/main/java/schule/ngb/zm/util/FileLoader.java b/src/main/java/schule/ngb/zm/util/FileLoader.java new file mode 100644 index 0000000..3ffa18e --- /dev/null +++ b/src/main/java/schule/ngb/zm/util/FileLoader.java @@ -0,0 +1,88 @@ +package schule.ngb.zm.util; + +import java.io.*; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public final class FileLoader { + + public static final Charset ASCII = StandardCharsets.US_ASCII; + + public static final Charset UTF8 = StandardCharsets.UTF_8; + + public static final Charset UTF16 = StandardCharsets.UTF_16; + + public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; + + + public static List loadLines( String source ) { + return loadLines(source, UTF8); + } + + public static List loadLines( String source, Charset charset ) { + try { + return Files.readAllLines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset); + } catch( IOException | URISyntaxException ex ) { + LOG.warn(ex, "Error while loading lines from source <%s>", source); + } + + return Collections.EMPTY_LIST; + } + + public static String loadText( String source ) { + return loadText(source, UTF8); + } + + public static String loadText( String source, Charset charset ) { + try { + return Files.readString(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset); + } catch( IOException | URISyntaxException ex ) { + LOG.warn(ex, "Error while loading text from source <%s>", source); + } + + return ""; + } + + public static double[][] loadDoubles( String source, char separator, boolean skipFirst ) { + return loadDoubles(source, separator, skipFirst, UTF8); + } + + public static double[][] loadDoubles( String source, char separator, boolean skipFirst, Charset charset ) { + try { + int n = skipFirst ? 1 : 0; + return Files + .lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset) + .skip(n) + .map( + (line) -> Arrays + .stream(line.split(Character.toString(separator))) + .mapToDouble( + (value) -> { + try { + return Double.parseDouble(value); + } catch( NumberFormatException nfe ) { + return 0.0; + } + } + ).toArray() + ).toArray(double[][]::new); + } catch( IOException | URISyntaxException ex ) { + LOG.warn(ex, "Error while loading double values from csv source <%s>", source); + } + + return new double[0][0]; + } + + public FileLoader() { + } + + private static final Log LOG = Log.getLogger(FileLoader.class); + +} diff --git a/src/main/java/schule/ngb/zm/util/ImageLoader.java b/src/main/java/schule/ngb/zm/util/ImageLoader.java index 3bddbfb..07010a8 100644 --- a/src/main/java/schule/ngb/zm/util/ImageLoader.java +++ b/src/main/java/schule/ngb/zm/util/ImageLoader.java @@ -17,7 +17,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; -public class ImageLoader { +public final class ImageLoader { public static boolean caching = true; @@ -47,7 +47,7 @@ public class ImageLoader { /** * Lädt ein Bild von der angegebenen Quelle source und gibt das - * Bild zurück oder null, wenn das Bild nicht geladen werden + * Bild zurück oder {@code null}, wenn das Bild nicht geladen werden * konnte. Ist ein Bild mit der angegebenen Quelle im Cache, wird das * gespeicherte Bild zurückgegeben. Dies kann mit {@code caching = false} * verhindert werden. diff --git a/src/main/java/schule/ngb/zm/util/Noise.java b/src/main/java/schule/ngb/zm/util/Noise.java new file mode 100644 index 0000000..e638f49 --- /dev/null +++ b/src/main/java/schule/ngb/zm/util/Noise.java @@ -0,0 +1,366 @@ +package schule.ngb.zm.util; + +import java.util.Random; + +/** + * Zufallsgenerator für Perlin-Noise. + *

+ * Die Implementierung basiert auf dem von Ken Perlin entwickelten Algorithmus + * und wurde anhand der Beschreibung von + * FLAFLA2 implementiert. + */ +public class Noise { + + private static final int N = 256; + + private static final int M = N - 1; + + /** + * Interne Permutationstabelle für diesen Generator + */ + private int[] p; + + private double octaves = 1, persistence = .5; + + private double frequency = 1; + + private double amplitude = 1; + + private int repeat = -1; + + private double rangeMin = 0.0, rangeMax = 1.0; + + public Noise() { + this(null); + } + + public Noise( long seed ) { + init(new Random(seed)); + } + + /** + * Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator. + * + * @param rand + */ + public Noise( Random rand ) { + init(rand); + } + + public double getOctaves() { + return octaves; + } + + public void setOctaves( double pOctaves ) { + this.octaves = pOctaves; + } + + public double getPersistence() { + return persistence; + } + + public void setPersistence( double pPersistence ) { + this.persistence = pPersistence; + } + + public double getFrequency() { + return frequency; + } + + public void setFrequency( double pFrequency ) { + this.frequency = pFrequency; + } + + public double getAmplitude() { + return amplitude; + } + + public void setAmplitude( double pAmplitude ) { + this.amplitude = pAmplitude; + } + + public void setRange( double pRangeMin, double pRangeMax ) { + this.rangeMin = pRangeMin; + this.rangeMax = pRangeMax; + } + + public double getRangeMin() { + return rangeMin; + } + + public double getRangeMax() { + return rangeMax; + } + + public int getRepeat() { + return repeat; + } + + public void setRepeat( int pRepeat ) { + this.repeat = pRepeat; + } + + public double noise( double x ) { + double total = 0; + double freq = this.frequency; + double amp = this.amplitude; + double maxValue = 0; + + for( int i = 0; i < octaves; i++ ) { + total += perlin(x * freq) * amp; + + maxValue += amp; + + amp *= persistence; + freq *= 2; + } + + return lerp(rangeMin, rangeMax, (total / maxValue)); + } + + public double noise( double x, double y ) { + double total = 0; + double freq = this.frequency; + double amp = this.amplitude; + double maxValue = 0; + + for( int i = 0; i < octaves; i++ ) { + total += perlin(x * freq, y * freq) * amp; + + maxValue += amp; + + amp *= persistence; + freq *= 2; + } + + return lerp(rangeMin, rangeMax, (total / maxValue)); + } + + public double noise( double x, double y, double z ) { + double total = 0; + double freq = this.frequency; + double amp = this.amplitude; + double maxValue = 0; + + for( int i = 0; i < octaves; i++ ) { + total += perlin(x * freq, y * freq, z * freq) * amp; + + maxValue += amp; + + amp *= persistence; + freq *= 2; + } + + return lerp(rangeMin, rangeMax, (total / maxValue)); + } + + private double perlin( double x ) { + // @formatter:off + if( repeat > 0 ) { + x %= repeat; + } + + int xi = (int)x & M; + + double xf = x - (int)x; + + double u = fade(xf); + + int a, b; + a = p[ xi ]; + b = p[inc(xi)]; + + return (lerp(grad(a,xf), grad(b,xf-1), u) + 1) / 2; + // @formatter:on + } + + private double perlin( double x, double y ) { + // @formatter:off + if( repeat > 0 ) { + x %= repeat; + y %= repeat; + } + + int xi = (int) x & M; + int yi = (int) y & M; + + double xf = x - (int) x; + double yf = y - (int) y; + + double u = fade(xf); + double v = fade(yf); + + int aa, ab, ba, bb; + aa = p[p[ xi ] + yi ]; + ab = p[p[ xi ] + inc(yi)]; + ba = p[p[inc(xi)] + yi ]; + bb = p[p[inc(xi)] + inc(yi)]; + + double x1, x2; + x1 = lerp( + grad(aa, xf , yf), + grad(ba, xf-1, yf), + u); + x2 = lerp( + grad(ab, xf , yf-1), + grad(bb, xf-1, yf-1), + u); + + return (lerp(x1, x2, v) + 1) / 2; + // @formatter:on + } + + private double perlin( double x, double y, double z ) { + // @formatter:off + if( repeat > 0 ) { + x %= repeat; + y %= repeat; + z %= repeat; + } + + int xi = (int)x & M; + int yi = (int)y & M; + int zi = (int)z & M; + double xf = x - (int)x; + double yf = y - (int)y; + double zf = z - (int)z; + + double u = fade(xf); + double v = fade(yf); + double w = fade(zf); + + int aaa, aba, aab, abb, baa, bba, bab, bbb; + aaa = p[p[p[ xi ] + yi ] + zi ]; + aba = p[p[p[ xi ] + inc(yi)] + zi ]; + aab = p[p[p[ xi ] + yi ] + inc(zi)]; + abb = p[p[p[ xi ] + inc(yi)] + inc(zi)]; + baa = p[p[p[inc(xi)] + yi ] + zi ]; + bba = p[p[p[inc(xi)] + inc(yi)] + zi ]; + bab = p[p[p[inc(xi)] + yi ] + inc(zi)]; + bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)]; + + double x1, x2, y1, y2; + x1 = lerp( + grad(aaa, xf , yf, zf), + grad(baa, xf-1, yf, zf), + u); + x2 = lerp( + grad(aba, xf , yf-1, zf), + grad(bba, xf-1, yf-1, zf), + u); + y1 = lerp(x1, x2, v); + + x1 = lerp( + grad(aab, xf , yf, zf-1), + grad(bab, xf-1, yf, zf-1), + u); + x2 = lerp( + grad(abb, xf , yf-1, zf-1), + grad(bbb, xf-1, yf-1, zf-1), + u); + y2 = lerp(x1, x2, v); + + return (lerp(y1, y2, w) + 1) / 2; + // @formatter:on + } + + public void init( Random rand ) { + p = new int[N * 2]; + + if( rand == null ) { + System.arraycopy(PERLIN_PERMUTATION, 0, p, 0, N); + } else { + // Generate random permutation + for( int i = 0; i < N; i++ ) { + int n = rand.nextInt(N); + if( p[n] == 0 ) + p[n] = i; + else + i--; + } + } + + // Duplicate permutation array to prevent overflow errors + System.arraycopy(p, 0, p, N, N); + } + + private double fade( double t ) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + private double lerp( double a, double b, double t ) { + return a + t * (b - a); + } + + private int inc( int i ) { + ++i; + if( repeat > 0 ) + i = i % repeat; + return i; + } + + private double grad( int hash, double x ) { + switch( hash & 0x1 ) { + // @formatter:off + case 0x0: return x; + case 0x1: return -x; + default: return 0; + // @formatter:on + } + } + + + private double grad( int hash, double x, double y ) { + switch( hash & 0x3 ) { + // @formatter:off + case 0x0: return x; + case 0x1: return -x; + case 0x2: return y; + case 0x3: return -y; + default: return 0; + // @formatter:on + } + } + + private double grad( int hash, double x, double y, double z ) { + switch( hash & 0xF ) { + // @formatter:off + case 0x0: return x + y; + case 0x1: return -x + y; + case 0x2: return x - y; + case 0x3: return -x - y; + case 0x4: return x + z; + case 0x5: return -x + z; + case 0x6: return x - z; + case 0x7: return -x - z; + case 0x8: return y + z; + case 0x9: return -y + z; + case 0xA: return y - z; + case 0xB: return -y - z; + case 0xC: return y + x; + case 0xD: return -y + z; + case 0xE: return y - x; + case 0xF: return -y - z; + default: return 0; // never happens + // @formatter:on + } + } + + private static final int[] PERLIN_PERMUTATION = new int[]{ + 151, 160, 137, 91, 90, 15, + 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, + 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, + 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, + 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, + 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, + 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 + }; + +} diff --git a/src/main/java/schule/ngb/zm/util/Validator.java b/src/main/java/schule/ngb/zm/util/Validator.java index 50067b9..cb28143 100644 --- a/src/main/java/schule/ngb/zm/util/Validator.java +++ b/src/main/java/schule/ngb/zm/util/Validator.java @@ -136,8 +136,27 @@ public class Validator { } */ + + public static final T[] requireNotEmpty( T[] arr ) { + return requireNotEmpty(arr, (Supplier)null); + } + + public static final T[] requireNotEmpty( T[] arr, CharSequence msg ) { + return requireNotEmpty(arr, msg::toString); + } + public static final T[] requireNotEmpty( T[] arr, Supplier msg ) { - return requireSize(arr, 0, ()->"Parameter array may not be empty"); + if( arr.length == 0 ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be empty") : msg.get()); + return arr; + } + + public static final T[] requireSize( T[] arr, int size ) { + return requireSize(arr, size, (Supplier)null); + } + + public static final T[] requireSize( T[] arr, int size, CharSequence msg ) { + return requireSize(arr, size, msg::toString); } public static final T[] requireSize( T[] arr, int size, Supplier msg ) { @@ -146,6 +165,20 @@ public class Validator { return arr; } + public static final T[] requireValid( T[] arr ) { + return requireValid(arr, (Supplier)null); + } + + public static final T[] requireValid( T[] arr, CharSequence msg ) { + return requireValid(arr, msg::toString); + } + + public static final T[] requireValid( T[] arr, Supplier msg ) { + if( arr == null || arr.length > 0 ) + throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be null or empty") : msg.get()); + return arr; + } + private Validator() { } diff --git a/src/test/java/schule/ngb/zm/ColorTest.java b/src/test/java/schule/ngb/zm/ColorTest.java index 58f2ec3..1d72b52 100644 --- a/src/test/java/schule/ngb/zm/ColorTest.java +++ b/src/test/java/schule/ngb/zm/ColorTest.java @@ -17,9 +17,9 @@ class ColorTest { assertEquals(255, c.getAlpha()); c = Color.BLUE; - assertEquals(0, c.getRed()); - assertEquals(0, c.getGreen()); - assertEquals(255, c.getBlue()); + assertEquals(49, c.getRed()); + assertEquals(197, c.getGreen()); + assertEquals(244, c.getBlue()); assertEquals(255, c.getAlpha()); c = new Color(50, 133, 64, 33); @@ -28,7 +28,7 @@ class ColorTest { assertEquals(64, c.getBlue()); assertEquals(33, c.getAlpha()); - c = new Color(255, 0, 0); + c = new Color(240, 80, 37); assertEquals(Color.RED, c); c = new Color(33, 50); @@ -97,8 +97,9 @@ class ColorTest { assertEquals(c1, c2); Color yellow = new Color(255, 255, 0); - assertEquals(java.awt.Color.YELLOW, yellow); - assertNotEquals(java.awt.Color.YELLOW, Color.YELLOW); + assertNotEquals(java.awt.Color.YELLOW, yellow); + assertEquals(yellow, java.awt.Color.YELLOW); + assertNotEquals(Color.YELLOW, java.awt.Color.YELLOW); } @Test @@ -132,7 +133,7 @@ class ColorTest { @Test void getRGBColor() { - Color c1 = Color.getRGBColor(0xFFFF0000); + Color c1 = Color.getRGBColor(0xFFF05025); assertEquals(Color.RED, c1); } @@ -157,7 +158,7 @@ class ColorTest { Color c; float[] hsl; - c = Color.RED; + c = new Color(255, 0, 0); hsl = Color.RGBtoHSL(c.getRGBA(), null); assertArrayEquals(new float[]{0f,1f,.5f}, hsl, 0.0001f); @@ -183,28 +184,37 @@ class ColorTest { @Test void getRGBA() { Color yellow = new Color(255, 255, 0); - assertEquals(java.awt.Color.YELLOW.getRGB(), Color.YELLOW.getRGBA()); + assertEquals(java.awt.Color.YELLOW.getRGB(), yellow.getRGBA()); } @Test void getRed() { + Color clr = new Color(123, 92, 0); + assertEquals(123, clr.getRed()); } @Test void getGreen() { + Color clr = new Color(123, 92, 0); + assertEquals(92, clr.getGreen()); } @Test void getBlue() { + Color clr = new Color(123, 92, 0); + assertEquals(0, clr.getBlue()); } @Test void getAlpha() { + Color clr = new Color(123, 92, 0); + assertEquals(255, clr.getAlpha()); + Color clr2 = new Color(123, 92, 0, 45); + assertEquals(45, clr2.getAlpha()); } @Test void getJavaColor() { - assertEquals(java.awt.Color.YELLOW, Color.YELLOW.getJavaColor()); assertEquals(new java.awt.Color(255, 31, 124), new Color(255, 31, 124).getJavaColor()); } @@ -212,16 +222,8 @@ class ColorTest { void brighter() { } - @Test - void testBrighter() { - } - @Test void darker() { } - @Test - void testDarker() { - } - } diff --git a/src/test/java/schule/ngb/zm/ConstantsTest.java b/src/test/java/schule/ngb/zm/ConstantsTest.java index b77a2b2..529a696 100644 --- a/src/test/java/schule/ngb/zm/ConstantsTest.java +++ b/src/test/java/schule/ngb/zm/ConstantsTest.java @@ -71,23 +71,23 @@ class ConstantsTest { } @Test - void b() { - assertTrue(Constants.getBool(true)); - assertFalse(Constants.getBool(false)); - assertTrue(Constants.getBool(1)); - assertFalse(Constants.getBool(0)); - assertTrue(Constants.getBool(4.0)); - assertFalse(Constants.getBool(0.0)); - assertTrue(Constants.getBool(4.0f)); - assertFalse(Constants.getBool(0.0f)); - assertTrue(Constants.getBool(4L)); - assertFalse(Constants.getBool(0L)); - assertTrue(Constants.getBool("true")); - assertTrue(Constants.getBool("True")); - assertFalse(Constants.getBool("1")); - assertFalse(Constants.getBool("false")); - assertFalse(Constants.getBool("yes")); - assertFalse(Constants.getBool("no")); + void asBool() { + assertTrue(Constants.asBool(true)); + assertFalse(Constants.asBool(false)); + assertTrue(Constants.asBool(1)); + assertFalse(Constants.asBool(0)); + assertTrue(Constants.asBool(4.0)); + assertFalse(Constants.asBool(0.0)); + assertTrue(Constants.asBool(4.0f)); + assertFalse(Constants.asBool(0.0f)); + assertTrue(Constants.asBool(4L)); + assertFalse(Constants.asBool(0L)); + assertTrue(Constants.asBool("true")); + assertTrue(Constants.asBool("True")); + assertFalse(Constants.asBool("1")); + assertFalse(Constants.asBool("false")); + assertFalse(Constants.asBool("yes")); + assertFalse(Constants.asBool("no")); } @Test @@ -128,4 +128,45 @@ class ConstantsTest { assertEquals(.8f, Math.abs(t/(t+f)), .01f); } + @Test + void noise() { + double lastNoise = -1.0; + for( int i = 0; i < 100; i++ ) { + double thisNoise = Constants.noise(i * 0.005); + + assertInRange(thisNoise); + assertNotEquals(lastNoise, thisNoise); + assertEquals(thisNoise, Constants.noise(i * 0.005), 0.0001); + + lastNoise = thisNoise; + } + + lastNoise = -1.0; + for( int i = 0; i < 100; i++ ) { + double thisNoise = Constants.noise(i * 0.005, 0.1); + + assertInRange(thisNoise); + assertNotEquals(lastNoise, thisNoise); + assertEquals(thisNoise, Constants.noise(i * 0.005, 0.1), 0.0001); + + lastNoise = thisNoise; + } + + lastNoise = -1.0; + for( int i = 0; i < 100; i++ ) { + double thisNoise = Constants.noise(i * 0.005, 5.5, 100.0/(i+1)); + + assertInRange(thisNoise); + assertNotEquals(lastNoise, thisNoise); + assertEquals(thisNoise, Constants.noise(i * 0.005, 5.5, 100.0/(i+1)), 0.0001); + + lastNoise = thisNoise; + } + } + + private void assertInRange( double d ) { + assertFalse(Double.isNaN(d), "Noise value can't be NaN."); + assertTrue(0.0 <= d && 1.0 >= d, "Noise should be in Range 0 to 1. Was <" + d + ">."); + } + } diff --git a/src/test/java/schule/ngb/zm/TestAttraction.java b/src/test/java/schule/ngb/zm/TestAttraction.java index f0deb51..1e1438c 100644 --- a/src/test/java/schule/ngb/zm/TestAttraction.java +++ b/src/test/java/schule/ngb/zm/TestAttraction.java @@ -38,7 +38,7 @@ public class TestAttraction extends Zeichenmaschine { posC = new Vector(200, 100); velC = new Vector(1, 14); - drawing.translate(width /2, height /2); + drawing.translate(canvasWidth /2, canvasHeight /2); drawing.shear(0.1, 0.5); recht = new Rectangle(50, 50, 150, 75); @@ -84,7 +84,7 @@ public class TestAttraction extends Zeichenmaschine { shapes.clear(); double x = recht.getX(); - x = (x+100*delta)% width; + x = (x+100*delta)% canvasWidth; recht.setX(x); } diff --git a/src/test/java/schule/ngb/zm/TestShapes.java b/src/test/java/schule/ngb/zm/TestShapes.java index 146cff7..f821617 100644 --- a/src/test/java/schule/ngb/zm/TestShapes.java +++ b/src/test/java/schule/ngb/zm/TestShapes.java @@ -7,7 +7,6 @@ import schule.ngb.zm.shapes.Rectangle; import schule.ngb.zm.shapes.Shape; import java.awt.geom.Point2D; -import java.util.Random; public class TestShapes extends Zeichenmaschine { @@ -65,7 +64,7 @@ public class TestShapes extends Zeichenmaschine { public void shapePositions() { int pad = 24; - Rectangle bounds = new Rectangle(pad, pad, width-pad, height-pad); + Rectangle bounds = new Rectangle(pad, pad, canvasWidth -pad, canvasHeight -pad); Rectangle[] rects = new Rectangle[5]; for( int i = 0; i < rects.length; i++ ) { diff --git a/src/test/java/schule/ngb/zm/util/FileLoaderTest.java b/src/test/java/schule/ngb/zm/util/FileLoaderTest.java new file mode 100644 index 0000000..31b2d5b --- /dev/null +++ b/src/test/java/schule/ngb/zm/util/FileLoaderTest.java @@ -0,0 +1,81 @@ +package schule.ngb.zm.util; + +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class FileLoaderTest { + + @Test + void loadLines() { + String[] data; + List lines; + + data = new String[]{ + "Header1,Header2,Header3", + "1.1,1.2,1.3", + "2.1,2.2,2.3", + "3.1,3.2,3.3" + }; + + lines = FileLoader.loadLines("data_comma.csv"); + assertEquals(data.length, lines.size()); + for( int i = 0; i < lines.size(); i++ ) { + assertEquals(data[i], lines.get(i)); + } + + data = new String[]{ + "Nöme;Häder2;Straße", + "1.1;1.2;1.3", + "2.1;2.2;2.3", + "3.1;3.2;3.3" + }; + + lines = FileLoader.loadLines("data_semicolon_latin.csv", FileLoader.ISO_8859_1); + assertEquals(data.length, lines.size()); + for( int i = 0; i < lines.size(); i++ ) { + assertEquals(data[i], lines.get(i)); + } + } + + @Test + void loadText() { + String data; + String text; + + data = "Header1,Header2,Header3\n" + + "1.1,1.2,1.3\n" + + "2.1,2.2,2.3\n" + + "3.1,3.2,3.3\n"; + text = FileLoader.loadText("data_comma.csv"); + assertEquals(data, text); + + data = "Nöme;Häder2;Straße\n" + + "1.1;1.2;1.3\n" + + "2.1;2.2;2.3\n" + + "3.1;3.2;3.3\n"; + text = FileLoader.loadText("data_semicolon_latin.csv", FileLoader.ISO_8859_1); + assertEquals(data, text); + } + + @Test + void loadCsv() { + double[][] data; + double[][] csv; + + data = new double[][]{ + {1.1,1.2,1.3}, + {2.1,2.2,2.3}, + {3.1,3.2,3.3} + }; + csv = FileLoader.loadDoubles("data_comma.csv", ',', true); + assertArrayEquals(data, csv); + + csv = FileLoader.loadDoubles("data_semicolon_latin.csv", ';', true, FileLoader.ISO_8859_1); + assertArrayEquals(data, csv); + } + +} diff --git a/src/test/java/schule/ngb/zm/util/NoiseTest.java b/src/test/java/schule/ngb/zm/util/NoiseTest.java new file mode 100644 index 0000000..a92a0e1 --- /dev/null +++ b/src/test/java/schule/ngb/zm/util/NoiseTest.java @@ -0,0 +1,126 @@ +package schule.ngb.zm.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class NoiseTest { + + @Test + void noise() { + int N = 1000; + + Noise perlin = new Noise(); + Noise perlin1 = new Noise(1001); + Noise perlin2 = new Noise(2002); + Noise perlin3 = new Noise(2002); + + int pp1 = 0, p1p2 = 0; + for( int i = 1; i < N; i++ ) { + double x = i * 0.005; + + assertInRange(perlin.noise(x)); + assertInRange(perlin1.noise(x)); + assertInRange(perlin2.noise(x)); + if( perlin.noise(x) == perlin1.noise(x) ) { + pp1++; + } + if( perlin1.noise(x) == perlin2.noise(x) ) { + p1p2++; + } + assertEquals(perlin2.noise(x), perlin3.noise(x), "perlin2 and perlin3 should be equal for input " + x); + } + assertTrue(pp1 < N * 0.75, "perlin and perlin1 should not be equal more than 75% (was " + pp1 / 1000.0 + ")"); + assertTrue(p1p2 < N * 0.75, "perlin1 and perlin2 should not be equal more than 75% (was " + p1p2 / 1000.0 + ")"); + } + + private void assertInRange( double d ) { + assertTrue(0.0 <= d && 1.0 >= d); + } + + private void assertInRange( double d, double min, double max ) { + assertTrue(min <= d && max >= d); + } + + @Test + void noise2d() { + int N = 100; + + Noise perlin = new Noise(); + Noise perlin1 = new Noise(1001); + Noise perlin2 = new Noise(2002); + Noise perlin3 = new Noise(2002); + + int pp1 = 0, p1p2 = 0; + for( int i = 1; i < N; i++ ) { + for( int j = 0; j < N; j++ ) { + double x = i * 0.005; + double y = j * 0.001; + + assertInRange(perlin.noise(x, y)); + assertInRange(perlin1.noise(x, y)); + assertInRange(perlin2.noise(x, y)); + if( perlin.noise(x, y) == perlin1.noise(x, y) ) { + pp1++; + } + if( perlin1.noise(x, y) == perlin2.noise(x, y) ) { + p1p2++; + } + assertEquals(perlin2.noise(x, y), perlin3.noise(x, y), "perlin2 and perlin3 should be equal for input " + x + "," + y); + } + } + assertTrue(pp1 < N * N * 0.75, "perlin and perlin1 should not be equal more than 75% (was " + (pp1 / 1.0 * N * N) + ")"); + assertTrue(p1p2 < N * N * 0.75, "perlin1 and perlin2 should not be equal more than 75% (was " + (p1p2 / 1.0 * N * N) + ")"); + } + + @Test + void noise3d() { + int N = 100; + + Noise perlin = new Noise(); + Noise perlin1 = new Noise(1001); + Noise perlin2 = new Noise(2002); + Noise perlin3 = new Noise(2002); + + int pp1 = 0, p1p2 = 0; + for( int i = 1; i < N; i++ ) { + for( int j = 0; j < N; j++ ) { + for( int k = 0; k < N; k++ ) { + double x = i * 0.005; + double y = j * 0.001; + double z = k * 0.004; + + assertInRange(perlin.noise(x, y, z)); + assertInRange(perlin1.noise(x, y, z)); + assertInRange(perlin2.noise(x, y, z)); + if( perlin.noise(x, y, z) == perlin1.noise(x, y, z) ) { + pp1++; + } + if( perlin1.noise(x, y, z) == perlin2.noise(x, y, z) ) { + p1p2++; + } + assertEquals(perlin2.noise(x, y, z), perlin3.noise(x, y, z), "perlin2 and perlin3 should be equal for input " + x + "," + y); + } + } + } + assertTrue(pp1 < N * N * N* 0.75, "perlin and perlin1 should not be equal more than 75% (was " + (pp1 / 1.0 * N * N * N) + ")"); + assertTrue(p1p2 < N * N * N * 0.75, "perlin1 and perlin2 should not be equal more than 75% (was " + (p1p2 / 1.0 * N * N * N) + ")"); + } + + @Test + void range() { + Noise perlin = new Noise(1001); + perlin.setRange(100, 255); + + for( int i = 0; i < 1000; i++ ) { + assertInRange(perlin.noise(i * 0.005), 100, 255); + } + + perlin.setRange(-100, 100); + + for( int i = 0; i < 1000; i++ ) { + assertInRange(perlin.noise(i * 0.005), -100, 100); + } + } + +} diff --git a/src/test/resources/data_comma.csv b/src/test/resources/data_comma.csv new file mode 100644 index 0000000..6a1c58f --- /dev/null +++ b/src/test/resources/data_comma.csv @@ -0,0 +1,4 @@ +Header1,Header2,Header3 +1.1,1.2,1.3 +2.1,2.2,2.3 +3.1,3.2,3.3 diff --git a/src/test/resources/data_semicolon.csv b/src/test/resources/data_semicolon.csv new file mode 100644 index 0000000..1f7996d --- /dev/null +++ b/src/test/resources/data_semicolon.csv @@ -0,0 +1,4 @@ +Header1;Header2;Header3 +1.1;1.2;1.3 +2.1;2.2;2.3 +3.1;3.2;3.3 diff --git a/src/test/resources/data_semicolon_latin.csv b/src/test/resources/data_semicolon_latin.csv new file mode 100644 index 0000000..4bf3dd2 --- /dev/null +++ b/src/test/resources/data_semicolon_latin.csv @@ -0,0 +1,4 @@ +Nöme;Häder2;Straße +1.1;1.2;1.3 +2.1;2.2;2.3 +3.1;3.2;3.3