Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix camera rotation when exporting glTF #14562

Merged
merged 3 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/dev/loaders/src/glTF/2.0/glTFLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ export class GLTFLoader implements IGLTFLoader {
for (const name of this._gltf.extensionsRequired) {
const available = this._extensions.some((extension) => extension.name === name && extension.enabled);
if (!available) {
throw new Error(`Require extension ${name} is not available`);
throw new Error(`Required extension ${name} is not available`);
}
}
}
Expand Down Expand Up @@ -1481,7 +1481,8 @@ export class GLTFLoader implements IGLTFLoader {
babylonCamera.ignoreParentScaling = true;
camera._babylonCamera = babylonCamera;

babylonCamera.rotation = new Vector3(0, Math.PI, 0);
// Rotation by 180 as glTF has a different convention than Babylon.
babylonCamera.rotation.set(0, Math.PI, 0);

switch (camera.type) {
case CameraType.PERSPECTIVE: {
Expand Down
83 changes: 49 additions & 34 deletions packages/dev/serializers/src/glTF/2.0/glTFExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ import { _GLTFAnimation } from "./glTFAnimation";
import { Camera } from "core/Cameras/camera";
import { EngineStore } from "core/Engines/engineStore";
import { MultiMaterial } from "core/Materials/multiMaterial";
import { TargetCamera } from "core/Cameras/targetCamera";

// Matrix that converts handedness on the X-axis.
const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion.Identity(), Vector3.Zero());

// 180 degrees rotation in Y.
const rotation180Y = new Quaternion(0, 1, 0, 0);

function isNoopNode(node: Node, useRightHandedSystem: boolean): boolean {
if (!(node instanceof TransformNode)) {
return false;
Expand Down Expand Up @@ -1244,7 +1248,7 @@ export class _Exporter {
node.scale = babylonTransformNode.scaling.asArray();
}

const rotationQuaternion = Quaternion.RotationYawPitchRoll(babylonTransformNode.rotation.y, babylonTransformNode.rotation.x, babylonTransformNode.rotation.z);
const rotationQuaternion = Quaternion.FromEulerAngles(babylonTransformNode.rotation.x, babylonTransformNode.rotation.y, babylonTransformNode.rotation.z);
if (babylonTransformNode.rotationQuaternion) {
rotationQuaternion.multiplyInPlace(babylonTransformNode.rotationQuaternion);
}
Expand All @@ -1253,15 +1257,24 @@ export class _Exporter {
}
}

private _setCameraTransformation(node: INode, babylonCamera: Camera): void {
private _setCameraTransformation(node: INode, babylonCamera: TargetCamera): void {
if (!babylonCamera.position.equalsToFloats(0, 0, 0)) {
node.translation = babylonCamera.position.asArray();
}

const rotationQuaternion = (<any>babylonCamera).rotationQuaternion; // we target the local transformation if one.
const rotation = babylonCamera.rotation;
const rotationQuaternion = Quaternion.FromEulerAngles(rotation.x, rotation.y, rotation.z);
if (babylonCamera.rotationQuaternion) {
rotationQuaternion.multiplyInPlace(babylonCamera.rotationQuaternion);
bghgary marked this conversation as resolved.
Show resolved Hide resolved
}

// Rotation by 180 as glTF has a different convention than Babylon.
rotationQuaternion.multiplyInPlace(rotation180Y);

if (rotationQuaternion && !Quaternion.IsIdentity(rotationQuaternion)) {
node.rotation = rotationQuaternion.normalize().asArray();
rotationQuaternion.normalize();

if (!Quaternion.IsIdentity(rotationQuaternion)) {
node.rotation = rotationQuaternion.asArray();
}
}

Expand Down Expand Up @@ -1783,38 +1796,40 @@ export class _Exporter {
}

// Export babylon cameras to glTFCamera
const cameraMap = new Map<Camera, number>();
const cameraMap = new Map<TargetCamera, number>();
this._babylonScene.cameras.forEach((camera) => {
if (!this._options.shouldExportNode || this._options.shouldExportNode(camera)) {
const glTFCamera: ICamera = {
type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC,
};
if (!(camera instanceof TargetCamera) || (this._options.shouldExportNode && !this._options.shouldExportNode(camera))) {
return;
}

if (camera.name) {
glTFCamera.name = camera.name;
}
const glTFCamera: ICamera = {
type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC,
};

if (glTFCamera.type === CameraType.PERSPECTIVE) {
glTFCamera.perspective = {
aspectRatio: camera.getEngine().getAspectRatio(camera),
yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera),
znear: camera.minZ,
zfar: camera.maxZ,
};
} else if (glTFCamera.type === CameraType.ORTHOGRAPHIC) {
const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5;
const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5;
glTFCamera.orthographic = {
xmag: halfWidth,
ymag: halfHeight,
znear: camera.minZ,
zfar: camera.maxZ,
};
}
if (camera.name) {
glTFCamera.name = camera.name;
}

cameraMap.set(camera, this._cameras.length);
this._cameras.push(glTFCamera);
if (glTFCamera.type === CameraType.PERSPECTIVE) {
glTFCamera.perspective = {
aspectRatio: camera.getEngine().getAspectRatio(camera),
yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera),
znear: camera.minZ,
zfar: camera.maxZ,
};
} else if (glTFCamera.type === CameraType.ORTHOGRAPHIC) {
const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5;
const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5;
glTFCamera.orthographic = {
xmag: halfWidth,
ymag: halfHeight,
znear: camera.minZ,
zfar: camera.maxZ,
};
}

cameraMap.set(camera, this._cameras.length);
this._cameras.push(glTFCamera);
});

const [exportNodes, exportMaterials] = this._getExportNodes(nodes);
Expand Down Expand Up @@ -1842,7 +1857,7 @@ export class _Exporter {
}
}

if (babylonNode instanceof Camera) {
if (babylonNode instanceof TargetCamera) {
glTFNode.camera = cameraMap.get(babylonNode);
}

Expand Down Expand Up @@ -2051,7 +2066,7 @@ export class _Exporter {
}
return node;
});
} else if (babylonNode instanceof Camera) {
} else if (babylonNode instanceof TargetCamera) {
this._setCameraTransformation(node, babylonNode);
return node;
} else {
Expand Down