Files
2026-09-24 15:45:31 +02:00

22 KiB
Raw Permalink Blame History

Physik der Sandbox

Dieses Dokument erklärt, was die Physik-Engine berechnet und warum sie es so tut. Alle Formeln finden sich im Code wieder, meist mit derselben Bezeichnung. Die Zahlenwerte stehen in src/sandbox/config.py (PhysicsConfig).

Die Physik-Schicht (src/sandbox/physics/) verwendet ausschließlich das Standardmodul math sowie typing/dataclasses für Typangaben. Ein Architektur-Test stellt sicher, dass dort nie pygame importiert wird.

Inhalt

  1. Einheiten und Koordinaten
  2. Körper: Masse und Trägheit
  3. Integration mit festem Zeitschritt
  4. Ablauf eines Simulationsschritts
  5. Kollisionserkennung
  6. Impulsbasierte Kollisionsauflösung
  7. Restitution (Elastizität)
  8. Reibung und Rollen
  9. Rollwiderstand
  10. Positionskorrektur
  11. Ruhe ohne Zittern: Schlafmodus
  12. Tunneln verhindern
  13. Ziehen und Werfen
  14. Parameterübersicht

1. Einheiten und Koordinaten

Größe Einheit
Länge Pixel (px)
Zeit Sekunde (s)
Geschwindigkeit px/s
Masse Fläche × Dichte (px² · Dichte; nur Verhältnisse zählen)

Der Ursprung liegt oben links in der Welt, x zeigt nach rechts, y nach unten – wie auf dem Bildschirm. Die Schwerkraft ist deshalb g = (0, +980) px/s²; bei einem Maßstab von 100 px pro Meter entspricht das 9,8 m/s².

Weil y nach unten zeigt, bedeutet ein positiver Drehwinkel eine Drehung im Uhrzeigersinn auf dem Bildschirm. Die Formeln sind davon unabhängig, solange man konsequent a × b = a.x·b.y − a.y·b.x verwendet.

2. Körper: Masse und Trägheit

Jeder Körper (Body) hat eine Form, eine Position (Schwerpunkt), eine Geschwindigkeit und bei Kreisen zusätzlich Winkel und Winkelgeschwindigkeit.

Masse = Fläche × Dichte. Damit sind größere Körper bei gleicher Dichte schwerer:

Form Fläche Trägheitsmoment
Kreis (Radius r) π·r² I = ½·m·r² (homogene Scheibe)
Rechteck (halbe Kanten hw, hh) 4·hw·hh unendlich (dreht nie)

Herleitung des Scheiben-Trägheitsmoments: Die Scheibe zerfällt in dünne Ringe mit Radius s und Masse dm = m/(π·r²) · 2π·s·ds. Jeder Ring trägt s²·dm bei:

I = (2m / r²) · ∫₀ʳ s³ ds = (2m / r²) · r⁴/4 = ½·m·r²

Rechtecke sind laut Aufgabenstellung achsenparallel. Ein unendliches Trägheitsmoment (also 1/I = 0) garantiert, dass kein Drehimpuls sie jemals drehen kann.

Inverse Massen. Gespeichert werden zusätzlich 1/m und 1/I. Wände haben 1/m = 0 (unendliche Masse). So passen bewegliche und unbewegliche Körper ohne Sonderfälle in dieselben Formeln: Ein Impuls P ändert die Geschwindigkeit um P/m – bei einer Wand um 0.

3. Integration mit festem Zeitschritt

Semi-impliziter Euler

Pro Schritt dt = 1/120 s:

v ← v + g·dt          (zuerst die Geschwindigkeit …)
x ← x + v·dt          (… dann die Position mit der NEUEN Geschwindigkeit)
θ ← θ + ω·dt

Der explizite Euler (Position mit der alten Geschwindigkeit) gewinnt bei Schwingungen und Umlaufbahnen systematisch Energie und wird instabil. Der semi-implizite (symplektische) Euler kostet genauso wenig, bleibt aber energetisch stabil.

Für den freien Fall ergibt sich nach n Schritten exakt v = n·g·dt und s = g·dt²·n(n+1)/2. Das weicht nur um den Faktor (1 + 1/n) vom analytischen s = ½·g·t² ab; nach 0,5 s sind das 1,7 %. Genau diese Beziehung prüft ein Test.

Fester Zeitschritt, entkoppelt von der Bildrate

Die Stabilität hängt von dt ab. Mit der schwankenden Frame-Dauer als dt würde sich die Simulation auf jedem Rechner anders verhalten. Ein Ruckler von 200 ms ließe Körper außerdem durch andere hindurchspringen. Deshalb sammelt ein Akkumulator (FixedTimestep) die real vergangene Zeit und gibt sie in festen Portionen weiter:

akkumulator += frame_zeit
while akkumulator ≥ dt:  world.step(dt);  akkumulator −= dt
alpha = akkumulator / dt                  (Rest für die Interpolation)
  • Obergrenze max_steps_per_frame: Schafft der Rechner die Physik nicht in Echtzeit, wird der Rückstand verworfen, statt ihn endlos mitzuschleppen ("Spiral of Death").
  • Interpolation: Gezeichnet wird lerp(vorige_position, position, alpha). Die Physik läuft mit 120 Hz, der Bildschirm z. B. mit 60 Hz – ohne Interpolation ruckelt die Bewegung sichtbar.

4. Ablauf eines Simulationsschritts

World.step(dt) führt nacheinander aus:

flowchart TD
    A[Zustand merken<br/>für Interpolation] --> B[Geschwindigkeiten integrieren<br/>v += g·dt]
    B --> C[Broadphase<br/>Sweep-and-Prune]
    C --> D[Narrowphase<br/>Kontakte Körper–Körper]
    D --> E[Berührte Schläfer wecken]
    E --> F[Wandkontakte]
    F --> G[Impulse lösen<br/>Normale · Reibung · Rollwiderstand]
    G --> H[Geschwindigkeit begrenzen]
    H --> I[Positionen integrieren<br/>x += v·dt]
    I --> J[Positionskorrektur]
    J --> K[Notbremse an den Wänden]
    K --> L[Schlafmodus aktualisieren]

Vor jedem Schritt setzt die Anwendung die Geschwindigkeit eines gezogenen Körpers (siehe Abschnitt 13).

5. Kollisionserkennung

Jede Prüfung liefert einen Kontakt mit

  • Normale n – Einheitsvektor von Körper A zu Körper B,
  • Eindringtiefe – Überlappung entlang n (≥ 0),
  • Kontaktpunkt – Angriffspunkt der Kräfte.

Broadphase: Sweep-and-Prune

Alle Paare genau zu prüfen kostet n·(n−1)/2 Tests – bei 150 Körpern über 11 000 pro Schritt. Deshalb werden die umschließenden Boxen nach ihrer linken Kante sortiert. Für jede Box müssen nur die Nachfolger geprüft werden, die links von ihrer rechten Kante beginnen. Der Aufwand sinkt auf etwa O(n·log n + k) für k benachbarte Paare. Paare aus zwei schlafenden Körpern werden übersprungen.

Kreis–Kreis

d = |p_B − p_A|,   Kontakt, wenn d ≤ r_A + r_B
n = (p_B − p_A) / d,   Tiefe = r_A + r_B − d

Der Kontaktpunkt liegt in der Mitte des Überlappungsbereichs. Liegen die Mittelpunkte exakt aufeinander, ist die Richtung unbestimmt. Dann wird die feste Ersatznormale (0, −1) verwendet, statt durch 0 zu teilen.

Kreis–Rechteck

Den Kreismittelpunkt (relativ zum Rechteck) komponentenweise auf das Rechteck klemmen. Das liefert den nächstgelegenen Punkt q des Rechtecks:

q = (clamp(dx, −hw, hw), clamp(dy, −hh, hh))
Kontakt, wenn |Mittelpunkt − q| ≤ r

Sonderfall: Liegt der Kreismittelpunkt im Rechteck, ist q der Mittelpunkt selbst und liefert keine Richtung. Dann wird der Kreis über die nächstgelegene Kante hinausgeschoben; die Tiefe ist Radius + Abstand zu dieser Kante.

Rechteck–Rechteck

Zwei achsenparallele Rechtecke überlappen genau dann, wenn sich ihre Projektionen auf beide Achsen überlappen (Trennachsensatz):

overlap_x = hw_A + hw_B − |dx|,   overlap_y = hh_A + hh_B − |dy|

Getrennt wird entlang der Achse mit der kleineren Überlappung – das ist der kürzeste Weg aus der Durchdringung. Bei Gleichstand gewinnt die y-Achse, weil aufeinanderliegende Körper der häufigste Fall sind.

Wände als Halbebenen

Boden, Decke und Seiten sind Halbebenen n·p ≥ offset (die Normale zeigt in die Welt). Die Reichweite eines Rechtecks entlang n ist |n.x|·hw + |n.y|·hh, die eines Kreises r. Weil eine Halbebene unendlich dick ist, wird ein Körper auch dann erkannt, wenn er in einem Schritt komplett hinter die Wandlinie springt – er kann nicht hindurchtunneln.

6. Impulsbasierte Kollisionsauflösung

Herleitung des Kollisionsimpulses

Relativgeschwindigkeit des Kontaktpunkts (B relativ zu A), mit den Hebelarmen r vom Schwerpunkt zum Kontaktpunkt und ω × r = ω·(−r.y, r.x):

v_rel = (v_B + ω_B × r_B) − (v_A + ω_A × r_A),        v_n = v_rel · n

v_n < 0 bedeutet: Die Körper nähern sich an. Ein Impuls P = λ·n wirkt auf B positiv und auf A negativ (actio = reactio):

v_A' = v_A − λ·n/m_A        ω_A' = ω_A − (r_A × λn)/I_A
v_B' = v_B + λ·n/m_B        ω_B' = ω_B + (r_B × λn)/I_B

Eingesetzt ändert sich die Normalgeschwindigkeit linear in λ:

v_n' = v_n + λ·K,     K = 1/m_A + 1/m_B + (r_A × n)²/I_A + (r_B × n)²/I_B

Aus Newtons Stoßgesetz v_n' = −e·v_n folgt:

λ = −(1 + e) · v_n / K

1/K ist die effektive Masse des Kontakts – die träge Masse, die der Stoß entlang n "spürt". Für einen einzelnen zentralen Stoß erhält diese Formel den Gesamtimpuls exakt und liefert die Lehrbuchergebnisse. Beim elastischen Stoß zweier Kreise mit Massenverhältnis 1 : 3 und Geschwindigkeiten 200 bzw. −100 px/s kommen zum Beispiel exakt −250 und 50 px/s heraus. Ein Test prüft das.

Besonderheit dieser Engine: Bei Kreisen geht die Normale durch den Mittelpunkt (r ∥ n, also r × n = 0), und Rechtecke drehen sich nie (1/I = 0). Die Drehterme von K verschwinden daher für die Normale – Normalimpulse erzeugen kein Drehmoment. Nur die Reibung bringt Kreise ins Rollen.

Viele Kontakte: Sequential Impulses

Liegt ein Körper auf mehreren anderen, beeinflussen sich die Kontakte gegenseitig. Statt ein Gleichungssystem zu lösen, werden die Kontakte reihum mehrfach aufgelöst (Gauß-Seidel, velocity_iterations = 8). Zwei Details machen das Verfahren korrekt und stabil:

  1. Akkumulierte Impulse klemmen. Geklemmt wird der über alle Durchläufe summierte Impuls (P_n ≥ 0), nicht der Teilimpuls eines Durchlaufs. Ein späterer Durchlauf darf einen zu großen früheren Impuls also wieder zurücknehmen. Das Verfahren konvergiert so gegen die physikalische Lösung, in der Kontakte nur drücken und nie ziehen.
  2. Warm-Starting. Die akkumulierten Impulse des vorigen Schritts werden zu Beginn erneut angewendet. Ruhende Kontakte ändern sich kaum, der Startwert ist also schon fast richtig – Stapel stehen dadurch nach wenigen Durchläufen stabil. Hat sich die Normale stark gedreht (Skalarprodukt < 0,95), wird der alte Impuls verworfen.

Reihenfolge je Kontakt und Durchlauf: Normale → Reibung → Rollwiderstand. Reibung und Rollwiderstand sind durch den Normalimpuls begrenzt. Werden sie danach gelöst, gelten ihre Grenzen nach jedem Durchlauf exakt für den aktuellen Normalimpuls.

7. Restitution (Elastizität)

Die Stoßzahl e ∈ [0, 1] ist das Verhältnis von Trenn- zu Aufprallgeschwindigkeit: e = 0 völlig unelastisch, e = 1 vollkommen elastisch.

  • Energieverlust: Beim zentralen Stoß geht ΔE = ½·μ·(1 − e²)·v_rel² verloren (μ = m_A·m_B/(m_A + m_B), die reduzierte Masse). Ein Test prüft genau diesen Wert.
  • Sprunghöhe: Ein fallender Ball erreicht nach dem Aufprall e² seiner Fallhöhe (Energie ∝ v²).
  • Mischregel: e = max(e_A, e_B) – ein Gummiball springt auch von einem Holzklotz ab. Wände haben e = 0, sodass allein das Material des Körpers zählt.
  • Schwellwert (restitution_threshold = 50 px/s): Unterhalb dieser Aufprallgeschwindigkeit wird nicht abgeprallt. Sonst würden aufliegende Körper durch winzige Dauer-Hüpfer zittern.
  • Aufprallgeschwindigkeit vom Schrittbeginn: v_n wird aus den Geschwindigkeiten vor Schwerkraft und Warm-Starting bestimmt (Body.previous_velocity). Nähme man die Geschwindigkeit nach der Schwerkraft, prallte ein Ball mit e·(v + g·dt) statt e·v ab. Ein vollkommen elastischer Ball (e = 1) gewönne dann mit jedem Aufprall Höhe – Energie aus dem Nichts. So springt er dagegen immer wieder genau auf seine Fallhöhe; ein Test prüft das über mehrere Sprünge.
  • Gehaltene Körper prallen nicht ab: Die Hand fängt den Stoß ab. Andernfalls würde ein gegen ein Hindernis gedrückter Körper in jedem Schritt zurückprallen, sofort wieder herangezogen werden und dabei sichtbar zittern.

8. Reibung und Rollen

Coulomb-Reibung: Entlang der Tangente t = (−n.y, n.x) soll die Gleitgeschwindigkeit verschwinden. Der Reibungsimpuls ist aber begrenzt:

|P_t| ≤ μ · P_n

Reicht die Grenze, haften die Körper (Haftreibung). Sonst rutschen sie mit konstanter Verzögerung μ·g (Gleitreibung): Eine Box mit 300 px/s kommt nach v²/(2μg) zum Stehen. Gemischt wird mit dem geometrischen Mittel μ = √(μ_A·μ_B) – ein glatter Partner macht den Kontakt glatt.

Rollen: Der Reibungsimpuls greift am Kontaktpunkt an, nicht im Schwerpunkt. Bei Kreisen erzeugt er daher ein Drehmoment r × P_t, die effektive Masse enthält den Term (r × t)²/I. Eine rutschende Scheibe wird gebremst und zugleich in Drehung versetzt, bis die Rollbedingung v = ω·r gilt. Drehimpulserhaltung um den Kontaktpunkt liefert die Endgeschwindigkeit – unabhängig vom Reibwert:

v_end = v₀ / (1 + I/(m·r²)) = v₀ / 1,5 = ⅔ · v₀

Auch das prüft ein Test. Damit man die Drehung sieht, zeichnet der Renderer in jeden Kreis einen Radius-Strich.

9. Rollwiderstand

Reines Rollen verbraucht keine Reibungsenergie – ein rollender Kreis würde ewig rollen. Ein Rechteck auf einem kleinen Kreis führe darauf wie auf einer Rolle davon. Real verformen sich Kreis und Unterlage ein wenig, was ein bremsendes Moment erzeugt:

M ≤ μ_r · N · r        (μ_r = rolling_resistance = 0,1)

Pro Schritt entspricht das einem Drehimpuls ≤ μ_r · P_n · r. Umgesetzt ist das wie die Reibung: als begrenzter Impuls, hier aber auf die relative Winkelgeschwindigkeit ω_B − ω_A, mit der effektiven Masse 1/(1/I_A + 1/I_B). Weil die Grenze mit der Normalkraft wächst, kommen schwer belastete "Rollen" schnell zur Ruhe, während ein frei rollender Ball weit rollt.

Für einen rollenden Ball ergibt sich eine konstante Verzögerung:

a = μ_r·g / (1 + I/(m·r²)) = μ_r·g / 1,5 ≈ 65 px/s²

Ein Ball mit 200 px/s rollt so etwa 300 px weit. Der Test misst genau diese Verzögerung.

10. Positionskorrektur

Impulse korrigieren nur Geschwindigkeiten. Durch den diskreten Zeitschritt überlappen Körper trotzdem ein wenig: Ein fallender Körper "landet" meist schon etwas in der Unterlage. Ohne Korrektur würden Stapel langsam ineinander einsinken.

Die Korrektur verschiebt die Positionen direkt ("Projektion"), im Verhältnis der inversen Massen:

C = factor · max(Tiefe − slop, 0)        (höchstens max_position_correction)
p_A −= n · C · (1/m_A) / (1/m_A + 1/m_B)
p_B += n · C · (1/m_B) / (1/m_A + 1/m_B)
  • Warum Positionen statt Zusatzgeschwindigkeit (Baumgarte)? Eine Geschwindigkeit, die Körper auseinandertreibt, bleibt nach der Trennung erhalten und schleudert sie davon – besonders bei großen Überlappungen, etwa wenn ein Objekt mitten in einen Haufen gesetzt wird. Die Projektion erzeugt keine Bewegungsenergie.
  • Slop (0,5 px): Eine kleine Resttiefe bleibt stehen. So wird der Kontakt im nächsten Schritt sicher wieder erkannt, statt ständig zwischen "Kontakt" und "kein Kontakt" zu wechseln – auch das wäre sichtbares Zittern.
  • Aktuelle Tiefe ohne erneute Kollisionsprüfung: Tiefe bei der Erkennung minus die seither erfolgte Relativverschiebung entlang n. Das ist exakt für Wände und Rechtecke und eine sehr gute Näherung für Kreise.
  • Notbremse: Kein Körper darf mehr als max_wall_penetration (12 px) in eine Wand ragen. Der Wert liegt bewusst über der größten Eindringtiefe, die ein frei fliegender Körper in einem Schritt erreichen kann (MAX_SPEED · dt ≈ 10,8 px). Normale Aufpralle löst deshalb immer der Solver; die Bremse greift nur in Extremfällen, etwa wenn ein gezogenes Objekt einen Körper gegen die Wand quetscht. Würde sie schon bei normalen Aufprallen greifen, verschöbe sie Positionen und fügte dabei Lageenergie hinzu.

11. Ruhe ohne Zittern: Schlafmodus

Ein Solver mit endlich vielen Iterationen hinterlässt immer winzige Restbewegungen. Ein Stapel "atmet" dadurch sichtbar oder kriecht. Deshalb werden ruhende Körper eingefroren:

  1. Jeder Körper zählt, wie lange er ununterbrochen fast stillsteht (|v| ≤ 4 px/s und |ω| ≤ 0,1 rad/s).
  2. Körper, die sich direkt oder über andere berühren, bilden eine Insel (Union-Find über die Kontakte). Wände verbinden keine Inseln, sonst wäre alles am Boden eine einzige Insel.
  3. Eine Insel schläft ein, wenn alle ihre Körper seit time_to_sleep (0,5 s) ruhen. Jeder Körper merkt sich dabei seine Insel (Body.sleep_island).
  4. Schlafende Körper werden weder integriert noch aufgelöst – das spart auch Rechenzeit.
  5. Aufwecken – immer die ganze Insel: bei Berührung durch einen wachen, beweglichen Körper, beim Greifen, beim Umschalten der Schwerkraft und wenn ein Körper der Insel gelöscht wird.

Warum Inseln statt einzelner Körper? Sonst könnte ein schlafender Körper unter einem wachen liegen. Der wache weckt ihn, beide ruhen, einer schläft früher ein, der andere weckt ihn wieder – ein endloses Hin und Her.

Warum wacht die ganze Insel auf? Zwei schlafende Körper werden nicht auf Kontakte untereinander geprüft. Wird die unterste Kiste eines schlafenden Stapels geweckt und im selben Schritt weggestoßen – oder im Pausenmodus weggezogen –, bemerkte sonst niemand, dass die Kisten darüber ihre Stütze verloren haben. Sie schliefen in der Luft weiter. Weil alle Körper, die einander berühren, gemeinsam eingeschlafen sind, genügt es, ihre Insel gemeinsam zu wecken.

Zusammen mit Schwellwert (Abschnitt 7), Slop (Abschnitt 10), Warm-Starting (Abschnitt 6) und Rollwiderstand (Abschnitt 9) kommen Stapel und Haufen vollständig zur Ruhe. Ein Test lässt 60 zufällige Körper fallen und verlangt, dass nach 8 s alle schlafen.

Grenze des Verfahrens: Wie jeder Schlafmodus mit Geschwindigkeitsschwellen kann er eine Lage einfrieren, die kein echtes Gleichgewicht ist. Ein Beispiel: Ein Kreis liegt wenige Pixel außermittig auf einem anderen Kreis. Ist die Kontaktnormale steiler geneigt, als der Rollwiderstand hält (tan α > μ_r), rollt er ab. Anfangs so langsam, dass er 0,5 s lang unter 4 px/s bleibt und einschläft. Er bleibt dann liegen, bis ihn etwas berührt, und rollt erst danach weiter. Die Auswirkung beschränkt sich auf wenige Pixel. Strengere Schwellen würden solche Fälle seltener machen, ließen dafür aber auch ruhende Haufen später einschlafen.

12. Tunneln verhindern

Die Kollisionserkennung ist diskret: Sie sieht nur die Positionen am Ende jedes Schritts.

  • Wände sind Halbebenen und damit ohnehin tunnelsicher (Abschnitt 5).

  • Körper untereinander: Fliegen zwei der kleinsten Körper (Radius MIN_BODY_SIZE = 12 px) frontal aufeinander zu, darf ihre Relativbewegung pro Schritt die Summe ihrer Radien nicht übersteigen. Sonst springen die Mittelpunkte aneinander vorbei:

    2·v·dt < 2·MIN_BODY_SIZE   ⇒   v < MIN_BODY_SIZE / dt = 1440 px/s
    

    Mit Sicherheitsfaktor 0,9 ergibt das MAX_SPEED ≈ 1296 px/s. Jede Geschwindigkeit wird nach dem Lösen der Impulse darauf begrenzt, ebenso die Wurfgeschwindigkeit. Aus demselben Grund ist bei Rechtecken keine halbe Kantenlänge kleiner als MIN_BODY_SIZE, jede Seite ist also mindestens 24 px lang.

  • Auch die Winkelgeschwindigkeit ist begrenzt (MAX_SPEED / MIN_BODY_SIZE).

13. Ziehen und Werfen

Ein gezogener Körper wird nicht an die Mausposition teleportiert. Dann könnte er durch andere Körper und Wände hindurchgeschoben werden. Stattdessen setzt DragController vor jedem Physikschritt die Geschwindigkeit in Richtung Ziel:

v = (Ziel − Position) · rate        (rate = 60/s; rate·dt ≤ 1 verhindert Überschwingen)

Danach löst der Solver die Kontakte ganz normal. Das gezogene Objekt kollidiert also weiterhin: Leichtere Körper werden weggeschoben, Wände und schwere Stapel bremsen es. Das Ziel wird so geklemmt, dass der Körper ganz in die Welt passt. Gehaltene Körper unterliegen keiner Schwerkraft ("die Hand trägt sie"), prallen nicht ab ("die Hand fängt den Stoß ab", siehe Abschnitt 7) und schlafen nie ein. Beim Greifen wird die Schlaf-Insel des Körpers geweckt.

Folge der fehlenden Restitution: Schlägt man mit einem gehaltenen Objekt einen Ball, stößt es ihn wie ein unelastischer Schläger. Der Ball fliegt deshalb etwas langsamer davon als bei einem freien, elastischen Stoß (in einer Messung des Reviews um etwa 13 %). Dafür steht ein gegen ein Hindernis gedrücktes Objekt ruhig.

Werfen: Beim Loslassen erhält der Körper die Mausgeschwindigkeit der letzten Frames. VelocityTracker glättet sie in zwei Stufen:

  1. Zeitfenster: mittlere Geschwindigkeit über die letzten 0,1 s (Weg / Zeit zwischen ältester und neuester Probe) – unabhängig von der Bildrate.
  2. Exponentielle Glättung: v ← v + (v_roh − v)·(1 − e^(−Δt/τ)) mit τ = 0,05 s. Der Exponentialterm macht die Glättung ebenfalls bildratenunabhängig.

Hält man die Maus vor dem Loslassen still, klingt die Geschwindigkeit schnell ab, und das Objekt fällt einfach herunter. Im Pausenmodus wird direkt verschoben und nicht geworfen.

14. Parameterübersicht

Alle Werte stehen in config.py und lassen sich dort anpassen.

Parameter Wert Bedeutung
gravity (0, 980) px/s² Schwerkraft
time_step 1/120 s fester Zeitschritt
velocity_iterations 8 Solver-Durchläufe pro Schritt
position_iterations 2 Durchläufe der Positionskorrektur
restitution_threshold 50 px/s darunter kein Abprallen
penetration_slop 0,5 px geduldete Resttiefe
position_correction_factor 0,4 Anteil der Korrektur pro Durchlauf
max_position_correction 6 px Obergrenze der Korrektur pro Durchlauf
max_speed ≈ 1296 px/s Tunnel-Schutz
rolling_resistance 0,1 Rollwiderstandsbeiwert μ_r
sleep_linear_speed / sleep_angular_speed 4 px/s / 0,1 rad/s Ruheschwellen
time_to_sleep 0,5 s Ruhezeit bis zum Einschlafen
wall_friction / wall_restitution 0,6 / 0 Wandmaterial
max_wall_penetration 12 px Notbremse an den Wänden (> MAX_SPEED · dt)