Skip to content

Commit fc791e4

Browse files
authored
RH camera fixes (#16691)
This fixes an issue we have with cameras when in right-handed system where setting `rotation` or `rotationQuaterion` on a camera in right-handed system actually sets the camera 180 flipped from what is represented in the view matrix. You can see this problem using this PG: https://playground.babylonjs.com/#BDQ72M#1 On line 14, we are setting the `rotation` to `0,0,0`, but the rotation from the world matrix of the camera is `0,PI,0`. This change fixes the problem, but it is a breaking change for setting the rotation properties since it will behave 180 flipped from before. The default orientation of camera will still work as before (i.e., always point to +Z for both LH and RH) to maintain backwards compatibility as much as possible. The fix involves changing the reference point of camera. The code mistakenly uses the same reference point of `0,0,1` for both LH and RH view matrix. Babylon target camera's LH and RH view matrices are flipped by Z. Thus, the reference point should also flip by Z for the math to work. There is a secondary problem in `setTarget` where it is hard-coded to use LH math. This has been fixed to consider RH.
1 parent a0d2f95 commit fc791e4

File tree

4 files changed

+38
-57
lines changed

4 files changed

+38
-57
lines changed

packages/dev/core/src/Cameras/arcRotateCamera.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1394,7 +1394,7 @@ export class ArcRotateCamera extends TargetCamera {
13941394
this._viewMatrix.addAtIndex(12, this.targetScreenOffset.x);
13951395
this._viewMatrix.addAtIndex(13, this.targetScreenOffset.y);
13961396
}
1397-
this._currentTarget = target;
1397+
this._currentTarget.copyFrom(target);
13981398
return this._viewMatrix;
13991399
}
14001400

packages/dev/core/src/Cameras/targetCamera.ts

Lines changed: 31 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Node.AddNodeConstructor("TargetCamera", (name, scene) => {
1212
return () => new TargetCamera(name, Vector3.Zero(), scene);
1313
});
1414

15+
// Temporary cache variables to avoid allocations.
16+
const TmpMatrix = Matrix.Zero();
17+
const TmpQuaternion = Quaternion.Identity();
18+
1519
/**
1620
* A target camera takes a mesh or position as a target and continues to look at it while it moves.
1721
* This is the base of the follow, arc rotate cameras and Free camera
@@ -36,13 +40,12 @@ export class TargetCamera extends Camera {
3640
*/
3741
@serialize()
3842
public updateUpVectorFromRotation = false;
39-
private _tmpQuaternion = new Quaternion();
4043

4144
/**
4245
* Define the current rotation of the camera
4346
*/
4447
@serializeAsVector3()
45-
public rotation = new Vector3(0, 0, 0);
48+
public rotation: Vector3;
4649

4750
/**
4851
* Define the current rotation of the camera as a quaternion to prevent Gimbal lock
@@ -79,35 +82,27 @@ export class TargetCamera extends Camera {
7982
@serializeAsMeshReference("lockedTargetId")
8083
public lockedTarget: any = null;
8184

82-
/** @internal */
83-
public _currentTarget = Vector3.Zero();
84-
/** @internal */
85-
public _initialFocalDistance = 1;
86-
/** @internal */
87-
public _viewMatrix = Matrix.Zero();
88-
/** @internal */
89-
public _camMatrix = Matrix.Zero();
90-
/** @internal */
91-
public _cameraTransformMatrix = Matrix.Zero();
92-
/** @internal */
93-
public _cameraRotationMatrix = Matrix.Zero();
85+
protected readonly _currentTarget = Vector3.Zero();
86+
protected _initialFocalDistance = 1;
87+
protected readonly _viewMatrix = Matrix.Zero();
9488

9589
/** @internal */
96-
public _referencePoint = new Vector3(0, 0, 1);
90+
public readonly _cameraTransformMatrix = Matrix.Zero();
9791
/** @internal */
98-
public _transformedReferencePoint = Vector3.Zero();
92+
public readonly _cameraRotationMatrix = Matrix.Zero();
93+
94+
protected readonly _referencePoint: Vector3;
95+
protected readonly _transformedReferencePoint = Vector3.Zero();
9996

100-
protected _deferredPositionUpdate = new Vector3();
101-
protected _deferredRotationQuaternionUpdate = new Quaternion();
102-
protected _deferredRotationUpdate = new Vector3();
97+
protected readonly _deferredPositionUpdate = new Vector3();
98+
protected readonly _deferredRotationQuaternionUpdate = new Quaternion();
99+
protected readonly _deferredRotationUpdate = new Vector3();
103100
protected _deferredUpdated = false;
104101
protected _deferOnly: boolean = false;
105102

106103
/** @internal */
107104
public _reset: () => void;
108105

109-
private _defaultUp = Vector3.Up();
110-
111106
/**
112107
* Instantiates a target camera that takes a mesh or position as a target and continues to look at it while it moves.
113108
* This is the base of the follow, arc rotate cameras and Free camera
@@ -119,6 +114,11 @@ export class TargetCamera extends Camera {
119114
*/
120115
constructor(name: string, position: Vector3, scene?: Scene, setActiveOnSceneIfNoneActive = true) {
121116
super(name, position, scene, setActiveOnSceneIfNoneActive);
117+
118+
this._referencePoint = Vector3.Forward(this.getScene().useRightHandedSystem);
119+
120+
// Set the y component of the rotation to Math.PI in right-handed system for backwards compatibility.
121+
this.rotation = new Vector3(0, this.getScene().useRightHandedSystem ? Math.PI : 0, 0);
122122
}
123123

124124
/**
@@ -265,36 +265,17 @@ export class TargetCamera extends Camera {
265265

266266
this._referencePoint.normalize().scaleInPlace(this._initialFocalDistance);
267267

268-
Matrix.LookAtLHToRef(this.position, target, this._defaultUp, this._camMatrix);
269-
this._camMatrix.invert();
270-
271-
this.rotation.x = Math.atan(this._camMatrix.m[6] / this._camMatrix.m[10]);
272-
273-
const vDir = target.subtract(this.position);
274-
275-
if (vDir.x >= 0.0) {
276-
this.rotation.y = -Math.atan(vDir.z / vDir.x) + Math.PI / 2.0;
268+
if (this.getScene().useRightHandedSystem) {
269+
Matrix.LookAtRHToRef(this.position, target, Vector3.UpReadOnly, TmpMatrix);
277270
} else {
278-
this.rotation.y = -Math.atan(vDir.z / vDir.x) - Math.PI / 2.0;
271+
Matrix.LookAtLHToRef(this.position, target, Vector3.UpReadOnly, TmpMatrix);
279272
}
273+
TmpMatrix.invert();
280274

281-
this.rotation.z = 0;
275+
const rotationQuaternion = this.rotationQuaternion || TmpQuaternion;
276+
Quaternion.FromRotationMatrixToRef(TmpMatrix, rotationQuaternion);
282277

283-
if (isNaN(this.rotation.x)) {
284-
this.rotation.x = 0;
285-
}
286-
287-
if (isNaN(this.rotation.y)) {
288-
this.rotation.y = 0;
289-
}
290-
291-
if (isNaN(this.rotation.z)) {
292-
this.rotation.z = 0;
293-
}
294-
295-
if (this.rotationQuaternion) {
296-
Quaternion.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, this.rotationQuaternion);
297-
}
278+
rotationQuaternion.toEulerAnglesToRef(this.rotation);
298279
}
299280

300281
/**
@@ -450,7 +431,7 @@ export class TargetCamera extends Camera {
450431
* @returns the current camera
451432
*/
452433
private _rotateUpVectorWithCameraRotationMatrix(): TargetCamera {
453-
Vector3.TransformNormalToRef(this._defaultUp, this._cameraRotationMatrix, this.upVector);
434+
Vector3.TransformNormalToRef(Vector3.UpReadOnly, this._cameraRotationMatrix, this.upVector);
454435
return this;
455436
}
456437

@@ -482,8 +463,8 @@ export class TargetCamera extends Camera {
482463
if (this.rotationQuaternion) {
483464
Axis.Y.rotateByQuaternionToRef(this.rotationQuaternion, this.upVector);
484465
} else {
485-
Quaternion.FromEulerVectorToRef(this.rotation, this._tmpQuaternion);
486-
Axis.Y.rotateByQuaternionToRef(this._tmpQuaternion, this.upVector);
466+
Quaternion.FromEulerVectorToRef(this.rotation, TmpQuaternion);
467+
Axis.Y.rotateByQuaternionToRef(TmpQuaternion, this.upVector);
487468
}
488469
}
489470
this._computeViewMatrix(this.position, this._currentTarget, this.upVector);

packages/dev/loaders/src/glTF/2.0/glTFLoader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,8 +1569,8 @@ export class GLTFLoader implements IGLTFLoader {
15691569
this._babylonScene._blockEntityCollection = false;
15701570
camera._babylonCamera = babylonCamera;
15711571

1572-
// Rotation by 180 as glTF has a different convention than Babylon.
1573-
babylonCamera.rotation.set(0, Math.PI, 0);
1572+
// glTF cameras look towards the local -Z axis.
1573+
babylonCamera.setTarget(new Vector3(0, 0, -1));
15741574

15751575
switch (camera.type) {
15761576
case CameraType.PERSPECTIVE: {

packages/tools/tests/test/visualization/config.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,25 +1055,25 @@
10551055
},
10561056
{
10571057
"title": "GLTF Serializer Camera, left-handed",
1058-
"playgroundId": "#O0M0J9#8",
1058+
"playgroundId": "#O0M0J9#20",
10591059
"replace": "//options//, roundtripCount = 1; useRightHandedSystem = false;",
10601060
"referenceImage": "glTFSerializerCameraLeftHand.png"
10611061
},
10621062
{
10631063
"title": "GLTF Serializer Camera, right-handed",
1064-
"playgroundId": "#O0M0J9#8",
1064+
"playgroundId": "#O0M0J9#20",
10651065
"replace": "//options//, roundtripCount = 1; useRightHandedSystem = true;",
10661066
"referenceImage": "glTFSerializerCameraRightHand.png"
10671067
},
10681068
{
10691069
"title": "GLTF Serializer Camera, left-handed, round trip twice",
1070-
"playgroundId": "#O0M0J9#8",
1070+
"playgroundId": "#O0M0J9#20",
10711071
"replace": "//options//, roundtripCount = 2; useRightHandedSystem = false;",
10721072
"referenceImage": "glTFSerializerCameraLeftHand.png"
10731073
},
10741074
{
10751075
"title": "GLTF Serializer Camera, right-handed, round trip twice",
1076-
"playgroundId": "#O0M0J9#8",
1076+
"playgroundId": "#O0M0J9#20",
10771077
"replace": "//options//, roundtripCount = 2; useRightHandedSystem = true;",
10781078
"referenceImage": "glTFSerializerCameraRightHand.png"
10791079
},

0 commit comments

Comments
 (0)