From d82ee1410abcf0f032b4b3e1859f14582f056f9e Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 23 Dec 2021 09:21:11 +0100 Subject: [PATCH] Basisklassen --- src/schule/ngb/zm/Aktualisierbar.java | 23 ++ src/schule/ngb/zm/Ebene.java | 106 +++++++ src/schule/ngb/zm/Farbe.java | 95 ++++++ src/schule/ngb/zm/Konstanten.java | 145 +++++++++ src/schule/ngb/zm/Leinwand.java | 118 ++++++++ src/schule/ngb/zm/Vektor.java | 251 ++++++++++++++++ src/schule/ngb/zm/Zeichenbar.java | 31 ++ src/schule/ngb/zm/Zeichenebene.java | 417 ++++++++++++++++++++++++++ src/schule/ngb/zm/Zeichenfenster.java | 313 +++++++++++++++++++ 9 files changed, 1499 insertions(+) create mode 100644 src/schule/ngb/zm/Aktualisierbar.java create mode 100644 src/schule/ngb/zm/Ebene.java create mode 100644 src/schule/ngb/zm/Farbe.java create mode 100644 src/schule/ngb/zm/Konstanten.java create mode 100644 src/schule/ngb/zm/Leinwand.java create mode 100644 src/schule/ngb/zm/Vektor.java create mode 100644 src/schule/ngb/zm/Zeichenbar.java create mode 100644 src/schule/ngb/zm/Zeichenebene.java create mode 100644 src/schule/ngb/zm/Zeichenfenster.java diff --git a/src/schule/ngb/zm/Aktualisierbar.java b/src/schule/ngb/zm/Aktualisierbar.java new file mode 100644 index 0000000..9841ecf --- /dev/null +++ b/src/schule/ngb/zm/Aktualisierbar.java @@ -0,0 +1,23 @@ +package schule.ngb.zm; + +/** + * Aktualisierbare Objekte können in regelmäßigen Intervallen (meist einmal + * pro Frame) ihren Zustand aktualisieren. Diese Änderung kann abhängig vom + * Zeitintervall (in Sekunden) zum letzten Aufruf passieren. + */ +public interface Aktualisierbar { + + /** + * Gibt an, ob das Objekt gerade auf Aktualisierungen reagiert. + * @return true, wenn das Objekt aktiv ist. + */ + public boolean istAktiv(); + + /** + * Änderung des Zustandes des Objekts abhängig vom Zeitintervall + * delta in Sekunden. + * @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden). + */ + public void aktualisieren( double delta ); + +} diff --git a/src/schule/ngb/zm/Ebene.java b/src/schule/ngb/zm/Ebene.java new file mode 100644 index 0000000..fd64ccf --- /dev/null +++ b/src/schule/ngb/zm/Ebene.java @@ -0,0 +1,106 @@ +package schule.ngb.zm; + +import java.awt.*; +import java.awt.image.BufferedImage; + +public abstract class Ebene extends Konstanten implements Zeichenbar, Aktualisierbar { + + protected BufferedImage puffer; + + protected Graphics2D zeichnung; + + protected boolean sichtbar = true; + + protected boolean aktiv = true; + + + public Ebene() { + this(STD_BREITE, STD_HOEHE); + } + + public Ebene( int pBreite, int pHoehe ) { + zeichnungErstellen(pBreite, pHoehe); + } + + public int getBreite() { + return puffer.getWidth(); + } + + public int getHoehe() { + return puffer.getHeight(); + } + + public void setGroesse( int pBreite, int pHoehe ) { + if( puffer != null ) { + zeichnenWiederherstellen(pBreite, pHoehe); + } else { + zeichnungErstellen(pBreite, pHoehe); + } + } + + private void zeichnungErstellen( int pBreite, int pHoehe ) { + puffer = new BufferedImage(pBreite, pHoehe, BufferedImage.TYPE_INT_ARGB); + zeichnung = puffer.createGraphics(); + + // add antialiasing + RenderingHints hints = new RenderingHints( + RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON + ); + hints.put( + RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY + ); + zeichnung.addRenderingHints(hints); + } + + private void zeichnenWiederherstellen( int pBreite, int pHoehe ) { + BufferedImage oldCanvas = puffer; + zeichnungErstellen(pBreite, pHoehe); + zeichnung.drawImage(oldCanvas, 0, 0, null); + } + + /** + * Leert die Ebene und löscht alles bisher gezeichnete. + */ + public abstract void leeren(); + + /** + * Zeichnet den Puffer auf die Grafik-Instanz. + * + * @param pGraphics + */ + @Override + public void zeichnen( Graphics2D pGraphics ) { + if( sichtbar ) { + pGraphics.drawImage(puffer, 0, 0, null); + } + } + + @Override + public boolean istSichtbar() { + return sichtbar; + } + + public void verstecken() { + sichtbar = false; + } + + public void zeigen() { + sichtbar = true; + } + + public void umschalten() { + sichtbar = !sichtbar; + } + + @Override + public void aktualisieren( double delta ) { + } + + @Override + public boolean istAktiv() { + return aktiv; + } + +} diff --git a/src/schule/ngb/zm/Farbe.java b/src/schule/ngb/zm/Farbe.java new file mode 100644 index 0000000..2c2327f --- /dev/null +++ b/src/schule/ngb/zm/Farbe.java @@ -0,0 +1,95 @@ +package schule.ngb.zm; + +import java.awt.*; + +public class Farbe extends Color { + + public static final Farbe SCHWARZ = new Farbe(Color.BLACK); + public static final Farbe WEISS = new Farbe(Color.WHITE); + public static final Farbe GRAU = new Farbe(Color.GRAY); + public static final Farbe DUNKELGRAU = new Farbe(Color.DARK_GRAY); + public static final Farbe HELLGRAU = new Farbe(Color.LIGHT_GRAY); + + public static final Farbe ROT = new Farbe(Color.RED); + public static final Farbe GRUEN = new Farbe(Color.GREEN); + public static final Farbe BLAU = new Farbe(Color.BLUE); + public static final Farbe GELB = new Farbe(Color.YELLOW); + public static final Farbe ORANGE = new Farbe(Color.ORANGE); + public static final Farbe CYAN = new Farbe(Color.CYAN); + public static final Farbe MAGENTA = new Farbe(Color.MAGENTA); + public static final Farbe PINK = new Farbe(Color.PINK); + + public static final Farbe HGGRUEN = new Farbe(0, 165, 81); + public static final Farbe HGROT = new Farbe(151, 54, 60); + + + public Farbe( int pGrau ) { + super(pGrau, pGrau, pGrau, 255); + } + + public Farbe( int pGrau, int pAlpha ) { + super(pGrau, pGrau, pGrau, pAlpha); + } + + + public Farbe( int pRot, int pGruen, int pBlau ) { + super(pRot, pGruen, pBlau); + } + + public Farbe( int pRot, int pGruen, int pBlau, int pAlpha ) { + super(pRot, pGruen, pBlau, pAlpha); + } + + public Farbe( Color pColor ) { + super(pColor.getRed(), pColor.getGreen(), pColor.getBlue(), pColor.getAlpha()); + } + + public Farbe( Color pColor, int pAlpha ) { + super(pColor.getRed(), pColor.getGreen(), pColor.getBlue(), pAlpha); + } + + public static Farbe vonRGB( int pRGB ) { + return new Farbe( + (pRGB >> 16) & 255, + (pRGB >> 8) & 255, + pRGB & 255, + (pRGB >> 24) & 255); + } + + public static Farbe vonHexcode( String pHexcode ) { + if( pHexcode.startsWith("#") ) { + pHexcode = pHexcode.substring(1); + } + + int red = Integer.valueOf(pHexcode.substring(0, 2), 16); + int green = Integer.valueOf(pHexcode.substring(2, 4), 16); + int blue = Integer.valueOf(pHexcode.substring(4, 6), 16); + + int alpha = 255; + if( pHexcode.length() == 8 ) { + alpha = Integer.valueOf(pHexcode.substring(6, 8), 16); + } + + return new Farbe(red, green, blue, alpha); + } + + public static Farbe morphen( Color pFarbe1, Color pFarbe2, double pFactor ) { + if( pFactor < 0.0 || pFarbe2 == null ) { + return new Farbe(pFarbe1); + } + if( pFactor > 1.0 || pFarbe1 == null ) + return new Farbe(pFarbe2); + double pFactorInv = 1 - pFactor; + return new Farbe( + (int) (pFactorInv * pFarbe1.getRed() + pFactor * pFarbe2.getRed()), + (int) (pFactorInv * pFarbe1.getGreen() + pFactor * pFarbe2.getGreen()), + (int) (pFactorInv * pFarbe1.getBlue() + pFactor * pFarbe2.getBlue()), + (int) (pFactorInv * pFarbe1.getAlpha() + pFactor * pFarbe2.getAlpha()) + ); + } + + public Farbe kopie() { + return new Farbe(this); + } + +} diff --git a/src/schule/ngb/zm/Konstanten.java b/src/schule/ngb/zm/Konstanten.java new file mode 100644 index 0000000..87b6d23 --- /dev/null +++ b/src/schule/ngb/zm/Konstanten.java @@ -0,0 +1,145 @@ +package schule.ngb.zm; + +import schule.ngb.zm.formen.Konturform; + +import java.awt.*; +import java.awt.geom.Arc2D; + +public class Konstanten { + + public static final String APP_NAME = "Zeichenmaschine"; + + public static final int STD_BREITE = 400; + public static final int STD_HOEHE = 400; + public static final int STD_FPS = 60; + + public static Color STD_HINTERGRUND = new Color(200, 200, 200); + + + public static final int DURCHGEZOGEN = Konturform.DURCHGEZOGEN; + public static final int GESTRICHELT = Konturform.GESTRICHELT; + public static final int GEPUNKTET = Konturform.GEPUNKTET; + + public static final int OFFEN = Arc2D.OPEN; + public static final int GESCHLOSSEN = Arc2D.CHORD; + public static final int KREISTEIL = Arc2D.PIE; + + + public static final byte ZENTRUM = 0; + public static final byte NORDEN = 1 << 0; + public static final byte OSTEN = 1 << 2; + public static final byte SUEDEN = 1 << 3; + public static final byte WESTEN = 1 << 4; + + public static final byte NORDOSTEN = NORDEN | OSTEN; + public static final byte SUEDOSTEN = SUEDEN | OSTEN; + public static final byte NORDWESTEN = NORDEN | WESTEN; + public static final byte SUEDWESTEN = SUEDEN | WESTEN; + + public static final byte MITTE = ZENTRUM; + public static final byte OBEN = NORDEN; + public static final byte RECHTS = OSTEN; + public static final byte UNTEN = SUEDEN; + public static final byte LINKS = WESTEN; + + + public static final Farbe SCHWARZ = Farbe.SCHWARZ; + public static final Farbe WEISS = Farbe.WEISS; + public static final Farbe ROT = Farbe.ROT; + public static final Farbe BLAU = Farbe.BLAU; + public static final Farbe GRUEN = Farbe.GRUEN; + public static final Farbe GELB = Farbe.GELB; + + public Farbe farbe( int pGrau ) { + return farbe(pGrau, pGrau, pGrau, 255); + } + + public Farbe farbe( int pGrau, int pAlpha ) { + return farbe(pGrau, pGrau, pGrau, pAlpha); + } + + public Farbe farbe(int red, int green, int blue) { + return farbe(red, green, blue, 255); + } + + public Farbe farbe(int red, int green, int blue, int alpha) { + if (red < 0 || red >= 256) + throw new IllegalArgumentException("red must be between 0 and 255"); + if (green < 0 || green >= 256) + throw new IllegalArgumentException("green must be between 0 and 255"); + if (blue < 0 || blue >= 256) + throw new IllegalArgumentException("blue must be between 0 and 255"); + if (alpha < 0 || alpha >= 256) + throw new IllegalArgumentException("alpha must be between 0 and 255"); + + return new Farbe(red, green, blue, alpha); + } + + + + public double abs( double x ) { + return Math.abs(x); + } + + public double vorzeichen( double x ) { + return Math.signum(x); + } + + public double runden( double x ) { + return Math.round(x); + } + + public double abrunden( double x ) { + return Math.floor(x); + } + + public double aufrunden( double x ) { + return Math.ceil(x); + } + + public double sin( double x ) { + return Math.sin(x); + } + + public double cos( double x ) { + return Math.cos(x); + } + + public double tan( double x ) { + return Math.tan(x); + } + + public double arcsin( double x ) { + return Math.asin(x); + } + + public double arccos( double x ) { + return Math.acos(x); + } + + public double arctan( double x ) { + return Math.atan(x); + } + + public double beschraenken( double x, double max ) { + if( x > max ) { + return max; + } + return x; + } + + public double beschraenken( double x, double min, double max ) { + if( x > max ) { + return max; + } + if( x < min ) { + return min; + } + return x; + } + + public double morphen( double pVon, double pNach, double pFaktor ) { + return pVon - pFaktor*(pVon+pNach); + } + +} diff --git a/src/schule/ngb/zm/Leinwand.java b/src/schule/ngb/zm/Leinwand.java new file mode 100644 index 0000000..fe6040d --- /dev/null +++ b/src/schule/ngb/zm/Leinwand.java @@ -0,0 +1,118 @@ +package schule.ngb.zm; + +import schule.ngb.zm.formen.Formenebene; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus + * mehreren Ebenen, auf denen auf verschiedene Arten gezeichnet werden kann. Die + * Ebenen lassen sich beliebig übereinander anordenen, ausblenden oder wieder + * löschen. + * + * Jede Ebene besitzt eine Zeichenfläche, auf der ihre Zeichnung liegt. Diese + * zeichenflächen werden pro Frame einmal von "unten" nach "oben" auf diese + * Leinwand gezeichnet. + */ +public class Leinwand extends JComponent { + + + private LinkedList ebenen; + + public Leinwand( int pBreite, int pHoehe ) { + Dimension dim = new Dimension(pBreite, pHoehe); + super.setSize(pBreite, pHoehe); + this.setPreferredSize(dim); + this.setMinimumSize(dim); + + ebenen = new LinkedList<>(); + ebenen.add(new Zeichenebene()); + ebenen.add(new Formenebene()); + setBackground(Konstanten.STD_HINTERGRUND); + } + + @Override + public void setSize( int pBreite, int pHoehe ) { + Dimension dim = new Dimension(pBreite, pHoehe); + super.setSize(pBreite, pHoehe); + this.setPreferredSize(dim); + this.setMinimumSize(dim); + + for( Ebene ebene : ebenen ) { + ebene.setGroesse(getWidth(), getHeight()); + } + } + + public void hinzu( Ebene pEbene ) { + if( pEbene != null ) { + pEbene.setGroesse(getWidth(), getHeight()); + ebenen.add(pEbene); + } + } + + public void hinzu( int pIndex, Ebene pEbene ) { + if( pEbene != null ) { + pEbene.setGroesse(getWidth(), getHeight()); + ebenen.add(pIndex, pEbene); + } + } + + public java.util.List getEbenen() { + return ebenen; + } + + /** + * Holt die {@link Ebene} am Index i (beginnend bei 0). + * + * @param i Index der Ebene (beginnend bei 0). + * @return Die Ebene am Index i oder null. + * @throws IndexOutOfBoundsException Falls der Index nicht existiert. + */ + public Ebene getEbene( int i ) { + if( ebenen.size() > i ) { + return ebenen.get(i); + } else { + throw new IndexOutOfBoundsException("Keine Ebene mit dem Index " + i + " vorhanden (maximum: " + (ebenen.size() - 1) + ")."); + } + } + + /** + * Holt die erste Ebene des angegebenen Typs aus der Liste der Ebenen. + * Existiert keine solche Ebene, wird null zurückgegeben. + * + * @param pClazz Typ der Ebene. + * @param + * @return Erste Ebene vom angegeben Typ. + */ + public L getEbene( Class pClazz ) { + for( Ebene ebene : ebenen ) { + if( ebene.getClass().equals(pClazz) ) { + return pClazz.cast(ebene); + } + } + return null; + } + + public java.util.List getEbenen( Class pClazz ) { + ArrayList result = new ArrayList<>(ebenen.size()); + for( Ebene ebene : ebenen ) { + if( ebene.getClass().equals(pClazz) ) { + result.add(pClazz.cast(ebene)); + } + } + return result; + } + + public void paintComponent( Graphics g ) { + Graphics2D g2d = (Graphics2D) g.create(); + + for( Ebene ebene : ebenen ) { + ebene.zeichnen(g2d); + } + + g2d.dispose(); + } +} diff --git a/src/schule/ngb/zm/Vektor.java b/src/schule/ngb/zm/Vektor.java new file mode 100644 index 0000000..edfd1af --- /dev/null +++ b/src/schule/ngb/zm/Vektor.java @@ -0,0 +1,251 @@ +package schule.ngb.zm; + +import java.awt.geom.Point2D; + +public class Vektor extends Point2D.Double { + + public Vektor() { + x = 0.0; + y = 0.0; + } + + public Vektor(double pX, double pY) { + x = pX; + y = pY; + } + + public Vektor(Point2D.Double pPunkt) { + x = pPunkt.getX(); + x = pPunkt.getY(); + } + + public Vektor(Vektor pVektor) { + x = pVektor.x; + y = pVektor.y; + } + + public static Vektor zufall() { + return new Vektor(Math.random()*100, Math.random()*100); + } + + public static Vektor zufall( double min, double max ) { + return new Vektor(Math.random()*(max-min)+min, Math.random()*(max-min)+min); + } + + public Vektor kopie() { + return new Vektor(x, y); + } + + public Vektor set(double pX, double pY) { + x = pX; + y = pY; + return this; + } + + public Vektor set(Vektor pVektor) { + x = pVektor.x; + y = pVektor.y; + return this; + } + + public Vektor set(Point2D.Double pPunkt) { + x = pPunkt.getX(); + x = pPunkt.getY(); + return this; + } + + public void setX(double pX) { + x = pX; + } + + public void setY(double pY) { + y = pY; + } + + public Point2D.Double getPunkt() { + return new Point2D.Double(x, y); + } + + public double laenge() { + return Math.sqrt(x * x + y * y); + } + + public double laengeQuad() { + return x * x + y * y; + } + + public Vektor setLaenge(double pLaenge) { + normalisieren(); + return skalieren(pLaenge); + } + + public static Vektor setLaenge(Vektor pVektor, double pLaenge) { + Vektor vec = pVektor.kopie(); + vec.setLaenge(pLaenge); + return vec; + } + + public Vektor normalisieren() { + double len = laenge(); + if (len != 0 && len != 1) { + x /= len; + y /= len; + } + return this; + } + + public static Vektor normalisieren(Vektor pVektor) { + Vektor vec = pVektor.kopie(); + vec.normalisieren(); + return vec; + } + + public Vektor addieren(Vektor pVektor) { + x += pVektor.x; + y += pVektor.y; + return this; + } + + public Vektor addieren(double pX, double pY) { + x += pX; + y += pY; + return this; + } + + public static Vektor addieren(Vektor pVektor1, Vektor pVektor2) { + Vektor vec = pVektor1.kopie(); + vec.addieren(pVektor2); + return vec; + } + + public void subtrahieren(Vektor pVektor) { + x -= pVektor.x; + y -= pVektor.y; + } + + public void subtrahieren(double pX, double pY) { + x -= pX; + y -= pY; + } + + public static Vektor subtrahieren(Vektor pVektor1, Vektor pVektor2) { + Vektor vec = pVektor1.kopie(); + vec.subtrahieren(pVektor2); + return vec; + } + + public Vektor skalieren(double pSkalar) { + x *= pSkalar; + y *= pSkalar; + return this; + } + + public static Vektor skalieren(Vektor pVektor, double pSkalar) { + Vektor vec = pVektor.kopie(); + vec.skalieren(pSkalar); + return vec; + } + + public Vektor dividieren(double pSkalar) { + x /= pSkalar; + y /= pSkalar; + return this; + } + + public static Vektor dividieren(Vektor pVektor, double pSkalar) { + Vektor vec = pVektor.kopie(); + vec.dividieren(pSkalar); + return vec; + } + + public double abstand(Vektor pVektor) { + double dx = x - pVektor.x; + double dy = y - pVektor.y; + return Math.sqrt(dx * dx + dy * dy); + } + + public static double abstand(Vektor pVektor1, Vektor pVektor2) { + return pVektor1.abstand(pVektor2); + } + + public double dot(Vektor pVektor) { + return x * pVektor.x + y * pVektor.y; + } + + public double dot(double pX, double pY) { + return x * pX + y * pY; + } + + public static double dot(Vektor pVektor1, Vektor pVektor2) { + return pVektor1.dot(pVektor2); + } + + // See: http://allenchou.net/2013/07/cross-product-of-2d-vectors/ + public double cross(Vektor pVektor) { + return x * pVektor.y - pVektor.x * y; + } + + public static double cross(Vektor pVektor1, Vektor pVektor2) { + return pVektor1.cross(pVektor2); + } + + public void limitieren(double pMax) { + if (laengeQuad() > pMax * pMax) { + normalisieren(); + skalieren(pMax); + } + } + + public void beschraenken(double pMin, double pMax) { + if (pMin > pMax) { + throw new IllegalArgumentException("HVector.constrain(): pMin muss kleiner sein als pMax."); + } + if (laengeQuad() < pMin * pMin) { + normalisieren(); + skalieren(pMin); + } + if (laengeQuad() > pMax * pMax) { + normalisieren(); + skalieren(pMax); + } + } + + public double richtung() { + double angle = Math.atan2(y, x); + return angle; + } + + public double winkel() { + return richtung(); + } + + public Vektor drehen(double pWinkel) { + double temp = x; + x = x * Math.cos(pWinkel) - y * Math.sin(pWinkel); + y = temp * Math.sin(pWinkel) + y * Math.cos(pWinkel); + return this; + } + + public static Vektor drehen(Vektor pVektor, double pWinkel) { + Vektor vec = pVektor.kopie(); + vec.drehen(pWinkel); + return vec; + } + + public void linterp(Vektor pVektor, float t) { + x = x + (pVektor.x - x) * t; + y = y + (pVektor.y - y) * t; + } + + public static Vektor linterp(Vektor pVektor1, Vektor pVektor2, float t) { + Vektor vec = pVektor1.kopie(); + vec.linterp(pVektor2, t); + return vec; + } + + @Override + public String toString() { + return "schule.ngb.zm.Vektor{x = " + x + ", y = " + y + "}"; + } + +} diff --git a/src/schule/ngb/zm/Zeichenbar.java b/src/schule/ngb/zm/Zeichenbar.java new file mode 100644 index 0000000..6aa79fa --- /dev/null +++ b/src/schule/ngb/zm/Zeichenbar.java @@ -0,0 +1,31 @@ +package schule.ngb.zm; + +import java.awt.*; + +/** + * Zeichenbare Objekte können auf eine Zeichenfläche gezeichnet werden. + * In der Regel werden sie einmal pro Frame gezeichnet. + */ +public interface Zeichenbar { + + /** + * Gibt an, ob das Objekt derzeit sichtbar ist (also gezeichnet werden + * muss). + * + * @return true, wenn das Objekt sichtbar ist. + */ + public boolean istSichtbar(); + + /** + * Wird aufgerufen, um das Objekt auf die Zeichenfläche graphics + * zu zeichnen. + *

+ * Das Objekt muss dafür Sorge tragen, dass der Zustand der Zeichenfläche + * (Transformationsmatrix, Farbe, ...) erhalten bleibt. Das Objekt sollte + * also etwaige Änderungen am Ende des Aufrufs wieder rückgängig machen. + * + * @param graphics Die Zeichenfläche. + */ + public void zeichnen( Graphics2D graphics ); + +} diff --git a/src/schule/ngb/zm/Zeichenebene.java b/src/schule/ngb/zm/Zeichenebene.java new file mode 100644 index 0000000..b6000c7 --- /dev/null +++ b/src/schule/ngb/zm/Zeichenebene.java @@ -0,0 +1,417 @@ +package schule.ngb.zm; + +import java.awt.*; +import java.awt.geom.*; +import java.util.Stack; + +public class Zeichenebene extends Ebene { + + private int default_anchor = ZENTRUM; + + protected Color strokeColor; + + protected Color fillColor; + + protected double strokeWeight; + + protected int konturArt = DURCHGEZOGEN; + + public Color getColor() { + return fillColor; + } + + public void noFill() { + fillColor = null; + } + + public void setColor(int gray) { + setColor(gray, gray, gray, 255); + } + + public void setColor(int gray, int alpha) { + setColor(gray, gray, gray, alpha); + } + + public void setColor(int red, int green, int blue) { + setColor(red, green, blue, 255); + } + + public void setColor(int red, int green, int blue, int alpha ) { + if (red < 0 || red >= 256) throw new IllegalArgumentException("red must be between 0 and 255"); + if (green < 0 || green >= 256) throw new IllegalArgumentException("green must be between 0 and 255"); + if (blue < 0 || blue >= 256) throw new IllegalArgumentException("blue must be between 0 and 255"); + if (alpha < 0 || alpha >= 256) throw new IllegalArgumentException("alpha must be between 0 and 255"); + + setColor(new Color(red, green, blue, alpha)); + } + + public void setColor(Color pColor) { + fillColor = pColor; + zeichnung.setColor(pColor); + } + + public Color getStrokeColor() { + return strokeColor; + } + + public void noStroke() { + strokeColor = null; + } + + public void setStrokeColor(int gray) { + setStrokeColor(gray, gray, gray, 255); + } + + public void setStrokeColor(int gray, int alpha) { + setStrokeColor(gray, gray, gray, alpha); + } + + public void setStrokeColor(int red, int green, int blue) { + if (red < 0 || red >= 256) throw new IllegalArgumentException("red must be between 0 and 255"); + if (green < 0 || green >= 256) throw new IllegalArgumentException("green must be between 0 and 255"); + if (blue < 0 || blue >= 256) throw new IllegalArgumentException("blue must be between 0 and 255"); + setStrokeColor(red, green, blue, 255); + } + + public void setStrokeColor(int red, int green, int blue, int alpha ) { + if (red < 0 || red >= 256) throw new IllegalArgumentException("red must be between 0 and 255"); + if (green < 0 || green >= 256) throw new IllegalArgumentException("green must be between 0 and 255"); + if (blue < 0 || blue >= 256) throw new IllegalArgumentException("blue must be between 0 and 255"); + if (alpha < 0 || alpha >= 256) throw new IllegalArgumentException("alpha must be between 0 and 255"); + + setStrokeColor(new Color(red, green, blue, alpha)); + } + + public void setStrokeColor(Color pColor) { + strokeColor = pColor; + zeichnung.setColor(pColor); + } + + public void setStrokeWeight( double pWeight ) { + strokeWeight = pWeight; + zeichnung.setStroke(createStroke()); + } + + protected Stroke createStroke() { + switch(konturArt) { + case GEPUNKTET: + return new BasicStroke( + (float) strokeWeight, + BasicStroke.CAP_ROUND, + BasicStroke.JOIN_ROUND, + 10.0f, new float[]{1.0f, 5.0f}, 0.0f); + case GESTRICHELT: + return new BasicStroke( + (float) strokeWeight, + BasicStroke.CAP_ROUND, + BasicStroke.JOIN_ROUND, + 10.0f, new float[]{5.0f}, 0.0f); + default: + return new BasicStroke( + (float) strokeWeight, + BasicStroke.CAP_ROUND, + BasicStroke.JOIN_ROUND); + } + } + + public int getKonturArt() { + return konturArt; + } + + public void setKonturArt(int konturArt) { + this.konturArt = konturArt; + } + + private Stack transformStack = new Stack<>(); + + public Zeichenebene() { + super(); + transformStack.push(new AffineTransform()); + + strokeColor = Color.BLACK; + fillColor = Color.WHITE; + strokeWeight = 1.0; + } + + public Zeichenebene( int pWidth, int pHeight) { + super(pWidth, pHeight); + } + + public void setAnchor( int pAnchor ) { + default_anchor = pAnchor; + } + + public void leeren() { + clear(200); + } + + public void clear(int gray) { + clear(gray, gray, gray, 255); + } + + public void clear(int gray, int alpha) { + clear(gray, gray, gray, alpha); + } + + public void clear(int red, int green, int blue) { + clear(red, green, blue, 255); + } + + public void clear(int red, int green, int blue, int alpha) { + clear(new Color(red, green, blue, alpha)); + } + + public void clear(Color pColor) { + /*graphics.setBackground(pColor); + graphics.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());*/ + Color currentColor = zeichnung.getColor(); + pushMatrix(); + resetMatrix(); + zeichnung.setColor(pColor); + zeichnung.fillRect(0, 0, puffer.getWidth(), puffer.getHeight()); + zeichnung.setColor(currentColor); + popMatrix(); + } + + public void line(double x1, double y1, double x2, double y2) { + Shape line = new Line2D.Double(x1, y1, x2, y2); + //line = transformToCanvas(line); + + drawShape(line); + } + + public void pixel(double x, double y) { + square(x, y, 1); + } + + public void square(double x, double y, double w) { + rect(x, y, w, w); + } + + public void square(double x, double y, double w, int anchor) { + rect(x, y, w, w, anchor); + } + + public void rect(double x, double y, double w, double h) { + rect(x, y, w, h, default_anchor); + } + + public void rect(double x, double y, double w, double h, int anchor) { + Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, anchor); + Shape rect = new Rectangle2D.Double( + anchorPoint.getX(), anchorPoint.getY(), w, h + ); + //rect = transformToCanvas(rect); + + fillShape(rect); + drawShape(rect); + } + + public void point(double x, double y) { + circle(x - 1, y - 1, 2); + } + + public void circle(double x, double y, double d) { + ellipse(x, y, d, d, default_anchor); + } + + public void circle(double x, double y, double d, int anchor) { + ellipse(x, y, d, d, anchor); + } + + public void ellipse(double x, double y, double w, double h) { + ellipse(x, y, w, h, default_anchor); + } + + public void ellipse(double x, double y, double w, double h, int anchor) { + Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, anchor); + Shape ellipse = new Ellipse2D.Double( + anchorPoint.x, anchorPoint.y, w, h + ); + //ellipse = transformToCanvas(ellipse); + + fillShape(ellipse); + drawShape(ellipse); + } + + public void arc( double x, double y, double d, double angle1, double angle2 ) { + while (angle2 < angle1) angle2 += 360.0; + + Point2D.Double anchorPoint = getAnchorPoint(x, y, d, d, ZENTRUM); + Shape arc = new Arc2D.Double( + anchorPoint.x, + anchorPoint.y, + d, d, + angle1, angle2 - angle1, + Arc2D.OPEN + ); + //arc = transformToCanvas(arc); + + drawShape(arc); + } + + public void pie( double x, double y, double d, double angle1, double angle2 ) { + while (angle2 < angle1) angle2 += 360.0; + + Point2D.Double anchorPoint = getAnchorPoint(x, y, d, d, ZENTRUM); + Shape arc = new Arc2D.Double( + anchorPoint.x, + anchorPoint.y, + d, d, + angle1, angle2 - angle1, + Arc2D.PIE + ); + //arc = transformToCanvas(arc); + + fillShape(arc); + drawShape(arc); + } + + private Point2D.Double transformToCanvas(double x, double y) { + return transformToCanvas(new Point2D.Double(x, y)); + } + + private Point2D.Double transformToCanvas( Point2D.Double pPoint ) { + AffineTransform matrix = getMatrix(); + matrix.transform(pPoint, pPoint); + return pPoint; + } + + private Shape transformToCanvas( Shape pShape ) { + AffineTransform matrix = getMatrix(); + return matrix.createTransformedShape(pShape); + } + + private Point2D.Double transformToUser(double x, double y) { + return transformToUser(new Point2D.Double(x, y)); + } + + private Point2D.Double transformToUser( Point2D.Double pPoint ) { + AffineTransform matrix = getMatrix(); + + try { + matrix.inverseTransform(pPoint, pPoint); + } catch (NoninvertibleTransformException e) { + e.printStackTrace(); + } + + return pPoint; + } + + private Shape transformToUser( Shape pShape ) { + AffineTransform matrix = getMatrix(); + try { + matrix = matrix.createInverse(); + pShape = matrix.createTransformedShape(pShape); + } catch (NoninvertibleTransformException e) { + e.printStackTrace(); + } + return pShape; + } + + private AffineTransform getAnchorTransform( Shape pShape, int anchor ) { + AffineTransform at = new AffineTransform(); + Rectangle2D bounds = pShape.getBounds2D(); + switch(anchor) { + case ZENTRUM: + at.translate( + bounds.getWidth() / -2.0, + bounds.getHeight() / -2.0 + ); + break; + + case WESTEN: + at.translate( + 0, bounds.getHeight() / -2.0 + ); + break; + + case SUEDWESTEN: + at.translate( + 0, -1.0 * bounds.getHeight() + ); + break; + } + + return at; + } + + private Point2D.Double getAnchorPoint(double x, double y, double w, double h, int anchor) { + switch(anchor) { + case ZENTRUM: + x -= w / 2.0; + y -= h / 2.0; + break; + + case WESTEN: + y -= h / 2.0; + break; + + case SUEDWESTEN: + y -= h; + break; + } + + return new Point2D.Double(x, y); + } + + private Point2D.Double getAnchorPoint(Shape pShape, int anchor) { + Rectangle2D bounds = pShape.getBounds2D(); + return getAnchorPoint( + bounds.getX(), bounds.getY(), + bounds.getWidth(), bounds.getHeight(), anchor + ); + } + + private void fillShape( Shape pShape ) { + if (fillColor != null && fillColor.getAlpha() > 0.0) { + zeichnung.setColor(fillColor); + zeichnung.fill(pShape); + } + } + + private void drawShape( Shape pShape ) { + if (strokeColor != null && strokeColor.getAlpha() > 0.0 + && strokeWeight > 0.0 ) { + zeichnung.setColor(strokeColor); + zeichnung.draw(pShape); + } + } + + public void translate( double dx, double dy ) { + zeichnung.translate(dx, dy); + } + + public void scale( double factor ) { + zeichnung.scale(factor, factor); + } + + public void rotate( double pAngle ) { + zeichnung.rotate(Math.toRadians(pAngle)); + } + + public void shear( double dx, double dy ) { + zeichnung.shear(dx, dy); + } + + public AffineTransform getMatrix() { + return zeichnung.getTransform(); + } + + public void pushMatrix() { + transformStack.push(zeichnung.getTransform()); + } + + public void popMatrix() { + if( transformStack.isEmpty() ) { + resetMatrix(); + } else { + zeichnung.setTransform(transformStack.pop()); + } + } + + public void resetMatrix() { + zeichnung.setTransform(new AffineTransform()); + } + +} diff --git a/src/schule/ngb/zm/Zeichenfenster.java b/src/schule/ngb/zm/Zeichenfenster.java new file mode 100644 index 0000000..25a5892 --- /dev/null +++ b/src/schule/ngb/zm/Zeichenfenster.java @@ -0,0 +1,313 @@ +package schule.ngb.zm; + +import schule.ngb.zm.formen.Formenebene; + +import javax.swing.*; +import javax.swing.event.MouseInputListener; +import java.awt.*; +import java.awt.event.*; + +public class Zeichenfenster extends Konstanten implements Runnable, MouseInputListener, KeyListener { + + protected Object mouseLock = new Object(); + /* + * Attribute für den Zugriff aus Unterklassen. + */ + protected Leinwand leinwand; + protected Zeichenebene zeichnung; + protected Formenebene formen; + protected int tick = 0; + protected long laufzeit = 0L; + protected double delta = 0.0; + protected double mausX = 0.0, mausY = 0.0, lmausX = 0.0, lmausY = 0.0; + protected int breite = STD_BREITE, hoehe = STD_HOEHE; + /* + * Interne Attribute zur Steuerung der Zeichenamschine. + */ + // + private JFrame frame; + private boolean running = false; + private int framesPerSecond; + + public Zeichenfenster() { + this(APP_NAME); + } + + public Zeichenfenster( String pTitel ) { + this(STD_BREITE, STD_HOEHE, pTitel); + } + + public Zeichenfenster( int pBreite, int pHoehe, String pTitel ) { + frame = new JFrame(pTitel); + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch( Exception e ) { + System.err.println("Fehler beim Setzen des look and feel."); + } + + breite = pBreite; + hoehe = pHoehe; + + frame.setResizable(false); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + leinwand = new Leinwand(pBreite, pHoehe); + frame.setContentPane(leinwand); + + framesPerSecond = STD_FPS; + + zeichnung = getZeichenEbene(); + formen = getFormenEbene(); + + einstellungen(); + + frame.addMouseListener(this); + frame.addMouseMotionListener(this); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing( WindowEvent e ) { + running = false; + super.windowClosing(e); + } + }); + + frame.pack(); + frame.requestFocusInWindow(); + frame.setVisible(true); + + running = true; + new Thread(this).start(); + } + + public final void setSize( int pWidth, int pHeight ) { + //frame.setSize(pWidth, pHeight); + + if( leinwand != null ) { + leinwand.setSize(pWidth, pHeight); + } + breite = pWidth; + hoehe = pHeight; + frame.pack(); + } + + public final int getBreite() { + return breite; + } + + public final int getHoehe() { + return hoehe; + } + + public final void setTitel( String pTitel ) { + frame.setTitle(pTitel); + } + + public final Leinwand getLeinwand() { + return leinwand; + } + + public final void hinzu( Ebene pEbene ) { + leinwand.hinzu(pEbene); + } + + public final Zeichenebene getZeichenEbene() { + Zeichenebene layer = leinwand.getEbene(Zeichenebene.class); + if( layer == null ) { + layer = new Zeichenebene(getBreite(), getHoehe()); + leinwand.hinzu(0, layer); + } + return layer; + } + + public final Formenebene getFormenEbene() { + Formenebene layer = leinwand.getEbene(Formenebene.class); + if( layer == null ) { + layer = new Formenebene(getBreite(), getHoehe()); + leinwand.hinzu(layer); + } + return layer; + } + + public final int getFramesPerSecond() { + return framesPerSecond; + } + + public final void setFramesPerSecond( int pFramesPerSecond ) { + framesPerSecond = pFramesPerSecond; + } + + @Override + public final void run() { + long start = System.currentTimeMillis(); + long current = System.nanoTime(); + int _tick = 0; + long _runtime = 0; + tick = 0; + laufzeit = 0; + + vorbereiten(); + + while( running ) { + int dt = (int) ((System.nanoTime() - current) / 1E6); + current = System.nanoTime(); + delta = (dt / 1000.0); + + saveMousePosition(); + + aktualisieren(delta); + zeichnen(); + + if( leinwand != null ) { + //drawing.invalidate(); + frame.repaint(); + } + + try { + int sleep = Math.round(1000 / framesPerSecond); + if( dt >= sleep ) { + sleep -= dt % sleep; + } + Thread.sleep(Math.max(0, sleep)); + } catch( InterruptedException e ) { + // Interrupt not relevant + } finally { + _tick += 1; + _runtime = System.currentTimeMillis() - start; + tick = _tick; + laufzeit = _runtime; + } + } + } + + /* + * Methoden, die von Unterklassen überschrieben werden können / sollen. + */ + public void einstellungen() { + + } + + public void vorbereiten() { + + } + + public void zeichnen() { + + } + + public void aktualisieren( double delta ) { + running = false; + } + + /* + * Mouse handling + */ + + @Override + public void mouseClicked( MouseEvent e ) { + saveMousePosition(e.getPoint()); + mausklick(); + } + + public void mausklick() { + } + + @Override + public void mousePressed( MouseEvent e ) { + saveMousePosition(e.getPoint()); + maustasteRunter(); + } + + public void maustasteRunter() { + } + + @Override + public void mouseReleased( MouseEvent e ) { + saveMousePosition(e.getPoint()); + maustasteHoch(); + } + + public void maustasteHoch() { + } + + @Override + public void mouseEntered( MouseEvent e ) { + saveMousePosition(e.getPoint()); + } + + @Override + public void mouseExited( MouseEvent e ) { + saveMousePosition(e.getPoint()); + } + + @Override + public void mouseDragged( MouseEvent e ) { + saveMousePosition(e.getPoint()); + mausGezogen(); + } + + public void mausGezogen() { + + } + + @Override + public void mouseMoved( MouseEvent e ) { + saveMousePosition(e.getPoint()); + mausBewegt(); + } + + public void mausBewegt() { + + } + + private void saveMousePosition( Point pLocation ) { + //pmouseX = mouseX; + //pmouseY = mouseY; + /*synchronized(mouseLock) { + mouseX = pLocation.getX()-this.getRootPane().getX(); + mouseY = pLocation.getY()-this.getRootPane().getY(); + }*/ + } + + private void saveMousePosition() { + lmausX = mausX; + lmausY = mausY; + + java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation(); + java.awt.Point compLoc = leinwand.getLocationOnScreen(); + mausX = mouseLoc.x - compLoc.x; + mausY = mouseLoc.y - compLoc.y; + } + + /* + * Keyboard handling + */ + + @Override + public void keyTyped( KeyEvent e ) { + tastendruck(); + } + + public void tastendruck() { + + } + + @Override + public void keyPressed( KeyEvent e ) { + tasteRunter(); + } + + public void tasteRunter() { + + } + + @Override + public void keyReleased( KeyEvent e ) { + tasteHoch(); + } + + public void tasteHoch() { + + } +}