SwitchPredictionSmoothingJob makes entity rotation wrong

var currentRotation = transforms[i].Rotation;
smoothing.InitialRotation = math.mul(transforms[i].Rotation, math.inverse(smoothing.InitialRotation));
currentRotation = math.mul(currentRotation, math.inverse(math.slerp(smoothing.InitialRotation, quaternion.identity, smoothing.CurrentFactor)));

Even though I modified the code to set smoothing.CurrentFactor = 0, to make

currentRotation = math.mul(currentRotation, math.inverse(smoothing.InitialRotation));

I noticed that the final result currentRotation != transforms*.Rotation*. This leads to a significant change in rotation when the entity switches between prediction and interpolation.
Did I miss something?

That code is a bit misleading, it reuses variables and change their meaning.
If you want a more natural way of understanding this, try to follow what happens with the position.
smoothing.InitialPosition and Rotation become a local offset in the if (smoothing.CurrentFactor == 0). From then, it’s no longer a ghost’s position/rotation, but a delta that’s slowly lerping back to 0. In other words the LocalTransform is already at the target position, what that code does is offset the world position and lerp it back to offset=0 to make it match the target position.
Now a thing to notice here is how we apply that resulting offset. It’s based on local transform, not world.

I’m assuming you’re investigating this since you have weird glitches when switching? Is your ghost parented to another entity which has a different pos/rot by any chance?
Cause if your ghost is parented to another entity, switching will apply the wrong offset, which seems like a bug on our side. I was able to repro by just parenting a rotating cube to an empty entity with some rotation and position. When switching, the start pos/rot will be skewed and then slowly interpolate to the correct position/rotation.

In the NetCodeSample, it is observed that when the ball undergoes a switch, there is a noticeable change in its rotation. To observe this change more closely, I applied a texture to it.

In my example, however, the switch is performed by a parent object. Then, I can clearly see that during the switch, the child object is placed in an incorrect position due to the incorrect rotation of the parent object. It remains in this incorrect position until the switch is completed, after which it behaves normally.

Looking at the current interpolation implementation, there is a bug in the calculation of the quaternion when we smooth the error toward 0.

I may re-check my math but at first glance we need to invert the rotation update multiplication order.

if(currentFactor == 0)
   initialRotation = mul(currentRotation, inverse(initialRotation)); //delta rotation from from the initial to the current

//Quaternion are associative but not commutative. So you need to multiply the delta after the currentRotation in this case.
currentRotation = math,mul(inverse(math.slerp(initialRotation, identity, factor)), currentRotation);

//That for t = 0 lead to (because of associativity rules)
currentRotation = mul(mul(currentRotation, inverse(initialRotation)), currentRotation);
                          = mul(mul(initialRotation, inv(currentRotation)), currentRotation);
                          = mul(initialRotation, mul(inv(currentRotation), currentRotation));
                          = initialRotation;
1 Like

We can also invert instead the delta calculation order

initialRotation = mul(inverse(initialRotation),currentRotation);

and keep the update the same, and that should still lead to the correct interpolation.

you can immediately try that and check if that lead to the correct result

It work!!

Yes!
Will bug this.
Also for posterity for people reading this, I forgot in my answer above we don’t support ghosts parented to non-ghosts. Ghosts need to be root.

1 Like