Simulação simples de espaço usando Python e Box2D

Olá, Habr.

Este artigo foi inspirado na publicação recente de Modeling the Universe , onde o autor mostrou uma simulação muito interessante de vários fenômenos cósmicos. No entanto, o código apresentado não é fácil para iniciantes. Vou mostrar como fazer simulações físicas usando o mecanismo Box2D escrevendo apenas algumas linhas de código.

Vou me arriscar a cometer um erro, mas essa é a primeira descrição do Box2D para Python em Habré, preenchemos essa lacuna.



Para aqueles que estão interessados ​​em como isso funciona, os detalhes estão ocultos.

Box2D é uma biblioteca multiplataforma gratuita criada pela Blizzard Erin Catto. A biblioteca foi introduzida em 2007 e hoje foi portada para quase todas as plataformas. Também existe uma porta para Python, sua descrição é bastante confusa, mas espero que com a ajuda deste artigo tudo fique mais claro.

Introdução


A biblioteca pybox2d consiste em dois componentes - o próprio Box2D, uma biblioteca de plataforma cruzada para modelagem física e um módulo de renderização separado chamado Framework. A renderização é necessária se queremos ver os objetos criados na tela, o que é conveniente o suficiente para a nossa simulação. A classe Framework pode usar vários métodos de saída (mais aqui ), usaremos pygame. Se a biblioteca pygame estiver instalada, ela é "captada" automaticamente e nada mais precisa ser feito. Para instalar, basta digitar o comando pip install Box2D pygame .

O menor programa em execução usando o Box2D é mostrado abaixo. O código é multiplataforma e funcionará em qualquer lugar, tanto no Linux quanto no 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()

Como você pode ver, estamos criando uma classe de simulação que herda da estrutura já mencionada. Em seguida, criamos dois objetos chamando o método CreateBody . O primeiro é um objeto estático que define os limites do nosso mundo. O segundo objeto é do tipo b2_dynamicBody, os demais parâmetros (forma, tamanho, densidade, coeficiente de atrito, velocidade inicial) são óbvios no código. A função Step é chamada sempre que durante a simulação, usaremos isso no futuro. Se uma interface do usuário não é necessária, por exemplo, fazemos um back-end para o servidor, é claro que a classe Framework pode ser omitida, mas para nós é bastante conveniente.

Isso é tudo, execute o programa e veja a simulação finalizada:



Como você pode ver, criamos apenas dois objetos e especificamos seus parâmetros. Tudo funciona “fora da caixa” - gravidade, fricção, elasticidade, interação de corpos etc. Com base nisso, podemos prosseguir com a simulação do “espaço”.

Lançamos o "satélite"


Infelizmente, não há suporte embutido para a gravidade newtoniana no Box2D, você precisará adicioná-lo adicionando a função Step. Para o primeiro teste, criaremos dois corpos - um planeta e um satélite girando em torno dele.

Código fonte como um todo:

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

Como você pode ver, “desligamos” a gravidade padrão definindo o parâmetro self.world.gravity como 0. Também adicionamos o parâmetro G, que é a “constante gravitacional” do nosso mundo virtual, que é usada no cálculo do método Step. Também criamos dois objetos - um satélite e um planeta. É importante observar os parâmetros de densidade e raio. De acordo com esses parâmetros, a própria biblioteca Box2D calcula a massa usada no cálculo. Para calcular a força de interação, é usada a fórmula "escolar" usual da lei da gravidade de Newton :



Agora começamos a simulação. Não atingimos a primeira velocidade cósmica e, embora o satélite ainda circulasse o planeta inteiro, é difícil chamá-lo de "vôo":



aumentamos a velocidade alterando a linha de código para linearVelocity = (28, 0):



Nosso "satélite" entrou em órbita com sucesso em todo o "planeta"! Se a velocidade aumentar ainda mais, a órbita se tornará elíptica:



finalmente, descreveremos algo mais semelhante ao nosso "sistema solar", adicionando três planetas de tamanhos diferentes em órbitas diferentes:

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

Resultado:



vemos que quanto mais longe o planeta está do "sol", maior o seu período de revolução (3ª lei de Kepler). Infelizmente, o mecanismo Box2D não permite desenhar trilhas de movimento na tela, por isso é difícil "ver" a 1ª e a 2ª leis de Keppler, mas você pode ter certeza de que elas também são implementadas.

Conclusão


Como você pode ver, com o Box2D, simulações simples podem ser feitas com o mínimo de esforço. Obviamente, esse mecanismo ainda é um mecanismo de jogo, não científico, portanto você não deve esperar dele uma simulação correta de uma colisão de galáxias ou a expansão de matéria durante o Big Bang. Mas alguns padrões são bastante interessantes de se observar.

Todos concebidos em uma peça não se encaixam. Se as estimativas forem positivas, na segunda parte será possível considerar mais exemplos não triviais.

All Articles