Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Asmodiel

Treue Seele

  • »Asmodiel« ist der Autor dieses Themas

Beiträge: 150

Wohnort: Schweiz

Beruf: Applikationsentwickler

  • Private Nachricht senden

1

05.02.2009, 16:47

Entwicklung einer simplen Physik-Engine, Teil 1

Ahoi,

Inhalt

1. Vorwort
1.1 Einleitung
1.2 Themen in den Tutorials
1.3 Umgebung / Gestaltung der Engine
1.4 Vorkenntnisse
2. Hauptteil: 1. Teil des Tutorials (Partikel und Kräfte)
2.1 Vektoren
2.2 Partikel[list]
- Was sind Partikel in der SimpleEngine?
- Bestandteile eines Partikels
- Berechnen der Position / Geschwindigkeit
-> Einschub: Kräfte
- Zurück zur Geschwindigkeits-Berechnung[/list]
3. Beispielprogramm #1
4. Schlusswort

===

1. Vorwort

1.1 Einleitung

In dieser Tutorial-Serie möchte ich Möglichkeiten zeigen, wie man
eine einfache Physik-Engine erstellen kann. Dabei werde
ich versuchen, alles leicht verständlich zu halten.
Die Engine wird für den dreidimensionalen Raum entwickelt.

1.2 Themen in den Tutorials

Im ersten Teil geht es um die Grundlagen, den Aufbau der Engine
und grundlegende Dinge. Dazu gehören Kräfte, Partikel und
wichtige Formeln der Physik. In den folgenden Teilen werden
dann unter anderem noch nützliche Klassen (Sprungfedern, feste Verbindungen, etc.),
grössere Objekte und Kollisionen behandelt.

1.3 Umgebung / Gestaltung der Engine

Als Entwicklungsumgebung werde ich Visual C++ 2005 verwenden.
Für die Beispiele verwende ich dazu DirectX, allerdings werde
ich die grafische Theorie wenn möglich ausserhalb der Tutorials
und lediglich im Source Code halten.

Wichtig ist, dass die Engine unabhängig von jeglichen Spiele-Daten läuft.
Sie soll also überall einsatzbereit und nicht an ein bestimmtes Spiel
gebunden sein. Ich werde für die Engine eine Win32-DLL erstellen
und sie ganz einfach mal SimpleEngine nennen.

Zu jedem Teil der Serie wird es ein Beispielprogramm geben. Dort sollte
man keine grafischen Highlights erwarten, denn die Programme dienen
lediglich der Demonstration der Physik. ;)

1.4 Vorkenntnisse

Um das Tutorial zu verstehen, sollte man sich zumindest mit
Vektoren und später mit Matrizen auskennen. Ich gehe davon aus, dass hier
die meisten diese Kenntnisse haben, aber man kann nie sicher sein. :)
Natürlich schaden auch allgemeinere mathematische Kenntnisse nicht.

Nützlich sind auch generelle Erfahrungen mit der Spieleprogrammierung.

2. Hauptteil: 1. Teil des Tutorials (Partikel und Kräfte)

2.1 Vektoren

Für unsere Engine sind Vektoren sehr wichtig. Deshalb habe ich eine
Klasse für 3D-Vektoren implementiert (Vector3). Wie bereits gesagt sollte man
sich mit ihnen auskennen, daher werde ich auf die Implementierungs-
Details hier nicht eingehen. Der Code sollte allerdings ausreichend
kommentiert sein, falls man sich doch für die Implementierung interessieren sollte.

2.2 Partikel

Was sind Partikel in der SimpleEngine?

Wenn ich hier von Partikeln spreche, meine ich nicht nur winzige Objekte,
die im Raum herumschwirren. Es kann sich dabei auch um Pistolenkugeln,
Bälle oder Granaten handeln. Aber eben auch um Rauchpartikel oder ähnliches.
In der Regel sind Partikel recht klein gehalten.

Bestandteile eines Partikels

Wir wissen nun, was wir hier unter einem Partikel verstehen. Aber
was gehört eigentlich zu einem Partikel? Was muss ein Partikel können?
Unsere Partikel können eigentlich gar nicht viel. Sie existieren nur
und werden anhand von Kräften bewegt. Auf diese werde ich ein wenig später
noch eingehen.

Nehmen wir beispielsweise eine Metallkugel. Damit wir etwas mit ihr
anfangen können, benötigt sie Masse, eine Position und eine Bewegungsgeschwindigkeit.
Zur Position sollte es nicht viel zu sagen geben. :)

Die Masse ist für die Berechnungen wichtig. Schliesslich unterscheiden sich
die Auswirkungen von Kräften auf eine Metallkugel physikalisch doch ziemlich
von einer leichten Plastikkugel. Wir werden hier die inverse Masse verwenden,
da uns dies einige Vorteile bringt. (Einmal bei Divisionen durch 0,
und einmal bei der Tatsache, dass wir somit unendliche Masse simulieren
können (unbewegliche Partikel))

Bei der Bewegungsgeschwindigkeit handelt es sich um einen 3D-Vektor,
der die Bewegungsrichtung eines Partikels angibt. Wie der Name schon sagt,
ist auch die Geschwindigkeit enthalten: Je länger der Vektor, desto höher
die Geschwindigkeit des Partikels.

Beginnen wir mit der Implementierung dieses bisher einfachen Partikels:
(Getter / Setter nicht aufgelistet)

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
class Particle
{
private:
    float m_InverseMass;    // Inverse Masse

    Vector3 m_Velocity; // Bewegungsgeschwindigkeit; Wird bei jedem Durchlauf berechnet

    Vector3 m_Position; // Position; Wird bei jedem Durchlauf berechnet

public:
    void Move(float time);  // Berechnet die Position / Geschwindigkeit neu

};


Wenn wir die Masse eines Partikels mit dem normalen Wert setzen möchten,
tun wir dies über den Setter SetMass(float mass).
In diesem wird die Masse in die inverse Masse umgerechnet:

C-/C++-Quelltext

1
2
3
4
5
6
7
void SetMass(float mass)
{
    if(mass != 0.0f)
    {
        m_InverseMass = 1.0f / mass;
    }
}


Getter:

C-/C++-Quelltext

1
2
3
4
float GetMass()
{
    return 1.0f / m_InverseMass;
}


Berechnen der Position / Geschwindigkeit

Diese Informationen werden in jedem Frame neu berechnet.
Das geschieht in der Move(float time)-Methode eines Partikels.

time: Vergangene Zeit seit dem letzten Frame in Sekunden

Zuerst wird die Position berechnet. Dazu addieren wir zur Position
einfach nur den Vektor der Bewegungsgeschwindigkeit. Damit das zeitlich
auch korrekt abläuft, multiplizieren wir den Richtungsvektor vorher mit der vergangenen Zeit.

C-/C++-Quelltext

1
2
// Position berechnen

m_Position = m_Position + m_Velocity * time;


Für die Geschwindigkeitsberechnung benötigen wir eine Beschleunigung.
Und für die Beschleunigung brauchen wir Kräfte.

Einschub: Kräfte

Was für Kräfte werden wir hier einsetzen?
Nun, wenn wir einmal kurz zum Kühlschrank schweben möchten,
werden wir merken, dass wir gar nicht schweben können.
(Es sei denn, man hat ein Schwebe-Gerät oder sowas. :D)
Woran liegt das? An der Schwerkraft! Damit haben wir
eine der wichtigsten Kräfte entdeckt.
Ebenfalls vorstellbar wären Windkräfte, eine Strömung
im Wasser und ähnliches. Was man eben gerade so braucht,
um die Partikel zu bewegen.

Bei Kräften handelt es sich hier wieder um 3D-Vektoren.
Wir können Kräfte auf Partikel anwenden, fügen sie ihnen also
hinzu. Die Klasse Particle bekommt nun also noch einen 3D-Vektor
mit den Kräften. Neue Kräfte werden wir einfach zu diesem Vektor
addieren.

Beispiel:

(Link)

Simple Addition zweier Kräfte

Die Vektoren müssen dabei nicht normalisiert sein; je stärker
eine Kraft, desto länger der Vektor.
Nach jedem Frame werden hier die Kräfte wieder entfernt.
Dazu haben wir im Partikel eine neue Methode, die die Komponenten
des Vektors wieder auf 0.0 setzt.

Anmerkung:
Wenn immer dieselbe Kraft auf einen Partikel einwirkt, kann
man diesen Schritt natürlich auch weglassen, um Performance zu
sparen. Ein Beispiel dafür wäre, wenn beispielsweise nur die
Gravitationskraft auf den Partikel einwirkt und man diese nicht
in jedem Frame neu setzen möchte.

Um einem Partikel eine Kraft hinzuzufügen, rufen wir die Methode
AddForce auf, welche wie folgt implementiert ist:

C-/C++-Quelltext

1
2
3
4
void AddForce(const Vector3& force)
{
    m_ForceAccum = m_ForceAccum + force;
}


Wobei m_ForceAccum die schlussendlich wirkende Kraft
auf den Partikel ist. Mit ClearForceAccum() entfernen wir
die Gesamtkraft wieder. (Resp.: Setzen sie auf 0)

C-/C++-Quelltext

1
2
3
4
5
6
void ClearForceAccum()
{
    m_ForceAccum.x = 0.0f;
    m_ForceAccum.y = 0.0f;
    m_ForceAccum.z = 0.0f;
}


Zurück zur Geschwindigkeits-Berechnung

Nun haben wir Kräfte, die unseren Partikel beschleunigen können,
also können wir jetzt die Beschleunigung berechnen.
Wie bereits gesagt, hat die Masse Auswirkungen auf die Stärke
einer Kraft. Unsere Beschleunigung berechnen wir, indem wir
die Kräfte mit der inversen Masse multiplizieren.

Anmerkung:
Würden wir nicht mit der inversen Masse arbeiten,
wäre die Formel hier: Beschleunigung = Kraft / Masse.
So ist auch die ursprüngliche Formel nach Newton:
a = F / m

a: Beschleunigung in m/s/s
F: Kraft
m: Masse (kg)

In unserer Engine entspricht eine Einheit im Koordinatensystem einem Meter.
Das vereinfach die Anwendung der Formeln (und meiner Meinung nach auch
die gesamte Grössenordnung in einem Spiel. :P)

Die berechnete Beschleunigung addieren wir dann (nach der Multiplikation
mit der vergangenen Zeit) zur bisherigen Geschwindigkeit.
Die gesamte Methode Move(float time):

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
    
    // Alle Kräfte entfernen

    ClearForceAccum();
}


Temporäre Anmerkung:
Bestimmt ist nun endgültig aufgeflogen, dass ich im Vector3
keinen +=-Operator implementiert habe... Das war Faulheit,
und ich bitte um Verzeihung. ;) Wird aber geändert.

3. Beispielprogramm #1

Für das Beispielprogramm habe ich mir zuerst überlegt,
einfach ein paar fallende Partikel darzustellen. Aber
das wäre dann doch zu langweilig. :)

Wir werden eine Kugel haben (hier mit der Masse 1.5) und diese
mit einem Windantrieb in die Luft heben. Die Stärke des Antriebs
können wir mit den Pfeiltasten steuern.

Anmerkung:
Das ganze wurde hier nun nicht so sauber programmiert. Beispielsweise
hätte man den Ventilator in eine eigene Klasse packen können. Aus
Zeitgründen und der Einfachheit halber habe ich aber wirklich alles sehr
simpel gehalten. Mit den interessanteren Themen kommen dann aber
auch die interessanteren Beispiele. ;)

Dazu habe ich einmal ein Grundgerüst für die Beispielprogramme
zusammengestellt. Diese drei Dateien / Klassen dienen lediglich dem
Erstellen des Fensters und dem Rendern:

- main.cpp: Aufbau des Fensters, Hauptschleife
- Graphics: Initialisierung von D3D, Rendern
- TestParticle: Laden eines Partikelmeshs und Rendern

Die Physik kommt dann in der Klasse FallingParticles zum Einsatz.
Sie stellt sozusagen die "Game"-Klasse dar.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FallingParticles
{
private:
    Graphics*   m_Graphics; // Zeiger auf Graphics (Singleton)

    TestParticle    m_Particle; // Testpartikel (lediglich zum Rendern)

    Vector3     m_TestVent; // Position des 'Ventilators'

    Vector3     m_VentForce; // Kraft des 'Ventilators'

    Vector3     m_Gravity; // Gravitations-Kraft

public:
    FallingParticles();
    bool Load();
    void Move(float time);
    void Render();
    void Present();
};


In jedem Frame werden Render, Move und Present aufgerufen.
Move wird dabei auch hier die vergangene Zeit seit dem letzten
Frame in Sekunden mitgegeben.

Im Konstruktor wird alles initialisiert:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
FallingParticles::FallingParticles()
:   m_Graphics(Graphics::Instance()),   // Instanz von Graphics holen

    m_TestVent(0.0f, -0.1f, 0.0f),      // Ventilator-Position

    m_VentForce(0.0f, 0.0f, 0.0f)       // Ventilator-Kraft

    m_Gravity = Vector3(0.0f, -GRAVITY, 0.0f) // Gravitations-Vektor erstellen

{
    // Partikeldaten setzen

    m_Particle.SetMass(1.5f);
    m_Particle.SetPosition(0.0f, 0.0f, 0.0f);
    m_Particle.SetVelocity(0.0f, 0.0f, 0.0f);
}


Für den Gravitations-Vektor habe ich in der Engine eine
Datei "SPUtil.h" erstellt, welche bisher lediglich die
Gravitations-Konstante GRAVITY enthält. Diese beträgt 9.81.
(Durchschnittliche Gravitationsbeschleunigung an der Erdoberfläche)

Diese Kraft fügen wir dem Partikel hinzu.
(Wenn man nicht möchte, dass die Schwerkraft von der Masse
beeinflusst wird, sollte man diese Kraft separat in die Partikel-
Klasse nehmen. Da die Konstante eigentlich schon in m/s/s vorhanden
ist und nicht mehr berechnet werden müsste, könnte man sie dann
direkt der Beschleunigung hinzufügen.Ich fand den Effekt aber
nicht schlecht. :P)

Man kann auch mal etwas mit der Gravitation herumspielen.
Beispielsweise, wenn man eine geringe Gravitation möchte oder
wenn man einfach nur man an die Decke gehen möchte. :D

Nun zum Wichtigsten, der Move-Methode:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void FallingParticles::Move(float time)
{   
    // Steuerung

    if(GetKeyState(VK_UP) & 0x80)
    {
        m_VentForce.y += 0.5;
    }
    
    if(GetKeyState(VK_DOWN) & 0x80)
    {
        if(m_VentForce.y > 0.0f)
        {
            m_VentForce.y -= 0.5;
        }
    }
    
    const Vector3& parPos(m_Particle.GetPosition());
    
    // Partikel-Position prüfen; Wenn y <= 0, Geschwindigkeit umdrehen

    // -> Ball spickt wieder hoch

    if(parPos.y <= 0)
    {
        m_Particle.SetPosition(0.0f, 0.1f, 0.0f);
        m_Particle.SetVelocity(-m_Particle.GetVelocity() * 0.75f);
    }
    
    // Entfernung für Abschwächung der Kraft berechnen

    float entf = parPos.y - m_TestVent.y;
    
    // Einwirkende Kraft mit Abschwächung durch Entfernung berechnen

    Vector3 force;  
    force.y = m_VentForce.y * (1.0f / entf);
    m_Particle.AddForce(force);
    
    // Gravitation hinzufügen

    m_Particle.AddForce(m_Gravity);
    
    // Geschwindigkeit / Position neu berechnen

    m_Particle.Move(time);
}


Diese sollte durch die Kommentare recht selbsterklärend sein.
Je weiter der Ball vom Ventilator entfernt ist, desto mehr wird
die Kraft abgeschwächt. Hier habe ich dazu wirklich simple Berechnungen
verwendet, besonders beim Abprall des Balls. Es wurden einfach
Werte genommen, die das Ganze akzeptabel aussehen lassen.
In diesem Fall haben diese Werte also nichts mit der Physik-Engine
zu tun und haben auch keine besondere Bedeutung.

Download des bisherigen "Engine"-Codes und des Beispielprojekts:
Klick

4. Schlusswort

Das wäre es dann auch schon mit dem ersten Teil.
Im nächsten Teil werden wir mehr über die Kräfteverwaltung
erfahren, sowie über Sprungfedern und vielleicht auch feste Verbindungen.
Also, nach ein paar Grundlagen zu den interessanteren Dingen!

Korrekturen und Meinungen bitte posten.
Ich denke, besonders Korrekturen sind noch nötig. *Kopf verloren*
Und bevor ich mich an weitere Teile wage:
Besteht überhaupt Interesse? :P

Gruss
Asmo
Eat a beaver - Save a tree.

Anonymous

unregistriert

2

05.02.2009, 16:59

Naja, das ganze als ne Engine zu bezeichnen, naja...
Hat für mich mehr mit Mathematischem Grundwissen als mit richtiger Phsyik zu tun.

Anonymous

unregistriert

3

05.02.2009, 17:02

Hi!

Ich hatte noch keine Zeit (da auf Arbeit) das Tutorial zu lesen, werde es aber heute Abend mal tun und meinen Senf und Ketchup dazu geben. :)

p.s.: Habs nach Tutorials verschoben.

Asmodiel

Treue Seele

  • »Asmodiel« ist der Autor dieses Themas

Beiträge: 150

Wohnort: Schweiz

Beruf: Applikationsentwickler

  • Private Nachricht senden

4

05.02.2009, 17:04

Zitat von »"Coders-Square"«

Naja, das ganze als ne Engine zu bezeichnen, naja...
Hat für mich mehr mit Mathematischem Grundwissen als mit richtiger Phsyik zu tun.


Teil 1 -> Grundlegende und einfache Dinge, daher auch das "Engine".
Die Physik-lastigeren Teile der Engine kommen dann eher ab Teil 2 vor.
Eat a beaver - Save a tree.

5

05.02.2009, 17:09

Ich bin mal gespannt,wie weit das geht. Weil Physik kann ja wirklich komplex werden. Wenn es hinterher so weit ist, mit Modellen, die sich polygongenau bewegen (z.B. Kisten stapeln, bis der Turm umkippt oder so) wird es richtig interessantw erden. Von daher fände ich einen Ausblick, was noch kommen wird schön.
Lieber dumm fragen, als dumm bleiben!

6

05.02.2009, 17:37

Jop, das Thema ist sicherlich interessant, ich warte mal auf den nächsten Teil. In diesem war ja nichts wirklich Spannendes dabei, mal schauen was nach der Einführung kommt.
Positiv ist auf jeden Fall, dass ich mal keine Rechtschreibfehler gefunden hab, denn das nervt bei etwas "längeren" Texten dann doch.

K-Bal

Alter Hase

Beiträge: 703

Wohnort: Aachen

Beruf: Student (Elektrotechnik, Technische Informatik)

  • Private Nachricht senden

7

05.02.2009, 18:12

Richtig schönes Tutorial, verständlich und gut formuliert ;)

Asmodiel

Treue Seele

  • »Asmodiel« ist der Autor dieses Themas

Beiträge: 150

Wohnort: Schweiz

Beruf: Applikationsentwickler

  • Private Nachricht senden

8

05.02.2009, 20:01

Zitat von »"K-Bal"«

Richtig schönes Tutorial, verständlich und gut formuliert ;)


Oo Thx!

@Jonathan_Klein:
Kisten stapeln u.Ä. wird sicherlich vorkommen.
Zu viel über das Kommende kann ich noch nicht sagen,
vorkommende Teile werden aber sein:
- Sprungfedern und Verbindungen
- Kollisionen (und damit grössere Objekte)
- Evtl. Rag Doll
(Je nach Beliebtheit. :))

Mehr steht halt noch nicht fest. :/
Eat a beaver - Save a tree.

Anonymous

unregistriert

9

05.02.2009, 20:23

So jetzt hatte ich auch mal Zeit zum lesen und gebe nun mal Senf und Ketchup dazu ab :)

Erstmal vorab: Ich bin sehr positiv überrascht (was ich übrigends sehr selten bin) über das Tutorial und es war auch sehr angenehm zu lesen. Es war sehr verständlich (bis auf ein paar Dinge zu denen ich gleich noch kommen werde) und beinhaltet gute Erklärungen zu einem Thema, wofür es sehr sehr wenig gute Tutorials gibt. Dein Tutorial gehört auf jedenfall in die obere Liga, wenn du es so fortführst.

Ein paar Dinge haben mich jedoch doch etwas gestört:

  • Mir fehlt ein Index, du gliederst zwar sehr schön, aber der Index fehlt halt zur Gliederung. Ich persönlich finde es immer sehr angenehm wenn ein Index drin ist, damit ich "in etwa" weiß wo etwas steht, wieviel ich ungefähr scrollen muss usw.

  • Was mich sehr gestört hat beim lesen (auf der Couch, wo ich im Firefox gezoomt habe) ist, dass du irgendwie nach jeder Zeile einen Absatzt machst und nicht durchgehend schreibst. Das empfinde ich auch auf meinem 22" Flachbildschirm sehr "unschön" und das zerstört irgendwie den "look'n feel"

  • Etwas sehr verwirrend war beim Lesen auch, dass du erst von "Bewegungsgeschwindigkeit" und dann von "Richtungsvektor" sprichst. Natürlich ist beides das Selbe, aber du machst keinen sauberen Übergang von dem einen Wort zum anderen Wort. Ich habe mich sogar selbst dabei erwischt als ich mich fragte: "Momente mal? Noch ein Vektor? Er hat doch nur Bewegungsgeschwindigkeit und Position?"

  • Als du mit dem Ventilator-Beispiel gekommen bist, und ich noch mal das AddForce gesehen habe, stellte ich mir folgende Frage: "Hat eine Kraft nicht auch eine Position und ggf. sogar eine Masse? Denn deine ganze Berechnung so von wegen Entfernung usw. hätte man glaube ich leichter in eine Klasse "Force" auslagern können, welche in dem Beispiel den Ventilator beschreibt.

  • Was bedeutet m/s/s?

  • Du benutzt in deinem Beispiel die Konstante 0,75 bei der Invertierung des Richtungsvektors. Woher kommt diese Konstante? Ist das genau so eine wie die Erdanziehung?

Bis auf das, kann ich nur Lob aussprechen :)

TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

10

05.02.2009, 21:17

gefällt mir auch sehr gut! hab ich als physikmuffel sogar verstanden :lol: . ich warte gespannt auf folgendes!

Werbeanzeige