Au fil des flows

1avr/130

Jouons avec la physique : Simulateur de tissu

Il y'a peu, je suis tombé sur une démonstration HTML5 où l'on manipule un tissu à l'aide de la souris. J'ai été surpris par un fait, outre que ce soit du JavaScript, le code source de cet exemple faisait moins de 300 lignes.

Surprenant, comment en 350 lignes, ce code exécute cette simulation avec une telle fluidité ?
Au final, j'ai trouvé quelques réponses dans les commentaires de Reddit. Cet exemple utilise pour ses calculs l'algorithme Intégration de Verlet et un système de contrainte assez simple.

Voulant comprendre le fonctionnement de ce code, je l'ai porté en C++ en utilisant le framework Qt. Cette implémentation n'est pas optimisée. Par exemple, je traite les coordonnées x et y comme 2 champs de structure là où je pourrai les traiter sur la forme d'un tableau 2D. Il est possible qu'à l'avenir, je refactore ce code pour pouvoir utiliser cet exemple dans un environnement 3D comme Openglide. Je suis conscient que le code reste très monolithique. Le calcul et le rendu se font dans les mêmes classes.

Vous pouvez télécharger le code de ce portage sur GitHub.

Integration de Verlet

Comment transmettre le mouvement ? Pour ceux qui ont quelques restes de mécanique, vous pensez de suite, Newton, vecteur, application d'une ou plusieurs forces, accélération, intégrales, équation différentielle ... De suite ça parait très compliqué.

Il existe un algorithme offrant une facilité d'utilisation pour le calcul de déplacement. Cet algorithme c'est l'intégration de Verlet.
Au lieu de s'appuyer sur une équation différentielle position actuelle, vitesse et accélération, cet algorithme s'appuie sur une équation différentielle ayant trois arguments la position actuelle, la position précédente et l'accélération.

/*!
 * Methode qui calcule la nouvelle position du point en s'appuyant sur l'intégration de Verlet
 */
void Point::Update(qreal delta)
{
    // On applique la gravité
    this -> AddForce(0, this -> m_gravity * this -> m_mass);

    qreal vx = this -> m_x - this -> m_px;
    qreal vy = this -> m_y - this -> m_py;

    // Verlet integration equation
    delta *= delta;
    qreal nx = this -> m_x + vx + this -> m_ax * delta;
    qreal ny = this -> m_y + vy + this -> m_ay * delta;

    this -> m_px = this -> m_x;
    this -> m_py = this -> m_y;
    this -> m_x = nx;
    this -> m_y = ny;

    this -> m_ax = 0;
    this -> m_ay = 0;
}

Comment la souris interagit t'elle avec le tissu ?

Dans la méthode UpdateMouse, l'algorithme d'origine compare la position actuelle de la souris avec la position précédente et se sert de cette différence pour établir une vitesse. Est ce une bonne vision de la réalité ?

J'ai fait un choix légèrement différent du code d'origine, finissant sur la même conclusion.

J'utilise la position précédente et actuelle de la souris pour appliquer une accélération :

this -> AddForce((this -> m_mouse -> x - this -> m_mouse -> px) * 1000,
                             (this -> m_mouse -> y - this -> m_mouse -> py) * 1000);

Pourquoi 1000 ? La force de la gravité est fixée à 900 dans ce modèle (normalement, 9,81m/s²). J'ai choisi un coefficient qui avait un impact. Je ne pourrai pas expliquer ce choix en m'appuyant sur la mécanique.

On résout l'effet de cette force au travers de l'intégration de Verlet.

void Point::UpdateMouse()
{
    Q_CHECK_PTR(this -> m_mouse);
    if (this -> m_mouse -> down == false)
    {
        return;
    }

    qreal diff_x = this -> x() - this -> m_mouse -> x;
    qreal diff_y = this -> y() - this -> m_mouse -> y;
    qreal distance = qSqrt(diff_x * diff_x + diff_y * diff_y);
    if (this -> m_mouse -> button == Qt::LeftButton)
    {
        if (distance < this -> m_mouseInfluence)
        {
            this -> AddForce((this -> m_mouse -> x - this -> m_mouse -> px) * 1000,
                             (this -> m_mouse -> y - this -> m_mouse -> py) * 1000);
        }
    }
    else if (distance < this -> m_mouseCut)
    {
        this -> ClearConstraint();
    }
}

J'ai rajouté quelques interactions au clavier (sur les lettres Q,Z,D,S) :

void Point::UpdateKeyboard()
{
    if (this -> m_keyboard->keyup_down)
    {
        this -> AddForce(0, -2000);
    }

    if (this -> m_keyboard-> keydown_down)
    {
        this -> AddForce(0, 2000);
    }

    if (this -> m_keyboard -> keyright_down)
    {
        this -> AddForce(2000, 0);
    }

    if (this -> m_keyboard -> keyleft_down)
    {
        this -> AddForce(-2000, 0);
    }
}

Resolution des contraintes entre les mailles

La résolution de contrainte se fait dans la classe Constraint. Voici l'illustration de ce qui se passe :

void Constraint::Solve()
{
    qreal diff_x = this -> m_p1 -> x() - this -> m_p2 -> x();
    qreal diff_y = this -> m_p1 -> y() - this -> m_p2 -> y();
    qreal distance = qSqrt(diff_x * diff_x + diff_y * diff_y);
    qreal diff_distance = (this -> m_length - distance)/distance;


    // Il ne devrait jamais y avoir rupture (800 - 30)/(30)
    if (diff_distance > this -> m_tearDistance)
    {
        this -> m_p1 -> RemoveConstraint(this);
    }

    // scalar_1 is the ratio from p1 mass in comparaison with the p1 mass + p2 mass
    qreal scalar_1 = ((1 / this -> m_p1 -> mass()) / (1 / this -> m_p1 -> mass() + 1 / this -> m_p2 -> mass()));
    qreal scalar_2 = 1 - scalar_1;

    // Find a way to use directly arithmetic operation
    qreal x1_new = this -> m_p1 -> x() + diff_x * scalar_1 * diff_distance;
    this -> m_p1 -> setX(x1_new);
    qreal y1_new = this -> m_p1 -> y() + diff_y * scalar_1 * diff_distance;
    this -> m_p1 -> setY(y1_new);

    qreal x2_new = this -> m_p2 -> x() - diff_x * scalar_2 * diff_distance;
    this -> m_p2 -> setX(x2_new);
    qreal y2_new = this -> m_p2 -> y() - diff_y * scalar_2 * diff_distance;
    this -> m_p2 -> setY(y2_new);
}

Conclusion

Je ne connaissai pas ces algorithmes. Ce sont des outils surprenants, je les imaginai bien plus complexe. Ils n'ont pas vocation à faire de la simulation physique mais offre des possibilités surprenantes. J'ai la sensation d'être devant un univers immense et j'hésite où poser le pied après.

En savoir plus

Posted by Fabien Arcellier

Remplis sous: Non classé Laisser un commentaire
Commentaires (0) Trackbacks (0)

Aucun commentaire pour l'instant


Leave a comment

Aucun trackbacks pour l'instant