Simulation spatiale simple à l'aide de Python et Box2D

Bonjour, Habr.

Cet article est inspiré de la récente publication de Modeling the Universe , où l'auteur a montré une simulation très intéressante de divers phénomènes cosmiques. Cependant, le code qui y est présenté n'est pas facile pour les débutants. Je vais vous montrer comment faire des simulations physiques en utilisant le moteur Box2D en écrivant quelques lignes de code.

Je vais tenter ma chance, mais c'est la première description de Box2D pour Python sur Habré, nous comblons cette lacune.



Pour ceux qui sont intéressés par la façon dont cela fonctionne, les détails sont sous la coupe.

Box2D est une bibliothèque multiplateforme gratuite créée par Blizzard Erin Catto. La bibliothèque a été introduite en 2007 et aujourd'hui, elle a été portée sur presque toutes les plateformes. Il existe également un port pour Python, sa description est assez déroutante, mais j'espère qu'avec l'aide de cet article, tout deviendra plus clair.

introduction


La bibliothèque pybox2d se compose de deux composants - Box2D lui-même, une bibliothèque multiplateforme pour la modélisation physique et un module de rendu distinct appelé Framework. Le rendu est nécessaire si nous voulons voir les objets créés à l'écran, ce qui est assez pratique pour notre simulation. La classe Framework peut utiliser différentes méthodes de sortie (plus ici ), nous utiliserons pygame. Si la bibliothèque pygame est installée, elle est "récupérée" automatiquement et rien d'autre ne doit être fait. Pour installer, entrez simplement la commande pip install Box2D pygame .

Le plus petit programme en cours d'exécution utilisant Box2D est illustré ci-dessous. Le code est multiplateforme et fonctionnera partout, à la fois sur Linux et 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()

Comme vous pouvez le voir, nous créons une classe de simulation qui hérite du Framework déjà mentionné. Ensuite, nous créons deux objets en appelant la méthode CreateBody . Le premier est un objet statique qui définit les limites de notre monde. Le deuxième objet est de type b2_dynamicBody, d'autres paramètres (forme, taille, densité, coefficient de frottement, vitesse initiale) sont évidents d'après le code. La fonction Step est appelée à chaque fois pendant la simulation, nous l'utiliserons à l'avenir. Si l'interface utilisateur n'est pas nécessaire, par exemple, nous faisons un backend pour le serveur, alors bien sûr, la classe Framework peut être omise, mais pour nous, c'est assez pratique.

C'est tout, lancez le programme et voyez la simulation terminée:



Comme vous pouvez le voir, nous venons de créer deux objets et de spécifier leurs paramètres. Tout fonctionne «out of the box» - gravité, frottement, élasticité, interaction des corps, etc. Sur cette base, nous pouvons procéder à notre simulation «spatiale».

Nous lançons le "satellite"


Malheureusement, il n'y a pas de support intégré pour la gravité newtonienne dans Box2D, vous devrez l'ajouter vous-même en ajoutant la fonction Step. Pour le premier test, nous allons créer deux corps - une planète et un satellite tournant autour d'elle.

Code source dans son ensemble:

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()

Comme vous pouvez le voir, nous «désactivons» la gravité standard en définissant le paramètre self.world.gravity sur 0. Nous ajoutons également le paramètre G, c'est la «constante gravitationnelle» de notre monde virtuel, qui est utilisée dans le calcul de la méthode Step. Nous avons également créé deux objets - un satellite et une planète. Il est important de noter les paramètres de densité et de rayon. Selon ces paramètres, la bibliothèque Box2D calcule elle-même la masse utilisée dans le calcul. Pour calculer la force d'interaction, la formule «école» habituelle de la loi de gravité de Newton est utilisée :



Nous commençons maintenant la simulation. Nous n'avons pas atteint la première vitesse cosmique, et bien que le satellite ait encore encerclé la planète entière, il est difficile de l'appeler «vol»: nous



augmentons la vitesse en changeant la ligne de code en linearVelocity = (28, 0):



Notre "satellite" a réussi à entrer en orbite autour de la "planète"! Si la vitesse est encore augmentée, l'orbite deviendra elliptique:



Enfin, nous allons représenter quelque chose de plus similaire à notre «système solaire», en ajoutant trois planètes de tailles différentes sur différentes orbites:

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))

Résultat:



on voit que plus la planète est éloignée du «soleil», plus sa période de révolution est longue (3e loi de Kepler). Malheureusement, le moteur Box2D ne permet pas de tracer des traces de mouvement sur l'écran, il est donc difficile de «voir» les 1ère et 2e lois de Keppler, mais vous pouvez être sûr qu'elles sont également implémentées.

Conclusion


Comme vous pouvez le voir, avec Box2D, des simulations simples peuvent être effectuées avec un minimum d'effort. Bien sûr, ce moteur est toujours un moteur de jeu, pas un moteur scientifique, vous ne devriez donc pas en attendre une simulation correcte d'une collision de galaxies ou de l'expansion de la matière pendant le Big Bang. Mais certains modèles sont assez intéressants à regarder.

Tous conçus en une seule pièce ne correspondaient pas. Si les estimations sont positives, dans la deuxième partie, il sera possible de considérer des exemples plus non triviaux.

All Articles