Wie verhindere ich Jitter zwischen nahezu stationären Physikobjekten?

8

Ich habe eine benutzerdefinierte Physik-Engine implementiert und bin ziemlich nahe daran, dass sie so funktioniert, wie ich es gerne hätte. Es gibt eine Gravitationskraft, Kollisionen und Kollisionsreaktionen. Leider scheint es bei nahezu stationären Objekten einen gewissen Jitter zu geben, was höchstwahrscheinlich auf unveränderlich niedrige physikalische Zecken zurückzuführen ist.

Kreise gestapelt in einem Box Jitter.

Ich habe online gesucht und einige der gefundenen Implementierungen ausprobiert, einschließlich einiger meiner eigenen Versuche. Hier sind die Lösungen, die ich ausprobiert habe:

  • Dämpfungsbewegung, wenn Geschwindigkeit / Impuls / potentielle Energie unter einem Schwellenwert liegen.
  • Anwendung der Schwerkraft nur, wenn Geschwindigkeit / Impuls / potentielle Energie über dem Schwellenwert liegen.
  • Implementieren einer Schlaffunktion. Das überprüft die Position des Objekts für die letzten 60 Frames und schläft, wenn es sich nicht außerhalb eines Schwellenwert-Begrenzungsrahmens bewegt hat.
  • Durchlaufen der Objekte von oben nach unten, wenn Kollisionstests und Auflösungen angewendet werden.

Hier ist mein 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);
    }
}

Wie verhindere ich Jitter zwischen nahezu stationären Physikobjekten?

あ ら ま あ
quelle

Antworten:

1

Nun, es stellt sich heraus, dass eine meiner booleschen Prüfungen dieses Problem verursacht hat.

Dieser Code hier:

if (ball == collider) 
{ 
    break;
}

Brach alles. Ich hatte den Eindruck, dass es Kollisionen mit sich selbst einfach ignorieren würde, aber aus irgendeinem Grund wurde verhindert, dass Kollisionen in der richtigen Reihenfolge auftreten. Ich weiß nicht genau warum, ich denke, es ist ein Fehler in der Spiel-Engine, die ich benutze. Wie auch immer, hier ist der Arbeitscode, der einen Schlafzustand für alle Bälle implementiert - wenn die Bewegung nach 30 Bildern auf einen bestimmten Begrenzungsbereich beschränkt ist, wird das Objekt in einen Schlafzustand versetzt, in dem keine Kräfte auf es ausgeübt werden (Schwerkraft in diesem Fall) Beispiel). Es wird geweckt, nachdem es durch etwas außerhalb dieses Begrenzungsbereichs verschoben wurde - normalerweise durch eine Anpassung oder Kollision durch einen anderen Ball.

    // 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);
        }
    }
あ ら ま あ
quelle
Wahrscheinlich, warum die Pause Dinge kaputt gemacht hat: Sie durchlaufen Kollider und möchten die Kollision mit sich selbst überspringen. Wenn Sie jedoch den Ball überspringen, möchten Sie den Rest der Collider beenden. Wenn Sie break verwenden, wird die Schleife beendet und es besteht keine Möglichkeit, den Rest der Collider zu überprüfen. Ihr geänderter Code vermeidet dies im Wesentlichen, indem Sie prüfen, ob ball! = Collider ist, und dann alle Ihre Aufgaben erledigen.
Richard Hansen
Hallo Richard - Ich habe das letzte Nacht im Bett bemerkt ... Gutes Beispiel dafür, welche Art von Code ich ohne Kaffee in meinen Adern schreibe ...
ら ま あ
@ Gnemlock sorry, fertig.
ら ま あ