/* * 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 ea.internal.util.Logger; /** * Ein <code>Physik</code>-Client, der eine rudimentäre Implementierung <b>Newton'scher Mechanik</b> * implementieren soll. Physik-Objekte, die von einem solchen Client betreut werden, zeichnen sich * durch folgende Eigenschaften aus:<br /> <ul> <li>Sie haben eine aktuelle * </b>Geschwindigkeit</b>.</li> <li>Es wirkt eine <b>Kraft</b> (ggf. stellvertretend als Summe * mehrerer Kräfte) auf sie.<li> <li>Sie haben eine <b>Masse</b>.</li> <li>Die Kraft wirkt eine * <b>Beschleunigung</b> - also eine <i>Änderung der Geschwindigkeit</i> auf sie aus. Die * Beschleunigung hängt ausserdem von der <i>Masse</i> des Objekts ab.</li> <li>Es wirkt konstant * <b>Reibung</b>. In diesem Modell bedeutet dies eine <i>dynamische Verminderung der aktuellen * Geschwindigkeit</i>. Die Verminderung ist stärker, je höher die Geschwindigkeit ist. Ihre * generelle Intensität lässt sich jedoch ändern.</li> <li><b>Neue Kräfte</b> können atomar (also * "von jetzt auf gleich", nicht über einen Zeitraum (z.B. 100 ms)) auf Objekte angewendet werden. * Das ist Vergleichbar mit einem schnellen Stoß in eine bestimmte Richtung. Dies lässt sich durch * einen <b>Impuls</b> realisieren, aber auch über direkte Eingabe einer * Geschwindigkeitsänderung.</li> <li>Es können Kräfte <b>dauerhaft</b> auf ein Objekt wirken, wie * zum Beispiel die <i>Schwerkraft</i>.</li> <li>Mehrere Objekte können <b>kollidieren</b>. Dann * prallen sie <i>elastisch</i> voneinander ab. Dies funktioniert intern über * <i>Impulsrechnung</i>.</li> </ul> * * @author Michael Andonie */ @SuppressWarnings ( "serial" ) public class MechanikClient extends PhysikClient implements Ticker { /** * Die Grenze d, ab der Vektoren v mit |v|<d auf 0 abgerundet werden. */ private static float THRESHOLD = 0.00001f; /** * Setzt einen neuen Threshold d. Ein Objekt, dass sich mit |v| < d bewegt, * wird angehalten (die Engine sorgt also manuell für v' = 0) * @param threshold Der Threshold d (in px), ab dem die Engine Geschwindigkeitsvektoren * auf 0 setzt. */ public static void tresholdSetzen(float threshold) { THRESHOLD = threshold; } /** * Das Intervall, in dem die Spielmechanik upgedated wird <b>in Sekunden</b>. Wird benutzt für * die Extrapolation. Orientiert sich an der <b>Update-Geschwindigkeit</b> der Zeichenebene * * @see ea.internal.gra.Zeichner#UPDATE_INTERVALL */ static final float DELTA_T = (float) ea.internal.gra.Zeichner.UPDATE_INTERVALL * 0.001f; /** * Der Timer, der sich aller Mechanik-Clients annimmt. */ public static Manager MECH_TIMER = new Manager(); /** * Diese Konstante gibt an, wie viele Meter ein Pixel hat. Das ist normalerweise ein sehr * <b>kleiner</b> Wert (Standard: 0.01f). */ private static float METER_PRO_PIXEL = 0.001f; /** * Der Listener zum hoehren von Faellen. */ private FallReagierbar fallListener = FallDummy.getDummy(); /** * Das StehReagierbar-Interface, das auf stehen reagieren soll. */ private StehReagierbar sListener = StehDummy.getDummy(); /** * Die kritische Tiefe, bei der der Fall-Listener informiert wird. */ private int kritischeTiefe = 0; /** * Die aktuelle Geschwindigkeit v des Client-Objekts.<br /> <b>Einheit: m/s</b> */ private Vektor velocity; /** * Die letzte Geschwindigkeit v des Client-Objekts.<br /> Wird fuer das Absterben der Reibung * benutzt <b>Einheit: m/s</b> */ private Vektor lastVelocity; /** * Die aktuelle Kraft F, die konstant auf das Client-Objekt wirkt. <br /> <b>Einheit: N = m/s^2</b><br /> * Konkret z.B. Schwerkraft. */ private Vektor force; /** * Die aktuelle Masse m des Objekts. <br/> <b>Einheit: Kilogramm</b> */ private float masse = 30.0f; /** * Gibt an, ob das Objekt <b>beeinflussbar</b> ist. * * @see #beeinflussbarSetzen(boolean) */ private boolean beeinflussbar = true; /** * Der Luftwiderstandskoeffizient des Objekts ist eine Vereinfachung des * Luftwiderstandsmodells.<br /> F_W = 1/2 * c_W * A * rho * v^2<br /> Heuristik: <br /> F_W = * luftwiderstandskoeffizient * v^2<br /> * <p/> * Der Koeffizient ist <b>nichtnegativ</b>. */ private float luftwiderstandskoeffizient = 40f; /** * Gibt an, wie viel Energie beim Aufprall gegen dieses Objekt (als nicht beeinflussbares Objekt) * erhalten bleibt. * 1 ~= 100% * 0 ~= 0% */ private float elastizitaet = 0.34f; /** * Der Collider für schnelle und effiziente Praekollisionstests. */ private KreisCollider collider; /** * Konstruktor erstellt einen neuen Mechanik-Client. * * @param ziel * das Ziel-Objekt für diesen Client. */ public MechanikClient (Raum ziel) { super(ziel); collider = ziel.dimension().umschliessenderKreis(); einfluesseZuruecksetzen(); MECH_TIMER.anmelden(this, ea.internal.gra.Zeichner.UPDATE_INTERVALL); CollisionHandling.anmelden(this); } /** * Setzt, wie viele Meter auf einen Pixel im Spielfenster gehen. * * @param meterpropixel * Die Anzahl an Metern, die auf einen Pixel fallen.<br/> Beispiele:<br /> <ul> * <li><code>10(.0f)</code> => Auf einen Pixel fallen <b>10</b> Meter. => Ein Meter = 0,1 * Pixel</li> <li><code>0.1f</code> => Auf einen Pixel fallen <b>0,1</b> Meter. => Ein Meter = * 10 Pixel</li> </ul> */ public static void setzeMeterProPixel (float meterpropixel) { if (meterpropixel <= 0.0f) { throw new IllegalArgumentException("Die Anzahl an Metern pro Pixel muss positiv sein!"); } else if (MECH_TIMER.hatAktiveTicker()) { throw new RuntimeException("Die Anzahl von Metern pro Pixel kann nach der Nutzung der " + "Physik nicht mehr geändert werden!"); } METER_PRO_PIXEL = meterpropixel; } /** * @return the velocity */ public Vektor getVelocity () { return velocity; } /** * In der <code>tick()</code>-Methode des Mechanik-Clients wird die <b>diskrete * Weiterrechnung</b> der verschiedenen Parameter realisiert sowie die Anwendung der * Geschwindigkeit auf die aktuelle Position des Client-Objekts. Dies ist vergleichbar mit der * <i>Methode der kleinen Schritte</i> aus der Physik. */ @Override public void tick () { //Kraftaenderung -> Kraft_aktuell = Kraft + Luftwiderstand //Luftwiderstand = 1/2 * c_W * A * rho * v^2 //Heuristik: luftwiderstandskoeffizient * v^2 Vektor momentanekraft = force.summe(velocity.gegenrichtung().multiplizieren((luftwiderstandskoeffizient * velocity.laenge()))); //Beschleunigungsbestimmung -> a = F / m //Delta v bestimmen -> delta v = a * delta t = F * (delta t / m) //v_neu = v_alt + delta v velocity = velocity.summe(momentanekraft.multiplizieren(DELTA_T / masse)); //Delta s bestimmen -> delta s = v_neu * delta t + [1/2 * a_neu * (delta t)^2] // =~= v_neu * delta t [heuristik] //bewegen um delta s bewegen(velocity.multiplizieren(DELTA_T).teilen(METER_PRO_PIXEL)); //System.out.println("Move:" + velocity.multiplizieren(DELTA_T)); //Critical Depth: if (ziel.positionY() > kritischeTiefe) fallListener.fallReagieren(); //Genügend für Ende? -> Heuristik: |v| < d [mit d geschickt gewählt] Vektor dif = velocity.differenz(lastVelocity); if (dif.manhattanLength() < THRESHOLD && dif.manhattanLength() != 0) { System.out.println("T"); velocity = Vektor.NULLVEKTOR; } //Update: Lasvelocity für den nächsten Step ist die aktuelle lastVelocity = velocity; } /** * {@inheritDoc} */ @Override public boolean bewegen (Vektor v) { ziel.verschieben(v); collider.verschieben(v); return false; } /** * {@inheritDoc} */ @Override public void aufloesen () { CollisionHandling.abmelden(this); } /** * {@inheritDoc} Löst einen Impulssprung aus. Nur aus Kompatibilitätsgründen vorhanden. * * @return always <code>true</code>. */ @Override @Deprecated public boolean sprung (int kraft) { this.impulsHinzunehmen(new Vektor(60, 0)); return true; } /** * {@inheritDoc} Aktiviert / Deaktiviert eine Standardschwerkraft. Nur aus * Kompatibilitätsgründen vorhanden. */ @Override @Deprecated public void schwerkraftAktivSetzen (boolean aktiv) { force = aktiv ? new Vektor(0, 10) : Vektor.NULLVEKTOR; } /** * {@inheritDoc} */ @Override public void kritischeTiefeSetzen (int tiefe) { this.kritischeTiefe = tiefe; } /** * {@inheritDoc} */ @Override public void fallReagierbarAnmelden (FallReagierbar f, int tiefe) { this.fallListener = f; this.kritischeTiefeSetzen(tiefe); } /** * {@inheritDoc} */ @Override public void stehReagierbarAnmelden (StehReagierbar s) { this.sListener = s; } /** * {@inheritDoc} */ @Override public boolean steht () { // TODO Auto-generated method stub return false; } /** * {@inheritDoc} */ @Override @Deprecated public void schwerkraftSetzen (int schwerkraft) { this.kraftSetzen(new Vektor(0, 0.01f * schwerkraft)); } /** * {@inheritDoc} */ @Override public void impulsHinzunehmen (Vektor impuls) { //Grundrechnung: //p + delta p = m * v_neu //(m * v_alt) + delta p = m * v_neu //v_neu = v_alt + ([delta p] / m) this.velocity = velocity.summe(impuls.teilen(masse)); } /** * {@inheritDoc} */ @Override public void geschwindigkeitHinzunehmen (Vektor geschwindigkeit) { //v_neu = v_alt + delta v this.velocity = velocity.summe(geschwindigkeit); } /** * {@inheritDoc} */ @Override public float getLuftwiderstandskoeffizient () { return luftwiderstandskoeffizient; } /** * {@inheritDoc} */ @Override public boolean istBeeinflussbar () { return beeinflussbar; } /** * {@inheritDoc} */ @Override public float getMasse () { return masse; } /** * {@inheritDoc} */ @Override public Vektor getForce () { return force; } /** * @return Die Elastizitaet des Objekts. */ public float getElastizitaet() { return elastizitaet; } /** * Setzt die Elastizität für dieses Objekt neu. Hat nur einen Effekt, wenn * dieses Objekt nicht beeinflussbar ist. * @param elastizitaet Die Elastizität dieses Objekts in %. 1 = Voller Energieerhalt * --- 0 = Voller Energieverlust */ public void setElastizitaet(float elastizitaet) { if(elastizitaet < 0) { Logger.error("Die Elastizität eines Objekts kann nicht negativ sein. Die Eingabe war " + elastizitaet + " ."); return; } this.elastizitaet = elastizitaet; } /** * {@inheritDoc} */ @Override public void luftwiderstandskoeffizientSetzen (float luftwiderstandskoeffizient) { if (luftwiderstandskoeffizient < 0) { throw new IllegalArgumentException("Der Luftwiderstandskoeffizient darf nicht negativ sein! Eingabe war " + luftwiderstandskoeffizient + "."); } this.luftwiderstandskoeffizient = luftwiderstandskoeffizient; } /** * {@inheritDoc} */ @Override public void beeinflussbarSetzen (boolean beeinflussbar) { this.beeinflussbar = beeinflussbar; } /** * {@inheritDoc} */ @Override public void masseSetzen (float masse) { this.masse = masse; } /** * {@inheritDoc} */ @Override public void kraftSetzen (Vektor kraft) { this.force = kraft; } /** * {@inheritDoc} */ @Override public void geschwindigkeitSetzen (Vektor geschwindigkeit) { this.velocity = geschwindigkeit; } /** * {@inheritDoc} */ @Override public void einfluesseZuruecksetzen () { force = Vektor.NULLVEKTOR; velocity = Vektor.NULLVEKTOR; lastVelocity = Vektor.NULLVEKTOR; } /** * {@inheritDoc} */ @Override public void kraftAnwenden (Vektor kraft, float t_kraftuebertrag) { //es gilt in dieser Heuristik: p = F * t_kraftübertrag //=>p = kraft * t_kraftübertrag //=> Impuls p anwenden. impulsHinzunehmen(kraft.multiplizieren(t_kraftuebertrag)); } /** * Gibt den Collider zurück. * * @return Der Collider des Elements. */ public KreisCollider collider () { return collider; } }