Breaking change in target camera rotations when using right-handed system

Starting with version 8.10.1, any code that modifies the rotation or rotationQuaternion property of a target camera when the scene is in right-handed system (i.e., scene.useRightHandedSystem = true) will behave exactly 180° flipped on the Y axis than before. This is due to a bug with the target camera code that fails to take the handedness of the scene into account. See RH camera fixes by bghgary · Pull Request #16691 · BabylonJS/Babylon.js for details.

For additional back story, we ran into this problem because we were trying to fix a camera issue with the glTF exporter. After a lot of investigation from both @alexchuber and myself, we discovered this problem in the code.

Please shout if this causes trouble for your scenarios.

9 Likes

thanks for the note @bghgary

2 Likes

Hello!

I may have found a slight vulnerability in this change; sadly, I cannot reproduce it in any playground, but in the previous version, this.rotation.z was forced to 0, which was a good thing because in the new version, rotationQuaternion.toEulerAnglesToRef(this.rotation); might not return a perfect 0 (which is what is happening in my project), triggering an update to the upVector in the getViewMatrix function as expected.

This means that every time I update the target — i.e. every time I move — the upVector gets updated.

The easiest fix is to add these lines at the end of setTarget:

this.rotation.z = 0;
if (this.rotationQuaternion) {
  Quaternion.FromEulerVectorToRef(this.rotation, this.rotationQuaternion);
}

For now, the workaround is to add these lines to my project:

const originalSetTarget = TargetCamera.prototype.setTarget;
// FIXME: Temporary override to fix roll being edited during setTarget...
TargetCamera.prototype.setTarget = function (target: Vector3): void {
  originalSetTarget.call(this, target);
  this.rotation.z = 0;
  if (this.rotationQuaternion) Quaternion.FromEulerVectorToRef(this.rotation, this.rotationQuaternion);
};

:innocent:

Edit: You could also replace the above line to always use a TmpQuaternion.
From line 275, it would look like something like this:

Quaternion.FromRotationMatrixToRef(TmpMatrix, TmpQuaternion);
TmpQuaternion.toEulerAnglesToRef(this.rotation);

this.rotation.z = 0;
if (this.rotationQuaternion) {
  Quaternion.FromEulerVectorToRef(this.rotation, this.rotationQuaternion);
}

or even crazier:

Quaternion.FromRotationMatrixToRef(TmpMatrix, TmpQuaternion).toEulerAnglesToRef(this.rotation);

this.rotation.z = 0; // ensure the roll remains unchanged
if (this.rotationQuaternion) {
  Quaternion.FromEulerVectorToRef(this.rotation, this.rotationQuaternion);
}

On a side note, I absolutely love all these high-level functions! <3

3 Likes

Yup, this is already fixed. See Camera is getting a Z rotation? - Questions - Babylon.js.

1 Like

Wow nice!
But I see in the fix that you are not editing this.rotationQuaternion after setting this.rotation.z = 0.

From what I read in _getViewMatrix, you might add:

if (this._rotationQuaternion) this._rotationQuaternion.z = 0;

To avoid having the update of the upVector when using the rotationQuaternion.

This solution is better than the one I proposed earlier, which again uses Quaternion.FromEulerVectorToRef, and both guarantee that rotation and rotationQuaternion remain “parallel”.

Hmm, I don’t think we can do this since this will change the rotation in many cases. TBH, I have no idea what the code is doing in _getViewMatrix checking just z for the quaternion. I’m not sure what that even means.

Also, my fix is just a patch to preserve the original behavior. There is most likely another issue with how the up vector is being computed. Before I set rotation.z to zero, it is already very close to zero. This should not cause the camera rotation to go crazy, but because of that check in _getViewMatrix, z has to be exactly 0 for the code to behave the same as before.

Yeah you’re right… the code in getViewMatrix might be incorrect, as z in a quaternion is not z in Euler (I think…)

In the previous version, the quaternion was set after the rotation.z being set 0.
So, this:
if (this.rotationQuaternion) {
Quaternion.FromEulerVectorToRef(this.rotation, this.rotationQuaternion);
}

Truly ensure the same behavior as before… not that it would make much difference…

I’ll update my project to 8.12.1 asap, I guess I can remove my workaround now!!!

Thank you :slight_smile:

1 Like

Yeah, I saw that from your original message. It shouldn’t make a difference because floating point is going to be imprecise anyways. Having a z value close to zero vs zero should result in a similar quaternion. My change computes the quaternion first, so I can’t just set the Euler z component to zero first like before.

1 Like