Les joints

Un élément très important des environnements de simulation est le Joint ou articulation ou jointure en français. Cela vient faire le lien entre les forces appliquées à un objet et ses liens avec l’environnement que ce soit à un objet static ou à un autre objet mobile. Le premier que nous allons voir est le distance Joint. Il permet de lier deux objet ensemble sans spécifier un mouvement. Les objets liés par un distance Joint apparaîtront ensembles, comme liés par une force interne. Bien sûr ces liens sont plus ou moins solides. On peut même renseigner une fréquence d’oscillation harmonique et un coefficient d’amortissement. 

  1. Particules associées

https://github.com/Thibautomaton/DuoParticlesWithDistanceJoints

Reprenons l’exemple des particules. Ne serait-il pas une amélioration fantastique d’avoir les particules liées entre elles. Elles s’emetraient alors par deux comme des atomes d’hydrogène liés dans une molécule de dihydrogène. Comment faire ?

Tout d’abord donnons un body à nos particules pour qu’elles soient capables d’interagir dans le monde de Box2D.

dans le __init__ de Particle on rajoute :

        self.body = world.CreateDynamicBody(

            position = pixelsToWorld((self.x, self.y))

        )

        box2dW = scalarPixelsToWorld(self.w/2)

        box2dH = scalarPixelsToWorld(self.h/2)

        ps = polygonShape(

            box = (box2dW, box2dH)

        )

        self.body.CreateFixture(

            shape = ps,

            density = 1,

            friction = 0.3,

            restitution=0.5

        )

On a donc créé un DynamicBody capable d’être bougé, avec une shape qui correspond à une boîte dont on a défini les dimensions dans le monde Box2D. Dans CreateFixture on donne en paramètre cette shape, plus des arguments qui définissent le comportement de l’objet dans la simulation physique.

Dans la méthode __init__ on ajoute deux lignes : 

        init_force = vec2(random.uniform(-2, 2), random.uniform(-1, 0))

        self.applyForce(init_force)

Cette méthode applyForce on la reconnait pour notre travail avec les particules dans processing. Cependant on se rend compte qu’on a pas initialisé de vitesse ou d’accélération sur lesquels la méthode applyForce pourrait influer. Pas de soucis, tout est géré par Box2D. 

On utilise : 

        force = vec2(force)

        self.body.ApplyLinearImpulse(force, self.body.worldCenter, wake=True)

Apply linear impulse a un effet direct sur le monde Box2D dans lequel existent les particules. Celà va créer une force définie par un vecteur et l’appliquer à l’accélération de la particule. Chose que l’on a déjà vu, mais ici tout passe sous le manteau, et Box2D se charge d’appliquer les forces.

Ensuite Particle prend une fonction display() et isDead() qui n’ont pas changés.

Le changement a lieu dans ParticleSystem. On avait vu qu’il pouvait être à propos de définir la gravité comme propriété de particleSystem, mais ici tout objet qui a un body subit la gravité. C’est dans addParticle que l’on fait appel à la méthode CreateDistanceJoint.  En effet, on veut que quand deux particules sont créées, celles-ci soient tout de suite liées par ce Joint. Dans le code ceci prend cette forme : 

    def addParticle(self):

        p1 = Particle(self.origin[0], self.origin[1], self.world)

        p2 = Particle(self.origin[0]+random.uniform(-1, 1), self.origin[1]+random.uniform(-1, 1), self.world)

        self.particles.append(p1)

        self.particles.append(p2)

        self.world.CreateDistanceJoint(

            bodyA = p1.body,

            bodyB = p2.body,

            anchorA = p1.body.worldCenter,

            anchorB = p2.body.worldCenter,

            length = scalarPixelsToWorld(10 ),

            frequencyHz = 0,

            dampingRatio = 0

        )

On crée deux particules, et un distance Joint, qui prend en argument : les body des particules, l’ancrage qui est l’endroit o`u les forces liées au distanceJoint s’appliquent,  la longueur qui est la distance nominale entre les deux objets lorsqu’aucune force n’est appliqué, et la fréquence et l’amortissement. 

Il serait intéressant de continuer cet exercice en créant des associations plus complexes de plusieurs particules pour représenter des molécules dans un environnement libre.

  1. Un pont avec les distanceJoint

https://github.com/Thibautomaton/BridgeWithDistanceJoints/tree/main

On crée des objets logs, qui cette fois vont être ronds pour représenter des rondins de bois, constituant un pont. On leur attribue un body et une fixation, car sinon on ne peut pas créer le distance Joint.

        cs = circleShape(

            radius = scalarPixelToWorld(self.r)

        )

            self.body = world.CreateDynamicBody(

                position=pixelsToWorld((self.x, self.y))

            )

            self.body.CreateFixture(

                shape = cs,

                density = 1,

                friction = 0.3,

                restitution = 0.5

            )

On utilise la fonction draw circle avec le rayon pour faire apparaître le rondin de bois à l’écran.

Dans le cas où un booléen True est passé en argument à Log, on crée plus un DynamicBody mais un StaticBody. 

Voyons o`u ceci est appelé dans le fichier bridge.py

    def __init__(self, world):

        self.logs = []

        self.logs.append(Log(0, HEIGHT/2, 8, world, True))

        for i in range(1, 100):

            self.logs.append(Log(8*i, HEIGHT/2, 5, world, False))

            world.CreateDistanceJoint(

                bodyA = self.logs[i-1].body,

                bodyB = self.logs[i].body,

                anchorA = self.logs[i-1].body.worldCenter,

                anchorB = self.logs[i].body.worldCenter,

                length = scalarPixelToWorld(8),

                frequencyHz = 0,

                dampingRatio = 0

            )

        self.logs.append(Log(8*100, HEIGHT/2, 8, world, True))

        world.CreateDistanceJoint(

            bodyA=self.logs[99].body,

            bodyB=self.logs[100].body,

            anchorA=self.logs[99].body.worldCenter,

            anchorB=self.logs[100].body.worldCenter,

            length=scalarPixelToWorld(8),

            frequencyHz=0,

            dampingRatio=0

        )

On crée un log, tous les 8 pixels, en passant bordure à False on crée des DynamicBody. Dans les paramètres du Joint on passe le body du log précédent et de celui courant. La longueur convertie en coordonnées du monde est d’exactement 8 pixels.

En fait on crée un premier et un dernier log avec bordure à True (donc pour en faire des staticBody) pour que ceux-là soient fixes dans le plan, quelque soit la charge qui leur est appliquée. De plus, ils ne subiront pas la gravité, ce qui permet au pont d’éviter de tomber dans le vide dès son initialisation. 

Dans la visualisation du code, on voit que dès que la charge est trop importante, l’écart entre les logs augmente (ce qui a pour effet d’amener plus de blocs au même endroit et donc d’augmenter encore la charge). C’est pourquoi la distance donnée entre deux body n’est que nominale et dépend des forces exercées.

CONCLUSION

Voici un premier exemple d’interaction que l’on a pu créer avec des distance Joints. Dans la suite nous nous intéresserons aux autres joints. Notamment nous explorerons les revolute joint, qui peuvent être particulièrement intéressants puisqu’on peut les rendre actif, comme mûs par un moteur, ce qui dans l’univers des simulations robotiques est un vrai début. En effet, en créant des axes à plusieurs revolute joint mûs par des moteurs, on pourra tester dans un environnement 2d simulés, des algorithmes capables d’interagir dans le monde réel. On peut en effet imaginer de simuler un algorithme génétique ou de reinforcement learning d’abord dans une simulation 2d moins coûteuse en ressource, avant de passer à une simulation 3d par exemple avec Isaac Sim. De plus, il est à noter que les moteurs de jeu comme Unity ou Godot dans l’implémentation de la physique sont économes dans la gestion des calculs et sont difficilement utilisables en robotique. D’où l’intérêt d’un environnement 2d moins coûteux comme première étape. 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Retour en haut