/* * 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); } }