Làm thế nào để tôi ngăn ngừa jitter giữa các đối tượng vật lý gần như đứng yên?


8

Tôi đã thực hiện một công cụ vật lý tùy chỉnh và tôi khá gần để nó hoạt động như tôi muốn. Có một lực hấp dẫn, va chạm và phản ứng va chạm. Thật không may, dường như có một số jitter trong số các đối tượng gần đứng yên, rất có thể là do các dấu hiệu vật lý thấp không thể thay đổi.

Các vòng tròn xếp chồng lên nhau trong một hộp jitter.

Tôi đã xem trực tuyến và đã thử một số triển khai tôi đã tìm thấy, bao gồm một số nỗ lực của riêng tôi. Dưới đây là các giải pháp tôi đã thử:

  • Chuyển động giảm xóc khi tốc độ / động lượng / năng lượng tiềm năng dưới một ngưỡng.
  • Chỉ áp dụng trọng lực khi tốc độ / động lượng / năng lượng tiềm năng vượt quá ngưỡng.
  • Thực hiện chức năng ngủ. kiểm tra vị trí của đối tượng trong 60 khung hình cuối cùng và ngủ nếu nó không di chuyển ra ngoài hộp giới hạn ngưỡng.
  • Lặp lại qua các đối tượng từ trên xuống dưới khi áp dụng thử nghiệm và giải quyết va chạm.

Đây là mã của tôi:

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

Làm thế nào để tôi ngăn ngừa jitter giữa các đối tượng vật lý gần như đứng yên?


Câu trả lời:


1

Chà, hóa ra là một trong những kiểm tra boolean của tôi đã gây ra vấn đề này.

Mã này ở đây:

if (ball == collider) 
{ 
    break;
}

Đã phá vỡ mọi thứ. Tôi có ấn tượng rằng nó chỉ đơn giản là bỏ qua va chạm với chính nó nhưng vì bất kỳ lý do gì, nó đã ngăn chặn các va chạm xảy ra theo đúng thứ tự. Không chắc chắn chính xác tại sao, tôi nghĩ đó là một lỗi trong công cụ trò chơi tôi đang sử dụng. Dù sao, đây là mã làm việc, thực hiện trạng thái ngủ cho tất cả các quả bóng - khi chuyển động sau 30 khung hình bị giới hạn ở vùng giới hạn nhất định, vật thể được đặt ở trạng thái ngủ, trong đó không có lực nào tác dụng lên nó (trọng lực trong này ví dụ). Nó được đánh thức sau khi nó bị dịch chuyển ra ngoài khu vực giới hạn này bởi một thứ gì đó - thường là sự điều chỉnh hoặc va chạm bởi một quả bóng khác.

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

Có lẽ tại sao sự phá vỡ đã phá vỡ mọi thứ: Bạn đang vòng qua các máy va chạm và muốn bỏ qua va chạm với chính mình. Tuy nhiên, khi bạn bỏ qua bóng, bạn muốn hoàn thành việc đi qua phần còn lại của máy va chạm. Nếu bạn sử dụng break, nó sẽ chấm dứt vòng lặp và không có cơ hội kiểm tra phần còn lại của các colliders. Mã sửa đổi của bạn tránh điều này về cơ bản bằng cách kiểm tra xem bóng! = Máy va chạm và sau đó thực hiện tất cả nội dung của bạn.
Richard Hansen

Xin chào Richard - Nhận ra điều này khi ngồi trên giường tối qua ... Ví dụ hay về loại mã tôi viết mà không có cà phê trong huyết quản ...
あ ら ま

@Gnemlock xin lỗi, xong rồi.
あ ら ま
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.