babylon.glTFAnimation.ts 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. /// <reference path="../../../../dist/preview release/glTF2Interface/babylon.glTF2Interface.d.ts"/>
  2. module BABYLON.GLTF2 {
  3. /**
  4. * @hidden
  5. * Interface to store animation data.
  6. */
  7. export interface _IAnimationData {
  8. /**
  9. * Keyframe data.
  10. */
  11. inputs: number[],
  12. /**
  13. * Value data.
  14. */
  15. outputs: number[][],
  16. /**
  17. * Animation interpolation data.
  18. */
  19. samplerInterpolation: AnimationSamplerInterpolation,
  20. /**
  21. * Minimum keyframe value.
  22. */
  23. inputsMin: number,
  24. /**
  25. * Maximum keyframe value.
  26. */
  27. inputsMax: number,
  28. }
  29. /**
  30. * @hidden
  31. */
  32. export interface _IAnimationInfo {
  33. /**
  34. * The target channel for the animation
  35. */
  36. animationChannelTargetPath: AnimationChannelTargetPath,
  37. /**
  38. * The glTF accessor type for the data.
  39. */
  40. dataAccessorType: AccessorType.VEC3 | AccessorType.VEC4,
  41. /**
  42. * Specifies if quaternions should be used.
  43. */
  44. useQuaternion: boolean
  45. }
  46. /**
  47. * @hidden
  48. * Enum for handling in tangent and out tangent.
  49. */
  50. enum _TangentType {
  51. /**
  52. * Specifies that input tangents are used.
  53. */
  54. INTANGENT,
  55. /**
  56. * Specifies that output tangents are used.
  57. */
  58. OUTTANGENT
  59. }
  60. /**
  61. * @hidden
  62. * Utility class for generating glTF animation data from BabylonJS.
  63. */
  64. export class _GLTFAnimation {
  65. /**
  66. * @ignore
  67. *
  68. * Creates glTF channel animation from BabylonJS animation.
  69. * @param babylonTransformNode - BabylonJS mesh.
  70. * @param animation - animation.
  71. * @param animationChannelTargetPath - The target animation channel.
  72. * @param convertToRightHandedSystem - Specifies if the values should be converted to right-handed.
  73. * @param useQuaternion - Specifies if quaternions are used.
  74. * @returns nullable IAnimationData
  75. */
  76. public static _CreateNodeAnimation(babylonTransformNode: TransformNode, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean, animationSampleRate: number): Nullable<_IAnimationData> {
  77. const inputs: number[] = [];
  78. const outputs: number[][] = [];
  79. const keyFrames = animation.getKeys();
  80. const minMaxKeyFrames = _GLTFAnimation.calculateMinMaxKeyFrames(keyFrames);
  81. const interpolationOrBake = _GLTFAnimation._DeduceInterpolation(keyFrames, animationChannelTargetPath, useQuaternion);
  82. const frameDelta = minMaxKeyFrames.max - minMaxKeyFrames.min;
  83. const interpolation = interpolationOrBake.interpolationType;
  84. const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation;
  85. if (shouldBakeAnimation) {
  86. _GLTFAnimation._CreateBakedAnimation(babylonTransformNode, animation, animationChannelTargetPath, minMaxKeyFrames.min, minMaxKeyFrames.max, animation.framePerSecond, animationSampleRate, inputs, outputs, minMaxKeyFrames, convertToRightHandedSystem, useQuaternion);
  87. }
  88. else {
  89. if (interpolation === AnimationSamplerInterpolation.LINEAR || interpolation === AnimationSamplerInterpolation.STEP) {
  90. _GLTFAnimation._CreateLinearOrStepAnimation(babylonTransformNode, animation, animationChannelTargetPath, frameDelta, inputs, outputs, convertToRightHandedSystem, useQuaternion);
  91. }
  92. else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) {
  93. _GLTFAnimation._CreateCubicSplineAnimation(babylonTransformNode, animation, animationChannelTargetPath, frameDelta, inputs, outputs, convertToRightHandedSystem, useQuaternion);
  94. }
  95. else {
  96. _GLTFAnimation._CreateBakedAnimation(babylonTransformNode, animation, animationChannelTargetPath, minMaxKeyFrames.min, minMaxKeyFrames.max, animation.framePerSecond, animationSampleRate, inputs, outputs, minMaxKeyFrames, convertToRightHandedSystem, useQuaternion);
  97. }
  98. }
  99. if (inputs.length && outputs.length) {
  100. const result: _IAnimationData = {
  101. inputs: inputs,
  102. outputs: outputs,
  103. samplerInterpolation: interpolation,
  104. inputsMin: shouldBakeAnimation ? minMaxKeyFrames.min : Tools.FloatRound(minMaxKeyFrames.min / animation.framePerSecond),
  105. inputsMax: shouldBakeAnimation ? minMaxKeyFrames.max : Tools.FloatRound(minMaxKeyFrames.max / animation.framePerSecond)
  106. }
  107. return result;
  108. }
  109. return null;
  110. }
  111. private static _DeduceAnimationInfo(animation: Animation): Nullable<_IAnimationInfo> {
  112. let animationChannelTargetPath: Nullable<AnimationChannelTargetPath> = null;
  113. let dataAccessorType = AccessorType.VEC3;
  114. let useQuaternion: boolean = false;
  115. let property = animation.targetProperty.split('.');
  116. switch (property[0]) {
  117. case 'scaling': {
  118. animationChannelTargetPath = AnimationChannelTargetPath.SCALE;
  119. break;
  120. }
  121. case 'position': {
  122. animationChannelTargetPath = AnimationChannelTargetPath.TRANSLATION;
  123. break;
  124. }
  125. case 'rotation': {
  126. dataAccessorType = AccessorType.VEC4;
  127. animationChannelTargetPath = AnimationChannelTargetPath.ROTATION;
  128. break;
  129. }
  130. case 'rotationQuaternion': {
  131. dataAccessorType = AccessorType.VEC4;
  132. useQuaternion = true;
  133. animationChannelTargetPath = AnimationChannelTargetPath.ROTATION;
  134. break;
  135. }
  136. default: {
  137. Tools.Error(`Unsupported animatable property ${property[0]}`);
  138. }
  139. }
  140. if (animationChannelTargetPath) {
  141. return { animationChannelTargetPath: animationChannelTargetPath, dataAccessorType: dataAccessorType, useQuaternion: useQuaternion };
  142. }
  143. else {
  144. Tools.Error('animation channel target path and data accessor type could be deduced');
  145. }
  146. return null;
  147. }
  148. /**
  149. * @ignore
  150. * Create node animations from the transform node animations
  151. * @param babylonTransformNode
  152. * @param runtimeGLTFAnimation
  153. * @param idleGLTFAnimations
  154. * @param nodeMap
  155. * @param nodes
  156. * @param binaryWriter
  157. * @param bufferViews
  158. * @param accessors
  159. * @param convertToRightHandedSystem
  160. */
  161. public static _CreateNodeAnimationFromTransformNodeAnimations(babylonTransformNode: TransformNode, runtimeGLTFAnimation: IAnimation, idleGLTFAnimations: IAnimation[], nodeMap: { [key: number]: number }, nodes: INode[], binaryWriter: _BinaryWriter, bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHandedSystem: boolean, animationSampleRate: number) {
  162. let glTFAnimation: IAnimation;
  163. if (babylonTransformNode.animations) {
  164. for (let animation of babylonTransformNode.animations) {
  165. let animationInfo = _GLTFAnimation._DeduceAnimationInfo(animation);
  166. if (animationInfo) {
  167. glTFAnimation = {
  168. name: animation.name,
  169. samplers: [],
  170. channels: []
  171. }
  172. _GLTFAnimation.AddAnimation(`${animation.name}`,
  173. animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation,
  174. babylonTransformNode,
  175. animation,
  176. animationInfo.dataAccessorType,
  177. animationInfo.animationChannelTargetPath,
  178. nodeMap,
  179. binaryWriter,
  180. bufferViews,
  181. accessors,
  182. convertToRightHandedSystem,
  183. animationInfo.useQuaternion,
  184. animationSampleRate
  185. );
  186. if (glTFAnimation.samplers.length && glTFAnimation.channels.length) {
  187. idleGLTFAnimations.push(glTFAnimation);
  188. }
  189. }
  190. };
  191. }
  192. }
  193. /**
  194. * @ignore
  195. * Create node animations from the animation groups
  196. * @param babylonScene
  197. * @param glTFAnimations
  198. * @param nodeMap
  199. * @param nodes
  200. * @param binaryWriter
  201. * @param bufferViews
  202. * @param accessors
  203. * @param convertToRightHandedSystem
  204. */
  205. public static _CreateNodeAnimationFromAnimationGroups(babylonScene: Scene, glTFAnimations: IAnimation[], nodeMap: { [key: number]: number }, nodes: INode[], binaryWriter: _BinaryWriter, bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHandedSystem: boolean, animationSampleRate: number) {
  206. let glTFAnimation: IAnimation;
  207. if (babylonScene.animationGroups) {
  208. let animationGroups = babylonScene.animationGroups;
  209. for (let animationGroup of animationGroups) {
  210. glTFAnimation = {
  211. name: animationGroup.name,
  212. channels: [],
  213. samplers: []
  214. }
  215. for (let targetAnimation of animationGroup.targetedAnimations) {
  216. let target = targetAnimation.target;
  217. let animation = targetAnimation.animation;
  218. if (target instanceof Mesh || target.length === 1 && target[0] instanceof Mesh) { // TODO: Update to support bones
  219. let animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation);
  220. if (animationInfo) {
  221. let babylonMesh = target instanceof Mesh ? target : target[0] as Mesh;
  222. _GLTFAnimation.AddAnimation(`${animation.name}`,
  223. glTFAnimation,
  224. babylonMesh,
  225. animation,
  226. animationInfo.dataAccessorType,
  227. animationInfo.animationChannelTargetPath,
  228. nodeMap,
  229. binaryWriter,
  230. bufferViews,
  231. accessors,
  232. convertToRightHandedSystem,
  233. animationInfo.useQuaternion,
  234. animationSampleRate
  235. );
  236. }
  237. }
  238. };
  239. if (glTFAnimation.channels.length && glTFAnimation.samplers.length) {
  240. glTFAnimations.push(glTFAnimation);
  241. }
  242. };
  243. }
  244. }
  245. private static AddAnimation(name: string, glTFAnimation: IAnimation, babylonTransformNode: TransformNode, animation: Animation, dataAccessorType: AccessorType, animationChannelTargetPath: AnimationChannelTargetPath, nodeMap: { [key: number]: number }, binaryWriter: _BinaryWriter, bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHandedSystem: boolean, useQuaternion: boolean, animationSampleRate: number) {
  246. let animationData = _GLTFAnimation._CreateNodeAnimation(babylonTransformNode, animation, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion, animationSampleRate);
  247. let bufferView: IBufferView;
  248. let accessor: IAccessor;
  249. let keyframeAccessorIndex: number;
  250. let dataAccessorIndex: number;
  251. let outputLength: number;
  252. let animationSampler: IAnimationSampler;
  253. let animationChannel: IAnimationChannel;
  254. if (animationData) {
  255. let nodeIndex = nodeMap[babylonTransformNode.uniqueId];
  256. // Creates buffer view and accessor for key frames.
  257. let byteLength = animationData.inputs.length * 4;
  258. bufferView = _GLTFUtilities.CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} keyframe data view`);
  259. bufferViews.push(bufferView);
  260. animationData.inputs.forEach(function (input) {
  261. binaryWriter.setFloat32(input);
  262. });
  263. accessor = _GLTFUtilities.CreateAccessor(bufferViews.length - 1, `${name} keyframes`, AccessorType.SCALAR, AccessorComponentType.FLOAT, animationData.inputs.length, null, [animationData.inputsMin], [animationData.inputsMax]);
  264. accessors.push(accessor);
  265. keyframeAccessorIndex = accessors.length - 1;
  266. // create bufferview and accessor for keyed values.
  267. outputLength = animationData.outputs.length;
  268. byteLength = dataAccessorType === AccessorType.VEC3 ? animationData.outputs.length * 12 : animationData.outputs.length * 16;
  269. // check for in and out tangents
  270. bufferView = _GLTFUtilities.CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} data view`);
  271. bufferViews.push(bufferView);
  272. animationData.outputs.forEach(function (output) {
  273. output.forEach(function (entry) {
  274. binaryWriter.setFloat32(entry);
  275. });
  276. });
  277. accessor = _GLTFUtilities.CreateAccessor(bufferViews.length - 1, `${name} data`, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null, null, null);
  278. accessors.push(accessor);
  279. dataAccessorIndex = accessors.length - 1;
  280. // create sampler
  281. animationSampler = {
  282. interpolation: animationData.samplerInterpolation,
  283. input: keyframeAccessorIndex,
  284. output: dataAccessorIndex
  285. }
  286. glTFAnimation.samplers.push(animationSampler);
  287. // create channel
  288. animationChannel = {
  289. sampler: glTFAnimation.samplers.length - 1,
  290. target: {
  291. node: nodeIndex,
  292. path: animationChannelTargetPath
  293. }
  294. }
  295. glTFAnimation.channels.push(animationChannel);
  296. }
  297. }
  298. /**
  299. * Create a baked animation
  300. * @param babylonTransformNode BabylonJS mesh
  301. * @param animation BabylonJS animation corresponding to the BabylonJS mesh
  302. * @param animationChannelTargetPath animation target channel
  303. * @param minFrame minimum animation frame
  304. * @param maxFrame maximum animation frame
  305. * @param fps frames per second of the animation
  306. * @param inputs input key frames of the animation
  307. * @param outputs output key frame data of the animation
  308. * @param convertToRightHandedSystem converts the values to right-handed
  309. * @param useQuaternion specifies if quaternions should be used
  310. */
  311. private static _CreateBakedAnimation(babylonTransformNode: TransformNode, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, minFrame: number, maxFrame: number, fps: number, sampleRate: number, inputs: number[], outputs: number[][], minMaxFrames: { min: number, max: number }, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
  312. let value: number | Vector3 | Quaternion;
  313. let quaternionCache: Quaternion = Quaternion.Identity();
  314. let previousTime: Nullable<number> = null;
  315. let time: number;
  316. let maxUsedFrame: Nullable<number> = null;
  317. let currKeyFrame: Nullable<IAnimationKey> = null;
  318. let nextKeyFrame: Nullable<IAnimationKey> = null;
  319. let prevKeyFrame: Nullable<IAnimationKey> = null;
  320. let endFrame: Nullable<number> = null;
  321. minMaxFrames.min = Tools.FloatRound(minFrame / fps);
  322. let keyFrames = animation.getKeys();
  323. for (let i = 0, length = keyFrames.length; i < length; ++i) {
  324. endFrame = null;
  325. currKeyFrame = keyFrames[i];
  326. if (i + 1 < length) {
  327. nextKeyFrame = keyFrames[i + 1];
  328. if (currKeyFrame.value.equals(nextKeyFrame.value)) {
  329. if (i === 0) { // set the first frame to itself
  330. endFrame = currKeyFrame.frame;
  331. }
  332. else {
  333. continue;
  334. }
  335. }
  336. else {
  337. endFrame = nextKeyFrame.frame;
  338. }
  339. }
  340. else {
  341. // at the last key frame
  342. prevKeyFrame = keyFrames[i - 1];
  343. if (currKeyFrame.value.equals(prevKeyFrame.value)) {
  344. continue;
  345. }
  346. else {
  347. endFrame = maxFrame;
  348. }
  349. }
  350. if (endFrame) {
  351. for (let f = currKeyFrame.frame; f <= endFrame; f += sampleRate) {
  352. time = Tools.FloatRound(f / fps);
  353. if (time === previousTime) {
  354. continue;
  355. }
  356. previousTime = time;
  357. maxUsedFrame = time;
  358. value = animation._interpolate(f, 0, undefined, animation.loopMode);
  359. _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, convertToRightHandedSystem, useQuaternion);
  360. }
  361. }
  362. }
  363. if (maxUsedFrame) {
  364. minMaxFrames.max = maxUsedFrame;
  365. }
  366. }
  367. private static _ConvertFactorToVector3OrQuaternion(factor: number, babylonTransformNode: TransformNode, animation: Animation, animationType: number, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean): Nullable<Vector3 | Quaternion> {
  368. let property: string[];
  369. let componentName: string;
  370. let value: Nullable<Quaternion | Vector3> = null;
  371. const basePositionRotationOrScale = _GLTFAnimation._GetBasePositionRotationOrScale(babylonTransformNode, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion);
  372. if (animationType === Animation.ANIMATIONTYPE_FLOAT) { // handles single component x, y, z or w component animation by using a base property and animating over a component.
  373. property = animation.targetProperty.split('.');
  374. componentName = property ? property[1] : ''; // x, y, or z component
  375. value = useQuaternion ? BABYLON.Quaternion.FromArray(basePositionRotationOrScale).normalize() : BABYLON.Vector3.FromArray(basePositionRotationOrScale);
  376. switch (componentName) {
  377. case 'x': {
  378. value[componentName] = (convertToRightHandedSystem && useQuaternion && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) ? -factor : factor;
  379. break;
  380. }
  381. case 'y': {
  382. value[componentName] = (convertToRightHandedSystem && useQuaternion && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) ? -factor : factor;
  383. break;
  384. }
  385. case 'z': {
  386. value[componentName] = (convertToRightHandedSystem && !useQuaternion && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) ? -factor : factor;
  387. break;
  388. }
  389. case 'w': {
  390. (value as Quaternion).w = factor;
  391. break;
  392. }
  393. default: {
  394. Tools.Error(`glTFAnimation: Unsupported component type "${componentName}" for scale animation!`);
  395. }
  396. }
  397. }
  398. return value;
  399. }
  400. private static _SetInterpolatedValue(babylonTransformNode: TransformNode, value: Nullable<number | Vector3 | Quaternion>, time: number, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, quaternionCache: Quaternion, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
  401. const animationType = animation.dataType;
  402. let cacheValue: Vector3 | Quaternion;
  403. inputs.push(time);
  404. if (typeof value === "number") {
  405. value = this._ConvertFactorToVector3OrQuaternion(value as number, babylonTransformNode, animation, animationType, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion);
  406. }
  407. if (value) {
  408. if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
  409. if (useQuaternion) {
  410. quaternionCache = value as Quaternion;
  411. }
  412. else {
  413. cacheValue = value as Vector3;
  414. Quaternion.RotationYawPitchRollToRef(cacheValue.y, cacheValue.x, cacheValue.z, quaternionCache);
  415. }
  416. if (convertToRightHandedSystem) {
  417. _GLTFUtilities.GetRightHandedQuaternionFromRef(quaternionCache);
  418. if (!babylonTransformNode.parent) {
  419. quaternionCache = Quaternion.FromArray([0, 1, 0, 0]).multiply(quaternionCache);
  420. }
  421. }
  422. outputs.push(quaternionCache.asArray());
  423. }
  424. else {
  425. cacheValue = value as Vector3;
  426. if (convertToRightHandedSystem && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) {
  427. _GLTFUtilities.GetRightHandedPositionVector3FromRef(cacheValue);
  428. if (!babylonTransformNode.parent) {
  429. cacheValue.x *= -1;
  430. cacheValue.z *= -1;
  431. }
  432. }
  433. outputs.push(cacheValue.asArray());
  434. }
  435. }
  436. }
  437. /**
  438. * Creates linear animation from the animation key frames
  439. * @param babylonTransformNode BabylonJS mesh
  440. * @param animation BabylonJS animation
  441. * @param animationChannelTargetPath The target animation channel
  442. * @param frameDelta The difference between the last and first frame of the animation
  443. * @param inputs Array to store the key frame times
  444. * @param outputs Array to store the key frame data
  445. * @param convertToRightHandedSystem Specifies if the position data should be converted to right handed
  446. * @param useQuaternion Specifies if quaternions are used in the animation
  447. */
  448. private static _CreateLinearOrStepAnimation(babylonTransformNode: TransformNode, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, frameDelta: number, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
  449. for (let keyFrame of animation.getKeys()) {
  450. inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds.
  451. _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, convertToRightHandedSystem, useQuaternion);
  452. };
  453. }
  454. /**
  455. * Creates cubic spline animation from the animation key frames
  456. * @param babylonTransformNode BabylonJS mesh
  457. * @param animation BabylonJS animation
  458. * @param animationChannelTargetPath The target animation channel
  459. * @param frameDelta The difference between the last and first frame of the animation
  460. * @param inputs Array to store the key frame times
  461. * @param outputs Array to store the key frame data
  462. * @param convertToRightHandedSystem Specifies if the position data should be converted to right handed
  463. * @param useQuaternion Specifies if quaternions are used in the animation
  464. */
  465. private static _CreateCubicSplineAnimation(babylonTransformNode: TransformNode, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, frameDelta: number, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
  466. animation.getKeys().forEach(function (keyFrame) {
  467. inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds.
  468. _GLTFAnimation.AddSplineTangent(
  469. babylonTransformNode,
  470. _TangentType.INTANGENT,
  471. outputs,
  472. animationChannelTargetPath,
  473. AnimationSamplerInterpolation.CUBICSPLINE,
  474. keyFrame,
  475. frameDelta,
  476. useQuaternion,
  477. convertToRightHandedSystem
  478. );
  479. _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, convertToRightHandedSystem, useQuaternion);
  480. _GLTFAnimation.AddSplineTangent(
  481. babylonTransformNode,
  482. _TangentType.OUTTANGENT,
  483. outputs,
  484. animationChannelTargetPath,
  485. AnimationSamplerInterpolation.CUBICSPLINE,
  486. keyFrame,
  487. frameDelta,
  488. useQuaternion,
  489. convertToRightHandedSystem
  490. );
  491. });
  492. }
  493. private static _GetBasePositionRotationOrScale(babylonTransformNode: TransformNode, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
  494. let basePositionRotationOrScale: number[];
  495. if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
  496. if (useQuaternion) {
  497. if (babylonTransformNode.rotationQuaternion) {
  498. basePositionRotationOrScale = babylonTransformNode.rotationQuaternion.asArray();
  499. if (convertToRightHandedSystem) {
  500. _GLTFUtilities.GetRightHandedQuaternionArrayFromRef(basePositionRotationOrScale);
  501. if (!babylonTransformNode.parent) {
  502. basePositionRotationOrScale = Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(basePositionRotationOrScale)).asArray();
  503. }
  504. }
  505. }
  506. else {
  507. basePositionRotationOrScale = BABYLON.Quaternion.Identity().asArray();
  508. }
  509. }
  510. else {
  511. basePositionRotationOrScale = babylonTransformNode.rotation.asArray();
  512. _GLTFUtilities.GetRightHandedNormalArray3FromRef(basePositionRotationOrScale);
  513. }
  514. }
  515. else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
  516. basePositionRotationOrScale = babylonTransformNode.position.asArray();
  517. if (convertToRightHandedSystem) {
  518. _GLTFUtilities.GetRightHandedPositionArray3FromRef(basePositionRotationOrScale);
  519. }
  520. }
  521. else { // scale
  522. basePositionRotationOrScale = babylonTransformNode.scaling.asArray();
  523. }
  524. return basePositionRotationOrScale;
  525. }
  526. /**
  527. * Adds a key frame value
  528. * @param keyFrame
  529. * @param animation
  530. * @param outputs
  531. * @param animationChannelTargetPath
  532. * @param basePositionRotationOrScale
  533. * @param convertToRightHandedSystem
  534. * @param useQuaternion
  535. */
  536. private static _AddKeyframeValue(keyFrame: IAnimationKey, animation: Animation, outputs: number[][], animationChannelTargetPath: AnimationChannelTargetPath, babylonTransformNode: TransformNode, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
  537. let value: number[];
  538. let newPositionRotationOrScale: Nullable<Vector3 | Quaternion>;
  539. const animationType = animation.dataType;
  540. if (animationType === Animation.ANIMATIONTYPE_VECTOR3) {
  541. value = keyFrame.value.asArray();
  542. if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
  543. const array = Vector3.FromArray(value);
  544. let rotationQuaternion = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z);
  545. if (convertToRightHandedSystem) {
  546. _GLTFUtilities.GetRightHandedQuaternionFromRef(rotationQuaternion);
  547. if (!babylonTransformNode.parent) {
  548. rotationQuaternion = Quaternion.FromArray([0, 1, 0, 0]).multiply(rotationQuaternion);
  549. }
  550. }
  551. value = rotationQuaternion.asArray();
  552. }
  553. else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
  554. if (convertToRightHandedSystem) {
  555. _GLTFUtilities.GetRightHandedNormalArray3FromRef(value);
  556. if (!babylonTransformNode.parent) {
  557. value[0] *= -1;
  558. value[2] *= -1;
  559. }
  560. }
  561. }
  562. outputs.push(value); // scale vector.
  563. }
  564. else if (animationType === Animation.ANIMATIONTYPE_FLOAT) { // handles single component x, y, z or w component animation by using a base property and animating over a component.
  565. newPositionRotationOrScale = this._ConvertFactorToVector3OrQuaternion(keyFrame.value as number, babylonTransformNode, animation, animationType, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion);
  566. if (newPositionRotationOrScale) {
  567. if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
  568. let posRotScale = useQuaternion ? newPositionRotationOrScale as Quaternion : Quaternion.RotationYawPitchRoll(newPositionRotationOrScale.y, newPositionRotationOrScale.x, newPositionRotationOrScale.z).normalize();
  569. if (convertToRightHandedSystem) {
  570. _GLTFUtilities.GetRightHandedQuaternionFromRef(posRotScale);
  571. if (!babylonTransformNode.parent) {
  572. posRotScale = Quaternion.FromArray([0, 1, 0, 0]).multiply(posRotScale);
  573. }
  574. }
  575. outputs.push(posRotScale.asArray());
  576. }
  577. else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
  578. if (convertToRightHandedSystem) {
  579. _GLTFUtilities.GetRightHandedNormalVector3FromRef(newPositionRotationOrScale as Vector3);
  580. if (!babylonTransformNode.parent) {
  581. newPositionRotationOrScale.x *= -1;
  582. newPositionRotationOrScale.z *= -1;
  583. }
  584. }
  585. }
  586. outputs.push(newPositionRotationOrScale.asArray());
  587. }
  588. }
  589. else if (animationType === Animation.ANIMATIONTYPE_QUATERNION) {
  590. value = (keyFrame.value as Quaternion).normalize().asArray();
  591. if (convertToRightHandedSystem) {
  592. _GLTFUtilities.GetRightHandedQuaternionArrayFromRef(value);
  593. if (!babylonTransformNode.parent) {
  594. value = Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(value)).asArray();
  595. }
  596. }
  597. outputs.push(value);
  598. }
  599. else {
  600. Tools.Error('glTFAnimation: Unsupported key frame values for animation!');
  601. }
  602. }
  603. /**
  604. * Determine the interpolation based on the key frames
  605. * @param keyFrames
  606. * @param animationChannelTargetPath
  607. * @param useQuaternion
  608. */
  609. private static _DeduceInterpolation(keyFrames: IAnimationKey[], animationChannelTargetPath: AnimationChannelTargetPath, useQuaternion: boolean): { interpolationType: AnimationSamplerInterpolation, shouldBakeAnimation: boolean } {
  610. let interpolationType: AnimationSamplerInterpolation | undefined;
  611. let shouldBakeAnimation = false;
  612. let key: IAnimationKey;
  613. if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION && !useQuaternion) {
  614. return { interpolationType: AnimationSamplerInterpolation.LINEAR, shouldBakeAnimation: true };
  615. }
  616. for (let i = 0, length = keyFrames.length; i < length; ++i) {
  617. key = keyFrames[i];
  618. if (key.inTangent || key.outTangent) {
  619. if (interpolationType) {
  620. if (interpolationType !== AnimationSamplerInterpolation.CUBICSPLINE) {
  621. interpolationType = AnimationSamplerInterpolation.LINEAR;
  622. shouldBakeAnimation = true;
  623. break;
  624. }
  625. }
  626. else {
  627. interpolationType = AnimationSamplerInterpolation.CUBICSPLINE;
  628. }
  629. }
  630. else {
  631. if (interpolationType) {
  632. if (interpolationType === AnimationSamplerInterpolation.CUBICSPLINE ||
  633. (key.interpolation && (key.interpolation === AnimationKeyInterpolation.STEP) && interpolationType !== AnimationSamplerInterpolation.STEP)) {
  634. interpolationType = AnimationSamplerInterpolation.LINEAR;
  635. shouldBakeAnimation = true;
  636. break;
  637. }
  638. }
  639. else {
  640. if (key.interpolation && (key.interpolation === AnimationKeyInterpolation.STEP)) {
  641. interpolationType = AnimationSamplerInterpolation.STEP;
  642. }
  643. else {
  644. interpolationType = AnimationSamplerInterpolation.LINEAR;
  645. }
  646. }
  647. }
  648. }
  649. if (!interpolationType) {
  650. interpolationType = AnimationSamplerInterpolation.LINEAR;
  651. }
  652. return { interpolationType: interpolationType, shouldBakeAnimation: shouldBakeAnimation };
  653. }
  654. /**
  655. * Adds an input tangent or output tangent to the output data
  656. * If an input tangent or output tangent is missing, it uses the zero vector or zero quaternion
  657. * @param tangentType Specifies which type of tangent to handle (inTangent or outTangent)
  658. * @param outputs The animation data by keyframe
  659. * @param animationChannelTargetPath The target animation channel
  660. * @param interpolation The interpolation type
  661. * @param keyFrame The key frame with the animation data
  662. * @param frameDelta Time difference between two frames used to scale the tangent by the frame delta
  663. * @param useQuaternion Specifies if quaternions are used
  664. * @param convertToRightHandedSystem Specifies if the values should be converted to right-handed
  665. */
  666. private static AddSplineTangent(babylonTransformNode: TransformNode, tangentType: _TangentType, outputs: number[][], animationChannelTargetPath: AnimationChannelTargetPath, interpolation: AnimationSamplerInterpolation, keyFrame: IAnimationKey, frameDelta: number, useQuaternion: boolean, convertToRightHandedSystem: boolean) {
  667. let tangent: number[];
  668. let tangentValue: Vector3 | Quaternion = tangentType === _TangentType.INTANGENT ? keyFrame.inTangent : keyFrame.outTangent;
  669. if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) {
  670. if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
  671. if (tangentValue) {
  672. if (useQuaternion) {
  673. tangent = (tangentValue as Quaternion).scale(frameDelta).asArray();
  674. }
  675. else {
  676. const array = (tangentValue as Vector3).scale(frameDelta);
  677. tangent = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z).asArray();
  678. }
  679. if (convertToRightHandedSystem) {
  680. _GLTFUtilities.GetRightHandedQuaternionArrayFromRef(tangent);
  681. if (!babylonTransformNode.parent) {
  682. tangent = Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(tangent)).asArray();
  683. }
  684. }
  685. }
  686. else {
  687. tangent = [0, 0, 0, 0];
  688. }
  689. }
  690. else {
  691. if (tangentValue) {
  692. tangent = (tangentValue as Vector3).scale(frameDelta).asArray();
  693. if (convertToRightHandedSystem) {
  694. if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
  695. _GLTFUtilities.GetRightHandedPositionArray3FromRef(tangent);
  696. if (!babylonTransformNode.parent) {
  697. tangent[0] *= -1; // x
  698. tangent[2] *= -1; // z
  699. }
  700. }
  701. }
  702. }
  703. else {
  704. tangent = [0, 0, 0];
  705. }
  706. }
  707. outputs.push(tangent);
  708. }
  709. }
  710. /**
  711. * Get the minimum and maximum key frames' frame values
  712. * @param keyFrames animation key frames
  713. * @returns the minimum and maximum key frame value
  714. */
  715. private static calculateMinMaxKeyFrames(keyFrames: IAnimationKey[]): { min: number, max: number } {
  716. let min: number = Infinity;
  717. let max: number = -Infinity;
  718. keyFrames.forEach(function (keyFrame) {
  719. min = Math.min(min, keyFrame.frame);
  720. max = Math.max(max, keyFrame.frame);
  721. });
  722. return { min: min, max: max };
  723. }
  724. }
  725. }