proejct commit
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
# Physik für Einsteiger – wie die Sandbox funktioniert
|
||||
|
||||
Dieses Dokument erklärt die Physik der Sandbox Schritt für Schritt und ohne Vorwissen.
|
||||
Jeder Abschnitt sagt, **was** physikalisch passiert, **warum** das Programm es so macht und
|
||||
**wo** es im Code steht. Die kompakte Formelsammlung für Fortgeschrittene ist
|
||||
[PHYSICS.md](PHYSICS.md).
|
||||
|
||||
## Inhalt
|
||||
|
||||
1. [Die Grundidee: Die Welt als Daumenkino](#1-die-grundidee-die-welt-als-daumenkino)
|
||||
2. [Vektoren: Pfeile mit Richtung und Länge](#2-vektoren-pfeile-mit-richtung-und-länge)
|
||||
3. [Position, Geschwindigkeit, Beschleunigung](#3-position-geschwindigkeit-beschleunigung)
|
||||
4. [Masse und Trägheit](#4-masse-und-trägheit)
|
||||
5. [Der Simulationsschritt: Euler-Verfahren](#5-der-simulationsschritt-euler-verfahren)
|
||||
6. [Warum ein fester Zeitschritt?](#6-warum-ein-fester-zeitschritt)
|
||||
7. [Kollisionen erkennen](#7-kollisionen-erkennen)
|
||||
8. [Kollisionen auflösen: Impulse](#8-kollisionen-auflösen-impulse)
|
||||
9. [Elastizität: Wie stark etwas abprallt](#9-elastizität-wie-stark-etwas-abprallt)
|
||||
10. [Reibung](#10-reibung)
|
||||
11. [Drehung und Rollen](#11-drehung-und-rollen)
|
||||
12. [Viele Kontakte gleichzeitig](#12-viele-kontakte-gleichzeitig)
|
||||
13. [Wenn Körper ineinanderstecken: Positionskorrektur](#13-wenn-körper-ineinanderstecken-positionskorrektur)
|
||||
14. [Ruhe: Warum nichts zittert](#14-ruhe-warum-nichts-zittert)
|
||||
15. [Tunneln: Warum nichts durch Wände fliegt](#15-tunneln-warum-nichts-durch-wände-fliegt)
|
||||
16. [Ziehen und Werfen mit der Maus](#16-ziehen-und-werfen-mit-der-maus)
|
||||
17. [Alles zusammen: ein kompletter Schritt](#17-alles-zusammen-ein-kompletter-schritt)
|
||||
18. [Zum Ausprobieren](#18-zum-ausprobieren)
|
||||
|
||||
---
|
||||
|
||||
## 1. Die Grundidee: Die Welt als Daumenkino
|
||||
|
||||
Ein Computer kann Bewegung nicht "fließend" berechnen. Er macht es wie ein Daumenkino: Er
|
||||
berechnet viele Einzelbilder dicht hintereinander. Die Sandbox berechnet die Welt
|
||||
**120-mal pro Sekunde**. Zwischen zwei Bildern liegt also die Zeit
|
||||
|
||||
```text
|
||||
dt = 1/120 s ≈ 0,0083 s ("dt" = Delta t = kleines Zeitstück)
|
||||
```
|
||||
|
||||
In jedem dieser Schritte fragt das Programm für jeden Körper:
|
||||
|
||||
1. Welche Kräfte wirken? (Schwerkraft)
|
||||
2. Wie ändert sich dadurch die Geschwindigkeit?
|
||||
3. Wie ändert sich dadurch die Position?
|
||||
4. Stößt der Körper irgendwo an? Wenn ja: Wie reagieren beide Körper?
|
||||
|
||||
Mehr ist Physik-Simulation im Kern nicht. Der Rest dieses Dokuments erklärt die einzelnen
|
||||
Punkte genauer.
|
||||
|
||||
## 2. Vektoren: Pfeile mit Richtung und Länge
|
||||
|
||||
Viele Größen in der Physik haben nicht nur einen Betrag, sondern auch eine **Richtung**.
|
||||
"Der Ball fliegt 5 m/s schnell" reicht nicht – man muss wissen, *wohin*. Deshalb rechnet man
|
||||
mit **Vektoren**: Pfeilen mit x- und y-Anteil.
|
||||
|
||||
```text
|
||||
v = (3, 4) bedeutet: 3 nach rechts, 4 nach unten (y zeigt wie am Bildschirm nach unten)
|
||||
```
|
||||
|
||||
Die wichtigsten Operationen (alle in [`physics/vector.py`](../src/sandbox/physics/vector.py)):
|
||||
|
||||
| Operation | Rechnung | Bedeutung |
|
||||
|---|---|---|
|
||||
| Addition `a + b` | (a.x+b.x, a.y+b.y) | Pfeile aneinanderhängen: "erst 3 nach rechts, dann 2 nach oben" |
|
||||
| Skalierung `a * 2` | (2·a.x, 2·a.y) | Pfeil doppelt so lang, gleiche Richtung |
|
||||
| Länge `a.length()` | √(x² + y²) | Satz des Pythagoras: wie lang ist der Pfeil? (3,4) hat Länge 5 |
|
||||
| Normalisieren `a.normalized()` | a / Länge | Pfeil auf Länge 1 bringen – übrig bleibt nur die *Richtung* |
|
||||
| Skalarprodukt `a.dot(b)` | a.x·b.x + a.y·b.y | "Wie viel von a zeigt in Richtung b?" |
|
||||
| Kreuzprodukt `a.cross(b)` | a.x·b.y − a.y·b.x | "Wie stark dreht a um b herum?" (für Drehungen) |
|
||||
|
||||
**Das Skalarprodukt ist der wichtigste Trick der ganzen Engine.** Ist `n` ein Pfeil der
|
||||
Länge 1 (z. B. "senkrecht vom Boden weg"), dann gibt `v.dot(n)` an, wie schnell sich etwas
|
||||
*in Richtung n* bewegt. Ein Ball mit `v = (5, 8)` (schräg nach unten) und der
|
||||
Boden-Richtung `n = (0, 1)` (nach unten) liefert `v·n = 8`: Er fällt mit 8 Einheiten pro
|
||||
Sekunde auf den Boden zu. Die 5 nach rechts spielen für den Aufprall keine Rolle.
|
||||
|
||||
## 3. Position, Geschwindigkeit, Beschleunigung
|
||||
|
||||
Drei Größen hängen wie eine Kette zusammen:
|
||||
|
||||
| Größe | Einheit hier | Was sie ändert |
|
||||
|---|---|---|
|
||||
| Position x | px | – |
|
||||
| Geschwindigkeit v | px/s | die Position: "so viele Pixel pro Sekunde" |
|
||||
| Beschleunigung a | px/s² | die Geschwindigkeit: "pro Sekunde wird man so viel schneller" |
|
||||
|
||||
**Schwerkraft** ist eine Beschleunigung. Auf der Erde sind das 9,81 m/s². Die Sandbox rechnet
|
||||
in Pixeln und nimmt an, dass 100 px einem Meter entsprechen, also:
|
||||
|
||||
```text
|
||||
g = (0, 980) px/s² → pro Sekunde wird ein fallender Körper 980 px/s schneller
|
||||
```
|
||||
|
||||
Beispiel: Ein Ball, der aus der Ruhe fällt, ist nach 0,5 s 490 px/s schnell und hat
|
||||
½·g·t² = ½ · 980 · 0,25 ≈ 122 px zurückgelegt. Genau das prüft der Test
|
||||
`test_free_fall_matches_half_g_t_squared`.
|
||||
|
||||
## 4. Masse und Trägheit
|
||||
|
||||
**Masse** sagt, wie schwer es ist, einen Körper zu beschleunigen. Ein Stoß, der einen
|
||||
Tischtennisball wegschießt, bewegt eine Bowlingkugel kaum. In der Sandbox gilt:
|
||||
|
||||
```text
|
||||
Masse = Fläche × Dichte
|
||||
Kreis: Fläche = π · r²
|
||||
Rechteck: Fläche = Breite · Höhe
|
||||
```
|
||||
|
||||
Ein doppelt so großer Kreis (doppelter Radius) hat die vierfache Fläche und damit die
|
||||
vierfache Masse.
|
||||
|
||||
**Trägheitsmoment** ist dasselbe Prinzip für Drehungen: Wie schwer ist es, einen Körper in
|
||||
Drehung zu versetzen? Für eine Scheibe gilt `I = ½ · m · r²`. Masse, die weit außen sitzt,
|
||||
dreht sich schwerer an. Deshalb steht dort das r².
|
||||
|
||||
Rechtecke drehen sich in dieser Sandbox **nie** (die Aufgabe verlangt achsenparallele
|
||||
Rechtecke). Programmtechnisch bekommen sie ein unendliches Trägheitsmoment. Dann kann kein
|
||||
noch so großer Stoß sie drehen.
|
||||
|
||||
**Trick mit dem Kehrwert:** Der Code speichert zusätzlich `inv_mass = 1/Masse`. Wände
|
||||
bekommen `inv_mass = 0`, das heißt "unendlich schwer". So braucht man für Wände keine
|
||||
Sonderfälle: Die Rechnung "Geschwindigkeitsänderung = Stoß · inv_mass" ergibt bei einer Wand
|
||||
automatisch 0 – sie bewegt sich nicht.
|
||||
|
||||
## 5. Der Simulationsschritt: Euler-Verfahren
|
||||
|
||||
Wie kommt man von "Beschleunigung" zu "neuer Position"? Mit dem einfachsten Verfahren der
|
||||
Welt, benannt nach Leonhard Euler:
|
||||
|
||||
```text
|
||||
neue Geschwindigkeit = alte Geschwindigkeit + Beschleunigung · dt
|
||||
neue Position = alte Position + neue Geschwindigkeit · dt
|
||||
```
|
||||
|
||||
Beispiel mit dt = 1/120 s für einen fallenden Ball:
|
||||
|
||||
| Schritt | Geschwindigkeit (px/s) | Position y (px) |
|
||||
|---|---|---|
|
||||
| 0 | 0 | 100,00 |
|
||||
| 1 | 0 + 980/120 = 8,17 | 100 + 8,17/120 = 100,07 |
|
||||
| 2 | 16,33 | 100,20 |
|
||||
| 3 | 24,50 | 100,41 |
|
||||
|
||||
**Wichtiges Detail:** In der zweiten Zeile steht die *neue* Geschwindigkeit. Das heißt
|
||||
"semi-impliziter Euler". Die naheliegende Variante mit der *alten* Geschwindigkeit hat einen
|
||||
fiesen Fehler: Sie erzeugt bei Schwingungen und Pendelbewegungen langsam immer mehr
|
||||
Energie, bis die Simulation "explodiert". Die hier verwendete Variante kostet genauso wenig,
|
||||
bleibt aber stabil. Im Code: `World._integrate_velocities` und `World._integrate_positions`
|
||||
in [`physics/world.py`](../src/sandbox/physics/world.py).
|
||||
|
||||
## 6. Warum ein fester Zeitschritt?
|
||||
|
||||
Das Bild wird z. B. 60-mal pro Sekunde gezeichnet, aber nicht exakt regelmäßig. Mal dauert
|
||||
ein Bild 15 ms, mal 18 ms, und wenn Windows gerade etwas anderes tut, auch einmal 200 ms.
|
||||
|
||||
Würde die Physik diese schwankende Zeit als dt benutzen, gäbe es zwei Probleme:
|
||||
|
||||
- Die Simulation liefe auf jedem Rechner etwas anders.
|
||||
- Bei einem Ruckler von 200 ms würde ein Ball in *einem* Schritt 200 px weit springen und
|
||||
dabei vielleicht durch einen anderen Körper hindurch.
|
||||
|
||||
Deshalb gibt es einen **Akkumulator** ("Zeitsparschwein") in
|
||||
[`physics/timestep.py`](../src/sandbox/physics/timestep.py): Die echte Zeit wird
|
||||
gesammelt und in festen Portionen von 1/120 s an die Physik ausgezahlt. Bei 60 Bildern pro
|
||||
Sekunde sind das meist genau 2 Physikschritte pro Bild. Bleibt ein Rest, wartet er auf das
|
||||
nächste Bild.
|
||||
|
||||
Beim Zeichnen wird zusätzlich zwischen dem vorletzten und dem letzten Physikzustand
|
||||
**interpoliert** (Zwischenwert gebildet). Dadurch wirkt die Bewegung flüssig, auch wenn
|
||||
Physik und Bildschirm nicht im selben Takt laufen.
|
||||
|
||||
## 7. Kollisionen erkennen
|
||||
|
||||
Nach jedem Schritt muss das Programm herausfinden, welche Körper sich überlappen. Für jede
|
||||
Berührung erzeugt es einen **Kontakt** mit drei Angaben:
|
||||
|
||||
- **Normale n**: ein Pfeil der Länge 1, der von Körper A zu Körper B zeigt. In diese
|
||||
Richtung werden die Körper auseinandergedrückt.
|
||||
- **Eindringtiefe**: wie weit sich die Körper überlappen.
|
||||
- **Kontaktpunkt**: wo sie sich berühren.
|
||||
|
||||
### Kreis gegen Kreis – der einfachste Fall
|
||||
|
||||
Zwei Kreise berühren sich, wenn der Abstand ihrer Mittelpunkte kleiner ist als die Summe
|
||||
der Radien:
|
||||
|
||||
```text
|
||||
A ●───────● B Abstand d = 30, Radien 20 + 20 = 40
|
||||
╲___╱ ╲___╱ → Überlappung 40 − 30 = 10 px
|
||||
Normale n = (B − A) / d → Pfeil von A nach B, Länge 1
|
||||
```
|
||||
|
||||
### Kreis gegen Rechteck
|
||||
|
||||
Man sucht den Punkt des Rechtecks, der dem Kreismittelpunkt am nächsten liegt. Dafür
|
||||
"klemmt" man die Kreismitte in das Rechteck: Liegt sie links vom Rechteck, nimmt man den
|
||||
linken Rand, liegt sie darüber, den oberen Rand usw. Ist dieser nächste Punkt näher als der
|
||||
Radius, berühren sich die beiden.
|
||||
|
||||
### Rechteck gegen Rechteck
|
||||
|
||||
Zwei achsenparallele Rechtecke überlappen genau dann, wenn sie sich **sowohl** in
|
||||
x-Richtung **als auch** in y-Richtung überlappen. Getrennt werden sie entlang der Richtung
|
||||
mit der kleineren Überlappung – das ist der kürzeste Weg nach draußen.
|
||||
|
||||
### Wände
|
||||
|
||||
Boden, Decke und Seiten sind "Halbebenen": Alles hinter der Wandlinie gilt als Wand. Der
|
||||
Vorteil: Auch ein Körper, der in einem Schritt komplett hinter die Wand gesprungen ist, wird
|
||||
noch erkannt und zurückgeschoben.
|
||||
|
||||
### Schneller suchen: Broadphase
|
||||
|
||||
Bei 150 Körpern gibt es über 11 000 mögliche Paare – zu viele, um jedes genau zu prüfen.
|
||||
Deshalb sortiert [`physics/broadphase.py`](../src/sandbox/physics/broadphase.py) alle Körper
|
||||
von links nach rechts. Zwei Körper, die in x-Richtung weit auseinanderliegen, können sich
|
||||
nicht berühren und werden gar nicht erst genau geprüft ("Sweep-and-Prune").
|
||||
|
||||
## 8. Kollisionen auflösen: Impulse
|
||||
|
||||
Jetzt der wichtigste Teil. Zwei Körper berühren sich und bewegen sich aufeinander zu. Was
|
||||
passiert?
|
||||
|
||||
### Impuls – "Wucht"
|
||||
|
||||
Der **Impuls** eines Körpers ist Masse mal Geschwindigkeit: `p = m · v`. Ein schwerer,
|
||||
langsamer Lkw und ein leichter, schneller Ball können gleich viel "Wucht" haben.
|
||||
|
||||
Ein **Stoß** (ebenfalls Impuls genannt, Formelzeichen P) ist eine sehr kurze, starke Kraft,
|
||||
etwa beim Aufprall. Er ändert die Geschwindigkeit sofort:
|
||||
|
||||
```text
|
||||
Geschwindigkeitsänderung = Stoß / Masse
|
||||
```
|
||||
|
||||
### Actio = Reactio
|
||||
|
||||
Newtons drittes Gesetz: Wenn A gegen B drückt, drückt B genauso stark gegen A zurück. Der
|
||||
Stoß P wirkt also auf B in Richtung der Normalen und auf A genau entgegengesetzt:
|
||||
|
||||
```text
|
||||
v_A neu = v_A − P · n / m_A
|
||||
v_B neu = v_B + P · n / m_B
|
||||
```
|
||||
|
||||
Dadurch bleibt der Gesamtimpuls erhalten. Was A an Wucht verliert, gewinnt B.
|
||||
|
||||
### Wie groß muss der Stoß sein?
|
||||
|
||||
Man schaut, wie schnell sich die beiden Körper aufeinander zu bewegen – entlang der
|
||||
Normalen, also mit dem Skalarprodukt aus Abschnitt 2:
|
||||
|
||||
```text
|
||||
v_n = (v_B − v_A) · n (negativ = sie nähern sich an)
|
||||
```
|
||||
|
||||
Dann sucht man den Stoß P, der diese Annäherung genau stoppt (bzw. umkehrt, siehe nächster
|
||||
Abschnitt). Weil der Stoß beide Körper beeinflusst, hängt die nötige Stärke von beiden
|
||||
Massen ab:
|
||||
|
||||
```text
|
||||
P = −(1 + e) · v_n / (1/m_A + 1/m_B)
|
||||
```
|
||||
|
||||
Den Nenner `1/(1/m_A + 1/m_B)` nennt man **effektive Masse**. Bei einer Wand
|
||||
(`1/m = 0`) ist sie einfach die Masse des Körpers. Bei zwei gleich schweren Körpern ist sie
|
||||
halb so groß, weil der andere Körper mit ausweicht.
|
||||
|
||||
**Rechenbeispiel:** Eine Kugel (m = 1) fliegt mit 10 px/s senkrecht auf den Boden, der
|
||||
völlig unelastisch ist (e = 0). Dann ist v_n = −10, die effektive Masse ist 1 (Wand), und
|
||||
der Stoß ist P = −(1 + 0) · (−10) / 1 = 10. Die neue Geschwindigkeit ist 10 − 10 = 0: Die
|
||||
Kugel bleibt liegen.
|
||||
|
||||
Im Code: [`physics/solver.py`](../src/sandbox/physics/solver.py), die ausführliche
|
||||
Herleitung steht am Dateianfang.
|
||||
|
||||
## 9. Elastizität: Wie stark etwas abprallt
|
||||
|
||||
Die **Stoßzahl e** (in der Sidebar "Elastizität") sagt, wie viel der Geschwindigkeit nach
|
||||
dem Aufprall übrig bleibt:
|
||||
|
||||
| e | Verhalten | Beispiel |
|
||||
|---|---|---|
|
||||
| 0 | bleibt kleben, prallt nicht ab | Knetmasse |
|
||||
| 0,5 | prallt mit halber Geschwindigkeit ab | Holzklotz |
|
||||
| 0,8 | springt kräftig zurück | Gummiball |
|
||||
| 1 | prallt ohne Verlust ab | ideale Superkugel |
|
||||
|
||||
Daraus folgt eine schöne Regel: Ein Ball springt nach dem Aufprall auf **e²** seiner
|
||||
Fallhöhe. Mit e = 0,8 sind das 0,64, also 64 % der Höhe. (Die Energie hängt vom Quadrat der
|
||||
Geschwindigkeit ab, deshalb das Quadrat.)
|
||||
|
||||
Zwei praktische Sonderregeln:
|
||||
|
||||
- **Unter 50 px/s kein Abprallen.** Ein Körper, der auf dem Boden liegt, "fällt" wegen der
|
||||
Schwerkraft in jedem Schritt minimal in den Boden. Würde er davon jedes Mal ein winziges
|
||||
bisschen abprallen, würde er ständig zittern.
|
||||
- **Gehaltene Objekte prallen nicht ab.** Die "Hand" fängt den Stoß ab – sonst würde ein
|
||||
gegen eine Wand gedrücktes Objekt dauernd zurückfedern.
|
||||
|
||||
## 10. Reibung
|
||||
|
||||
Reibung bremst, wenn zwei Flächen aneinander entlangrutschen. Sie wirkt **quer zur
|
||||
Normalen**, also entlang der Berührfläche (dieser Richtung sagt man "Tangente").
|
||||
|
||||
Das Coulomb-Gesetz: Die Reibung kann höchstens so stark sein wie der Andruck mal dem
|
||||
**Reibungskoeffizienten μ**:
|
||||
|
||||
```text
|
||||
Reibung ≤ μ · Andruck
|
||||
```
|
||||
|
||||
- Ist wenig Schwung da, reicht die Reibung, um die Bewegung ganz zu stoppen: **Haftreibung**.
|
||||
Eine Kiste bleibt auf dem Boden stehen.
|
||||
- Ist mehr Schwung da, rutscht die Kiste trotz Reibung weiter und wird gleichmäßig langsamer:
|
||||
**Gleitreibung**. Sie bremst mit `μ · g`. Eine Kiste mit 300 px/s und μ ≈ 0,5 rutscht
|
||||
etwa 90 px weit.
|
||||
|
||||
Der Test `test_sliding_box_decelerates_with_coulomb_friction` prüft genau diesen Bremsweg.
|
||||
|
||||
## 11. Drehung und Rollen
|
||||
|
||||
Kreise können sich drehen. Die **Winkelgeschwindigkeit ω** (Omega) sagt, wie schnell, in
|
||||
Radiant pro Sekunde (eine volle Umdrehung sind 2π ≈ 6,28 rad).
|
||||
|
||||
Warum dreht sich ein Kreis überhaupt? Die Reibung greift nicht in der Mitte an, sondern
|
||||
unten am Kontaktpunkt. Ein Stoß, der nicht durch die Mitte geht, erzeugt ein
|
||||
**Drehmoment**, ähnlich wie ein Schraubenschlüssel. Genau dafür braucht man das
|
||||
Kreuzprodukt aus Abschnitt 2: `Hebelarm × Kraft` ergibt, wie stark gedreht wird.
|
||||
|
||||
**Rollen** heißt: Der Kontaktpunkt steht still, der Ball dreht sich genau passend zu seiner
|
||||
Geschwindigkeit:
|
||||
|
||||
```text
|
||||
v = ω · r
|
||||
```
|
||||
|
||||
Ein Experiment, das die Sandbox exakt nachrechnet: Stößt man eine Scheibe an, sodass sie
|
||||
ohne Drehung über den Boden rutscht, bremst die Reibung sie und bringt sie gleichzeitig zum
|
||||
Drehen. Irgendwann rollt sie – und dann hat sie **genau ⅔ ihrer Startgeschwindigkeit**,
|
||||
unabhängig davon, wie stark die Reibung ist. Den kleinen Strich in jedem Kreis zeichnet das
|
||||
Programm, damit man die Drehung sehen kann.
|
||||
|
||||
**Rollwiderstand:** Ein perfekt rollender Ball würde nie anhalten, denn reines Rollen
|
||||
verbraucht keine Reibungsenergie. In der Realität verformen sich Ball und Boden ein wenig,
|
||||
das bremst. Die Sandbox bildet das mit einem kleinen Gegen-Drehmoment nach. Ohne diese
|
||||
Bremse würde ein Rechteck, das auf einem Kreis liegt, darauf wie auf einer Rolle
|
||||
davonfahren.
|
||||
|
||||
## 12. Viele Kontakte gleichzeitig
|
||||
|
||||
Liegt eine Kiste in einem Stapel, hat sie Kontakte nach oben und nach unten. Jeder Stoß an
|
||||
einem Kontakt verändert die Situation an den anderen Kontakten. Man kann sie nicht einzeln
|
||||
nacheinander "fertig" rechnen.
|
||||
|
||||
Die Lösung heißt **Sequential Impulses** und funktioniert wie beim Aufräumen eines Zimmers
|
||||
in mehreren Runden: Man geht alle Kontakte durch und korrigiert jeden ein bisschen. Dann
|
||||
geht man sie noch einmal durch, weil die Korrekturen sich gegenseitig beeinflusst haben.
|
||||
Nach **8 Runden** pro Schritt ist das Ergebnis sehr genau.
|
||||
|
||||
Zwei Tricks machen das Verfahren stabil:
|
||||
|
||||
- **Kontakte können nur drücken, nicht ziehen.** Die Summe aller Stöße an einem Kontakt darf
|
||||
nie negativ werden, sonst würden Körper aneinanderkleben.
|
||||
- **Warm-Starting:** Ein ruhender Stapel sieht im nächsten Schritt fast genauso aus wie
|
||||
jetzt. Deshalb startet man mit den Stößen des letzten Schritts statt bei null und ist
|
||||
sofort fast fertig.
|
||||
|
||||
## 13. Wenn Körper ineinanderstecken: Positionskorrektur
|
||||
|
||||
Weil nur in festen Schritten gerechnet wird, landet ein fallender Ball meist schon ein
|
||||
kleines Stück *im* Boden, bevor der Aufprall erkannt wird. Die Stöße korrigieren nur die
|
||||
Geschwindigkeit, nicht die Überlappung. Ohne Gegenmaßnahme würden Stapel langsam
|
||||
ineinandersinken.
|
||||
|
||||
Deshalb schiebt der Solver die Körper nach jedem Schritt ein Stück auseinander – direkt über
|
||||
ihre Position, **ohne** ihnen Geschwindigkeit zu geben. Würde man sie stattdessen
|
||||
"wegschubsen", würden stark überlappende Körper (zum Beispiel, wenn man ein Objekt mitten
|
||||
in einen Haufen setzt) davonfliegen.
|
||||
|
||||
Eine winzige Überlappung von 0,5 px (der "Slop") lässt man absichtlich stehen. So bleibt der
|
||||
Kontakt im nächsten Schritt sicher bestehen, statt ständig zwischen "berührt" und "berührt
|
||||
nicht" zu wechseln – auch das wäre sichtbares Zittern.
|
||||
|
||||
## 14. Ruhe: Warum nichts zittert
|
||||
|
||||
Auch mit allen Tricks bleiben winzige Restbewegungen übrig. Die Lösung ist der
|
||||
**Schlafmodus** ([`physics/sleep.py`](../src/sandbox/physics/sleep.py)):
|
||||
|
||||
1. Jeder Körper zählt, wie lange er schon fast stillsteht (langsamer als 4 px/s).
|
||||
2. Körper, die sich berühren, bilden eine Gruppe (eine "Insel").
|
||||
3. Hat **die ganze Insel** eine halbe Sekunde lang fast stillgestanden, wird sie
|
||||
eingefroren: Sie bewegt sich nicht mehr und kostet keine Rechenzeit.
|
||||
4. Stößt etwas gegen einen schlafenden Körper, wacht **die ganze Insel** wieder auf.
|
||||
|
||||
Warum die ganze Insel? Weckt man nur die unterste Kiste eines Stapels und zieht sie weg,
|
||||
würde die Kiste darüber sonst in der Luft weiterschlafen. Genau diesen Fehler hat das
|
||||
unabhängige Review gefunden, und genau so wurde er behoben.
|
||||
|
||||
## 15. Tunneln: Warum nichts durch Wände fliegt
|
||||
|
||||
Weil nur in Schritten gerechnet wird, könnte ein sehr schneller, kleiner Körper in einem
|
||||
Schritt komplett über einen anderen hinwegspringen, ohne dass je eine Überlappung auftritt.
|
||||
Das nennt man **Tunneln**.
|
||||
|
||||
Zwei Gegenmaßnahmen:
|
||||
|
||||
- **Wände sind unendlich dick** (Halbebenen). Ein Körper kann nicht "hinter" sie gelangen,
|
||||
ohne erkannt zu werden.
|
||||
- **Geschwindigkeitsgrenze:** Die kleinsten Körper haben einen Radius von 12 px. Solange
|
||||
sich zwei davon pro Schritt zusammen weniger als ihre Radien-Summe bewegen, können ihre
|
||||
Mittelpunkte nicht aneinander vorbeispringen. Daraus ergibt sich eine Höchstgeschwindigkeit
|
||||
von etwa 1300 px/s, auf die jeder Körper begrenzt wird.
|
||||
|
||||
## 16. Ziehen und Werfen mit der Maus
|
||||
|
||||
Würde man ein gezogenes Objekt einfach an die Mausposition "teleportieren", könnte man es
|
||||
durch Wände und andere Objekte hindurchschieben. Stattdessen bekommt es in jedem
|
||||
Physikschritt die **Geschwindigkeit**, die es zur Maus bringt:
|
||||
|
||||
```text
|
||||
v = (Mausziel − Position) · 60
|
||||
```
|
||||
|
||||
Danach behandelt die Physik es wie jeden anderen Körper. Es schiebt leichte Objekte weg und
|
||||
wird von Wänden gestoppt.
|
||||
|
||||
**Werfen:** Beim Loslassen bekommt das Objekt die Geschwindigkeit der Maus. Die Maus
|
||||
zittert aber und liefert unregelmäßige Werte. Deshalb wird geglättet
|
||||
([`interaction/velocity_tracker.py`](../src/sandbox/interaction/velocity_tracker.py)):
|
||||
erst ein Mittelwert über die letzten 0,1 Sekunden, dann noch eine weiche Glättung. Hält man
|
||||
die Maus vor dem Loslassen still, sinkt die Geschwindigkeit auf fast null – das Objekt fällt
|
||||
dann einfach herunter.
|
||||
|
||||
## 17. Alles zusammen: ein kompletter Schritt
|
||||
|
||||
So sieht ein einzelner Physikschritt (1/120 s) in `World.step` aus:
|
||||
|
||||
1. Zustand merken (für das flüssige Zeichnen und das Abprallen).
|
||||
2. Schwerkraft anwenden: `v = v + g · dt`.
|
||||
3. Mögliche Kollisionspaare grob vorsortieren (Broadphase).
|
||||
4. Kontakte genau bestimmen; schlafende Inseln wecken, die berührt werden.
|
||||
5. Kontakte mit den Wänden bestimmen.
|
||||
6. Stöße berechnen – 8 Runden lang, mit Abprallen, Reibung und Rollwiderstand.
|
||||
7. Geschwindigkeit begrenzen (gegen Tunneln).
|
||||
8. Bewegen: `x = x + v · dt`.
|
||||
9. Restliche Überlappungen durch Verschieben abbauen.
|
||||
10. Notbremse: Niemand steckt tiefer als 12 px in einer Wand.
|
||||
11. Schlafmodus aktualisieren.
|
||||
|
||||
## 18. Zum Ausprobieren
|
||||
|
||||
Am besten versteht man Physik, indem man damit spielt:
|
||||
|
||||
- Setze die **Elastizität auf 1** und lass einen Ball fallen: Er springt immer wieder genau
|
||||
auf seine Starthöhe. Bei 0,5 wird jeder Sprung deutlich niedriger.
|
||||
- Wirf einen kleinen Kreis mit **Dichte 0,2** gegen einen großen mit **Dichte 5**: Der
|
||||
leichte prallt ab, der schwere bewegt sich kaum (Impulserhaltung).
|
||||
- Baue einen Turm aus Rechtecken und ziehe die unterste Kiste weg: Der Rest fällt.
|
||||
- Schalte mit **G** die Schwerkraft aus und wirf Objekte: Sie fliegen geradeaus, bis sie
|
||||
etwas treffen (Newtons erstes Gesetz: Trägheit).
|
||||
- Drücke **F1**: Gelbe Punkte zeigen die Kontaktpunkte, rote Striche die Normalen, graue
|
||||
Umrandungen schlafende Körper.
|
||||
- Ändere Werte in [`config.py`](../src/sandbox/config.py), zum Beispiel `gravity` oder
|
||||
`rolling_resistance`, und beobachte die Wirkung.
|
||||
Reference in New Issue
Block a user