🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Interpolation & Extrapolation Help

Started by
2 comments, last by hplus0603 8 years, 9 months ago

Hello,

I'm currently in the process of working on getting my connected players to appear smoothly lerping from the other client's perspective.

This is how my implementation works currently:

  • Every .125 seconds if a particular player has moved in anyway I send updated Transform information to the server, which relays that information back to all other connected clients.
  • Once that Update message is received from the server about the particular client's updated Transform information, I save it's current position, whatever it is as the previous position in my NetObjectTransformInterpolator
    • These positions are stored in an std::queue<TransformInfo>, where TransformInfo contains the TimeStamp & Transform
    • My TimeStamp that I append on is : double timeStamp = NetObjectTransformInterpolator::GetElapsedTime() + .175 + NetObjectTransformInterpolator::GetFrameRatio()
  • My TimeStamp that I'm utilizing is just an elapsedTime hard-coded within my NetObjectTransformInterpolator that updates every frame with the current deltaTime in the LogicUpdate
  • From here the NetObjectTransformInterpolator will choose the TransformInfo at the top of the std::queue and move from the particular Object's previous position to the moveTarget position, Lerping based off the time ratio between

My current implementation appears to work mostly, but there is something off about it. It's smooth mostly, but there appears to be a noticable jitter or something that I'm having trouble resolving once the player begins moving & ends moving. As well as when the player is moving at a bit of a faster speed.

Here is my code implementation:


void NetObjectTransformInterpolator::OnLogicUpdate(UpdateEvent* updateEvent)
{
  NetObjectTransformInterpolator& interp = NetObjectTransformInterpolator::GetInstance();

  frameRatio = 1.0 - (FrameRateController::GetFPS() / 60.0); // 60.0 is our target fps
  for (auto& interpObj : interp.objectTransformInfo)
  {
    ObjectMoveInfo* moveInfo = interpObj.second.get();

    if (moveInfo->transInfo.size())
    {
      if (moveInfo->needsMoveTarget)
      {
        moveInfo->moveTarget = &moveInfo->transInfo.front();
        moveInfo->needsMoveTarget = false;
      }

      if (elapsedTime < moveInfo->moveTarget->timeStamp)
      {
        // .175 compensates for the networkUpdateTime & the frameRatio compensates for client side lag
        double t = 1.0 - ((moveInfo->moveTarget->timeStamp - elapsedTime) / (.175 + frameRatio));

        InterpToPosition(t, moveInfo);
        InterpToRotation(t, moveInfo);
        InterpToScale(t, moveInfo);
      }
      else // I don't know what to do here, this is supposed to extrapolate
      {
        InterpToPosition(1.0, moveInfo);
        InterpToRotation(1.0, moveInfo);
        InterpToScale(1.0, moveInfo);
      }

      DetermineIfReachedCurrentTarget(moveInfo);
    }
  }

  elapsedTime += updateEvent->dt;
}

One of the main issues is I'm unsure how to extrapolate my positions if my elapsedTime has surpassed the moveInfo->moveTarget->timeStamp. Currently I just interpolate to set it straight to it's Transform, hence the 1.0 value. DetermineIfReachedCurrentTarget function is also responsible for popping the std::queue & setting the next moveInfo->moveTarget. I am also unsure about my frameRatio calculation necessity. I noticed I needed to do this once I started developing on my laptop which runs this application with a lower frame rate then my desktop.

I have been looking at some other implementations that utilize the NetworkTime from the server. I'd definitely prefer to do that, but I'm not sure if I have the correct NetworkTime calculation. Currently, every 1 second, when I receive my PING reply from my server, I also have attached to that the current SystemTime in milliseconds that I piece off and store on each client's side.

Thank you for any insight helping me to figure out this problem. My main goal here is just to make it so the client's are lerping smoothly, instead of smoothly with a noticable jitter.

Advertisement
The first obvious question is: Are you properly receiving all packets, or are you having TCP packet-merge issues?

The second obvious question is: If you're using UDP, do you re-send data every tick, or would a packet drop right when I stop just end up with me in the wrong position until I start moving again (and thus send another packet)?

Instead of "InterpToPosition()" updating the state, you probably want a function called "PositionAtTime()" and "RotationAtTime()."
Those functions will then look at the given time, and the time stamped data it has, and output an appropriately interpolated value.
That ends up being easier to prove correct than something which attempts to incrementally update the values.
enum Bool { True, False, FileNotFound };

I am using TCP packets currently. I don't think I'm having merge issues, just not calculating something correctly it seems. Originally I was just setting the Transform info to be just the UpdateTransform packet received without the interpolation which produced the expected results without the interpolation.

This is what my InterpToPosition function is currently doing:


void NetObjectTransformInterpolator::InterpToPosition(double t, ObjectMoveInfo* objMoveInfo)
{
  objMoveInfo->gameObj->m_Pos = DirectX::XMVectorLerp(objMoveInfo->prevTrans.transform.pos, objMoveInfo->moveTarget->transform.pos, t);
}

It definitely is producing a smoother movement compared to when I was setting just the position information received from the UpdateTransform packet, but it seems whenever my code gets to:


else
{
  InterpToPosition(1.0, moveInfo);
  InterpToRotation(1.0, moveInfo);
  InterpToScale(1.0, moveInfo);
}

Is when the subtle jittery behavior occurs.

This is what I'm doing in my DetermineIfReachedCurrentTarget function:


void NetObjectTransformInterpolator::DetermineIfReachedCurrentTarget(ObjectMoveInfo* objMoveInfo)
{
  TransformInfo& transInfo = objMoveInfo->transInfo.front();
  Vec3 targetDir = (objMoveInfo->gameObj->m_Pos - transInfo.transform.pos);
  if (targetDir.GetSquareLength() < .2f)
  {
    objMoveInfo->transInfo.pop();
    objMoveInfo->needsMoveTarget = true;
  }
}
One thing you need to do is that, each time you receive a new position, you should set the 'start interpolation position' to *the calculated interpolated position* you last saw, and set the 'target future position' to the received position. That way, you won't "jump" to the previously received update position when you receive a new packet.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement