/*
* Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine.
*
* Copyright (c) 2011 - 2014 Michael Andonie and contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ea;
import java.io.Serializable;
/**
* Ein nicht grafisches Rechteck auf der Zeichenebene, das eine allgemeine Fläche beschreibt.
*
* @author Michael Andonie
*/
public final class BoundingRechteck implements Serializable {
private static final long serialVersionUID = 99L;
/**
* <b>Reelle</b> <code>x</code>-Position des Rechtecks
*/
public final float x;
/**
* <b>Reelle</b> <code>y</code>-Position des Rechtecks
*/
public final float y;
/**
* <b>Reelle</b> Breite des Rechtecks
*/
public final float breite;
/**
* <b>Reelle</b> Höhe des Rechtecks
*/
public final float hoehe;
/**
* Konstruktor für Objekte der Klasse <code>BoundingRechteck</code> mit <b>reellen</b> Werten.
*
* @param x
* Die <code>x</code>-Koordinate der <i>oberen linken Ecke</i> des Rechtecks
* @param y
* Die <code>y</code>-Koordinate der <i>oberen linken Ecke</i> des Rechtecks
* @param dX
* Die Breite des Bounding-Rechtecks
* @param dY
* Die Höhe des Bounding-Rechtecks
*/
public BoundingRechteck (float x, float y, float dX, float dY) {
this.x = x;
this.y = y;
this.breite = dX;
this.hoehe = dY;
}
/**
* Berechnet aus diesem rein aus Zahlen bestehenden Rahmen ein Rechteck, das in der Zeichenebene
* darstellbar ist.
*
* @return Ein neues Rechteck-Objekt, das genau dieses BoundingRechteck abdeckt
*/
public Rechteck ausDiesem () {
return new Rechteck(x, y, breite, hoehe);
}
/**
* Ein Mittenangleich mit einem anderen BoundingRechteck
*
* @param r
* Das BoundingRechteck, an dessen Mitte auch die dieses Rechtecks sein soll.
*/
public BoundingRechteck mittenAngleichInstanz (BoundingRechteck r) {
return this.mittenAngleichInstanz(r.zentrum());
}
/**
* Gibt ein neues BoundingRechteck zurück, das seinen Punkt genau im angegebenen Zentrum hat.
*
* @param p
* Das Zentrum des zurückzugebenden BoundingRechtecks.
*
* @return Ein BoundingRechteck mit der gleichen Höhe und Breite wie dieses, jedoch so
* verschoben, dass es mit seiner Mitte im angegebenen Zentrum liegt.
*/
public BoundingRechteck mittenAngleichInstanz (Punkt p) {
Punkt z = this.zentrum();
return this.verschobeneInstanz(new Vektor(p.realX() - z.realX(), p.realY() - z.realY()));
}
/**
* Berechnet den Mittelpunkt dieses BoundingRechtecks in der Zeichenebene.
*
* @return Der Punkt mit den Koordinaten, der im Zentrum des Rechtecks liegt (bei ungeraden
* Koordinaten mit Abrundung)
*/
public Punkt zentrum () {
return new Punkt(x + ((breite) / 2), y + ((hoehe) / 2));
}
/**
* Berechnet ein neues BoundingRechteck mit denselben Maßen wie dieses, jedoch um einen
* bestimmten Vektor verschoben.
*
* @param v
* Der Vektor, der die Verschiebung des neuen Objektes von diesem beschreibt.
*
* @return Ein neues <code>BoundingRechteck</code>-Objekt, das die selbe Maße wie dieses hat,
* jedoch um die entsprechende Verschiebung verschoben ist.
*/
public BoundingRechteck verschobeneInstanz (Vektor v) {
return new BoundingRechteck(x + v.x, y + v.y, breite, hoehe);
}
/**
* Berechnet aus diesem und einem weiteren BoundingRechteck ein neues, dass die beiden genau
* fasst.
*
* @param r
* Das zweite Rechteck fuer die Berechnung
*
* @return Ein neues BoundingRechteck, dass die beiden Rechtecke genau umfasst.
*/
public BoundingRechteck summe (BoundingRechteck r) {
float x, y, dX, dY;
if (r.x < this.x) {
x = r.x;
} else {
x = this.x;
}
if (r.y < this.y) {
y = r.y;
} else {
y = this.y;
}
if (r.x + r.breite > this.x + this.breite) {
dX = (r.x + r.breite) - x;
} else {
dX = (this.x + this.breite) - x;
}
if (r.y + r.hoehe > this.y + this.hoehe) {
dY = (r.y + r.hoehe) - y;
} else {
dY = (this.y + this.hoehe) - y;
}
return new BoundingRechteck(x, y, dX, dY);
}
/**
* Berechnet, ob dieses Rechteck über einer Grenze liegt und wenn <b>nicht</b>, dann berechnet
* es eines, das gerade so an der Untergrenze liegt.
*
* @param untergrenze
* Die Grenze, auf der das Ergebnis maximal liegen darf.
*
* @return Ein BoundingRechteck derselben Höhe und Breite wie dieses, das in jedem Fall über,
* oder auf der Grenze liegt, wenn es passt, ist es <code>this</code>.
*/
public BoundingRechteck ueber (int untergrenze) {
if (y + hoehe < untergrenze) {
return this;
} else {
return new BoundingRechteck(x, untergrenze - hoehe, breite, hoehe);
}
}
/**
* Berechnet, ob dieses Rechteck unter einer Grenze liegt, und wenn <b>nicht</b>, dann berechnet
* es eines, das gerade so an der Obergrenze liegt.
*
* @param obergrenze
* Die Grenze, auf der das Ergebnis maximal liegen darf.
*
* @return Ein BoundingRechteck derselben Hoehe und Breite wie dieses, das in jedem Fall unter,
* oder auf der Grenze liegt, wenn es passt, ist es <code>this</code>.
*/
public BoundingRechteck unter (int obergrenze) {
if (y > obergrenze) {
return this;
} else {
return new BoundingRechteck(x, obergrenze, breite, hoehe);
}
}
/**
* Berechnet, ob dieses Rechteck rechts von einer bestimmten Grenze liegt, und wenn
* <b>nicht</b>, dann berechnet es eines, das gerade so an der linken Extremgrenze liegt.
*
* @param grenzeLinks
* Der Wert, den das Ergebnisrechteck maximal links sein darf
*
* @return Ein BoundingRechteck derselben Höhe und Breite, das in jedem rechts jenseits oder auf
* der Grenze liegt.<br /> Wenn diese Eigenschaften bereits von diesem Objekt erfüllt werden, so
* wird <code>this</code> zurückgegeben.
*/
public BoundingRechteck rechtsVon (int grenzeLinks) {
if (x > grenzeLinks) {
return this;
} else {
return new BoundingRechteck(grenzeLinks, y, breite, hoehe);
}
}
/**
* Berechnet, ob dieses Rechteck links von einer bestimmten Grenze liegt, und wenn <b>nicht</b>,
* dann berechnet es eines, das gerade so an der rechten Extremgrenze liegt.
*
* @param grenzeRechts
* Der Wert, den das Ergebnisrechteck maximal rechts sein darf
*
* @return Ein BoundingRechteck derselben Höhe und Breite, das in jedem Fall links jenseits oder
* auf der Grenze liegt.<br /> Wenn diese Eigenschaften bereits von diesem Objekt erfüllt
* werden, so wird <code>this</code> zurückgegeben.
*/
public BoundingRechteck linksVon (int grenzeRechts) {
if (x + breite < grenzeRechts) {
return this;
} else {
return new BoundingRechteck(grenzeRechts - breite, y, breite, hoehe);
}
}
/**
* Gibt ein neues BoundingRechteck mit selber Höhe und Breite, jedoch einer bestimmten, zu
* definierenden Position.<br /> Diese Position ist die der <i>linken oberen Ecke</i> des
* BoundingRechtecks.
*
* @param realX
* Die <i>X-Koordinate der linken oberen Ecke</i> des BoundingRechtecks
* @param realY
* Die <i>Y-Koordinate der linken oberen Ecke</i> des BoundingRechtecks
*
* @return Ein neues BoundingRechteck mit der eingegebenen Position und derselben Breite und
* Höhe.
*/
public BoundingRechteck anPosition (float realX, float realY) {
return new BoundingRechteck(realX, realY, breite, hoehe);
}
/**
* Gibt einen <code>BoundingKreis</code> aus, der das Rechteck minimal, aber voll umschließt.
*
* @return der <code>BoundingKreis</code> aus, der das Rechteck minimal, aber voll umschließt.
* Die Ecken des Rechtecks liegen alle auf dem Kreis.
*/
public KreisCollider umschliessenderKreis () {
Punkt z = this.zentrum();
return new KreisCollider(z, z.abstand(new Punkt(x, y)));
}
/**
* Testet, ob sich ein Dreieck in dem BoundingRechteck befindet.<br /> Hierbei wird zuerst
* getestet, ob ein Punkt des Dreiecks im Rechteck ist, dann, falls nötig ob ein Punkt des
* Rechtecks im Dreieck ist.
*/
public boolean schneidet (Dreieck d) {
if (d == null) {
return false;
}
Punkt[] punkte = d.punkte();
for (int i = 0; i < punkte.length; i++) {
if (istIn(punkte[i])) {
return true;
}
}
punkte = this.punkte();
for (int i = 0; i < punkte.length; i++) {
if (d.beinhaltet(punkte[i])) {
return true;
}
}
return false;
}
/**
* Testet, ob ein Punkt sich in dem BoundingRechteck befindet.
*
* @param p
* Der Punkt, der getestet werden soll
*
* @return true, wenn der Punkt in dem BoundingRechteck ist
*/
public boolean istIn (Punkt p) {
return (p.realX() >= this.x && p.realY() >= this.y && p.realX() <= (x + breite) && p.realY() <= (y + hoehe));
}
/**
* Berechnet die vier Eckpunkte des umfassenden {@link ea.BoundingRechteck}s
*
* @return Array mit den vier Eckpunkten des umfassenden {@link ea.BoundingRechteck}s
*/
public Punkt[] punkte () {
return new Punkt[] {new Punkt(x, y), new Punkt(x + breite, y), new Punkt(x, y + hoehe), new Punkt(x + breite, y + hoehe)};
}
/**
* Diese Methoden prüft, ob dieses Bounding-Rechteck ein zweites vollkommen umschliesst.<br />
* <i>Gemeinsame Ränder zählen <b>AUCH</b> als umschliessen!</i>
*
* @param innen
* Das Innere Bounding-Rechteck. Es soll geprüft werden, ob dieses Vollkommen von dem die
* Methode ausführenden Rechteck umschlossen wird.
*
* @return <code>true</code>, wenn das <b>ausfuehrende Bounding-Rechteck das als Argument
* übergebene BoundingRechteck voll umschliesst</b>, sonst <code>false</code>.
*/
public boolean umschliesst (BoundingRechteck innen) {
return (this.x <= innen.x && this.y <= innen.y && (this.x + this.breite) >= (innen.x + innen.breite) && (this.y + this.hoehe) >= (innen.y + innen.hoehe));
}
/**
* Berechnet, ob dieses BoundingRechteck auf einem zweiten "steht".
*
* @param r
* Das BoundingRechteck, auf dem dieses stehen koennte
*
* @return <code>true</code>, wenn dies so ist, sonst <code>false</code>.
*/
public boolean stehtAuf (BoundingRechteck r) {
if ((r.x + r.breite) > this.x && r.x < (this.x + this.breite)) {
return (r.y == this.y + this.hoehe);
}
return false;
}
/**
* Berechnet, wie weit man waagrecht ein BoundingRechteck verschieben müsste, damit es dieses
* nicht mehr berührt.
*
* @param r
* Das BoundingRechteck, das eventuell verschoben werden müsste.
*
* @return Die Zahl, die angibt, wie weit man es verschieben muesste, oder 0 wenn sich die
* beiden nicht berühren.
*/
public float verschiebenX (BoundingRechteck r) {
if (!this.schneidetBasic(r)) {
return 0;
}
if (r.linksVon(this)) {
return this.x - (r.x + r.breite);
} else {
return (this.x + this.breite) - r.x;
}
}
/**
* Testet, ob ein anderes BoundingRechteck dieses schneidet.<br /> Schneiden bedeutet folgendes
* im Sinne der Engine Alpha:<br /> <i>Beide Rechtecke teilen sich mindestens einen (aber
* meistens mehrere) Punkte auf der Zeichenebene</i>.
*
* @param fig
* Das zweite zu testende BoundingRechteck
*
* @return <code>true</code>, wenn sich die beiden schneiden, sonst <code>false</code>.
*/
public boolean schneidetBasic (BoundingRechteck fig) {
if (fig.y < (this.y + this.hoehe) && (fig.y + fig.hoehe) > this.y) {
if ((fig.x + fig.breite) > this.x && fig.x < (this.x + this.breite)) {
return true;
}
}
return false;
}
/**
* Berechnet, ob dieses BoundingRechteck links von einem zweiten ist
*
* @param r
* Das Rechteck, bei dem dies getestet werden soll
*
* @return <code>true</code>, wenn dieses Rechteck rechts von dem anderen ist, sonst
* <code>false</code>.
*/
public boolean linksVon (BoundingRechteck r) {
return ((this.x) < (r.x));
}
/**
* Berechnet, wie weit man senkrecht ein BoundingRechteck verschieben müsste, damit es dieses
* nicht mehr berührt.
*
* @param r
* Das BoundingRechteck, das eventuell verschoben werden müsste.
*
* @return Die Zahl, die angibt, wie weit man es verschieben müsste, oder 0 wenn sich die beiden
* nicht berühren.
*/
public float verschiebenY (BoundingRechteck r) {
if (!this.schneidetBasic(r)) {
return 0;
}
if (r.ueber(this)) {
return this.y - (r.y + r.hoehe);
} else {
return (this.y + this.hoehe) - r.y;
}
}
/**
* Berechnet, ob dieses BoundingRechteck ueber einem zweiten ist
*
* @param r
* Das Rechteck, bei dem dies getestet werden soll
*
* @return <code>true</code>, wenn dieses Rechteck rechts von dem anderen ist, sonst
* <code>false</code>.
*/
public boolean ueber (BoundingRechteck r) {
return ((this.y) < (r.y));
}
/**
* Berechnet den Höhenunterschied zwischen dem Fuß des höheren und dem Kopf des tieferen
* BoundingRechtecks.
*
* @param r
* Das BoundingRechteck, dessen Höhenunterschied zu diesem gefunden werden soll
*
* @return Der <b>absolute (also niemals negative)</b> Unterschied in der Höhe zwischen den
* beiden Objekten. <b>Überlagern sie sich, so ist der Rückgabewert 0</b>!
*/
public float hoehenUnterschied (BoundingRechteck r) {
if (this.schneidetBasic(r)) {
return 0;
}
if (this.y < r.y) { // Dieses Rechteck ist das Hoehere!!
return r.y - (this.y + this.hoehe);
} else { // Das andere Rechteck ist realHoeher!!
return this.y - (r.y + r.hoehe);
}
}
/**
* Transformiert dieses Boudning-Rechteck auf 2 Weisen: Einmal in der Postion und zusätzlich in
* seiner Höhe.
*
* @param v
* Der Vektor, der die Verschiebung beschreibt.
* @param dHoehe
* Die Höhen<b>änderung</b>.
*
* @return Ein neues BoundingRechteck, das verschoben und in seiner Höhe geändert ist.
*/
public BoundingRechteck verschErhoeht (Vektor v, int dHoehe) {
return new BoundingRechteck(x + v.x, y + v.y, breite, hoehe + dHoehe);
}
/**
* Sollte dieses Bounding-Rechteck nicht voll innerhalb eines bestimmten anderen, äußeren
* Rechtecks liegen, so wird versucht, dieses Bounding-Rechteck <i>in das andere mit möglichst
* wenig Verschiebung</i> zu bringen. Diese Methode wird intern für die Beschränkung des
* Kamera-Bereiches genutzt.
* <p/>
* <div class='hinweisProbleme'><b>Achtung</b>: Voraussetzung dafuer, dass dieser Algorithmus
* Sinn macht ist, dass das äußere Rechteck ausreichend größer als dieses ist!</div>
*
* @param aussen
* Das äußere Rechteck, innerhalb dessen sich das Ergebnis-Rechteck befinden wird (sollte das
* äußere ausreichend groß sein).
*
* @return Das Ergebnis-Rechteck, das sich im äußeren Rechteck befinden wird.
*/
public BoundingRechteck in (BoundingRechteck aussen) {
float realX = this.x, realY = this.y;
if (this.x < aussen.x) {
realX = aussen.x;
}
if (this.x + this.breite > aussen.x + aussen.breite) {
realX = aussen.x + aussen.breite - this.breite;
}
if (this.y < aussen.y) {
realY = aussen.y;
}
if (this.y + this.hoehe > aussen.y + aussen.hoehe) {
realY = aussen.y + aussen.hoehe - this.hoehe;
}
return new BoundingRechteck(realX, realY, this.breite, this.hoehe);
}
/**
* Erstellt einen Klon von diesem BoundingRechteck.
*
* @return Ein neues BoundingRechteck mit genau demselben Zustand wie dieses.
*/
public BoundingRechteck klon () {
return new BoundingRechteck(x, y, breite, hoehe);
}
/**
* Gibt eine String-Repräsentation dieses Objektes aus.
*
* @return Die String-Repräsentation dieses Objektes. Hierin wird Auskunft über alle 4
* ausschlaggebenden Zahlen (<code>x</code>, <code>y</code>, <code>dX</code> und <code>dY</code>
* gemacht)
*/
@Override
public String toString () {
return "Bounding-Rechteck: x:" + x + " y: " + y + " dX: " + breite + " dY: " + hoehe;
}
/**
* Gibt die <b>reelle</b> X-Koordinate der oberen linken Ecke aus.
*
* @return Die <b>reelle</b> X-Koordinate der oberen linken Ecke dieses BoundingRechtecks.
*
* @see #getRealY()
* @see #getRealBreite()
* @see #getRealHoehe()
*/
public float getRealX () {
return x;
}
/**
* Gibt die <b>reelle</b> Y-Koordinate der oberen linken Ecke aus.
*
* @return Die <b>reelle</b> Y-Koordinate der oberen linken Ecke dieses BoundingRechtecks.
*
* @see #getRealX()
* @see #getRealBreite()
* @see #getRealHoehe()
*/
public float getRealY () {
return y;
}
/**
* Gibt die <b>reelle</b> Breite aus.
*
* @return Die <b>reelle</b> Breite dieses BoundingRechtecks.
*
* @see #getRealX()
* @see #getRealY()
* @see #getRealHoehe()
*/
public float getRealBreite () {
return breite;
}
/**
* Gibt die <b>reelle</b> Hoehe aus.
*
* @return Die <b>reelle</b> Hoehe dieses BoundingRechtecks.
*
* @see #getRealX()
* @see #getRealY()
* @see #getRealBreite()
*/
public float getRealHoehe () {
return hoehe;
}
/**
* Gibt die exakte Position der linken oberen Ecke dieses Bounding-Rechtecks
* aus.
* @return die Position des BoundingRechtecks, beschrieben durch den Punkt der
* linken oberen Ecke dieses Objekts.
*/
public Punkt position() {
return new Punkt(x,y);
}
}