Bagaimana cara mencegah jitter di antara benda-benda fisika yang tidak bergerak?


8

Saya telah menerapkan mesin fisika khusus, dan saya cukup dekat untuk membuatnya berfungsi seperti yang saya inginkan. Ada gaya gravitasi, tumbukan dan respons tumbukan. Sayangnya, tampaknya ada beberapa kegelisahan di antara benda-benda yang hampir diam, kemungkinan besar karena kutu fisika rendah yang tidak dapat diubah.

Lingkaran ditumpuk di dalam kotak jitter.

Saya telah melihat online, dan mencoba beberapa implementasi yang saya temukan, termasuk beberapa upaya saya sendiri. Inilah solusi yang saya coba:

  • Gerakan redaman ketika kecepatan / momentum / energi potensial di bawah ambang batas.
  • Hanya menerapkan gravitasi ketika kecepatan / momentum / energi potensial berada di atas ambang batas.
  • Menerapkan fungsi tidur. yang memeriksa posisi objek untuk 60 frame terakhir, dan tidur jika belum bergerak di luar kotak pembatas ambang batas.
  • Iterasi melalui objek dari atas ke bawah saat menerapkan pengujian dan resolusi tabrakan.

Ini kode saya:

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

Bagaimana cara mencegah jitter di antara benda-benda fisika yang tidak bergerak?


Jawaban:


1

Ya, ternyata salah satu cek boolean saya yang menyebabkan masalah ini.

Kode ini di sini:

if (ball == collider) 
{ 
    break;
}

Merusak segalanya. Saya mendapat kesan bahwa itu hanya akan mengabaikan tabrakan dengan dirinya sendiri tetapi untuk alasan apa pun, itu mencegah tabrakan terjadi dalam urutan yang benar. Tidak begitu yakin mengapa, saya pikir itu adalah bug di mesin permainan yang saya gunakan. Bagaimanapun, ini adalah kode kerja, yang menerapkan kondisi tidur untuk semua bola - ketika gerakan setelah 30 frame terbatas pada area pembatas tertentu, objek diletakkan dalam kondisi tidur, selama tidak ada gaya yang diterapkan padanya (gravitasi dalam hal ini). contoh). Itu dibangunkan setelah dipindahkan di luar area pembatas ini oleh sesuatu - biasanya penyesuaian atau tabrakan dengan bola lain.

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

Mungkin mengapa istirahat itu melanggar hal-hal: Anda melewati colliders dan ingin melewatkan bertabrakan dengan diri sendiri. Namun, ketika Anda melewatkan bola, Anda ingin menyelesaikan melalui sisa colliders. Jika Anda menggunakan break, itu mengakhiri loop, dan tidak mendapatkan kesempatan untuk memeriksa sisa colliders. Kode Anda yang dimodifikasi menghindari hal ini pada dasarnya dengan memeriksa apakah ball! = Collider dan kemudian melakukan semua hal Anda.
Richard Hansen

Hai Richard - Menyadari hal ini sambil duduk di tempat tidur tadi malam ... Contoh yang bagus dari kode seperti apa yang saya tulis tanpa kopi di nadi saya ...
あ ら ま あ

@Gnemlock maaf, sudah selesai.
あ ら ま あ
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.