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

Update GLTF Animation serializer to include Camera. #12686

Merged
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 117 additions & 95 deletions packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { _GLTFUtilities } from "./glTFUtilities";
import type { IAnimationKey } from "core/Animations/animationKey";
import { AnimationKeyInterpolation } from "core/Animations/animationKey";

import { Camera } from "core/Cameras/camera";
import { Light } from "core/Lights/light";

/**
* @hidden
* Interface to store animation data.
Expand Down Expand Up @@ -77,11 +80,23 @@ enum _TangentType {
*/
OUTTANGENT,
}

type TransformableType = TransformNode | Camera | Light;

/**
* @hidden
* Utility class for generating glTF animation data from BabylonJS.
*/
export class _GLTFAnimation {
/**
* Determine if a node is transformable - ie has properties it should be part of animation of transformation.
* @param babylonNode the node to test
* @returns true if can be animated, false otherwise. False if the parameter is null or undefined.
*/
private static _IsTransformable(babylonNode: Node): babylonNode is TransformableType {
return babylonNode && (babylonNode instanceof TransformNode || babylonNode instanceof Camera || babylonNode instanceof Light);
}

/**
* @ignore
*
Expand All @@ -94,62 +109,25 @@ export class _GLTFAnimation {
* @returns nullable IAnimationData
*/
public static _CreateNodeAnimation(
babylonTransformNode: TransformNode,
babylonTransformNode: Node,
animation: Animation,
animationChannelTargetPath: AnimationChannelTargetPath,
convertToRightHandedSystem: boolean,
useQuaternion: boolean,
animationSampleRate: number
): Nullable<_IAnimationData> {
const inputs: number[] = [];
const outputs: number[][] = [];
const keyFrames = animation.getKeys();
const minMaxKeyFrames = _GLTFAnimation._CalculateMinMaxKeyFrames(keyFrames);
const interpolationOrBake = _GLTFAnimation._DeduceInterpolation(keyFrames, animationChannelTargetPath, useQuaternion);
const frameDelta = minMaxKeyFrames.max - minMaxKeyFrames.min;
if (this._IsTransformable(babylonTransformNode)) {
const inputs: number[] = [];
const outputs: number[][] = [];
const keyFrames = animation.getKeys();
const minMaxKeyFrames = _GLTFAnimation._CalculateMinMaxKeyFrames(keyFrames);
const interpolationOrBake = _GLTFAnimation._DeduceInterpolation(keyFrames, animationChannelTargetPath, useQuaternion);
const frameDelta = minMaxKeyFrames.max - minMaxKeyFrames.min;

const interpolation = interpolationOrBake.interpolationType;
const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation;
const interpolation = interpolationOrBake.interpolationType;
const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation;

if (shouldBakeAnimation) {
_GLTFAnimation._CreateBakedAnimation(
babylonTransformNode,
animation,
animationChannelTargetPath,
minMaxKeyFrames.min,
minMaxKeyFrames.max,
animation.framePerSecond,
animationSampleRate,
inputs,
outputs,
minMaxKeyFrames,
convertToRightHandedSystem,
useQuaternion
);
} else {
if (interpolation === AnimationSamplerInterpolation.LINEAR || interpolation === AnimationSamplerInterpolation.STEP) {
_GLTFAnimation._CreateLinearOrStepAnimation(
babylonTransformNode,
animation,
animationChannelTargetPath,
frameDelta,
inputs,
outputs,
convertToRightHandedSystem,
useQuaternion
);
} else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) {
_GLTFAnimation._CreateCubicSplineAnimation(
babylonTransformNode,
animation,
animationChannelTargetPath,
frameDelta,
inputs,
outputs,
convertToRightHandedSystem,
useQuaternion
);
} else {
if (shouldBakeAnimation) {
_GLTFAnimation._CreateBakedAnimation(
babylonTransformNode,
animation,
Expand All @@ -164,19 +142,58 @@ export class _GLTFAnimation {
convertToRightHandedSystem,
useQuaternion
);
} else {
if (interpolation === AnimationSamplerInterpolation.LINEAR || interpolation === AnimationSamplerInterpolation.STEP) {
_GLTFAnimation._CreateLinearOrStepAnimation(
babylonTransformNode,
animation,
animationChannelTargetPath,
frameDelta,
inputs,
outputs,
convertToRightHandedSystem,
useQuaternion
);
} else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) {
_GLTFAnimation._CreateCubicSplineAnimation(
babylonTransformNode,
animation,
animationChannelTargetPath,
frameDelta,
inputs,
outputs,
convertToRightHandedSystem,
useQuaternion
);
} else {
_GLTFAnimation._CreateBakedAnimation(
babylonTransformNode,
animation,
animationChannelTargetPath,
minMaxKeyFrames.min,
minMaxKeyFrames.max,
animation.framePerSecond,
animationSampleRate,
inputs,
outputs,
minMaxKeyFrames,
convertToRightHandedSystem,
useQuaternion
);
}
}
}

if (inputs.length && outputs.length) {
const result: _IAnimationData = {
inputs: inputs,
outputs: outputs,
samplerInterpolation: interpolation,
inputsMin: shouldBakeAnimation ? minMaxKeyFrames.min : Tools.FloatRound(minMaxKeyFrames.min / animation.framePerSecond),
inputsMax: shouldBakeAnimation ? minMaxKeyFrames.max : Tools.FloatRound(minMaxKeyFrames.max / animation.framePerSecond),
};
if (inputs.length && outputs.length) {
const result: _IAnimationData = {
inputs: inputs,
outputs: outputs,
samplerInterpolation: interpolation,
inputsMin: shouldBakeAnimation ? minMaxKeyFrames.min : Tools.FloatRound(minMaxKeyFrames.min / animation.framePerSecond),
inputsMax: shouldBakeAnimation ? minMaxKeyFrames.max : Tools.FloatRound(minMaxKeyFrames.max / animation.framePerSecond),
};

return result;
return result;
}
}

return null;
Expand Down Expand Up @@ -251,7 +268,7 @@ export class _GLTFAnimation {
animationSampleRate: number
) {
let glTFAnimation: IAnimation;
if (babylonNode instanceof TransformNode) {
if (_GLTFAnimation._IsTransformable(babylonNode)) {
if (babylonNode.animations) {
for (const animation of babylonNode.animations) {
const animationInfo = _GLTFAnimation._DeduceAnimationInfo(animation);
Expand Down Expand Up @@ -414,10 +431,10 @@ export class _GLTFAnimation {
const targetAnimation = animationGroup.targetedAnimations[i];
const target = targetAnimation.target;
const animation = targetAnimation.animation;
if (target instanceof TransformNode || (target.length === 1 && target[0] instanceof TransformNode)) {
if (this._IsTransformable(target) || (target.length === 1 && this._IsTransformable(target[0]))) {
const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation);
if (animationInfo) {
const babylonTransformNode = target instanceof TransformNode ? (target as TransformNode) : (target[0] as TransformNode);
const babylonTransformNode = this._IsTransformable(target) ? target : (target[0] as TransformableType);
const convertToRightHandedSystem = convertToRightHandedSystemMap[babylonTransformNode.uniqueId];
_GLTFAnimation._AddAnimation(
`${animation.name}`,
Expand Down Expand Up @@ -541,7 +558,7 @@ export class _GLTFAnimation {
private static _AddAnimation(
name: string,
glTFAnimation: IAnimation,
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
animation: Animation,
dataAccessorType: AccessorType,
animationChannelTargetPath: AnimationChannelTargetPath,
Expand Down Expand Up @@ -669,7 +686,7 @@ export class _GLTFAnimation {
* @param useQuaternion specifies if quaternions should be used
*/
private static _CreateBakedAnimation(
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
animation: Animation,
animationChannelTargetPath: AnimationChannelTargetPath,
minFrame: number,
Expand Down Expand Up @@ -757,7 +774,7 @@ export class _GLTFAnimation {

private static _ConvertFactorToVector3OrQuaternion(
factor: number,
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
animation: Animation,
animationType: number,
animationChannelTargetPath: AnimationChannelTargetPath,
Expand Down Expand Up @@ -806,7 +823,7 @@ export class _GLTFAnimation {
}

private static _SetInterpolatedValue(
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
value: Nullable<number | Vector3 | Quaternion>,
time: number,
animation: Animation,
Expand All @@ -820,18 +837,25 @@ export class _GLTFAnimation {
const animationType = animation.dataType;
let cacheValue: Vector3 | Quaternion | number;
inputs.push(time);
if (typeof value === "number" && babylonTransformNode instanceof TransformNode) {
value = this._ConvertFactorToVector3OrQuaternion(
value as number,
babylonTransformNode,
animation,
animationType,
animationChannelTargetPath,
convertToRightHandedSystem,
useQuaternion
);
}

if (value) {
if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) {
outputs.push([value as number]);
return;
}

if (typeof value === "number") {
value = this._ConvertFactorToVector3OrQuaternion(
value as number,
babylonTransformNode,
animation,
animationType,
animationChannelTargetPath,
convertToRightHandedSystem,
useQuaternion
);
}

if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
if (useQuaternion) {
quaternionCache = value as Quaternion;
Expand All @@ -847,8 +871,6 @@ export class _GLTFAnimation {
}
}
outputs.push(quaternionCache.asArray());
} else if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) {
outputs.push([value as number]);
} else {
// scaling and position animation
cacheValue = value as Vector3;
Expand Down Expand Up @@ -877,7 +899,7 @@ export class _GLTFAnimation {
* @param useQuaternion Specifies if quaternions are used in the animation
*/
private static _CreateLinearOrStepAnimation(
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
animation: Animation,
animationChannelTargetPath: AnimationChannelTargetPath,
frameDelta: number,
Expand All @@ -904,7 +926,7 @@ export class _GLTFAnimation {
* @param useQuaternion Specifies if quaternions are used in the animation
*/
private static _CreateCubicSplineAnimation(
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
animation: Animation,
animationChannelTargetPath: AnimationChannelTargetPath,
frameDelta: number,
Expand Down Expand Up @@ -943,37 +965,37 @@ export class _GLTFAnimation {
}

private static _GetBasePositionRotationOrScale(
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
animationChannelTargetPath: AnimationChannelTargetPath,
convertToRightHandedSystem: boolean,
useQuaternion: boolean
) {
let basePositionRotationOrScale: number[];
if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
if (useQuaternion) {
if (babylonTransformNode.rotationQuaternion) {
basePositionRotationOrScale = babylonTransformNode.rotationQuaternion.asArray();
if (convertToRightHandedSystem) {
_GLTFUtilities._GetRightHandedQuaternionArrayFromRef(basePositionRotationOrScale);
if (!babylonTransformNode.parent) {
basePositionRotationOrScale = Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(basePositionRotationOrScale)).asArray();
}
const q: Quaternion = (<any>babylonTransformNode).rotationQuaternion;
pandaGaume marked this conversation as resolved.
Show resolved Hide resolved
basePositionRotationOrScale = (q ?? Quaternion.Identity()).asArray();
if (convertToRightHandedSystem) {
_GLTFUtilities._GetRightHandedQuaternionArrayFromRef(basePositionRotationOrScale);
if (!babylonTransformNode.parent) {
basePositionRotationOrScale = Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(basePositionRotationOrScale)).asArray();
}
} else {
basePositionRotationOrScale = Quaternion.Identity().asArray();
}
} else {
basePositionRotationOrScale = babylonTransformNode.rotation.asArray();
const r: Vector3 = (<any>babylonTransformNode).rotation;
pandaGaume marked this conversation as resolved.
Show resolved Hide resolved
basePositionRotationOrScale = (r ?? Vector3.Zero()).asArray();
_GLTFUtilities._GetRightHandedNormalArray3FromRef(basePositionRotationOrScale);
}
} else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
basePositionRotationOrScale = babylonTransformNode.position.asArray();
const p: Vector3 = (<any>babylonTransformNode).position;
basePositionRotationOrScale = (p ?? Vector3.Zero()).asArray();
if (convertToRightHandedSystem) {
_GLTFUtilities._GetRightHandedPositionArray3FromRef(basePositionRotationOrScale);
}
} else {
// scale
basePositionRotationOrScale = babylonTransformNode.scaling.asArray();
const s: Vector3 = (<any>babylonTransformNode).scaling;
basePositionRotationOrScale = (s ?? Vector3.One()).asArray();
}
return basePositionRotationOrScale;
}
Expand All @@ -993,7 +1015,7 @@ export class _GLTFAnimation {
animation: Animation,
outputs: number[][],
animationChannelTargetPath: AnimationChannelTargetPath,
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
convertToRightHandedSystem: boolean,
useQuaternion: boolean
) {
Expand Down Expand Up @@ -1151,7 +1173,7 @@ export class _GLTFAnimation {
* @param convertToRightHandedSystem Specifies if the values should be converted to right-handed
*/
private static _AddSplineTangent(
babylonTransformNode: TransformNode,
babylonTransformNode: TransformableType,
tangentType: _TangentType,
outputs: number[][],
animationChannelTargetPath: AnimationChannelTargetPath,
Expand Down