467 lines
21 KiB
Markdown
467 lines
21 KiB
Markdown
# 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.
|