import { AnimationSamplerInterpolation , AnimationChannelTargetPath , AccessorType , IAnimation , INode , IBufferView , IAccessor , IAnimationSampler , IAnimationChannel , AccessorComponentType } from "babylonjs-gltf2interface" ; import { Node } from "babylonjs/node" ; import { Nullable } from "babylonjs/types" ; import { Vector3 , Quaternion } from "babylonjs/Maths/math.vector" ; import { Tools } from "babylonjs/Misc/tools" ; import { Animation } from "babylonjs/Animations/animation" ; import { TransformNode } from "babylonjs/Meshes/transformNode" ; import { Scene } from "babylonjs/scene" ; import { MorphTarget } from "babylonjs/Morph/morphTarget" ; import { Mesh } from "babylonjs/Meshes/mesh" ; import { _BinaryWriter } from "./glTFExporter" ; import { _GLTFUtilities } from "./glTFUtilities" ; import { IAnimationKey , AnimationKeyInterpolation } from 'babylonjs/Animations/animationKey' ; hidden export interface _IAnimationData { /**
     * Keyframe data.
     */ inputs : number [ ] ; /**
     * Value data.
     */ outputs : number [ ] [ ] ; /**
     * Animation interpolation data.
     */ samplerInterpolation : AnimationSamplerInterpolation ; /**
     * Minimum keyframe value.
     */ inputsMin : number ; /**
     * Maximum keyframe value.
     */ inputsMax : number ; } hidden export interface _IAnimationInfo { /**
     * The target channel for the animation
     */ animationChannelTargetPath : AnimationChannelTargetPath ; /**
     * The glTF accessor type for the data.
     */ dataAccessorType : AccessorType . VEC3 | AccessorType . VEC4 | AccessorType . SCALAR ; /**
     * Specifies if quaternions should be used.
     */ useQuaternion : boolean ; } hidden enum _TangentType { /**
     * Specifies that input tangents are used.
     */ INTANGENT , /**
     * Specifies that output tangents are used.
     */ OUTTANGENT } hidden export class _GLTFAnimation { ignore param babylonTransformNode param animation param animationChannelTargetPath param convertToRightHandedSystem param useQuaternion returns public static _CreateNodeAnimation ( babylonTransformNode , animation , animationChannelTargetPath , convertToRightHandedSystem , useQuaternion , animationSampleRate ) { const inputs = [ ] ; const outputs = [ ] ; 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 ; 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 { _GLTFAnimation . _CreateBakedAnimation ( babylonTransformNode , animation , animationChannelTargetPath , minMaxKeyFrames . min , minMaxKeyFrames . max , animation . framePerSecond , animationSampleRate , inputs , outputs , minMaxKeyFrames , convertToRightHandedSystem , useQuaternion ) ; } } if ( inputs . length && outputs . length ) { const result = { 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 null ; } private static _DeduceAnimationInfo ( animation ) { let animationChannelTargetPath = null ; let dataAccessorType = AccessorType . VEC3 ; let useQuaternion = false ; let property = animation . targetProperty . split ( '.' ) ; switch ( property [ 0 ] ) { case 'scaling' : { animationChannelTargetPath = AnimationChannelTargetPath . SCALE ; break ; } case 'position' : { animationChannelTargetPath = AnimationChannelTargetPath . TRANSLATION ; break ; } case 'rotation' : { dataAccessorType = AccessorType . VEC4 ; animationChannelTargetPath = AnimationChannelTargetPath . ROTATION ; break ; } case 'rotationQuaternion' : { dataAccessorType = AccessorType . VEC4 ; useQuaternion = true ; animationChannelTargetPath = AnimationChannelTargetPath . ROTATION ; break ; } case 'influence' : { dataAccessorType = AccessorType . SCALAR ; animationChannelTargetPath = AnimationChannelTargetPath . WEIGHTS ; break ; } default : { Tools . Error ( `Unsupported animatable property ${ property [ 0 ] }` ) ; } } if ( animationChannelTargetPath ) { return { animationChannelTargetPath : animationChannelTargetPath , dataAccessorType : dataAccessorType , useQuaternion : useQuaternion } ; } else { Tools . Error ( 'animation channel target path and data accessor type could be deduced' ) ; } return null ; } ignore param babylonNode param runtimeGLTFAnimation param idleGLTFAnimations param nodeMap param nodes param binaryWriter param bufferViews param accessors param convertToRightHandedSystem param animationSampleRate public static _CreateNodeAnimationFromNodeAnimations ( babylonNode , runtimeGLTFAnimation , idleGLTFAnimations , nodeMap , nodes , binaryWriter , bufferViews , accessors , convertToRightHandedSystem , animationSampleRate ) { let glTFAnimation ; if ( babylonNode instanceof TransformNode ) { if ( babylonNode . animations ) { for ( let animation of babylonNode . animations ) { let animationInfo = _GLTFAnimation . _DeduceAnimationInfo ( animation ) ; if ( animationInfo ) { glTFAnimation = { name : animation . name , samplers : [ ] , channels : [ ] } ; _GLTFAnimation . AddAnimation ( `${ animation . name }` , animation . hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation , babylonNode , animation , animationInfo . dataAccessorType , animationInfo . animationChannelTargetPath , nodeMap , binaryWriter , bufferViews , accessors , convertToRightHandedSystem , animationInfo . useQuaternion , animationSampleRate ) ; if ( glTFAnimation . samplers . length && glTFAnimation . channels . length ) { idleGLTFAnimations . push ( glTFAnimation ) ; } } } } } } ignore param babylonNode param runtimeGLTFAnimation param idleGLTFAnimations param nodeMap param nodes param binaryWriter param bufferViews param accessors param convertToRightHandedSystem param animationSampleRate public static _CreateMorphTargetAnimationFromMorphTargetAnimations ( babylonNode , runtimeGLTFAnimation , idleGLTFAnimations , nodeMap , nodes , binaryWriter , bufferViews , accessors , convertToRightHandedSystem , animationSampleRate ) { let glTFAnimation ; if ( babylonNode instanceof Mesh ) { const morphTargetManager = babylonNode . morphTargetManager ; if ( morphTargetManager ) { for ( let i = 0 ; i < morphTargetManager . numTargets ; ++ i ) { const morphTarget = morphTargetManager . getTarget ( i ) ; for ( const animation of morphTarget . animations ) { const combinedAnimation = new Animation ( `${ animation . name }` , "influence" , animation . framePerSecond , animation . dataType , animation . loopMode , animation . enableBlending ) ; const combinedAnimationKeys = [ ] ; const animationKeys = animation . getKeys ( ) ; for ( let j = 0 ; j < animationKeys . length ; ++ j ) { let animationKey = animationKeys [ j ] ; for ( let k = 0 ; k < morphTargetManager . numTargets ; ++ k ) { if ( k == i ) { combinedAnimationKeys . push ( animationKey ) ; } else { combinedAnimationKeys . push ( { frame : animationKey . frame , value : 0 } ) ; } } } combinedAnimation . setKeys ( combinedAnimationKeys ) ; const animationInfo = _GLTFAnimation . _DeduceAnimationInfo ( combinedAnimation ) ; if ( animationInfo ) { glTFAnimation = { name : combinedAnimation . name , samplers : [ ] , channels : [ ] } ; _GLTFAnimation . AddAnimation ( animation . name , animation . hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation , babylonNode , combinedAnimation , animationInfo . dataAccessorType , animationInfo . animationChannelTargetPath , nodeMap , binaryWriter , bufferViews , accessors , convertToRightHandedSystem , animationInfo . useQuaternion , animationSampleRate , morphTargetManager . numTargets ) ; if ( glTFAnimation . samplers . length && glTFAnimation . channels . length ) { idleGLTFAnimations . push ( glTFAnimation ) ; } } } } } } } ignore param babylonScene param glTFAnimations param nodeMap param nodes param binaryWriter param bufferViews param accessors param convertToRightHandedSystemMap param animationSampleRate public static _CreateNodeAndMorphAnimationFromAnimationGroups ( babylonScene , glTFAnimations , nodeMap , nodes , binaryWriter , bufferViews , accessors , convertToRightHandedSystemMap , animationSampleRate ) { let glTFAnimation ; if ( babylonScene . animationGroups ) { const animationGroups = babylonScene . animationGroups ; for ( let animationGroup of animationGroups ) { const morphAnimations = new Map ( ) ; const sampleAnimations = new Map ( ) ; const morphAnimationMeshes = new Set ( ) ; const animationGroupFrameDiff = animationGroup . to - animationGroup . from ; glTFAnimation = { name : animationGroup . name , channels : [ ] , samplers : [ ] } ; for ( let i = 0 ; i < animationGroup . targetedAnimations . length ; ++ i ) { const targetAnimation = animationGroup . targetedAnimations [ i ] ; const target = targetAnimation . target ; const animation = targetAnimation . animation ; if ( target instanceof TransformNode || target . length === 1 && target [ 0 ] instanceof TransformNode ) { const animationInfo = _GLTFAnimation . _DeduceAnimationInfo ( targetAnimation . animation ) ; if ( animationInfo ) { const babylonTransformNode = target instanceof TransformNode ? target as TransformNode : target [ 0 ] as TransformNode ; const convertToRightHandedSystem = convertToRightHandedSystemMap [ babylonTransformNode . uniqueId ] ; _GLTFAnimation . AddAnimation ( `${ animation . name }` , glTFAnimation , babylonTransformNode , animation , animationInfo . dataAccessorType , animationInfo . animationChannelTargetPath , nodeMap , binaryWriter , bufferViews , accessors , convertToRightHandedSystem , animationInfo . useQuaternion , animationSampleRate ) ; } } else if ( target instanceof MorphTarget || target . length === 1 && target [ 0 ] instanceof MorphTarget ) { const animationInfo = _GLTFAnimation . _DeduceAnimationInfo ( targetAnimation . animation ) ; if ( animationInfo ) { const babylonMorphTarget = target instanceof MorphTarget ? target as MorphTarget : target [ 0 ] as MorphTarget ; if ( babylonMorphTarget ) { const babylonMorphTargetManager = babylonScene . morphTargetManagers . find ( ( morphTargetManager ) => { for ( let j = 0 ; j < morphTargetManager . numTargets ; ++ j ) { if ( morphTargetManager . getTarget ( j ) === babylonMorphTarget ) { return true ; } } return false ; } ) ; if ( babylonMorphTargetManager ) { const babylonMesh = babylonScene . meshes . find ( ( mesh ) => { return ( mesh as Mesh ) . morphTargetManager === babylonMorphTargetManager ; } ) as Mesh ; if ( babylonMesh ) { if ( ! morphAnimations . has ( babylonMesh ) ) { morphAnimations . set ( babylonMesh , new Map ( ) ) ; } morphAnimations . get ( babylonMesh ) ?. set ( babylonMorphTarget , animation ) ; morphAnimationMeshes . add ( babylonMesh ) ; sampleAnimations . set ( babylonMesh , animation ) ; } } } } } } morphAnimationMeshes . forEach ( ( mesh ) => { const morphTargetManager = mesh . morphTargetManager ! ; let combinedAnimationGroup = null ; const animationKeys = [ ] ; const sampleAnimation = sampleAnimations . get ( mesh ) ! ; const sampleAnimationKeys = sampleAnimation . getKeys ( ) ; const numAnimationKeys = sampleAnimationKeys . length ; for ( let i = 0 ; i < numAnimationKeys ; ++ i ) { for ( let j = 0 ; j < morphTargetManager . numTargets ; ++ j ) { const morphTarget = morphTargetManager . getTarget ( j ) ; const animationsByMorphTarget = morphAnimations . get ( mesh ) ; if ( animationsByMorphTarget ) { const morphTargetAnimation = animationsByMorphTarget . get ( morphTarget ) ; if ( morphTargetAnimation ) { if ( ! combinedAnimationGroup ) { combinedAnimationGroup = new Animation ( `${ animationGroup . name }_${ mesh . name }_MorphWeightAnimation` , "influence" , morphTargetAnimation . framePerSecond , Animation . ANIMATIONTYPE_FLOAT , morphTargetAnimation . loopMode , morphTargetAnimation . enableBlending ) ; } animationKeys . push ( morphTargetAnimation . getKeys ( ) [ i ] ) ; } else { animationKeys . push ( { frame : animationGroup . from + ( animationGroupFrameDiff / numAnimationKeys ) * i , value : morphTarget . influence , inTangent : sampleAnimationKeys [ 0 ] . inTangent ? 0 : undefined , outTangent : sampleAnimationKeys [ 0 ] . outTangent ? 0 : undefined } ) ; } } } } combinedAnimationGroup ! . setKeys ( animationKeys ) ; const animationInfo = _GLTFAnimation . _DeduceAnimationInfo ( combinedAnimationGroup ! ) ; if ( animationInfo ) { _GLTFAnimation . AddAnimation ( `${ animationGroup . name }_${ mesh . name }_MorphWeightAnimation` , glTFAnimation , mesh , combinedAnimationGroup ! , animationInfo . dataAccessorType , animationInfo . animationChannelTargetPath , nodeMap , binaryWriter , bufferViews , accessors , false , animationInfo . useQuaternion , animationSampleRate , morphTargetManager ?. numTargets ) ; } } ) ; if ( glTFAnimation . channels . length && glTFAnimation . samplers . length ) { glTFAnimations . push ( glTFAnimation ) ; } } } } private static AddAnimation ( name , glTFAnimation , babylonTransformNode , animation , dataAccessorType , animationChannelTargetPath , nodeMap , binaryWriter , bufferViews , accessors , convertToRightHandedSystem , useQuaternion , animationSampleRate , morphAnimationChannels ? ) { const animationData = _GLTFAnimation . _CreateNodeAnimation ( babylonTransformNode , animation , animationChannelTargetPath , convertToRightHandedSystem , useQuaternion , animationSampleRate ) ; let bufferView ; let accessor ; let keyframeAccessorIndex ; let dataAccessorIndex ; let outputLength ; let animationSampler ; let animationChannel ; if ( animationData ) { if ( morphAnimationChannels ) { let index = 0 ; let currentInput = 0 ; let newInputs = [ ] ; while ( animationData . inputs . length > 0 ) { currentInput = animationData . inputs . shift ( ) ! ; if ( index % morphAnimationChannels == 0 ) { newInputs . push ( currentInput ) ; } index ++ ; } animationData . inputs = newInputs ; } let nodeIndex = nodeMap [ babylonTransformNode . uniqueId ] ; let byteLength = animationData . inputs . length * 4 ; bufferView = _GLTFUtilities . _CreateBufferView ( 0 , binaryWriter . getByteOffset ( ) , byteLength , undefined , `${ name }  keyframe data view` ) ; bufferViews . push ( bufferView ) ; animationData . inputs . forEach ( function ( input ) { binaryWriter . setFloat32 ( input ) ; } ) ; accessor = _GLTFUtilities . _CreateAccessor ( bufferViews . length - 1 , `${ name }  keyframes` , AccessorType . SCALAR , AccessorComponentType . FLOAT , animationData . inputs . length , null , [ animationData . inputsMin ] , [ animationData . inputsMax ] ) ; accessors . push ( accessor ) ; keyframeAccessorIndex = accessors . length - 1 ; outputLength = animationData . outputs . length ; byteLength = _GLTFUtilities . _GetDataAccessorElementCount ( dataAccessorType ) * 4 * animationData . outputs . length ; bufferView = _GLTFUtilities . _CreateBufferView ( 0 , binaryWriter . getByteOffset ( ) , byteLength , undefined , `${ name }  data view` ) ; bufferViews . push ( bufferView ) ; animationData . outputs . forEach ( function ( output ) { output . forEach ( function ( entry ) { binaryWriter . setFloat32 ( entry ) ; } ) ; } ) ; accessor = _GLTFUtilities . _CreateAccessor ( bufferViews . length - 1 , `${ name }  data` , dataAccessorType , AccessorComponentType . FLOAT , outputLength , null , null , null ) ; accessors . push ( accessor ) ; dataAccessorIndex = accessors . length - 1 ; animationSampler = { interpolation : animationData . samplerInterpolation , input : keyframeAccessorIndex , output : dataAccessorIndex } ; glTFAnimation . samplers . push ( animationSampler ) ; animationChannel = { sampler : glTFAnimation . samplers . length - 1 , target : { node : nodeIndex , path : animationChannelTargetPath } } ; glTFAnimation . channels . push ( animationChannel ) ; } } param babylonTransformNode param animation param animationChannelTargetPath param minFrame param maxFrame param fps param inputs param outputs param convertToRightHandedSystem param useQuaternion private static _CreateBakedAnimation ( babylonTransformNode , animation , animationChannelTargetPath , minFrame , maxFrame , fps , sampleRate , inputs , outputs , minMaxFrames , convertToRightHandedSystem , useQuaternion ) { let value ; let quaternionCache = Quaternion . Identity ( ) ; let previousTime = null ; let time ; let maxUsedFrame = null ; let currKeyFrame = null ; let nextKeyFrame = null ; let prevKeyFrame = null ; let endFrame = null ; minMaxFrames . min = Tools . FloatRound ( minFrame / fps ) ; let keyFrames = animation . getKeys ( ) ; for ( let i = 0 , length = keyFrames . length ; i < length ; ++ i ) { endFrame = null ; currKeyFrame = keyFrames [ i ] ; if ( i + 1 < length ) { nextKeyFrame = keyFrames [ i + 1 ] ; if ( currKeyFrame . value . equals && currKeyFrame . value . equals ( nextKeyFrame . value ) || currKeyFrame . value === nextKeyFrame . value ) { if ( i === 0 ) { endFrame = currKeyFrame . frame ; } else { continue ; } } else { endFrame = nextKeyFrame . frame ; } } else { prevKeyFrame = keyFrames [ i - 1 ] ; if ( currKeyFrame . value . equals && currKeyFrame . value . equals ( prevKeyFrame . value ) || currKeyFrame . value === prevKeyFrame . value ) { continue ; } else { endFrame = maxFrame ; } } if ( endFrame ) { for ( let f = currKeyFrame . frame ; f <= endFrame ; f += sampleRate ) { time = Tools . FloatRound ( f / fps ) ; if ( time === previousTime ) { continue ; } previousTime = time ; maxUsedFrame = time ; let state = { key : 0 , repeatCount : 0 , loopMode : animation . loopMode } ; value = animation . _interpolate ( f , state ) ; _GLTFAnimation . _SetInterpolatedValue ( babylonTransformNode , value , time , animation , animationChannelTargetPath , quaternionCache , inputs , outputs , convertToRightHandedSystem , useQuaternion ) ; } } } if ( maxUsedFrame ) { minMaxFrames . max = maxUsedFrame ; } } private static _ConvertFactorToVector3OrQuaternion ( factor , babylonTransformNode , animation , animationType , animationChannelTargetPath , convertToRightHandedSystem , useQuaternion ) { let property ; let componentName ; let value = null ; const basePositionRotationOrScale = _GLTFAnimation . _GetBasePositionRotationOrScale ( babylonTransformNode , animationChannelTargetPath , convertToRightHandedSystem , useQuaternion ) ; if ( animationType === Animation . ANIMATIONTYPE_FLOAT ) { property = animation . targetProperty . split ( '.' ) ; componentName = property ? property [ 1 ] : '' ; value = useQuaternion ? Quaternion . FromArray ( basePositionRotationOrScale ) . normalize ( ) : Vector3 . FromArray ( basePositionRotationOrScale ) ; switch ( componentName ) { case 'x' : { value [ componentName ] = ( convertToRightHandedSystem && useQuaternion && ( animationChannelTargetPath !== AnimationChannelTargetPath . SCALE ) ) ? - factor : factor ; break ; } case 'y' : { value [ componentName ] = ( convertToRightHandedSystem && useQuaternion && ( animationChannelTargetPath !== AnimationChannelTargetPath . SCALE ) ) ? - factor : factor ; break ; } case 'z' : { value [ componentName ] = ( convertToRightHandedSystem && ! useQuaternion && ( animationChannelTargetPath !== AnimationChannelTargetPath . SCALE ) ) ? - factor : factor ; break ; } case 'w' : { ( value as Quaternion ) . w = factor ; break ; } default : { Tools . Error ( `glTFAnimation: Unsupported component type "${ componentName }" for scale animation!` ) ; } } } return value ; } private static _SetInterpolatedValue ( babylonTransformNode , value , time , animation , animationChannelTargetPath , quaternionCache , inputs , outputs , convertToRightHandedSystem , useQuaternion ) { const animationType = animation . dataType ; let cacheValue ; 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 . ROTATION ) { if ( useQuaternion ) { quaternionCache = value as Quaternion ; } else { cacheValue = value as Vector3 ; Quaternion . RotationYawPitchRollToRef ( cacheValue . y , cacheValue . x , cacheValue . z , quaternionCache ) ; } if ( convertToRightHandedSystem ) { _GLTFUtilities . _GetRightHandedQuaternionFromRef ( quaternionCache ) ; if ( ! babylonTransformNode . parent ) { quaternionCache = Quaternion . FromArray ( [ 0 , 1 , 0 , 0 ] ) . multiply ( quaternionCache ) ; } } outputs . push ( quaternionCache . asArray ( ) ) ; } else if ( animationChannelTargetPath === AnimationChannelTargetPath . WEIGHTS ) { outputs . push ( [ value as number ] ) ; } else { cacheValue = value as Vector3 ; if ( convertToRightHandedSystem && ( animationChannelTargetPath !== AnimationChannelTargetPath . SCALE ) ) { _GLTFUtilities . _GetRightHandedPositionVector3FromRef ( cacheValue ) ; if ( ! babylonTransformNode . parent ) { cacheValue . x *= - 1 ; cacheValue . z *= - 1 ; } } outputs . push ( cacheValue . asArray ( ) ) ; } } } param babylonTransformNode param animation param animationChannelTargetPath param frameDelta param inputs param outputs param convertToRightHandedSystem param useQuaternion private static _CreateLinearOrStepAnimation ( babylonTransformNode , animation , animationChannelTargetPath , frameDelta , inputs , outputs , convertToRightHandedSystem , useQuaternion ) { for ( let keyFrame of animation . getKeys ( ) ) { inputs . push ( keyFrame . frame / animation . framePerSecond ) ; _GLTFAnimation . _AddKeyframeValue ( keyFrame , animation , outputs , animationChannelTargetPath , babylonTransformNode , convertToRightHandedSystem , useQuaternion ) ; } } param babylonTransformNode param animation param animationChannelTargetPath param frameDelta param inputs param outputs param convertToRightHandedSystem param useQuaternion private static _CreateCubicSplineAnimation ( babylonTransformNode , animation , animationChannelTargetPath , frameDelta , inputs , outputs , convertToRightHandedSystem , useQuaternion ) { animation . getKeys ( ) . forEach ( function ( keyFrame ) { inputs . push ( keyFrame . frame / animation . framePerSecond ) ; _GLTFAnimation . AddSplineTangent ( babylonTransformNode , _TangentType . INTANGENT , outputs , animationChannelTargetPath , AnimationSamplerInterpolation . CUBICSPLINE , keyFrame , frameDelta , useQuaternion , convertToRightHandedSystem ) ; _GLTFAnimation . _AddKeyframeValue ( keyFrame , animation , outputs , animationChannelTargetPath , babylonTransformNode , convertToRightHandedSystem , useQuaternion ) ; _GLTFAnimation . AddSplineTangent ( babylonTransformNode , _TangentType . OUTTANGENT , outputs , animationChannelTargetPath , AnimationSamplerInterpolation . CUBICSPLINE , keyFrame , frameDelta , useQuaternion , convertToRightHandedSystem ) ; } ) ; } private static _GetBasePositionRotationOrScale ( babylonTransformNode , animationChannelTargetPath , convertToRightHandedSystem , useQuaternion ) { let basePositionRotationOrScale ; 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 ( ) ; } } } else { basePositionRotationOrScale = Quaternion . Identity ( ) . asArray ( ) ; } } else { basePositionRotationOrScale = babylonTransformNode . rotation . asArray ( ) ; _GLTFUtilities . _GetRightHandedNormalArray3FromRef ( basePositionRotationOrScale ) ; } } else if ( animationChannelTargetPath === AnimationChannelTargetPath . TRANSLATION ) { basePositionRotationOrScale = babylonTransformNode . position . asArray ( ) ; if ( convertToRightHandedSystem ) { _GLTFUtilities . _GetRightHandedPositionArray3FromRef ( basePositionRotationOrScale ) ; } } else { basePositionRotationOrScale = babylonTransformNode . scaling . asArray ( ) ; } return basePositionRotationOrScale ; } param keyFrame param animation param outputs param animationChannelTargetPath param basePositionRotationOrScale param convertToRightHandedSystem param useQuaternion private static _AddKeyframeValue ( keyFrame , animation , outputs , animationChannelTargetPath , babylonTransformNode , convertToRightHandedSystem , useQuaternion ) { let value ; let newPositionRotationOrScale ; const animationType = animation . dataType ; if ( animationType === Animation . ANIMATIONTYPE_VECTOR3 ) { value = keyFrame . value . asArray ( ) ; if ( animationChannelTargetPath === AnimationChannelTargetPath . ROTATION ) { const array = Vector3 . FromArray ( value ) ; let rotationQuaternion = Quaternion . RotationYawPitchRoll ( array . y , array . x , array . z ) ; if ( convertToRightHandedSystem ) { _GLTFUtilities . _GetRightHandedQuaternionFromRef ( rotationQuaternion ) ; if ( ! babylonTransformNode . parent ) { rotationQuaternion = Quaternion . FromArray ( [ 0 , 1 , 0 , 0 ] ) . multiply ( rotationQuaternion ) ; } } value = rotationQuaternion . asArray ( ) ; } else if ( animationChannelTargetPath === AnimationChannelTargetPath . TRANSLATION ) { if ( convertToRightHandedSystem ) { _GLTFUtilities . _GetRightHandedNormalArray3FromRef ( value ) ; if ( ! babylonTransformNode . parent ) { value [ 0 ] *= - 1 ; value [ 2 ] *= - 1 ; } } } outputs . push ( value ) ; } else if ( animationType === Animation . ANIMATIONTYPE_FLOAT ) { if ( animationChannelTargetPath === AnimationChannelTargetPath . WEIGHTS ) { outputs . push ( [ keyFrame . value ] ) ; } else { newPositionRotationOrScale = this . _ConvertFactorToVector3OrQuaternion ( keyFrame . value as number , babylonTransformNode , animation , animationType , animationChannelTargetPath , convertToRightHandedSystem , useQuaternion ) ; if ( newPositionRotationOrScale ) { if ( animationChannelTargetPath === AnimationChannelTargetPath . ROTATION ) { let posRotScale = useQuaternion ? newPositionRotationOrScale as Quaternion : Quaternion . RotationYawPitchRoll ( newPositionRotationOrScale . y , newPositionRotationOrScale . x , newPositionRotationOrScale . z ) . normalize ( ) ; if ( convertToRightHandedSystem ) { _GLTFUtilities . _GetRightHandedQuaternionFromRef ( posRotScale ) ; if ( ! babylonTransformNode . parent ) { posRotScale = Quaternion . FromArray ( [ 0 , 1 , 0 , 0 ] ) . multiply ( posRotScale ) ; } } outputs . push ( posRotScale . asArray ( ) ) ; } else if ( animationChannelTargetPath === AnimationChannelTargetPath . TRANSLATION ) { if ( convertToRightHandedSystem ) { _GLTFUtilities . _GetRightHandedNormalVector3FromRef ( newPositionRotationOrScale as Vector3 ) ; if ( ! babylonTransformNode . parent ) { newPositionRotationOrScale . x *= - 1 ; newPositionRotationOrScale . z *= - 1 ; } } } outputs . push ( newPositionRotationOrScale . asArray ( ) ) ; } } } else if ( animationType === Animation . ANIMATIONTYPE_QUATERNION ) { value = ( keyFrame . value as Quaternion ) . normalize ( ) . asArray ( ) ; if ( convertToRightHandedSystem ) { _GLTFUtilities . _GetRightHandedQuaternionArrayFromRef ( value ) ; if ( ! babylonTransformNode . parent ) { value = Quaternion . FromArray ( [ 0 , 1 , 0 , 0 ] ) . multiply ( Quaternion . FromArray ( value ) ) . asArray ( ) ; } } outputs . push ( value ) ; } else { Tools . Error ( 'glTFAnimation: Unsupported key frame values for animation!' ) ; } } param keyFrames param animationChannelTargetPath param useQuaternion private static _DeduceInterpolation ( keyFrames , animationChannelTargetPath , useQuaternion ) { let interpolationType ; let shouldBakeAnimation = false ; let key ; if ( animationChannelTargetPath === AnimationChannelTargetPath . ROTATION && ! useQuaternion ) { return { interpolationType : AnimationSamplerInterpolation . LINEAR , shouldBakeAnimation : true } ; } for ( let i = 0 , length = keyFrames . length ; i < length ; ++ i ) { key = keyFrames [ i ] ; if ( key . inTangent || key . outTangent ) { if ( interpolationType ) { if ( interpolationType !== AnimationSamplerInterpolation . CUBICSPLINE ) { interpolationType = AnimationSamplerInterpolation . LINEAR ; shouldBakeAnimation = true ; break ; } } else { interpolationType = AnimationSamplerInterpolation . CUBICSPLINE ; } } else { if ( interpolationType ) { if ( interpolationType === AnimationSamplerInterpolation . CUBICSPLINE || ( key . interpolation && ( key . interpolation === AnimationKeyInterpolation . STEP ) && interpolationType !== AnimationSamplerInterpolation . STEP ) ) { interpolationType = AnimationSamplerInterpolation . LINEAR ; shouldBakeAnimation = true ; break ; } } else { if ( key . interpolation && ( key . interpolation === AnimationKeyInterpolation . STEP ) ) { interpolationType = AnimationSamplerInterpolation . STEP ; } else { interpolationType = AnimationSamplerInterpolation . LINEAR ; } } } } if ( ! interpolationType ) { interpolationType = AnimationSamplerInterpolation . LINEAR ; } return { interpolationType : interpolationType , shouldBakeAnimation : shouldBakeAnimation } ; } param tangentType param outputs param animationChannelTargetPath param interpolation param keyFrame param frameDelta param useQuaternion param convertToRightHandedSystem private static AddSplineTangent ( babylonTransformNode , tangentType , outputs , animationChannelTargetPath , interpolation , keyFrame , frameDelta , useQuaternion , convertToRightHandedSystem ) { let tangent ; let tangentValue = tangentType === _TangentType . INTANGENT ? keyFrame . inTangent : keyFrame . outTangent ; if ( interpolation === AnimationSamplerInterpolation . CUBICSPLINE ) { if ( animationChannelTargetPath === AnimationChannelTargetPath . ROTATION ) { if ( tangentValue ) { if ( useQuaternion ) { tangent = ( tangentValue as Quaternion ) . asArray ( ) ; } else { const array = ( tangentValue as Vector3 ) ; tangent = Quaternion . RotationYawPitchRoll ( array . y , array . x , array . z ) . asArray ( ) ; } if ( convertToRightHandedSystem ) { _GLTFUtilities . _GetRightHandedQuaternionArrayFromRef ( tangent ) ; if ( ! babylonTransformNode . parent ) { tangent = Quaternion . FromArray ( [ 0 , 1 , 0 , 0 ] ) . multiply ( Quaternion . FromArray ( tangent ) ) . asArray ( ) ; } } } else { tangent = [ 0 , 0 , 0 , 0 ] ; } } else if ( animationChannelTargetPath === AnimationChannelTargetPath . WEIGHTS ) { if ( tangentValue ) { tangent = [ tangentValue as number ] ; } else { tangent = [ 0 ] ; } } else { if ( tangentValue ) { tangent = ( tangentValue as Vector3 ) . asArray ( ) ; if ( convertToRightHandedSystem ) { if ( animationChannelTargetPath === AnimationChannelTargetPath . TRANSLATION ) { _GLTFUtilities . _GetRightHandedPositionArray3FromRef ( tangent ) ; if ( ! babylonTransformNode . parent ) { tangent [ 0 ] *= - 1 ; tangent [ 2 ] *= - 1 ; } } } } else { tangent = [ 0 , 0 , 0 ] ; } } outputs . push ( tangent ) ; } } param keyFrames returns private static calculateMinMaxKeyFrames ( keyFrames ) { let min = Infinity ; let max = - Infinity ; keyFrames . forEach ( function ( keyFrame ) { min = Math . min ( min , keyFrame . frame ) ; max = Math . max ( max , keyFrame . frame ) ; } ) ; return { min : min , max : max } ; } }