GuessForecastingStartPosition encapsulated

This commit is contained in:
mischa 2024-04-26 19:43:55 +08:00
parent 5740cbc09f
commit 138a13fc86

View File

@ -302,6 +302,97 @@ protected override void ApplySnapshot(NTSnapshot interpolated)
}
}
// guess FORECASTING.startPosition
bool GuessForecastingStartPosition(out Vector3 position, out Quaternion rotation)
{
// first principles:
//
// BLENDING needs to interpolate between PREDICTING & FOLLOWING.
// the only way to do this without jitter and jumps is by
// interpolating from PREDICTION.endPosition to FOLLOWING.startPosition.
// anything else, no matter how smooth, will always cause jumps.
//
// PREDICTION.endPosition is easy: just remember before transition.
//
// FOLLOWING.startPosition is a bit harder.
// => we can sample snapshots @ blendingEndTime (if any).
// => if we haven't received it yet, we need to extrpolate based
// on current velocity to guess where we'll be at blendingEndTime.
position = Vector3.zero;
rotation = Quaternion.identity;
// first, see if there's a snapshot at blendingEndTime already.
// this would be super precise.
// returns false if there isn't any yet.
if (SnapshotInterpolation.TrySample(
clientSnapshots,
clientTimeline + blendingTime,
out int from,
out int to,
out double t))
{
// interpolate between from & to
NTSnapshot fromSnapshot = clientSnapshots[from];
NTSnapshot toSnapshot = clientSnapshots[to];
NTSnapshot interpolated = NTSnapshot.Interpolate(fromSnapshot, toSnapshot, t);
position = interpolated.position;
rotation = interpolated.rotation;
// debug colors
if (debugColors)
{
rend.material.color = blendingExactColor;
}
return true;
}
// if not, then we need to guess.
// calculate the velocity of the latest known state.
// because that's the most accurate velocity we have.
// then extrapolate forward to blendingEndTime.
// TODO UNIT TEST THIS
else if (clientSnapshots.Count >= 2)
{
NTSnapshot latest = clientSnapshots.Values[clientSnapshots.Count - 1];
NTSnapshot previous = clientSnapshots.Values[clientSnapshots.Count - 2];
float timeDelta = (float)(latest.remoteTime - previous.remoteTime); // remote time gives us exact remote velocity
Vector3 positionDelta = latest.position - previous.position;
Quaternion rotationDelta = (latest.rotation * Quaternion.Inverse(previous.rotation)).normalized; // always need to normalize after mult
// avoid division by zero
if (timeDelta > 0)
{
// now we have the remote velocity
Vector3 velocity = positionDelta / timeDelta;
// extrapolate this from latest time to blendingEndTime
// TODO validate rotation formula?
float timeToBlendingEnd = (float)(blendingEndTime - clientTimeline);
position = latest.position + velocity * timeToBlendingEnd;
rotation = latest.rotation * Quaternion.Slerp(Quaternion.identity, rotationDelta, timeToBlendingEnd / timeDelta);
// debug colors
if (debugColors)
{
rend.material.color = blendingGuessColor;
}
return true;
}
// this shouldn't really happen. if timedelta is zero: do nothing.
else
{
return false;
}
}
// if we don't have enough snapshots: do nothing. wait for more.
else
{
return false;
}
}
// store the latest FOLLOWING.startPosition guess for visual debugging
Vector3 followingStartPositionEstimate = Vector3.zero;
@ -352,75 +443,8 @@ void UpdateClient()
// => we can sample snapshots @ blendingEndTime (if any).
// => if we haven't received it yet, we need to extrpolate based
// on current velocity to guess where we'll be at blendingEndTime.
Vector3 followStartPosition = Vector3.zero;
Quaternion followStartRotation = Quaternion.identity;
// first, see if there's a snapshot at blendingEndTime already.
// this would be super precise.
// returns false if there isn't any yet.
if (SnapshotInterpolation.TrySample(
clientSnapshots,
clientTimeline + blendingTime,
out int from,
out int to,
out double t))
{
// interpolate between from & to
NTSnapshot fromSnapshot = clientSnapshots[from];
NTSnapshot toSnapshot = clientSnapshots[to];
NTSnapshot interpolated = NTSnapshot.Interpolate(fromSnapshot, toSnapshot, t);
followStartPosition = interpolated.position;
followStartRotation = interpolated.rotation;
// debug colors
if (debugColors)
{
rend.material.color = blendingExactColor;
}
}
// if not, then we need to guess.
// calculate the velocity of the latest known state.
// because that's the most accurate velocity we have.
// then extrapolate forward to blendingEndTime.
// TODO UNIT TEST THIS
else if (clientSnapshots.Count >= 2)
{
NTSnapshot latest = clientSnapshots.Values[clientSnapshots.Count - 1];
NTSnapshot previous = clientSnapshots.Values[clientSnapshots.Count - 2];
float timeDelta = (float)(latest.remoteTime - previous.remoteTime); // remote time gives us exact remote velocity
Vector3 positionDelta = latest.position - previous.position;
Quaternion rotationDelta = (latest.rotation * Quaternion.Inverse(previous.rotation)).normalized; // always need to normalize after mult
// avoid division by zero
if (timeDelta > 0)
{
// now we have the remote velocity
Vector3 velocity = positionDelta / timeDelta;
// extrapolate this from latest time to blendingEndTime
// TODO validate rotation formula?
float timeToBlendingEnd = (float)(blendingEndTime - clientTimeline);
followStartPosition = latest.position + velocity * timeToBlendingEnd;
followStartRotation = latest.rotation * Quaternion.Slerp(Quaternion.identity, rotationDelta, timeToBlendingEnd / timeDelta);
// debug colors
if (debugColors)
{
rend.material.color = blendingGuessColor;
}
}
// this shouldn't really happen. if timedelta is zero: do nothing.
else
{
return;
}
}
// if we don't have enough snapshots: do nothing. wait for more.
else
{
if (!GuessForecastingStartPosition(out Vector3 followStartPosition, out Quaternion followStartRotation))
return;
}
// store the latest FOLLOWING.startPosition guess for visual debugging
followingStartPositionEstimate = followStartPosition;