/*
* Datei: Geometry2D.java
* Autor(en): Lukas König
* Java-Version: 6.0
* Letzte Aenderung: 23.12.2008
*
* (c) This file and the EAS (Easy Agent Simulation) framework containing it
* is protected by Creative Commons by-nc-sa license. Any altered or
* further developed versions of this file have to meet the agreements
* stated by the license conditions.
*
* In a nutshell
* -------------
* You are free:
* - to Share -- to copy, distribute and transmit the work
* - to Remix -- to adapt the work
*
* Under the following conditions:
* - Attribution -- You must attribute the work in the manner specified by the
* author or licensor (but not in any way that suggests that they endorse
* you or your use of the work).
* - Noncommercial -- You may not use this work for commercial purposes.
* - Share Alike -- If you alter, transform, or build upon this work, you may
* distribute the resulting work only under the same or a similar license to
* this one.
*
* + Detailed license conditions (Germany):
* http://creativecommons.org/licenses/by-nc-sa/3.0/de/
* + Detailed license conditions (unported):
* http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en
*
* This header must be placed in the beginning of any version of this file.
*/
package eas.math.geometry;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import eas.miscellaneous.StaticMethods;
import eas.startSetup.ParCollection;
import eas.startSetup.marbBuilder.zeichenModi.AusgMerkm;
import eas.startSetup.marbBuilder.zeichenModi.ConstantsZeichenModi;
import eas.startSetup.marbBuilder.zeichenModi.Pol2DMitAusgMerkm;
/**
* Kollektion allgemeiner geometrischer Methoden für den 2-dimensionalen
* Raum.
*
* @author Lukas König
*/
public class Geometry2D implements Serializable {
private static final long serialVersionUID = 3249238938327700623L;
/**
* Berechnet eine Bezierkurve im Polygon (p1, p2, p3, p4). Der Abstand der
* Punkte wird durch <code>step</code> (0..1) gesteuert.
*
* @param p0 Punkt.
* @param p1 Punkt.
* @param p2 Punkt.
* @param p3 Punkt.
* @param step Schrittgröße.
*
* @return Die Punkte der Bezierkurve.
*/
public static Polygon2D bezierKurve(
final Vector2D p0,
final Vector2D p1,
final Vector2D p2,
final Vector2D p3,
final double step) {
Polygon2D kurve = new Polygon2D();
Vector2D punkt;
Vector2D dreiP0 = (new Vector2D(p0));
Vector2D dreiP1 = (new Vector2D(p1));
Vector2D dreiP2 = (new Vector2D(p2));
Vector2D sechsP1 = (new Vector2D(p1));
dreiP0.mult(3);
dreiP1.mult(3);
dreiP2.mult(3);
sechsP1.mult(6);
Vector2D punkt2, punkt3;
double tQuad, tKub;
for (double t = 0; t <= 1; t += step) {
tQuad = t * t;
tKub = t * tQuad;
punkt = new Vector2D(dreiP1);
punkt.sub(p0);
punkt.sub(dreiP2);
punkt.translate(p3);
punkt.mult(tKub);
punkt2 = new Vector2D(dreiP0);
punkt2.sub(sechsP1);
punkt2.translate(dreiP2);
punkt2.mult(tQuad);
punkt3 = new Vector2D(dreiP1);
punkt3.sub(dreiP0);
punkt3.mult(t);
punkt.translate(punkt2);
punkt.translate(punkt3);
punkt.translate(p0);
kurve.add(punkt);
}
return kurve;
}
/**
* Gibt ein Kreis-Polygon zurück.
*
* @param mitte Der Mittelpunkt.
* @param radius Der Radius.
* @param anzahl Die Schrittzahl.
*
* @return Das Kreispolygon.
*/
public static Polygon kreis(final Vector2D mitte,
final double radius,
final long anzahl) {
double schritt = 2 * Math.PI / anzahl;
Polygon kreis = new Polygon();
double winkel;
double x;
double y;
for (winkel = 0; winkel <= 2 * Math.PI; winkel += schritt) {
x = mitte.x + radius * Math.sin(winkel);
y = mitte.y + radius * Math.cos(winkel);
kreis.addPoint((int) x, (int) y);
}
return kreis;
}
/**
* Zeichnet eine Menge von Polygonen und (entsprechend der Vereinbarung
* im Interface ZeichenKit codierten) Strings, die als Liste übergeben
* werden.
*
* @param g Das Grafikobjekt, in das gezeichnet werden soll.
* @param polListe Die Liste der Polygone.
*/
public static void maleObjListe(
final Graphics g,
final List<Object> polListe) {
Iterator<Object> it;
Object obj;
int x;
int y;
String str;
AusgMerkm ausgabe;
Circle2D kreis;
boolean fuellungDrucken = ConstantsZeichenModi.STANDARD_FUELL_DRUCKEN;
boolean rahmenDrucken = ConstantsZeichenModi.STANDARD_RAHMEN_DRUCKEN;
Color fuellungFarbe = ConstantsZeichenModi.STANDARD_FUELL_FARBE;
Color rahmenFarbe = ConstantsZeichenModi.STANDARD_RAHMEN_FARBE;
String schriftart;
int schriftstil;
int schriftgroesse;
GradientPaint gp = null;
Graphics2D g2 = (Graphics2D) g;
BasicStroke s1 = new BasicStroke(BasicStroke.JOIN_MITER);
// BasicStroke s2 = new BasicStroke(BasicStroke.CAP_BUTT);
// if (params.getEinfacheDar()) {
// g2.setStroke(s2);
// } else {
g2.setStroke(s1);
// }
it = polListe.iterator();
while (it.hasNext()) {
obj = it.next();
if (obj.getClass().equals(Polygon.class)) {
if (fuellungDrucken) {
if (gp == null) {
g2.setColor(fuellungFarbe);
} else {
g2.setPaint(gp);
}
g2.fillPolygon((Polygon) obj);
}
if (rahmenDrucken) {
g2.setColor(rahmenFarbe);
g2.drawPolygon((Polygon) obj);
}
} else if (obj.getClass().equals(Circle2D.class)) {
kreis = (Circle2D) obj;
if (fuellungDrucken) {
if (gp == null) {
g2.setColor(fuellungFarbe);
} else {
g2.setPaint(gp);
}
g2.fillOval(
(int) (kreis.getCenter().x - kreis.getRadius()),
(int) (kreis.getCenter().y - kreis.getRadius()),
(int) (kreis.getRadius() * 2),
(int) (kreis.getRadius() * 2));
}
if (rahmenDrucken) {
g2.setColor(rahmenFarbe);
g2.drawOval(
(int) (kreis.getCenter().x - kreis.getRadius()),
(int) (kreis.getCenter().y - kreis.getRadius()),
(int) (kreis.getRadius() * 2),
(int) (kreis.getRadius() * 2));
}
} else if (obj.getClass().equals(AusgMerkm.class)) {
ausgabe = (AusgMerkm) obj;
fuellungDrucken = ausgabe.holeFuellungDrucken();
rahmenDrucken = ausgabe.holeRahmenDrucken();
fuellungFarbe = ausgabe.getFuellFarbe();
rahmenFarbe = ausgabe.getRahmenFarbe();
schriftart = ausgabe.getFontName();
schriftstil = ausgabe.getFontStyle();
schriftgroesse = ausgabe.getFontSize();
g2.setFont(new Font(schriftart, schriftstil, schriftgroesse));
gp = ausgabe.getGradPaint();
} else {
x = ((Integer) obj).intValue();
obj = it.next();
y = ((Integer) obj).intValue();
obj = it.next();
str = (String) obj;
g2.setColor(rahmenFarbe);
g2.drawString(str, x, y);
}
}
// Rücksetzen der Vordergrundfarbe.
g2.setColor(ConstantsZeichenModi.STANDARD_RAHMEN_FARBE);
}
/**
* Gibt die Stützpunkte einer Kurve zurück, die eine Annäherung an eine
* Kurve aus beliebig vielen Einzelstücken aus Bezierkurven dritten Grades
* ist.
*
* @param p Die Stützpunkte der Bezierkurven.
* @param v Die Vektoren an den inneren Stützpunkten.
* @param k Die Multiplikatoren für die inneren Vektoren.
* @param step Die Schrittweite bei der Bezierberechnung..
* @param params Die Parameter.
*
* @return Die Kurve aus Beziersegmenten.
*/
public static Polygon2D multiBezier(
final Polygon2D p,
final Polygon2D v,
final ArrayList<Double> k,
final double step,
final ParCollection params) {
Polygon2D kurve = new Polygon2D();
Polygon2D bezier;
Vector2D v1, v2;
if (p.nPoints() < 2 || p.nPoints() != v.nPoints() || v.nPoints() != k.size() + 2) {
StaticMethods.log(StaticMethods.LOG_ERROR,
"Falsche Parameter bei Aufruf von multiBezier.",
params);
throw new IllegalArgumentException();
}
v1 = new Vector2D(v.get(0));
v2 = new Vector2D(p.get(1));
v1.translate(p.get(0));
v2.sub(v.get(1));
bezier = eas.math.geometry.Geometry2D.bezierKurve(
p.get(0), v1, v2, p.get(1), step);
kurve.addAll(bezier);
for (int i = 1; i < p.nPoints() - 1; i++) {
v1 = new Vector2D(v.get(i));
v2 = new Vector2D(p.get(i + 1));
v1.mult(k.get(i - 1));
v1.translate(p.get(i));
v2.sub(v.get(i + 1));
bezier = eas.math.geometry.Geometry2D.bezierKurve(
p.get(i), v1, v2, p.get(i + 1), step);
kurve.addAll(bezier);
}
return kurve;
}
/**
* Ermittelt einen umschließenden Rahmen um alle Objekte in der Liste.
*
* @param objekte Die Objekte.
*
* @return Die linke obere und rechte untere Koordinate des
* Rahmens.
*/
public static Polygon2D rahmen(final List<Object> objekte) {
Vector2D maxPkt = new Vector2D(Double.NEGATIVE_INFINITY,
Double.NEGATIVE_INFINITY);
Vector2D minPkt = new Vector2D(Double.POSITIVE_INFINITY,
Double.POSITIVE_INFINITY);
Vector2D zwischMax = new Vector2D(Vector2D.NULL_VECTOR);
Vector2D zwischMin = new Vector2D(Vector2D.NULL_VECTOR);
Polygon2D rahmen = new Polygon2D();
int stringPos = 0;
int stringX = 0;
int stringY = 0;
for (Object o : objekte) {
if (o.getClass().equals(Polygon.class)) {
Polygon p = (Polygon) o;
zwischMax.x = p.getBounds().getMaxX();
zwischMax.y = p.getBounds().getMaxY();
zwischMin.x = p.getBounds().getMinX();
zwischMin.y = p.getBounds().getMinY();
} else if (o.getClass().equals(Circle2D.class)) {
Circle2D k = (Circle2D) o;
zwischMax = new Vector2D(k.getCenter());
zwischMin = new Vector2D(k.getCenter());
zwischMax.translate(new Vector2D(k.getRadius(), k.getRadius()));
zwischMin.sub(new Vector2D(k.getRadius(), k.getRadius()));
} else if (o.getClass().equals(Integer.class)) {
if (stringPos == 0) {
stringX = (Integer) o;
stringPos++;
} else {
stringY = (Integer) o;
stringPos = 0;
}
} else if (o.getClass().equals(String.class)) {
String s = (String) o;
double stringHeight = 10;
double stringWidth = s.length() * 6;
zwischMin = new Vector2D(stringX, stringY - stringHeight);
zwischMax = new Vector2D(stringX + stringWidth, stringY);
} else {
zwischMax = new Vector2D(maxPkt);
zwischMin = new Vector2D(minPkt);
}
if (zwischMax.x > maxPkt.x) {
maxPkt.x = zwischMax.x;
}
if (zwischMax.y > maxPkt.y) {
maxPkt.y = zwischMax.y;
}
if (zwischMin.x < minPkt.x) {
minPkt.x = zwischMin.x;
}
if (zwischMin.y < minPkt.y) {
minPkt.y = zwischMin.y;
}
}
rahmen.add(minPkt);
rahmen.add(maxPkt);
return rahmen;
}
/**
* Erzeugt eine ausgabefähige Objektliste aus einer Liste von Polygonen
* mit zugehörigen Ausgabemerkmalen.
*
* @param polAusg Die Polygone mit Ausgabemerkmalen.
* @param skalierung Die Skalierung.
* @param versch Die Verschiebung.
*
* @return Die ausgabefähige Objektliste.
*/
public static List<Object> erzeugeObjList(
final Pol2DMitAusgMerkm[] polAusg,
final double skalierung,
final Vector2D versch) {
List<Object> oL = new ArrayList<Object>(2 * polAusg.length);
AusgMerkm aktAusg;
Polygon2D aktPol;
Point2D p1;
Point2D p2;
GradientPaint grad;
Color fuell, rahmen;
for (int i = 0; i < polAusg.length; i++) {
if (polAusg[i] != null) {
aktAusg = polAusg[i].getAusg();
aktPol = polAusg[i].getPol();
if (aktAusg != null) {
if (aktAusg.getGradPaint() != null) {
p1 = aktAusg.getGradPaint().getPoint1();
p2 = aktAusg.getGradPaint().getPoint2();
grad = new GradientPaint(
(int) (p1.getX() * skalierung),
(int) (p1.getY() * skalierung),
aktAusg.getGradPaint().getColor1(),
(int) (p2.getX() * skalierung),
(int) (p2.getY() * skalierung),
aktAusg.getGradPaint().getColor2());
fuell = aktAusg.getFuellFarbe();
rahmen = aktAusg.getRahmenFarbe();
aktAusg = new AusgMerkm(
grad,
aktAusg.holeRahmenDrucken(),
aktAusg.holeFuellungDrucken());
aktAusg.setFuellFarbe(fuell);
aktAusg.setRahmenFarbe(rahmen);
}
oL.add(aktAusg);
}
oL.add(aktPol.toPol(skalierung, versch));
}
}
return oL;
}
/**
* Speichert eine Liste von Grafikobjekten als Image ab.
* Die Liste kann Objekte folgenden Typs enthalten:<BR>
* - Polygon (wird direkt gezeichnet mit den aktuellen Einstellungen
* für Hintergrund-, Vordergrundfarbe und Rahmeneigenschaften),<BR>
* - Kreisklasse (zeichnet wie oben direkt einen Kreis),<BR>
* - Ausgabemerkmale (setzt Farben, Rahmeneigenschaften usw. für alle
* NACHFOLGENDEN Objekte) und<BR>
* - aufeinanderfolgend zwei Integer-Objekte x und y und ein String-Objekt
* (wird als String gezeichnet, wobei die linke untere Ecke an den
* Koordinaten (x / y) ist).
*
* @param oL Zu speichernde Liste von Objekten.
* @param filename Dateiname (ohne Erweiterung) unter dem das Bild
* abgelegt werden soll.
* @param modus Der Speichermodus ("jpg", "png", etc.)
* @param params Die Parameter.
**/
public static void saveObjectsToFile(
final List<Object> oL,
final String filename,
final String modus,
final ParCollection params) {
BufferedImage buffImg;
boolean vorhanden = false;
String datNam;
if (filename.substring(filename.length() - 4).toLowerCase()
.equals("." + modus.toLowerCase())) {
datNam = params.getStdDirectory()
+ File.separator
+ filename;
} else {
datNam = params.getStdDirectory()
+ File.separator
+ filename
+ "." + modus;
}
for (int i = 0; i < ImageIO.getWriterFormatNames().length; i++) {
if (modus.equals(ImageIO.getWriterFormatNames()[i])) {
vorhanden = true;
}
}
if (!vorhanden) {
StaticMethods.log(StaticMethods.LOG_ERROR,
"Grafikmodus nicht vorhanden: " + modus,
params);
StaticMethods.log(StaticMethods.LOG_ERROR,
"Grafik nicht gespeichert: " + datNam,
params);
return;
}
buffImg = Geometry2D.erzBuffImgAusObjekten(oL);
try {
ImageIO.write(buffImg, modus, new File(datNam));
} catch (final Exception e) {
StaticMethods.log(StaticMethods.LOG_ERROR,
"Datei konnte nicht gespeichert werden: "
+ datNam,
params);
return;
}
StaticMethods.log(StaticMethods.LOG_INFO,
"Graphik gespeichert in: "
+ datNam,
params);
}
/**
* Erzeugt ein BufferedImage aus einer Objektliste. Diese kann enthalten:
* <BR>
* - Polygon (wird direkt gezeichnet mit den aktuellen Einstellungen
* für Hintergrund-, Vordergrundfarbe und Rahmeneigenschaften),<BR>
* - Kreisklasse (zeichnet wie oben direkt einen Kreis),<BR>
* - Ausgabemerkmale (setzt Farben, Rahmeneigenschaften usw. für alle
* NACHFOLGENDEN Objekte) und<BR>
* - aufeinanderfolgend zwei Integer-Objekte x und y und ein String-Objekt
* (wird als String gezeichnet, wobei die linke untere Ecke an den
* Koordinaten (x / y) ist).
*
* @param oL Zu speichernde Liste von Objekten.
* @param params Die Parameter.
*
* @return Das BufferedImage.
**/
public static BufferedImage erzBuffImgAusObjekten(
final List<Object> oL) {
Polygon2D rahmen = eas.math.geometry.Geometry2D.rahmen(oL);
Vector2D rahmenMin = rahmen.get(0);
Vector2D rahmenMax = rahmen.get(1);
rahmenMin.sub(new Vector2D(10, 10));
rahmenMax.translate(new Vector2D(10, 10));
double breite = rahmenMax.x - rahmenMin.x;
double hoehe = rahmenMax.y - rahmenMin.y;
if (breite <= 0) {
breite = 1;
}
if (hoehe <= 0) {
hoehe = 1;
}
BufferedImage buffImg = new BufferedImage(
(int) breite,
(int) hoehe,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) buffImg.getGraphics();
g2.translate(-rahmenMin.x, -rahmenMin.y);
g2.setStroke(new BasicStroke(BasicStroke.JOIN_MITER));
g2.setColor(Color.white);
g2.fillRect((int) rahmenMin.x,
(int) rahmenMin.y,
(int) (rahmenMax.x - rahmenMin.x),
(int) (rahmenMax.y - rahmenMin.y));
eas.math.geometry.Geometry2D.maleObjListe(g2, oL);
return buffImg;
}
/**
* Speichert ein Polygon als Grafik ab.
*
* @param p Zu speicherndes Polygon.
* @param filename Dateiname (ohne oder mit Erweiterung) unter dem das
* Bild abgelegt werden soll.
* @param modus Der Speichermodus ("jpg", "png", etc.)
* @param params Die Parameter.
*/
public static void saveSinglePolygonToFile(
final Polygon p,
final String filename,
final String modus,
final ParCollection params) {
ArrayList<Object> oL = new ArrayList<Object>(1);
oL.add(p);
eas.math.geometry.Geometry2D.saveObjectsToFile(
oL,
filename,
modus,
params);
}
/**
* Berechnet jeweils auf beiden Seiten einer Kurve aus zwei
* Streckensegmenten Schnittpunkte der vier die Kurve im Abstand "dicke"
* umhüllenden, zu den Teilstrecken parallelen Geraden.
*
* @param p1 Der erste Punkt.
* @param p2 Der zweite Punkt.
* @param p3 Der dritte Punkt.
* @param dicke Die Dicke des Pfeils an der Stelle.
*
* @return Die beiden Schnittpunkte.
*/
public static Polygon2D schnPkte(
final Vector2D p1,
final Vector2D p2,
final Vector2D p3,
final double dicke) {
Polygon2D pkte = new Polygon2D();
Vector2D v1 = new Vector2D(p2.x - p1.x, p2.y - p1.y);
Vector2D v2 = new Vector2D(p3.x - p2.x, p3.y - p2.y);
Vector2D c1 = new Vector2D(v1.y, -v1.x);
Vector2D c2 = new Vector2D(v2.y, -v2.x);
Vector2D p11;
Vector2D p12;
Vector2D p01;
Vector2D p02;
Line2D g1, h1, g2, h2;
c1.mult(dicke / (c1.length() * 2));
c2.mult(dicke / (c2.length() * 2));
p11 = new Vector2D(p1);
p12 = new Vector2D(p3);
p01 = new Vector2D(p1);
p02 = new Vector2D(p3);
p11.translate(c1);
p12.translate(c2);
p01.sub(c1);
p02.sub(c2);
v1.setLength(p1.distance(p2));
v2.setLength(p2.distance(p3));
g1 = new Line2D(p11, v1);
h1 = new Line2D(p12, v2);
g2 = new Line2D(p01, v1);
h2 = new Line2D(p02, v2);
pkte.add(g1.schnPktSpezial(h1));
pkte.add(g2.schnPktSpezial(h2));
return pkte;
}
}