Einfache Weltraumsimulation mit Python und Box2D

Hallo Habr.

Dieser Artikel wurde von der jüngsten Veröffentlichung von Modeling the Universe inspiriert , in der der Autor eine sehr interessante Simulation verschiedener kosmischer Phänomene zeigte. Der dort präsentierte Code ist jedoch für Anfänger nicht einfach. Ich zeige Ihnen, wie Sie mit der Box2D- Engine physisch modellieren, indem Sie nur ein paar Codezeilen schreiben.

Ich werde einen Fehler machen, aber dies ist die erste Beschreibung von Box2D für Python auf Habré. Wir füllen diese Lücke.



Für diejenigen, die daran interessiert sind, wie dies funktioniert, sind Details unter dem Schnitt.

Box2D ist eine kostenlose plattformübergreifende Bibliothek, die von Blizzard Erin Catto erstellt wurde. Die Bibliothek wurde 2007 eingeführt und heute auf fast alle Plattformen portiert. Es gibt auch einen Port für Python, dessen Beschreibung ziemlich verwirrend ist, aber ich hoffe, dass mit Hilfe dieses Artikels alles klarer wird.

Einführung


Die pybox2d- Bibliothek besteht aus zwei Komponenten - Box2D selbst, einer plattformübergreifenden Bibliothek für die physische Modellierung und einem separaten Rendering-Modul namens Framework. Das Rendern ist erforderlich, wenn die erstellten Objekte auf dem Bildschirm angezeigt werden sollen, was für unsere Simulation praktisch genug ist. Die Framework-Klasse kann verschiedene Ausgabemethoden verwenden (mehr hier ), wir werden Pygame verwenden. Wenn die Pygame-Bibliothek installiert ist, wird sie automatisch "abgeholt" und es muss nichts weiter getan werden. Geben Sie zur Installation einfach den Befehl pip install Box2D pygame ein .

Das kleinste laufende Programm mit Box2D ist unten dargestellt. Der Code ist plattformübergreifend und funktioniert überall, sowohl unter Linux als auch unter Windows.

from Box2D.examples.framework import Framework
from Box2D import *

class Simulation(Framework):
    def __init__(self):
        super(Simulation, self).__init__()

        # Ground body
        self.world.CreateBody(shapes=b2LoopShape(vertices=[(20, 0), (20, 40), (-20, 40), (-20, 0)]))
        # Dynamic body
        circle = b2FixtureDef(shape=b2CircleShape(radius=2), density=1, friction=1.0, restitution=0.5)
        self.world.CreateBody(type=b2_dynamicBody, position=b2Vec2(0,30), fixtures=circle, linearVelocity=(5, 0))

    def Step(self, settings):
        super(Simulation, self).Step(settings)

if __name__ == "__main__":
    Simulation().run()

Wie Sie sehen können, erstellen wir eine Simulationsklasse, die vom bereits erwähnten Framework erbt. Als Nächstes erstellen wir zwei Objekte, indem wir die CreateBody- Methode aufrufen . Das erste ist ein statisches Objekt, das die Grenzen unserer Welt definiert. Das zweite Objekt ist vom Typ b2_dynamicBody, andere Parameter (Form, Größe, Dichte, Reibungskoeffizient, Anfangsgeschwindigkeit) sind aus dem Code ersichtlich. Die Step- Funktion wird jedes Mal während der Simulation aufgerufen, wir werden sie in Zukunft verwenden. Wenn zum Beispiel keine Benutzeroberfläche benötigt wird, machen wir ein Backend für den Server, dann kann die Framework-Klasse natürlich weggelassen werden, aber für uns ist es sehr praktisch.

Das ist alles, führen Sie das Programm aus und sehen Sie die fertige Simulation:



Wie Sie sehen, haben wir gerade zwei Objekte erstellt und deren Parameter angegeben. Alles funktioniert „out of the box“ - Schwerkraft, Reibung, Elastizität, Interaktion von Körpern usw. Auf dieser Grundlage können wir mit unserer „Raum“ -Simulation fortfahren.

Wir starten den "Satelliten"


Leider gibt es in Box2D keine integrierte Unterstützung für die Newtonsche Schwerkraft. Sie müssen diese selbst hinzufügen, indem Sie die Step-Funktion hinzufügen. Für den ersten Test werden wir zwei Körper erschaffen - einen Planeten und einen Satelliten, der sich um ihn dreht.

Quellcode als Ganzes:

from Box2D import *
from Box2D.examples.framework import Framework


class Simulation(Framework):
    def __init__(self):
        super(Simulation, self).__init__()

        # Default gravity disable
        self.world.gravity = (0.0, 0.0)
        # Gravity constant
        self.G = 100

        # Planet
        circle = b2FixtureDef(shape=b2CircleShape(radius=5), density=1, friction=0.5, restitution=0.5)
        self.world.CreateBody(type=b2_dynamicBody, position=b2Vec2(0,0), fixtures=circle)

        # Satellite
        circle_small = b2FixtureDef(shape=b2CircleShape(radius=0.2), density=1, friction=0.5, restitution=0.2)
        self.world.CreateBody(type=b2_dynamicBody, position=b2Vec2(0, 10), fixtures=circle_small, linearVelocity=(20, 0))

    def Step(self, settings):
        super(Simulation, self).Step(settings)

        # Simulate the Newton's gravity
        for bi in self.world.bodies:
            for bk in self.world.bodies:
                if bi == bk:
                    continue

                pi, pk = bi.worldCenter, bk.worldCenter
                mi, mk = bi.mass, bk.mass
                delta = pk - pi
                r = delta.length
                if abs(r) < 1.0:
                    r = 1.0

                force = self.G * mi * mk / (r * r)
                delta.Normalize()
                bi.ApplyForce(force * delta, pi, True)

if __name__ == "__main__":
    Simulation().run()

Wie Sie sehen können, „schalten“ wir die Standardgravitation aus, indem wir den Parameter self.world.gravity auf 0 setzen. Wir fügen auch den Parameter G hinzu. Dies ist die „Gravitationskonstante“ unserer virtuellen Welt, die bei der Berechnung der Step-Methode verwendet wird. Wir haben auch zwei Objekte erstellt - einen Satelliten und einen Planeten. Es ist wichtig, die Dichte- und Radiusparameter zu beachten. Nach diesen Parametern berechnet die Box2D-Bibliothek selbst die Masse, die für die Berechnung verwendet wird. Zur Berechnung der Wechselwirkungskraft wird die übliche "Schul" -Formel des Newtonschen Gravitationsgesetzes verwendet :



Nun starten wir die Simulation. Wir haben die erste kosmische Geschwindigkeit nicht erreicht und obwohl der Satellit immer noch den gesamten Planeten umkreiste, ist es schwierig, ihn "Flug" zu nennen: Wir



erhöhen die Geschwindigkeit, indem wir die Codezeile in linearVelocity = (28, 0) ändern:



Unser "Satellit" hat erfolgreich die Umlaufbahn um den "Planeten" erreicht! Wenn die Geschwindigkeit weiter erhöht wird, wird die Umlaufbahn elliptisch:



Schließlich werden wir etwas ähnlicheres wie unser „Sonnensystem“ darstellen und drei Planeten unterschiedlicher Größe in unterschiedlichen Umlaufbahnen hinzufügen:

circle_small = b2FixtureDef(shape=b2CircleShape(radius=0.2), density=1, friction=0.5, restitution=0.2)
circle_medium = b2FixtureDef(shape=b2CircleShape(radius=0.3), density=1, friction=1.0, restitution=0.5)
self.world.CreateBody(type=b2_dynamicBody, position=b2Vec2(0, 6), fixtures=circle_small, linearVelocity=(37, 0))
self.world.CreateBody(type=b2_dynamicBody, position=b2Vec2(0, 10), fixtures=circle_small, linearVelocity=(28, 0))
self.world.CreateBody(type=b2_dynamicBody, position=b2Vec2(0, 15), fixtures=circle_medium, linearVelocity=(22, 0))

Ergebnis:



Wir sehen, dass je weiter der Planet von der „Sonne“ entfernt ist, desto länger ist seine Revolutionsperiode (Keplers 3. Gesetz). Leider erlaubt die Box2D-Engine nicht das Zeichnen von Bewegungsspuren auf dem Bildschirm, so dass es schwierig ist, Kepplers 1. und 2. Gesetz zu "sehen", aber Sie können sicher sein, dass sie auch implementiert sind.

Fazit


Wie Sie sehen, können mit Box2D einfache Simulationen mit minimalem Aufwand durchgeführt werden. Natürlich ist diese Engine immer noch eine Spiel-Engine, keine wissenschaftliche, daher sollten Sie keine korrekte Simulation einer Kollision von Galaxien oder der Ausdehnung von Materie während des Urknalls erwarten. Aber einige Muster sind sehr interessant anzusehen.

Alle in einem Stück konzipierten passten nicht. Wenn die Schätzungen positiv sind, können wir im zweiten Teil weitere nicht triviale Beispiele betrachten.

All Articles