0
\$\begingroup\$

I'm making multiplayer racing game and I have problem with interpolating position..

Here's the sequence.

  1. Server simulate the world and send vehicle transform packet through UDP socket every 33ms to all clients.
  2. Client receive packets and update next transform.
  3. Client interpolate transform between previous state and next state every frame.
void InterpolateWorldTransform(float elapsed, float updateRate)
{
    // updateRate is about 0.033f.
    // if updateRate is zero, don't interpolate..
    if (updateRate <= 0.0f) return;
    

    // I used mutex to prevent data-race.
    mProgressMut.lock();
    // if progress is more than 1.0f than make it 1.0f.
    float progress = std::min(1.0f, mProgress / updateRate);
    mProgress += elapsed;
    mProgressMut.unlock();

    const XMFLOAT3& prevOrigin = mPrevOrigin.GetXMFloat3();
    const XMFLOAT4& prevQuat = mPrevQuat.GetXMFloat4();

    const XMFLOAT3& correctOrigin = mCorrectionOrigin.GetXMFloat3();
    const XMFLOAT4& correctQuat = mCorrectionQuat.GetXMFloat4();

    // use Lerp for position and Slerp for rotation, both works well.
    mPosition = Vector3::Lerp(prevOrigin, correctOrigin, progress);
    mQuaternion = Vector4::Slerp(prevQuat, correctQuat, progress);
}

// This called in different thread.
void SetCorrectionTransform(SC::packet_player_transform* pck, float latency)
{
    mProgressMut.lock();
    mProgress = 0.0f;
    mProgressMut.unlock();

    mPrevOrigin = mCorrectionOrigin;
    mPrevQuat = mCorrectionQuat;

    mCorrectionOrigin.SetValue(pck->position);
    mCorrectionQuat.SetValue(pck->quaternion);
}

I used mutex for mProgress because this variable is accessed in different threads.

The result is not good. It jitters a lot even in local host.

I tested in remote too, and jittering even gets worse.

I also tried snapshot interpolation but my lack of knowledge and low experience, it didn't go well..

EDIT: Here is my snapshot interpolation code.(It works but still jitter)

#include "stdafx.h"
#include <queue>

class Interpolator
{
public:
    struct Entry
    {
        bool empty;
        Clock::time_point timeStamp;

        XMFLOAT3 position;
        XMFLOAT4 rotation;

        Entry()
            : empty{ true }
        {
        }

        Entry(const XMFLOAT3& pos, const XMFLOAT4& rot)
            : empty{ false } 
        {
            timeStamp = Clock::now();
            position = pos;
            rotation = rot;
        }
    };

public:
    Interpolator()
        : mProgress{ 0.0f } 
    {
    }

    void Enqueue(const XMFLOAT3& pos, const XMFLOAT4& rot)
    {
        mEntryQueueMut.lock();
        mEntryQueue.push(Entry(pos, rot));
        mEntryQueueMut.unlock();
    }

    void Interpolate(float dt, XMFLOAT3& targetPos, XMFLOAT4& targetRot)
    {
        mEntryQueueMut.lock();
        if (mEntryQueue.empty())
        {
            mEntryQueueMut.unlock();
            return;
        }

        Entry next = mEntryQueue.front();
        if (mPrevEntry.empty)
        {
            mEntryQueue.pop();
            mEntryQueueMut.unlock();

            mPrevEntry = next;
            targetPos = next.position;
            targetRot = next.rotation;
            return;
        }

        mEntryQueueMut.unlock();

        mProgress += dt;
        float timeBetween = GetDurationSec(next.timeStamp, mPrevEntry.timeStamp);
        float progress = std::min(1.0f, mProgress / timeBetween);

        targetPos = Vector3::Lerp(mPrevEntry.position, next.position, progress);
        targetRot = Vector4::Slerp(mPrevEntry.rotation, next.rotation, progress);

        if (progress >= 1.0f)
        {
            mEntryQueueMut.lock();
            mEntryQueue.pop();
            mEntryQueueMut.unlock();

            mPrevEntry = next;
            mProgress -= timeBetween;
        }
    }

private:
    static float GetDurationSec(Clock::time_point& a, Clock::time_point& b)
    {
        auto msec = std::chrono::duration_cast<std::chrono::milliseconds>
            (a - b).count();
        return (float)msec / 1000.0f;
    }

private:
    std::queue<Entry> mEntryQueue;
    std::mutex mEntryQueueMut;

    Entry mPrevEntry;
    float mProgress;
};
```
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You don't need to interpolate the movement during every frame. What you need to do is correct the movement of the client when it goes too far away from the position on the server.

If you do it this way, your vehicle won't be jittering as often. Instead, it will rubberband when it hits the movement threshold.

You can go really deep and find a much more complex way of solving your jittering issues, but what I just described would be the easiest way to go about it.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .