Come posso evitare il jitter tra oggetti di fisica quasi stazionaria?


8

Ho implementato un motore fisico personalizzato e sono abbastanza vicino a farlo funzionare come vorrei. C'è una forza gravitazionale, collisioni e risposta alle collisioni. Sfortunatamente, sembra esserci un po 'di jitter tra gli oggetti quasi stazionari, molto probabilmente a causa di zecche di fisica bassa immutabili.

Cerchi accatastati all'interno di un jitter box.

Ho cercato online e provato alcune delle implementazioni che ho trovato, inclusi alcuni dei miei tentativi. Ecco le soluzioni che ho provato:

  • Smorzamento del movimento quando velocità / quantità di moto / energia potenziale è inferiore a una soglia.
  • Applicare la gravità solo quando velocità / quantità di moto / energia potenziale è superiore alla soglia.
  • Implementazione di una funzione sleep. che controlla la posizione dell'oggetto per gli ultimi 60 fotogrammi e dorme se non si è spostato al di fuori di un limite di soglia.
  • Scorrendo gli oggetti dall'alto verso il basso durante l'applicazione del test di collisione e della risoluzione.

Ecco il mio codice:

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

Come posso evitare il jitter tra oggetti di fisica quasi stazionaria?


Risposte:


1

Bene, si scopre che uno dei miei controlli booleani stava causando questo problema.

Questo codice qui:

if (ball == collider) 
{ 
    break;
}

Stava rompendo tutto. Avevo l'impressione che avrebbe semplicemente ignorato le collisioni con se stesso, ma per qualsiasi motivo, era impedito che le collisioni avvenissero nell'ordine corretto. Non so esattamente perché, penso che sia un bug nel motore di gioco che sto usando. Comunque, ecco il codice di lavoro, che implementa uno stato di sonno per tutte le sfere - quando il movimento dopo 30 fotogrammi è limitato a una determinata area di delimitazione, l'oggetto viene messo in uno stato di sonno, durante il quale non vengono applicate forze ad esso (gravità in questo esempio). Viene svegliato dopo essere stato spostato al di fuori di quest'area di delimitazione da qualcosa - in genere una regolazione o una collisione di un'altra palla.

    // 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);
        }
    }

Probabilmente perché l'interruzione stava rompendo le cose: stai facendo un giro tra i collider e vuoi evitare di scontrarti con te stesso. Tuttavia, quando salti la palla, vuoi finire di passare attraverso il resto dei collider. Se usi break, termina il loop e non ha la possibilità di controllare il resto dei collider. Il tuo codice modificato evita questo essenzialmente controllando se ball! = Collider e quindi facendo tutto il tuo materiale.
Richard Hansen,

Ciao Richard - L'ho realizzato mentre ero seduto a letto ieri sera ... Un buon esempio di che tipo di codice scrivo senza caffè nelle mie vene ...
あ ら ま あ

@Gnemlock scusa, fatto.
あ ら ま あ
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.