/* * 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.internal.phy; import ea.*; import java.util.ArrayList; import java.util.concurrent.CopyOnWriteArrayList; /** * Ein Objekt der Klasse Physik behandelt eigenstaendig verschiedene Raum-Objekte als * Physik-Engine.<br /> Grundlegend behandelt sie Trefferkollisionen eigenstaendig, also bietet es * sich an, diese Eigenschaft zu nutzen, und zwar in dem Interface <code>KollisionsReagierbar</code>.<br * /> <br /> * <p/> * Weiterhin - und dies ist eine <b>essentielle Aufgabe fuer viele 2D-Spiele</b> - kann diese * Maschine<br /> - An gewuenschtren Raum-Objekten Schwerkraft erzeugen<br /> - Aktive Raum-Objekte * (<b>Aktivobjekte</b>, zum Beispiel Spielfiguren) so beeinlfussen, dass es fuer sie nicht moeglich * ist, passive Raum-Objekte (<b>PassivObjekte</b>, zum Beispiel Mauer, Waende, Boeden) zu * schneiden.<br /> <br /> Diese beiden Eigenschaften in Kombination erzeugen die Moeglichkeit, sehr * einfach eine funktionierende Spielewelt zu programmieren, in der bereits ein funktionierendes * System zum Fallen und Grenzen abstecken existiert.<br /> <br /> * * @author Michael Andonie */ @SuppressWarnings ( "serial" ) public class Physik extends Manager implements Ticker { /** * Die EINE Physik */ private static Physik physik; /** * Die Liste aller Kollisionstestauftraege */ private ArrayList<Auftrag> kollisionsListe = new ArrayList<>(); /** * Eine Liste aller Passiven Objekte. */ private CopyOnWriteArrayList<Passivator> passive = new CopyOnWriteArrayList<>(); /** * Eine Liste aller Gravitatoren (indirekt Aktivobjekte) */ private CopyOnWriteArrayList<Gravitator> gravitatoren = new CopyOnWriteArrayList<>(); /** * Der Rundenzaehler der Physik */ private int runde = 1; /** * Konstruktor. */ private Physik () { super(); Manager.standard.anmelden(new Ticker() { public void tick () { for (Auftrag a : kollisionsListe) { a.test(); } } }, 35); this.anmelden(this, 1); } /** * Neutralisiert die aktuelle Physik und macht Platz fuer eine neue. */ public static void neutralize () { if (physik == null) return; // Beendet Berechnungen physik.kill(); for (Passivator p : physik.passive) { p.ziel().neutralMachen(); } for (Gravitator g : physik.gravitatoren) { g.ziel().neutralMachen(); } physik = null; } /** * Realisierung eines <i>Singleton</i>. Da es nur eine Physik pro Anwendung gibt, garantiert * diese statische Methode, dass es nur ein Physik Objekt gibt, da keine Physik-Objekte erstellt * werden koennen. * * @return Das aktive Physik-Objekt. */ public static final Physik getPhysik () { if (physik == null) { physik = new Physik(); } return physik; } /** * Meldet einen Passivator an. * * @param p * Der Passivator, der anzumelden ist. */ public void passivAnmelden (Passivator p) { passive.add(p); } /** * Meldet einen Passivator wieder ab - Vorausgesetzt er war auch angemeldet. * * @param p * Der abzumeldende Passivator */ public void passivAbmelden (Passivator p) { passive.remove(p); } /** * Meldet einen Gravitator fuer Aktiv-Objekte an. * * @param g * Der anzumeldende Gravitator */ public void aktivAnmelden (Gravitator g) { gravitatoren.add(g); } /** * Meldet einen Gravitator wieder ab - Vorausgesetzt er war auch angemeldet. * * @param g * Der abzumeldende Gravitator */ public void aktivAbmelden (Gravitator g) { gravitatoren.remove(g); } /** * Setzt alle Aktiv-Objekte, die eine bestimmte Flaeche uebertreten, in einen Knoten. * * @param k * In diesen Knoten werden alle Aktiv-Objekte, die die Flaeche betreten eingefuegt. * @param b * Dieses BoundingRechteck beschreibt die kritische Flaeche. */ public synchronized void alleAktivenEinsetzen (Knoten k, BoundingRechteck b) { for (Gravitator g : gravitatoren) { if (g.ziel().inFlaeche(b)) { k.add(g.ziel()); } } } /** * Setzt alle Aktiv-Objekte, die eine bestimmte Flaeche uebertreten, nicht jedoch nach der * Verschiebung ein Passiv-Objekt schneiden, in einen Knoten. * * @param k * In diesen Knoten werden alle Aktiv-Objekte, die die Flaeche betreten, nicht jedoch nach der * Verschiebung problematisch waeren eingefuegt. * @param b * Dieses BoundingRechteck beschreibt die kritische Flaeche. * @param v * Die kritische Verschiebung. */ public synchronized void alleAktivenTestenUndEinsetzen (Knoten k, BoundingRechteck b, Vektor v) { for (Gravitator g : gravitatoren) { if (g.ziel().inFlaeche(b) && !inPassivem(g.ziel().dimension().verschobeneInstanz(v))) { k.add(g.ziel()); } } } /** * Prueft, ob eine Flaeche ein Passiv-Objekt schneidet. * * @param r * Die Flaeche der Ueberprueftung, als BoundingRechteck * * @return <code>true</code>, wenn diese Flaeche ein Passivobjekt schneidet, sonst * <code>false</code>. */ public synchronized boolean inPassivem (BoundingRechteck r) { for (Passivator p : passive) { if (p.in(r)) { return true; } } return false; } /** * Setzt alle Aktiv-Objekte, die eine bestimmte Flaeche uebertreten, nicht jedoch nach der * Verschiebung ein Passiv-Objekt schneiden - mit einer bestimmten Ausnahme - in einen Knoten. * * @param k * In diesen Knoten werden alle Aktiv-Objekte, die die Flaeche betreten, nicht jedoch nach der * Verschiebung problematisch waeren eingefuegt. * @param b * Dieses BoundingRechteck beschreibt die kritische Flaeche. * @param v * Die kritische Verschiebung. * @param p * Die eine Ausnahme als Passivator */ public synchronized void alleAktivenTestenUndEinsetzenOhne (Knoten k, BoundingRechteck b, Vektor v, Passivator p) { for (Gravitator g : gravitatoren) { if (g.ziel().inFlaeche(b) && !inPassivemAusser(g.ziel().dimension().verschobeneInstanz(v), p)) { k.add(g.ziel()); } } } /** * Prueft, ob eine Flaeche ein Passiv-Objekt - bis auf eine Ausnahme schneidet. * * @param r * Die Flaeche der Ueberprueftung, als BoundingRechteck * @param aus * Die eine Ausnahme, die bei den Kollisionstests nicht beruecksichtigt wird. * * @return <code>true</code>, wenn diese Flaeche ein Passivobjekt - ausser der einen Ausnahme - * schneidet, sonst <code>false</code>. */ public synchronized boolean inPassivemAusser (BoundingRechteck r, Passivator aus) { for (Passivator p : passive) { if (p.equals(aus)) { continue; } if (p.in(r)) { return true; } } return false; } /** * Gibt die Verschiebung zurueck, die noetig waere um das geblockte Bounding-Rechteck aus seinem * Zustand des Passiv-Blockiertseins zu loesen. * * @param r * Das zu entblockende BoundingRechteck * * @return Die Verschiebung, die noetig waere, um das BoundingRechteck aus dem * Passiv-Geblocktsein zu Loesen. Hat die Werte (0|0) fuer den Fall, dass das Bounding-Rechteck * gar nicht passiv blockiert ist. */ public synchronized Vektor entblocken (BoundingRechteck r) { for (Passivator p : passive) { if (p.in(r)) { float x = 0, y = 0; BoundingRechteck pas = p.ziel().dimension(); if (pas.y < r.y && pas.y + pas.hoehe > r.y + r.hoehe) { // X if (r.x > pas.x) { x = pas.x + pas.breite - r.x; } else { x = pas.x - (r.x + r.breite); } } else { // Y if (r.y > pas.y) { y = pas.y + pas.hoehe - r.y; } else { y = pas.y - (r.y + r.hoehe); } } Vektor retA = new Vektor(x, y); return retA.summe(this.entblocken(r.verschobeneInstanz(retA), retA)); } } return Vektor.NULLVEKTOR; } /** * Die Interne Block methode zum garantierten entblocken eines BoundingRechtecks OHNE * StackOverflow. * * @param r * Das zu entblockende BR * @param letzte * Die letzte Verschiebung (aus der Methode <code>entblocken(Vektor)</code>) * * @return Die noch noetige Verschiebung, um das Bounding-Rechteck sicher zu entblocken. * * @see #entblocken(BoundingRechteck) */ private synchronized Vektor entblocken (BoundingRechteck r, Vektor letzte) { for (Passivator p : passive) { if (p.in(r)) { float x = 0, y = 0; BoundingRechteck pas = p.ziel().dimension(); if (letzte.x != 0) { // X if (letzte.x > 0) { x = pas.x + pas.breite - r.x; } else { x = pas.x - (r.x + r.breite); } } else { // Y if (letzte.y > 0) { y = pas.y + pas.hoehe - r.y; } else { y = pas.y - (r.y + r.hoehe); } } Vektor retA = new Vektor(x, y); return retA.summe(this.entblocken(r.verschobeneInstanz(retA), retA)); } } return Vektor.NULLVEKTOR; } /** * Vereinfachte Form der anmelden()-Methode fuer Kollisionstests.<br /> Hierbei wird immer der * Code </code>code = 0</code> bei der <code>reagieren()</code>-Methode mitgegeben, somit ist * beim Aufruf dieser Methode <b>keine Fallunterscheidung innerhalb eines * <code>FallReagierbar</code>-Objektes moeglich!</b> * * @param k * Das KollisionsReagierbar-Objekt, das benachrichtigt wird, wenn beide Objekte kollidieren * @param r1 * Der erste Raum-Teil. Kollidieren beide, so wird das KollisionsReagierbar-Objekt * benachrichtigt * @param r2 * Der zweite Raum-Teil. Kollidieren beide, so wird das KollisionsReagierbar-Objekt * benachrichtigt * * @see #anmelden(KollisionsReagierbar, Raum, Raum, int) */ public void anmelden (KollisionsReagierbar k, Raum r1, Raum r2) { anmelden(k, r1, r2, 0); } /** * Meldet ein KollisionsReagierbar-Interface bei der Physik an. Zusammen mit den 2 auf Kollision * zu ueberwachenden Raum-Objekten sowie dem Code, der bei dem Aufruf beim Treffer zwischen den * beiden mitgegeben werden soll.<br /> <br /> <br /> <br /> * <p/> * Die <code>kollision(int code)</code>-Methode des anzumeldenden <code>KollisionsReagierbar</code>-Interfaces * wird ab sofort immer dann aufgerufen wenn:<br /> 1. beide Raum-Objekte schneiden<br /> und 2. * beide Raum-Objekte sichtbar sind<br /> <br /> <br /> <br /> <br /> <br /> * <p/> * Diese Methode wird solange immer wieder aufgerufen, wie die Kollision besteht! Wird also in * der <code>kollision(int code)</code>-Methode nicht dafuer gesorgt, dass sich die Objekte * nicht mehr schneiden, <b>so wird diese Methode wieder und wieder aufgerufen!</b> <br /> <br * /> <br /> <br /> <br /> <br /> * <p/> * <b>ACHTUNG</b><br /> Es sollten niemals 2 Knoten oder Geometrie (oder ein Knoten und ein * Geometrie) - Objekte gleichzeitig angemeldet werden. Ist dies der Fall, ist der * Kollisionstest nicht so genau, wie er seien koennte. * * @param k * Das KollisionsReagierbar-Objekt, das benachrichtigt wird, wenn beide Objekte kollidieren * @param r1 * Der erste Raum-Teil. Kollidieren beide, so wird das KollisionsReagierbar-Objekt * benachrichtigt * @param r2 * Der zweite Raum-Teil. Kollidieren beide, so wird das KollisionsReagierbar-Objekt * benachrichtigt * @param code * Der Code, der <b>dem <code>KollisionsReagierbar</code>-Objekt als Parameter in seiner * reagieren()-Methode mitgegeben werden soll</b>. */ public void anmelden (KollisionsReagierbar k, Raum r1, Raum r2, int code) { if (r2 instanceof Knoten) { Raum r = r1; r1 = r2; r2 = r; } kollisionsListe.add(new Auftrag(r1, r2, k, code)); } /** * Sorgt dafuer, das saemtliche Kollsiionsueberwachungsauftraege eines * <code>KollisionsReagierbar</code>-Interfaces nicht mehr ausgefuehrt werden. * * @param k * Das Interface, an dem jede Ueberwachung von Raum-Objekten abgebrochen werden soll. */ public void entfernen (KollisionsReagierbar k) { ArrayList<Auftrag> out = new ArrayList<>(); for (Auftrag a : kollisionsListe) { if (a.benachrichtigt(k)) { out.add(a); } } for (Auftrag a : out) { kollisionsListe.remove(a); } } /** * In diesem Tick findet ein DELTA-t der Physik statt (= 1ms). */ @Override public void tick () { for (Gravitator g : gravitatoren) { g.tick(runde); } if (runde == 10) { runde = 1; } else { runde++; } } // <editor-fold defaultstate="collapsed" desc="KollisionReagierbar"> /** * Die Auftraege, der Kollisionstests */ private final class Auftrag { // /** * Kollisionspartner eins/Ausgangspunkt der Kollisionstests */ private final Raum r1; /** * Kollisionspartner 2 */ private final Raum r2; /** * Der Listener */ private final KollisionsReagierbar listener; /** * Der Code dieses Auftrags */ private final int code; /** * Konstruktor * * @param r1 * Koll-Data 1 * @param r2 * Koll-Data 2 * @param k * Listener * @param code * Code */ public Auftrag (Raum r1, Raum r2, KollisionsReagierbar k, int code) { this.r1 = r1; this.r2 = r2; listener = k; this.code = code; } /** * Fuert einen Kollisionstest durch */ public void test () { if (r1.schneidet(r2) && r1.sichtbar() && r2.sichtbar()) { listener.kollision(code); } } /** * @return TRUE, wenn dieser Listener benachrichtigt wird */ public boolean benachrichtigt (KollisionsReagierbar k) { return listener == k; } }// </editor-fold> }