Du bist nicht angemeldet.

Werbeanzeige

Asmodiel

Treue Seele

  • »Asmodiel« ist der Autor dieses Themas

Beiträge: 150

Wohnort: Schweiz

Beruf: Applikationsentwickler

  • Private Nachricht senden

1

27.02.2009, 10:38

Entwicklung einer simplen Physik-Engine, Teil 2

Ahoi,

Inhalt
1 Einleitung
2 Ein Kräftegenerator
3 Federn
3.1 Wozu brauchen wir Federn?
3.2 Etwas Theorie
3.3 Implementierung
3.4 Ein Bungee-Seil
3.5 Berechnen der Federkonstante
3.6 Eine Dämpfung für Partikel
4 Beispielprojekt: Bungee-Partikel
5 Aufgaben
6 Schlusswort

Andere Teile
Teil 1: Einführung, Grundlagen und Partikel
Teil 2: Kräftegeneratoren und Sprungfedern



1 Einleitung

Willkommen zum zweiten Teil der Tutorial-Serie. In diesem werden wir Klassen entwickeln, um den Umgang mit Kräften zu erleichtern. Danach lernen wir einen sehr wichtigen Teil der Engine kennen, nämlich Federn. Diese Dinge werden sich wieder auf unsere Partikel beziehen, da es mit ihnen recht leicht ist, die Grundideen zu vermitteln. Später werden wir dann mit grösseren Objekten arbeiten, auf die wir die Klassen relativ leicht anpassen können. Desweiteren möchte ich noch einmal darauf hinweisen, dass es (besonders von hier an) sehr viele Möglichkeiten gibt, das Ganze zu implementieren. Ich stelle hier keine Regeln auf, sondern zeige lediglich eine Möglichkeit oder eine Lösungsidee. ;)
Also, fangen wir gleich an!

2 Ein Kräftegenerator

Wir wissen aus dem letzten Tutorial, dass Kräfte wichtig sind, um Partikel zu bewegen. Aber es gibt verschiedene Arten von Kräften, und wie wir sehen werden, können diese auch in vielen Gebieten zum Einsatz kommen. Auch die nachher folgenden Feder-Klassen werden schlussendlich gebraucht, um Kräfte herzustellen. Um die zukünftige Arbeit zu erleichtern, habe ich deshalb ein Interface für Kräftegeneratoren erstelllt.

C-/C++-Quelltext

1
2
3
4
5
6
class IParticleForceGenerator
{
public:
    virtual ~IParticleForceGenerator() {}
    virtual void UpdateForce(Particle* p, float time) = 0;
};


Neben dem virtuellen Destruktor besitzt das Interface eigentlich nur eine Methode, die überschrieben werden muss. UpdateForce() berechnet die Kraft und fügt sie dem übergebenen Partikel hinzu. Erstellen wir doch gleich einen ersten, simplen Schwerkraftgenerator, um ein Beispiel für die Anwendung zu zeigen.

Anmerkung:
Für unsere ersten Kräfte benötigen wir den Parameter time in der UpdateForce-Methode noch nicht. Aber für spätere Kräftegeneratoren kann der Wert nützlich sein und vielleicht möchte man auch mal eine eigene Kraft erstellen, die den Wert benötigt. Er wurde hier also eigentlich nur vorsichtshalber eingebracht und kann (momentan) entfernt werden.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
class ParticleGravityForceGen : public IParticleForceGenerator
{
private:
    Vector3 m_Gravity;
public:
    // Konstruktor; Setzt Gravitationsvektor

    ParticleGravityForceGen(const Vector3& gravity)
    :   m_Gravity(gravity) {}
    
    inline void UpdateForce(Particle* p, float time);
};


(Entschuldigt die langen Klassennamen. ;))

Also, so werden wir neue Arten von Kräften erstellen. Nun implementieren wir einfach die UpdateForce-Methode und schon können wir den Kräftegenerator anwenden.

C-/C++-Quelltext

1
2
3
4
5
void ParticleGravityForceGen::UpdateForce(Particle* p, float time)
{
    // Dem Partikel Schwerkraft hinzufügen

    p->AddForce(m_Gravity * p->GetMass());
}


Anmerkung:
Bereits im letzten Tutorial habe ich erwähnt, dass Gravitation immer unterschiedlich angewandt werden kann. Man könnte einem Partikel auch eine Membervariable "m_Gravity" hinzufügen und diese dann selbst setzen und direkt der Beschleunigung hinzufügen. Das würde Performance einsparen. Für die Demonstration fand ich diesen Weg aber nützlicher.

Beispielcode: Anwendung eines Kräftegenerators

Hier ist ein kleiner Code-Ausschnitt, wie man denn solch einen Generator anwenden wird.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
void Game::Move(float time)
{
    // m_Gravity ist ein ParticleGravityForceGen, der mit den Werten

    // (0.0f, -GRAVITY, 0.0f) initialisiert wurde.

    // (GRAVITY befindet sich in SPUtil.h)

    
    // Kräfte updaten

    m_Gravity.UpdateForce(&m_Particle, time);
    
    // Geschwindigkeit / Position neu berechnen

    m_Particle.Move(time);
}


Wie man sieht, ist die Anwendung sehr einfach.

3 Federn

Nun lernen wir etwas sehr wichtiges kennen: Federn. Damit sind Sprungfedern gemeint, wie beispielsweise so etwas:


Quelle: federnverband.de

Btw, wer hat schon nicht davon geträumt, grosse Sprungfedern an seine Schuhe zu basteln und damit herumzuhüpfen? Ich freue mich über jede Einsendung!


3.1 Wozu brauchen wir Federn?

Federn können viele verschiedene Anwendungen haben. Normale Sprungfedern, aber auch Seile für's Bungee Jumping und vollkommen andere Dinge, wie zum Beispiel der Auftrieb eines Objekts im Wasser, können damit simuliert werden. Man braucht nur etwas Fantasie. ;)

3.2 Etwas Theorie

Nun kommen wir zur Theorie der Federn. Gehen wir davon aus, dass eine Feder zwei Gegenstände verbindet. Beim Auseinanderziehen / Zusammendrücken einer Feder ensteht eine Kraft. Um diese zu berechnen, werden wir das hooke'sche Gesetz für elastisches Verhalten verwenden.

Hooke'sches Gesetz

Das hooke'sche Gesetz stellt uns eine Formel bereit, mit der wir die Kraft relativ einfach berechnen können:



F ist die resultierende Kraft, L ist die Längenänderung vom Ruhezustand (Siehe Bild unten). D ist die Federkonstante. Je höher der Wert dieser Konstante, desto "härter" die Feder.


L ist die Differenz der Ruhelänge und der (aktuellen) Länge zwischen Anfang und Ende der Feder.

3.3 Implementierung

Die vorherige Formel ist für den eindimensionalen Raum gedacht, also müssen wir sie nur noch für den Dreidimensionalen anpassen. Dazu beginnen wir doch gleich mit der Implementierung. Was uns interessiert, ist die Kraft, die von der Sprungfeder generiert wird. Also erstellen wir einen neuen Kraftgenerator und nennen ihn ParticleSpring:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
class ParticleSpring : public IParticleForceGenerator
{
private:
    Vector3 m_Anchor;       // Ankerpunkt der Sprungfeder

    float   m_Length;       // Länge der Feder im Ruhezustand

    float   m_SpringConst;  // Federkonstante

public:
    ParticleSpring(const Vector3& anchor, float restLength, float springConst);
    void UpdateForce(Particle* p, float time);
};


Die Feder braucht einen Ankerpunkt, also die Position des ersten Endes. Zudem dürfen die Ruhelänge und die Federkonstante nicht fehlen. Wie wir im Zweifelsfalle an eine brauchbare Federkonstante kommen, werden wir etwas später noch sehen. Etwas experimentieren schadet aber auch nie. ;) In diesem Fall verbindet die Feder ein Partikel und einen festen Ankerpunkt im Raum. Das kann natürlich beliebig angepasst werden. Beispielsweise können wir das Ganze so umkrempeln, dass zwei Partikel verbunden werden.

Nun kommen wir zur Implementierung der UpdateForce-Methode:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ParticleSpring::UpdateForce(Particle* p, float time)
{
    // Vektor vom Partikel zum Ankerpunkt berechnen

    Vector3 force = p->GetPosition() - m_Anchor;
    
    // Wir berechnen die Länge, um die die Feder gestreckt

    // bzw. zusammengedrückt wurde

    float magnitude = (force.Length() - m_Length) * (-m_SpringConst);
    
    // Kraft anhand der Formel berechnen

    force.Normalize();
    force = force * magnitude;
    
    p->AddForce(force);
}


Viel gibt es da nicht mehr zu erklären. Wir berechnen den Abstand zwischen Partikel und Ankerpunkt und subtrahieren davon die Ruhelänge der Feder, um die Veränderungslänge zu bekommen. Diese müssen wir dann nur noch in die Formel einsetzen, um die schlussendliche Kraft zu berechnen.

3.4 Ein Bungee-Seil

Nun können wir uns auch gleich ein Bungee-Seil basteln. Ich habe hier gleich eine eigene Klasse gemacht, auch wenn es sich eigentlich nur in einem Punkt von einer Sprungfeder unterscheidet: Es wirkt nur beim Auseinenanderziehen eine Kraft. Ist das Seil nicht gespannt, wirkt auch keine Kraft.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ParticleBungee::UpdateForce(Particle* p, float time)
{
    // Abstand zwischen Ankerpunkt und Partikelposition berechnen

    Vector3 force = p->GetPosition() - m_Anchor;    
    
    float distance = force.Length();
    
    // Wenn der Abstand kleiner als die Seillänge ist,

    // wirkt keine Kraft.

    if(distance <= m_Length) return;
    
    distance = -m_SpringConst * (distance - m_Length);
    
    force.Normalize();
    force = force * distance;
    
    p->AddForce(force);
}


Wir prüfen also einfach, ob der Abstand überhaupt grösser als die Seillänge ist. Schon haben wir ein Bungee-Seil.

3.5 Berechnen der Federkonstante

Eine brauchbare Federkonstante können wir mit Erfahrung und Experimentieren finden. Wenn's wirklich exakt sein soll, können wir aber auch einen Versuch im realen Leben durchführen und die Konstante dann so berechnen. Dazu nehmen wir eine Formel:

k = F/x = m*g/x

k ist die gesuchte Federkonstante und F ist die Kraft, die auf den fallenden Gegenstand wirkt (Masse * Gravitationskonstante). Wenn wir den Gegenstand in der realen Welt an die Feder (oder das Gummiseil) hängen, wird sie auseinandergezogen und kommt irgendwann "zur Ruhe". x ist die Differenz zwischen der Länge der gezogenen Feder und der Länge im Ruhezustand. Ein Beispiel:

Wir haben eine Kugel mit einer Masse von 1.2kg. Die Gravitationskonstante ist 9.81 (Durchschnitt an der Erdoberfläche). Wir möchten die Federkonstante eines Gummiseils ermitteln, das eine Länge von 2 Metern hat. Wir hängen nun die Kugel ans Gummiseil und warten, bis sich da nichts mehr bewegt. Die Seillänge beträgt nun 2.3 Meter. Der Unterschied ist also 0.3 Meter. Das setzen wir nun in die Formel ein:

Masse * Gravitationskonstante / Längendifferenz
-> 1.2 * 9.81 / 0.3 = 39.24

So kommen wir auf eine Federkonstante von 39.24.

3.6 Eine Dämpfung für Partikel

In der realen Welt werden Objekte in der Luft je nach Form zusätzlich abgebremst. Gerade beim Einsatz von Sprungfedern kann diese Dämpfung recht wichtig sein. Aber auch sonst finde ich sie sehr nützlich, weil man das Verhalten eines Partikels damit sehr beeinflussen kann. Wir fügen unseren Partikeln also einen Float-Wert für die Dämpfung hinzu. Diesen nenne ich hier m_Damping und erstelle noch Getter und Setter dafür. Nun aber zum Wichtigsten, nämlich der Berechnung der neuen Geschwindigkeit mit der Dämpfung:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Particle::Move(float time)
{
    // Position berechnen

    m_Position = m_Position + m_Velocity * time;
    
    // Beschleunigung berechnen

    Vector3 acc = m_ForceAccum * m_InverseMass;
    
    // Neue Geschwindigkeit berechnen

    m_Velocity = m_Velocity + acc * time;
    
    // Dämpfung hinzufügen

    m_Velocity = m_Velocity * powf(m_Damping, time);
    
    // Alle Kräfte entfernen

    ClearForceAccum();
}


Eine Zeile wurde hinzugefügt. Vorerst werden wir die Dämpfung so einbinden. Im nächsten Tutorial stelle ich Techniken vor, mit denen wir die Dämpfung durch einen Kräftegenerator ersetzen können, der mit viel komplexeren Dämpfungsmöglichkeiten arbeitet. Auf das Thema Dämpfung kommen wir also noch einmal zurück.

Anmerkung:
Wahrscheinlich werde ich diese Dämpfung aber normalerweise drinlassen, denn so ist die Berechnung schneller als mit einem Kräftegenerator.

4 Beispielprojekt: Bungee-Partikel

In diesem Beispielprogramm lassen wir einen Partikel Bungee springen. Wir können ihn mit den Pfeiltasten nach links und nach rechts steuern, um auch etwas Schwung in die Sache zu bringen. Dazu habe ich wieder dasselbe Grundgerüst wie im letzten Beispielprogramm verwendet. Wir steuern das Ganze aus der BungeeParticle-Klasse, die sozusagen unsere Game-Klasse darstellt.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BungeeParticle
{
private:
    Graphics*               m_Graphics;
    TestParticle            m_Particle;
    ParticleGravityForceGen m_Gravity;
    BungeeCord              m_Cord;
public:
    BungeeParticle();
    bool Load();
    void Move(float time);
    void Render();
    void Present();
};


Die Klasse besitzt ein BungeeCord. Das ist nichts weiter als eine abgeleitete Klasse von ParticleBungee aus unserer Physikengine, die zusätzlich eine Render-Methode besitzt. Diese stellt lediglich eine Linie zwischen dem Ankerpunkt und dem Partikel auf dem Bildschirm dar.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
BungeeParticle::BungeeParticle()
:   m_Graphics(Graphics::Instance()),
    m_Gravity(Vector3(0.0f, -GRAVITY, 0.0f)),
    m_Cord(Vector3(0.0f, 5.0f, 0.0f), 8.0f, 15.2f)
{
    // Init particle

    m_Particle.SetMass(0.250f);
    m_Particle.SetDamping(0.5f);
    m_Particle.SetPosition(0.0f, 5.0f, 0.0f);
    m_Particle.SetVelocity(0.0f, 0.0f, 0.0f);
}


Für die Gravitation benutzen wir hier einen Kraftgenerator. Wir initialisieren ihn mit der Gravitationskraft, die der Erdanziehungskraft entspricht. Unseren Ankerpunkt des Bungeeseils setzen wir auf eine Höhe von 5 Metern. Das Seil bekommt eine Länge von 8 Metern. (Autsch. xD)
Die Federkonstante ist durch Experimentieren entstanden. Diese Werte fand ich in etwa angebracht. Nun passen wir noch die Move-Methode an, um den Partikel zu bewegen:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void BungeeParticle::Move(float time)
{
    Vector3 control(0.0f, 0.0f, 0.0f);

    // Steuerung

    if(GetKeyState(VK_LEFT) & 0x80) control.x -= 2.0f;
    if(GetKeyState(VK_RIGHT) & 0x80) control.x += 2.0f;
    
    m_Particle.AddForce(control);
    
    // Kräfte updaten

    m_Gravity.UpdateForce(&m_Particle, time);
    
    m_Cord.UpdateForce(&m_Particle, time);
    
    // Geschwindigkeit / Position neu berechnen

    m_Particle.Move(time);
}


Zuerst fragen wir die Eingabe ab, um eine Steuerkraft auf den Partikel wirken zu lassen. So kann man die Flugbahn des Partikels beeinflussen. Dann fügen wir dem Partikel noch die Kräfte der Gravitation und des Bungeeseils hinzu und schon ist die Arbeit getan. In der Render-Methode rendern wir lediglich den Partikel, den Ankerpunkt und das Seil. Schon haben wir einen Bungee springenden Partikel.

5 Aufgaben

Hin und wieder werde ich nun Aufgaben anhängen, mit denen das Wissen vertieft / getestet werden kann. Wenn jemand wirklich eine eigene Engine machen und dann auch verwenden möchte, empfehle ich, die Aufgaben zu lösen. :)

1. In der Physik-Engine sind Sprungfedern sehr vielfältig einsetzbar. Wir haben ein Bungee-Seil damit gebastelt. Wofür könnten wir Sprungfedern noch einsetzen?

2. In unserem Kräftegenerator für die Gravitation verwenden wir folgende Code-Zeile, um einem Partikel die Gravitationskraft hinzuzufügen:

C-/C++-Quelltext

1
2
// p = Partikel, dem die Kraft hinzugefügt werden soll

p->AddForce(m_Gravity * p->GetMass());


Wieso multiplizieren wir hier noch mit der Masse des Partikels?

3. Zu unseren Partikeln haben wir eine Dämpfung hinzugefügt. Das ermöglicht uns, unterschiedliche Formen von Partikeln besser zu simulieren. Besonders bei Sprungfedern ist die Dämpfung wichtig. Wie würde sich ein Partikel an einer Feder verhalten, wenn wir diese komplett weglassen?

6 Schlusswort

Nun haben wir Sprungfedern anhand des Hooke'schen Gesetzes kennengelernt. Diese können wir für sehr viele Dinge einsetzen. Bisher bezieht sich noch alles auf Partikel. So können wir die Theorie einfacher kennenlernen. Später werden wir das alles auch auf andere und grössere Objekte anwenden. Im nächsten Teil betrachten wir einmal Kollisionen und normale Seile und Verbindungen.

Danke für's Lesen und schönen Gruss
Asmo

PS: Entschuldigt die späte Abgabe. (Urlaub geht vor. :D)


Download (Beispielprojekt + Aktuelle Engine)
Eat a beaver - Save a tree.

K-Bal

Alter Hase

Beiträge: 703

Wohnort: Aachen

Beruf: Student (Elektrotechnik, Technische Informatik)

  • Private Nachricht senden

2

27.02.2009, 11:16

Moin,

cooles Tutorial (wie auch schon das letzte ;)).

Eine Frage: wofür braucht UpdateForce float time? Wenn es später mal gebraucht wird ist das ja ok, aber du solltest wenigstens in den Text schreiben, was es damit auf sich hat ;)

Gruß Marius

3

28.02.2009, 22:02

Zitat von »"K-Bal"«

Eine Frage: wofür braucht UpdateForce float time?

Irgendwie muss das Physiksystem ja wissen wie viel Zeit seit der letzten Aktualisierung vergangen ist.
Wenn eine Kraft von 10 Newton auf einen Würfel wirkt, bewegt sich dieser in 10 Sekunden weiter als er es in 5 Sekunden würde.
Und genau dafür muss man dem Physiksystem sagen wie viel Zeit vergangen ist.

K-Bal

Alter Hase

Beiträge: 703

Wohnort: Aachen

Beruf: Student (Elektrotechnik, Technische Informatik)

  • Private Nachricht senden

4

01.03.2009, 00:07

Zitat von »"chriss"«

Zitat von »"K-Bal"«

Eine Frage: wofür braucht UpdateForce float time?

Irgendwie muss das Physiksystem ja wissen wie viel Zeit seit der letzten Aktualisierung vergangen ist.
Wenn eine Kraft von 10 Newton auf einen Würfel wirkt, bewegt sich dieser in 10 Sekunden weiter als er es in 5 Sekunden würde.
Und genau dafür muss man dem Physiksystem sagen wie viel Zeit vergangen ist.


Ich habe durchaus fundiertes Wissen in Physik und Konsorten wegen meinem Studium ;)

Er benutzt time einfach garnicht, der Parameter hat keine Verwendung, deswegen frage ich.

Asmodiel

Treue Seele

  • »Asmodiel« ist der Autor dieses Themas

Beiträge: 150

Wohnort: Schweiz

Beruf: Applikationsentwickler

  • Private Nachricht senden

5

01.03.2009, 00:19

Hab's angepasst. Vorerst brauchen wir den Parameter nicht,
er könnte also weggelassen werden. Ich fand's nach einigen
Überlegungen aber nützlich für später. :)
Eat a beaver - Save a tree.

K-Bal

Alter Hase

Beiträge: 703

Wohnort: Aachen

Beruf: Student (Elektrotechnik, Technische Informatik)

  • Private Nachricht senden

6

01.03.2009, 01:04

Dann sind ja jetzt alle glücklich ;) Freu mich auf den dritten Teil!

Asmodiel

Treue Seele

  • »Asmodiel« ist der Autor dieses Themas

Beiträge: 150

Wohnort: Schweiz

Beruf: Applikationsentwickler

  • Private Nachricht senden

7

01.03.2009, 01:30

Zitat von »"K-Bal"«

Dann sind ja jetzt alle glücklich ;) Freu mich auf den dritten Teil!


Und das freut mich! :) Dachte, das Interesse sei verloren gegangen.
Viel Kritik gab's ja bisher nicht. ;)
Eat a beaver - Save a tree.

8

01.03.2009, 01:33

Zitat

Dachte eher, das Interesse sei verloren gegangen.

So kommts mir auch vor, mir gefällts aber.
Vorallem ist es interessant zu erfahren, wie einfach das mit der Feder geht (ich weiß nicht, obs noch "bessere", schwierigere Möglichkeiten gibt; auf jeden Fall ist das ja auch schon gut). Ich bin in der 9. Klasse Gym, haben gerade Beschleunigung, und auch dort war ich erstaunt, wie einfach es jetzt noch ist.
Ich freue mich auf jeden Fall auch auf den nächsten Teil, was wird denn kommen?

Asmodiel

Treue Seele

  • »Asmodiel« ist der Autor dieses Themas

Beiträge: 150

Wohnort: Schweiz

Beruf: Applikationsentwickler

  • Private Nachricht senden

9

01.03.2009, 02:19

Jopp, mich hat es selbst erstaunt, wie einfach solche Dinge
zu berechnen sind. :)

Zitat von »"defaultplayer^^


Ich freue mich auf jeden Fall auch auf den nächsten Teil, was wird denn kommen?


Kollisionen und feste Verbindungen (unter anderem Seile). Wird wohl ein
etwas grösserer Teil. (Darf ja auch nicht mehr zu kleine machen,
sonst werden's nachher zu viele. :D)
Eat a beaver - Save a tree.

K-Bal

Alter Hase

Beiträge: 703

Wohnort: Aachen

Beruf: Student (Elektrotechnik, Technische Informatik)

  • Private Nachricht senden

10

01.03.2009, 12:03

Zitat von »"Asmodiel"«


Kollisionen und feste Verbindungen (unter anderem Seile). Wird wohl ein
etwas grösserer Teil. (Darf ja auch nicht mehr zu kleine machen,
sonst werden's nachher zu viele. :D)


Häppchen für Häppchen ist doch sehr gut ;) Dann hat man auch Zeit zum Verdauen :D

*Metaphermode aus*

Das nächste Thema klingt sehr spannend, da hab ich mich bisher noch nie mit beschäftigt ;)

Werbeanzeige