Comment éviter la gigue parmi les objets physiques quasi stationnaires?


8

J'ai implémenté un moteur physique personnalisé et je suis presque sur le point de le faire fonctionner comme je le voudrais. Il y a une force gravitationnelle, des collisions et une réponse aux collisions. Malheureusement, il semble y avoir une certaine gigue parmi les objets quasi stationnaires, probablement en raison de tics inchangeables à faible physique.

Cercles empilés à l'intérieur d'une boîte de gigue.

J'ai regardé en ligne et essayé certaines des implémentations que j'ai trouvées, y compris certaines de mes propres tentatives. Voici les solutions que j'ai essayées:

  • Mouvement d'amortissement lorsque la vitesse / l'élan / l'énergie potentielle est inférieure à un seuil.
  • Appliquer la gravité uniquement lorsque la vitesse / l'élan / l'énergie potentielle est supérieure au seuil.
  • Implémentation d'une fonction sommeil. qui vérifie la position de l'objet pour les 60 dernières images et s'endort s'il ne s'est pas déplacé en dehors d'une zone de délimitation de seuil.
  • Itération à travers les objets de haut en bas lors de l'application des tests de collision et de la résolution.

Voici mon code:

for each (auto ball in m_Balls)
{
    ball->Update(t);
    ball->Accelerate(m_Gravity);
}

// This disgusting hack sorts the balls by height. In a more complete physics
// implementation, I guess I could change the sorting based on the direction of
// gravitational force. This hack is necessary to prevent balls being pulled downwards
// into other balls by gravity; by calculating from the bottom of the pile of
// objects, we avoid issues that occur when adjustments push the object towards gravity.
m_Balls.sort([](const CSprite* a, const CSprite* b) 
    {return a->m_pos.m_y < b->m_pos.m_y; });

static float cor = 0.8f;

for each (auto ball in m_Balls)
{
    for each (auto collider in m_Walls)
    {
        if (collider->HitTest(ball, 1))
        {
            float offset = 0;
            auto n = Helper::GetNormal(ball, collider, offset);

            ball->SetPosition(ball->GetPosition() + (n * offset));

            auto r = ball->GetVelocity() - ((1 + cor) * Dot(ball->GetVelocity(), n) * n);

            ball->SetVelocity(r);

            ball->SetPosition(ball->GetPosition() + ball->GetVelocity() * DeltaTime());
        }
    }

    CVector adjustment;

    for each (auto collider in m_Balls)
    {
        if (ball == collider) 
        { 
            break;
        }

        auto diff = collider->GetPosition() - ball->GetPosition();

        float distance = diff.Length();

        if (distance <= (ball->GetWidth() / 2) + (collider->GetWidth() / 2))
        {
            auto midPoint = (ball->GetPosition() + collider->GetPosition()) * 0.5f;

            adjustment = diff.Normalise() * (ball->GetWidth() / 2 
                - Distance(ball->GetPosition(), midPoint));
            ball->SetPosition(ball->GetPosition() - adjustment);
            diff = collider->GetPosition() - ball->GetPosition();

            if (Dot(ball->GetVelocity() - collider->GetVelocity(), diff) > 0)
            {
                auto n = diff.Normalise();
                auto u = Dot(cor * ball->GetVelocity() - collider->GetVelocity(), n) * n;
                ball->Accelerate(-u);
                collider->Accelerate(u);
            }
        }
    }

    if (ball->GetSpeed() > MAX_SPEED)
    {
        ball->SetSpeed(MAX_SPEED);
    }
}

Comment éviter la gigue parmi les objets physiques quasi stationnaires?


Réponses:


1

Eh bien, il s'avère que l'un de mes contrôles booléens était à l'origine de ce problème.

Ce code ici:

if (ball == collider) 
{ 
    break;
}

Brisait tout. J'avais l'impression qu'il ignorerait simplement les collisions avec lui-même, mais pour une raison quelconque, cela empêcherait les collisions de se produire dans le bon ordre. Je ne sais pas exactement pourquoi, je pense que c'est un bug dans le moteur de jeu que j'utilise. Quoi qu'il en soit, voici le code de travail, qui implémente un état de sommeil pour toutes les boules - lorsque le mouvement après 30 images est limité à une certaine zone de délimitation, l'objet est mis dans un état de sommeil, pendant lequel aucune force ne lui est appliquée (la gravité dans ce exemple). Il est réveillé après avoir été déplacé à l'extérieur de cette zone de délimitation par quelque chose - généralement un ajustement ou une collision avec une autre balle.

    // This disgusting hack sorts the balls by height
    // In a more complete physics implementation I guess I could change the sorting based on the direction of gravitational force
    // This hack is necessary to prevent balls being pulled downwards into other balls by gravity... By calculating
    // From the bottom of the pile of objects, we avoid issues that occur when adjustments push the object towards gravity.
    m_Balls.sort([](const CSprite* a, const CSprite* b) { return a->m_pos.m_y < b->m_pos.m_y; });

    static float cor = 0.5f;

    for each (auto ball in m_Balls)
    {
        ball->Update(t);

        if (jitterBoundX[ball].size() < 30)
        {
            jitterBoundX[ball].push_back(ball->GetX());
            jitterBoundY[ball].push_back(ball->GetY());
        }
        else
        {
            jitterBoundX[ball].pop_front();
            jitterBoundY[ball].pop_front();
            jitterBoundX[ball].push_back(ball->GetX());
            jitterBoundY[ball].push_back(ball->GetY());

            float minx = jitterBoundX[ball].front();
            float maxx = minx;

            for each (auto f in jitterBoundX[ball])
            {
                if (f < minx) { minx = f; }
                if (f > maxx) { maxx = f; }
            }

            float miny = jitterBoundY[ball].front();
            float maxy = miny;

            for each (auto f in jitterBoundY[ball])
            {
                if (f < miny) { miny = f; }
                if (f > maxy) { maxy = f; }
            }

            auto xdif = maxx - minx;
            auto ydif = maxy - miny;

            if (ball->GetState() == 0 && xdif < 3 && ydif < 3)
            {
                ball->SetState(1);
            }
            else if (ball->GetState() == 1 && (xdif > 3 || ydif > 3))
            {
                ball->SetState(0);
            }
        }

        if (ball->GetState() == 0) 
        {
            ball->Accelerate(m_Gravity);
        }
        else
        {
            ball->SetVelocity(CVector(0, 0));
        }

        if (IsLButtonDown())
        {
            ball->Accelerate(0.3f * ((CVector)GetMouseCoords() - ball->GetPosition()));
        }

        for each (auto collider in m_Walls)
        {
            if (collider->HitTest(ball, 1))
            {
                float offset = 0;
                auto n = Helper::GetNormal(ball, collider, offset);

                ball->SetPosition(ball->GetPosition() + (n * offset));

                auto r = ball->GetVelocity() - ((1 + cor) * Dot(ball->GetVelocity(), n) * n);

                ball->SetVelocity(r);
            }
        }

        CVector adjustment;

        for each (auto collider in m_Balls)
        {
            // This breaks everything.
            //if (ball == collider) 
            //{ 
            //  break;
            //}

            if (ball->HitTest(collider, 0))
            {
                auto diff = collider->GetPosition() - ball->GetPosition();

                float distance = diff.Length();

                if (ball->HitTest(collider, 0))
                {
                    if (distance < (ball->GetWidth() / 2) + (collider->GetWidth() / 2))
                    {
                        auto midPoint = (ball->GetPosition() + collider->GetPosition()) * 0.5f;

                        auto discrepancy = (collider->GetWidth() / 2 - Distance(collider->GetPosition(), midPoint));
                        adjustment = diff.Normalise() * discrepancy;
                        collider->SetPosition(collider->GetPosition() + adjustment);
                        diff = collider->GetPosition() - ball->GetPosition();

                        //This actually seems to contribute to the wandering issue, it seems worth calculating the opposite collision
                        //As there may be adjustments made to the position during the previous iteration...
                        //if (gridSquares[GetGridIndex(midPoint, SPHERE_RAD)] == true)
                        //{
                        //  break;
                        //}
                        //gridSquares[GetGridIndex(midPoint, SPHERE_RAD)] = true;

                        if (Dot(ball->GetVelocity() - collider->GetVelocity(), diff) > 0)
                        {
                            auto n = diff.Normalise();
                            auto u = Dot(cor * ball->GetVelocity() - collider->GetVelocity(), n) * n;
                            ball->Accelerate(-u);
                            collider->Accelerate(u);
                        }
                    }
                }
            }
        }

        if (ball->GetSpeed() > MAX_SPEED)
        {
            ball->SetSpeed(MAX_SPEED);
        }
    }

Probablement pourquoi la rupture cassait les choses: vous parcourez les collisionneurs et vous voulez éviter les collisions avec vous-même. Cependant, lorsque vous sautez une balle, vous voulez finir de passer par les autres collisionneurs. Si vous utilisez break, il termine la boucle et n'a pas la possibilité de vérifier le reste des collisionneurs. Votre code modifié évite cela essentiellement en vérifiant si ball! = Collisionneur puis en faisant tout votre travail.
Richard Hansen

Salut Richard - J'ai réalisé cela en étant assis au lit hier soir ... Un bon exemple de ce type de code que j'écris sans café dans les veines ...
あ ら ま あ

@Gnemlock désolé, c'est fait.
あ ら ま あ
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.