Como evito o tremor entre objetos de física quase estacionários?


8

Estou implementando um mecanismo de física personalizado e estou bem perto de fazê-lo funcionar como gostaria. Existe uma força gravitacional, colisões e resposta a colisões. Infelizmente, parece haver algum tremor entre os objetos quase estacionários, provavelmente devido a carrapatos imutáveis ​​de baixa física.

Círculos empilhados dentro de um tremor de caixa.

Procurei on-line e tentei algumas das implementações que encontrei, incluindo algumas de minhas próprias tentativas. Aqui estão as soluções que tentei:

  • Amortecer o movimento quando a velocidade / momento / energia potencial estiver abaixo de um limite.
  • Aplicando a gravidade apenas quando a velocidade / momento / energia potencial está acima do limite.
  • Implementando uma função de suspensão. que verifica a posição do objeto nos últimos 60 quadros e dorme se não tiver saído de uma caixa delimitadora de limite.
  • Iterando os objetos de cima para baixo ao aplicar teste e resolução de colisão.

Aqui está o meu código:

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

Como evito o tremor entre objetos de física quase estacionários?


Respostas:


1

Bem, acontece que uma das minhas verificações booleanas estava causando esse problema.

Este código aqui:

if (ball == collider) 
{ 
    break;
}

Estava quebrando tudo. Fiquei com a impressão de que simplesmente ignoraria colisões consigo mesmas, mas por qualquer motivo, impediria que ocorressem colisões na ordem correta. Não sei exatamente por que, acho que é um bug no mecanismo de jogo que estou usando. De qualquer forma, aqui está o código de trabalho, que implementa um estado de suspensão para todas as bolas - quando o movimento após 30 quadros é confinado a determinada área delimitadora, o objeto é colocado em um estado de suspensão, durante o qual nenhuma força é aplicada a ele (gravidade nesta instância). É acordado depois de ser deslocado para fora desta área delimitadora por algo - normalmente um ajuste ou colisão por outra bola.

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

Provavelmente, por que o rompimento estava quebrando as coisas? No entanto, quando você pula a bola, quer terminar de passar pelo resto dos colididores. Se você usar o break, ele encerrará o loop e não terá a chance de verificar o restante dos colliders. Seu código modificado evita isso essencialmente, verificando se ball! = Collider e fazendo todas as suas coisas.
Richard Hansen

Oi Richard - Percebi isso enquanto estava sentado na cama ontem à noite ... Bom exemplo de que tipo de código eu escrevo sem café nas veias ...
あ ら ま あ

@Gnemlock desculpe, pronto.
8182 #
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.