Perfekte, unendlich präzise Spielphysik in Python (Teil 1)
Programmieren Sie mit absoluter Genauigkeit „Newton's Cradle“, „Tennisball & Basketball Drop“ und mehr
Dies ist der erste von vier Artikeln, die Ihnen zeigen, wie Sie eine perfekte Physik-Engine in Python programmieren. Es ist ein winziger Schritt in meinem großen Bestreben, die gesamte Physik, Mathematik und sogar Philosophie in Programmierung umzuwandeln. Durch dieses Projekt werden wir Überraschungen entdecken, unser Verständnis erweitern und (hoffentlich) Spaß haben. Der gesamte Code ist auf GitHub verfügbar .
Wir werden die Engine in vielerlei Hinsicht einschränken – zum Beispiel auf Newtonsche Kollisionen zwischen Kreisen und Linien. Wir werden jedoch die Genauigkeit der Engine nicht einschränken. Es repräsentiert alle Zeiten, Positionen und Geschwindigkeiten mit exakten Ausdrücken wie 8*sqrt(3)/3
. Mit anderen Worten, es vermeidet alle numerischen Näherungen.
Das Ergebnis sind perfekte Simulationen beispielsweise des Spielzeugs Newton's Cradle. (Um das Video erneut abzuspielen/anzuhalten, drücken Sie für die Schaltfläche in der unteren linken Ecke. Ton ist hilfreich.)
Was nützt eine Physik-Engine mit absoluter Genauigkeit? Unter anderem kann es unser Verständnis der physischen Welt verbessern. In diesem Artikel (Teil 1) sehen wir diese neuen Entdeckungen:
- Tennisball -Geschwindigkeitsbegrenzung – In einer beliebten Physikdemonstration lässt du einen Tennisball mit einem Basketball fallen. Der Tennisball springt viel schneller auf, als er fällt. Wie viel schneller? Wir werden sehen, dass die „Aufwärts“-Geschwindigkeit nie mehr als dreimal so hoch ist wie die „Abwärts“-Geschwindigkeit. Dies gilt auch dann, wenn wir den Basketball unendlich massiv machen.
- Good Vibrations – Stellen Sie sich einen sich bewegenden Tennisball vor, der zwischen einem Basketball und einer Wand gefangen ist. In der kurzen Zeit zwischen zwei Videobildern kann der Tennisball 80 Mal aufspringen. Eine Engine, die Annäherungen verwendet, könnte einige dieser Sprünge verpassen. Unser Motor zählt jeden einzelnen.
- Billard kaputt – Selbst mit unendlicher Präzision kann ein Billardbruch praktisch nicht rückgängig gemacht werden. Wir werden sehen, dass selbst wenn wir den „Schmetterlingseffekt“ und die Quantenunsicherheit entfernen, die Welt immer noch zufällig und nicht deterministisch ist.
Aufbau des Motors
Angenommen, wir haben bereits Python-Funktionen für:
- Geben Sie bei zwei beliebigen (möglicherweise beweglichen) Objekten in der Welt die genaue Zeitspanne zurück, bis sie sich gerade berühren. Die Antwort könnte „nie“ lauten.
- Wie genau wird die Kollision bei zwei kollidierenden Objekten ihre Geschwindigkeit und Richtung ändern?
Lassen Sie uns einen Ansatz durcharbeiten, indem wir drei physikalische Welten betrachten, beginnend mit Newtons Wiege.
Newtons Wiege
Die allgemeine Idee für unsere Engine ist es, die genaue Zeit bis zur nächsten Kollision auf der Welt zu finden. Gehen Sie zu genau dieser Zeit vor. Passen Sie die Geschwindigkeiten der kollidierenden Objekte an. Wiederholen.
Für Newton's Cradle richten wir zunächst Kreise und Wände ein.
from perfect_physics import World, Circle, Wall
circle_list = [Circle(x=1, y=0, r=1, vx=1, vy=0, m=1)]
for i in range(1, 6):
circle_list.append(Circle(x=i * 2 + 4, y=0, r=1, vx=0, vy=0, m=1))
wall_list = [Wall(x0=0, y0=0, x1=0, y1=1), Wall(x0=20, y0=0, x1=20, y1=1)]
world = World(circle_list, wall_list, xlim=(-1, 21), ylim=(-2, 2))
world.show()
- Finden Sie zwischen jedem Paar von Objekten die genaue Zeitspanne, in der sie kollidieren (falls vorhanden). Ignorieren Sie andere Paare und Kollisionen. — In diesem Fall kollidiert der erste (sich bewegende) Kreis mit dem zweiten Kreis in Zeitspanne 3. Wenn man diese Kollision ignoriert, kollidiert er auch mit dem dritten Kreis in Zeitspanne 5 usw. Er kollidiert zur Zeit mit der gegenüberliegenden Wand Spanne 18.
- Finden Sie die Zeitspanne der ersten Kollision(en) und stellen Sie die Weltuhr genau um diese Zeit vor. In diesem Fall 3.
- Passen Sie die Geschwindigkeiten aller an diesen Kollisionen beteiligten Objekte an. Wie bei Newtons Wiege erwartet, wird der erste Kreis stationär und der zweite Kreis beginnt sich zu bewegen.
- Wiederholen Sie so lange wie gewünscht. Im Beispiel kollidieren die zweite und die dritte Kugel als nächstes in der Zeitspanne 0. Dieses Video zeigt die Aktion Ereignis für Ereignis. (Ein „Ereignis“ ist entweder das Verschieben der Zeit bis zu einer Kollision oder das Anpassen der Geschwindigkeit basierend auf der Kollision)
Im Allgemeinen gilt für jeden Rahmen:
- Ermitteln Sie den Wert der Simulationsuhr für diesen Frame.
- Finden Sie das Kollisionsereignis, das diesem Uhrenwert vorausgeht.
- Verschieben Sie ausgehend von der Welt bei der vorangegangenen Kollision die Kreise zum Uhrwert des Rahmens. (Absichtlich kollidieren die Kreise während dieser Bewegung mit nichts.)
Kreis & Dreieck
Ich behaupte, dass Zeiten und Positionen alle genau sind, aber glaubst du mir? Hier ist ein Beweis durch einen Kreis, der von einem Dreieck eingeschrieben ist. Zuerst ist hier das Video, das nur die Ereignisse zeigt. Sie können sehen, dass die Uhr auf Ausdrücke wie eingestellt ist 8*sqrt(3)/3
.
Und hier ist das reguläre Video für den Kreis in einem Dreieck:
In diesem Fall fällt die Simulation in ein Muster. Ohne unendliche Präzision hätte das Muster übersehen werden können.
Tennisball & Basketball Drop
Schauen wir uns eine weitere Physikwelt an, um einen weiteren Vorteil der unendlichen Präzision zu sehen. Diese Welt beinhaltet einen Tennisball und einen Basketball, die sich mit Geschwindigkeit 1 auf eine Wand zubewegen. Die Masse des Basketballs ist 1000-mal so groß wie die des Tennisballs. (Unsere Welten haben keine Schwerkraft.)
from perfect_physics import World, Circle, Wall
big_radius = 10
world_width = 40
folder = root / f"part1/two_size{big_radius}"
big = Circle(x=world_width // 2, y=0, r=big_radius, vx=1, vy=0, m=big_radius**3)
little = Circle(x=big.x - big_radius - 1, y=0, r=1, vx=1, vy=0, m=1)
circle_list = [big, little]
wall_list = [Wall(x0=0, y0=0, x1=0, y1=1), Wall(x0=world_width, y0=0, x1=world_width, y1=1)]
world = World(circle_list, wall_list, xlim=(-1, world_width + 1), ylim=(-big_radius - 1, big_radius + 1))
world.run_in_place(2, show=True)
print([circle.vx for circle in world.circle_list])
Wie erwartet (falls Sie dies bereits in der physischen Welt ausprobiert haben) beschleunigt der Tennisball. Es überraschte mich jedoch, indem es nur etwa dreimal schneller ging.
Lassen Sie uns für einen Moment weiterspringen und die Tools verwenden, die wir in Teil 3 entwickeln werden. Dieser Python-Code zeigt die Wirkung eines Basketballs mit unendlicher Masse auf die Tennisbälle. Der Tennisball geht mit einer Geschwindigkeit von 1 hinein und mit einer Geschwindigkeit von genau 3 wieder hinaus.
from sympy import limit, simplify, oo
from perfect_physics import load
cc_velocity_solution = load("data/cc_velocity_solution.sympy")
a_vx, a_vy, b_vx, b_vy = cc_velocity_solution.subs([("a_x", 10), ("a_y", 0), ("a_r", 10), ("a_vx", -1), ("a_vy", 0),
("b_x", -1), ("b_y", 0), ("b_r", 1), ("b_vx", 1), ("b_vy", 0), ("b_m", 1)])
print(simplify(b_vx))
limit(b_vx, "a_m", oo)
# prints (1 - 3*a_m)/(a_m + 1)
# returns -3
Der Motor überraschte mich noch mehr, als ich ihn länger laufen ließ. Schauen Sie sich an, was bei Videozeit 20 Sekunden (Simulationszeit 200) passiert:
Der Basketball zerquetscht den Tennisball. Dadurch springt der Tennisball mehr als 80 Mal in einem einzigen Videoframe hin und her. Der Tennisball erreicht eine Geschwindigkeit von über 31. Auf der Audiospur des Videos erzeugt die Vibration des Tennisballs einen Ton von mehr als 2000 Hz (Beats/Sekunde).
Wenn wir statt einer genauen Berechnung nur einmal pro Bild abtasten würden, würden wir diese schnelle Aktion verpassen (oder falsch berechnen).
Zusammenfassung Teil 1
Es klappt! Wir haben eine perfekte Physik-Engine. Wir haben ein bisschen Physik in Programmierung verwandelt.
Wir gingen davon aus, dass wir mit zwei Python-Funktionen begannen, die uns – für jedes Paar von Objekten – zwei Dinge sagten: 1) Die Zeit bis zu ihrer nächsten Kollision (falls vorhanden) und 2) Die Auswirkung dieser Kollision auf ihre Geschwindigkeiten. Wir haben gesehen, wie man diese beiden Funktionen in eine perfekte Physik-Engine verwandelt, indem man sich wiederholt in der Zeit vorwärts bewegt bis zur/den nächsten Kollision(en) und die Geschwindigkeiten aller an der/den Kollision(en) beteiligten Objekte anpasst.
Wir haben Videos erstellt, indem wir die Kollision nachgeschlagen haben, die einem Videobild vorangegangen ist, und dann die Zeit nach vorne verschoben haben (ohne uns Gedanken über Kollisionen zu machen) zu diesem Bild.
Newton's Cradle, die erste Welt, verhielt sich wie erwartet. Die zweite Welt, ein von einem Dreieck einbeschriebener Kreis, der Ausdrücke wie 8*sqrt(3)/3
für Zeit usw. verwendet, fand ein Muster. Der Fall von Tennisball und Basketball deckte eine für mich überraschende Grenze der Geschwindigkeit des Tennisballs auf. Außerdem erzeugte es schnellere Sprünge, als weniger perfekte Methoden berechnen könnten.
Der nächste Artikel, Teil 2 , simuliert eine Billardpause mit überraschenden philosophischen Ergebnissen. Als nächstes werden wir in Teil 3 sehen, wie man den Computer dazu bringt, diese beiden Startfunktionen zu erstellen. Schließlich werden wir in Teil 4 die Engine ein wenig beschleunigen (aber nicht genug) und Einschränkungen diskutieren.
Wenn Sie Ideen für Simulationen haben, die ich ausführen soll, senden Sie sie mir bitte. Sie können die Grundlage eines Teils 5 werden.
Sie können diesen Code von CarlKCarlK/perfect-physics (github.com) herunterladen . Lassen Sie mich wissen, wenn Interesse besteht, und ich werde einen schöneren Installer erstellen.
Folgen Sie Carl M. Kadie – Medium , um Benachrichtigungen über die nächsten Teile zu erhalten. Alle Teile können kostenlos gelesen werden. Schließlich habe ich auf YouTube ältere (ungefähre) Physiksimulationen und einige Naturvideos, die versuchen, humorvoll zu sein .