animatable.ts 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. import { Animation } from "./animation";
  2. import { RuntimeAnimation } from "./runtimeAnimation";
  3. import { Nullable } from "../types";
  4. import { Observable } from "../Misc/observable";
  5. import { Scene } from "../scene";
  6. import { Matrix, Quaternion, Tmp, Vector3 } from '../Maths/math';
  7. import { PrecisionDate } from '../Misc/precisionDate';
  8. import { Bone } from '../Bones/bone';
  9. import { Node } from "../node";
  10. /**
  11. * Class used to store an actual running animation
  12. */
  13. export class Animatable {
  14. private _localDelayOffset: Nullable<number> = null;
  15. private _pausedDelay: Nullable<number> = null;
  16. private _runtimeAnimations = new Array<RuntimeAnimation>();
  17. private _paused = false;
  18. private _scene: Scene;
  19. private _speedRatio = 1;
  20. private _weight = -1.0;
  21. private _syncRoot: Animatable;
  22. /**
  23. * Gets or sets a boolean indicating if the animatable must be disposed and removed at the end of the animation.
  24. * This will only apply for non looping animation (default is true)
  25. */
  26. public disposeOnEnd = true;
  27. /**
  28. * Gets a boolean indicating if the animation has started
  29. */
  30. public animationStarted = false;
  31. /**
  32. * Observer raised when the animation ends
  33. */
  34. public onAnimationEndObservable = new Observable<Animatable>();
  35. /**
  36. * Observer raised when the animation loops
  37. */
  38. public onAnimationLoopObservable = new Observable<Animatable>();
  39. /**
  40. * Gets the root Animatable used to synchronize and normalize animations
  41. */
  42. public get syncRoot(): Animatable {
  43. return this._syncRoot;
  44. }
  45. /**
  46. * Gets the current frame of the first RuntimeAnimation
  47. * Used to synchronize Animatables
  48. */
  49. public get masterFrame(): number {
  50. if (this._runtimeAnimations.length === 0) {
  51. return 0;
  52. }
  53. return this._runtimeAnimations[0].currentFrame;
  54. }
  55. /**
  56. * Gets or sets the animatable weight (-1.0 by default meaning not weighted)
  57. */
  58. public get weight(): number {
  59. return this._weight;
  60. }
  61. public set weight(value: number) {
  62. if (value === -1) { // -1 is ok and means no weight
  63. this._weight = -1;
  64. return;
  65. }
  66. // Else weight must be in [0, 1] range
  67. this._weight = Math.min(Math.max(value, 0), 1.0);
  68. }
  69. /**
  70. * Gets or sets the speed ratio to apply to the animatable (1.0 by default)
  71. */
  72. public get speedRatio(): number {
  73. return this._speedRatio;
  74. }
  75. public set speedRatio(value: number) {
  76. for (var index = 0; index < this._runtimeAnimations.length; index++) {
  77. var animation = this._runtimeAnimations[index];
  78. animation._prepareForSpeedRatioChange(value);
  79. }
  80. this._speedRatio = value;
  81. }
  82. /**
  83. * Creates a new Animatable
  84. * @param scene defines the hosting scene
  85. * @param target defines the target object
  86. * @param fromFrame defines the starting frame number (default is 0)
  87. * @param toFrame defines the ending frame number (default is 100)
  88. * @param loopAnimation defines if the animation must loop (default is false)
  89. * @param speedRatio defines the factor to apply to animation speed (default is 1)
  90. * @param onAnimationEnd defines a callback to call when animation ends if it is not looping
  91. * @param animations defines a group of animation to add to the new Animatable
  92. * @param onAnimationLoop defines a callback to call when animation loops
  93. */
  94. constructor(scene: Scene,
  95. /** defines the target object */
  96. public target: any,
  97. /** defines the starting frame number (default is 0) */
  98. public fromFrame: number = 0,
  99. /** defines the ending frame number (default is 100) */
  100. public toFrame: number = 100,
  101. /** defines if the animation must loop (default is false) */
  102. public loopAnimation: boolean = false,
  103. speedRatio: number = 1.0,
  104. /** defines a callback to call when animation ends if it is not looping */
  105. public onAnimationEnd?: Nullable<() => void>,
  106. animations?: Animation[],
  107. /** defines a callback to call when animation loops */
  108. public onAnimationLoop?: Nullable<() => void>) {
  109. this._scene = scene;
  110. if (animations) {
  111. this.appendAnimations(target, animations);
  112. }
  113. this._speedRatio = speedRatio;
  114. scene._activeAnimatables.push(this);
  115. }
  116. // Methods
  117. /**
  118. * Synchronize and normalize current Animatable with a source Animatable
  119. * This is useful when using animation weights and when animations are not of the same length
  120. * @param root defines the root Animatable to synchronize with
  121. * @returns the current Animatable
  122. */
  123. public syncWith(root: Animatable): Animatable {
  124. this._syncRoot = root;
  125. if (root) {
  126. // Make sure this animatable will animate after the root
  127. let index = this._scene._activeAnimatables.indexOf(this);
  128. if (index > -1) {
  129. this._scene._activeAnimatables.splice(index, 1);
  130. this._scene._activeAnimatables.push(this);
  131. }
  132. }
  133. return this;
  134. }
  135. /**
  136. * Gets the list of runtime animations
  137. * @returns an array of RuntimeAnimation
  138. */
  139. public getAnimations(): RuntimeAnimation[] {
  140. return this._runtimeAnimations;
  141. }
  142. /**
  143. * Adds more animations to the current animatable
  144. * @param target defines the target of the animations
  145. * @param animations defines the new animations to add
  146. */
  147. public appendAnimations(target: any, animations: Animation[]): void {
  148. for (var index = 0; index < animations.length; index++) {
  149. var animation = animations[index];
  150. this._runtimeAnimations.push(new RuntimeAnimation(target, animation, this._scene, this));
  151. }
  152. }
  153. /**
  154. * Gets the source animation for a specific property
  155. * @param property defines the propertyu to look for
  156. * @returns null or the source animation for the given property
  157. */
  158. public getAnimationByTargetProperty(property: string): Nullable<Animation> {
  159. var runtimeAnimations = this._runtimeAnimations;
  160. for (var index = 0; index < runtimeAnimations.length; index++) {
  161. if (runtimeAnimations[index].animation.targetProperty === property) {
  162. return runtimeAnimations[index].animation;
  163. }
  164. }
  165. return null;
  166. }
  167. /**
  168. * Gets the runtime animation for a specific property
  169. * @param property defines the propertyu to look for
  170. * @returns null or the runtime animation for the given property
  171. */
  172. public getRuntimeAnimationByTargetProperty(property: string): Nullable<RuntimeAnimation> {
  173. var runtimeAnimations = this._runtimeAnimations;
  174. for (var index = 0; index < runtimeAnimations.length; index++) {
  175. if (runtimeAnimations[index].animation.targetProperty === property) {
  176. return runtimeAnimations[index];
  177. }
  178. }
  179. return null;
  180. }
  181. /**
  182. * Resets the animatable to its original state
  183. */
  184. public reset(): void {
  185. var runtimeAnimations = this._runtimeAnimations;
  186. for (var index = 0; index < runtimeAnimations.length; index++) {
  187. runtimeAnimations[index].reset(true);
  188. }
  189. this._localDelayOffset = null;
  190. this._pausedDelay = null;
  191. }
  192. /**
  193. * Allows the animatable to blend with current running animations
  194. * @see http://doc.babylonjs.com/babylon101/animations#animation-blending
  195. * @param blendingSpeed defines the blending speed to use
  196. */
  197. public enableBlending(blendingSpeed: number): void {
  198. var runtimeAnimations = this._runtimeAnimations;
  199. for (var index = 0; index < runtimeAnimations.length; index++) {
  200. runtimeAnimations[index].animation.enableBlending = true;
  201. runtimeAnimations[index].animation.blendingSpeed = blendingSpeed;
  202. }
  203. }
  204. /**
  205. * Disable animation blending
  206. * @see http://doc.babylonjs.com/babylon101/animations#animation-blending
  207. */
  208. public disableBlending(): void {
  209. var runtimeAnimations = this._runtimeAnimations;
  210. for (var index = 0; index < runtimeAnimations.length; index++) {
  211. runtimeAnimations[index].animation.enableBlending = false;
  212. }
  213. }
  214. /**
  215. * Jump directly to a given frame
  216. * @param frame defines the frame to jump to
  217. */
  218. public goToFrame(frame: number): void {
  219. var runtimeAnimations = this._runtimeAnimations;
  220. if (runtimeAnimations[0]) {
  221. var fps = runtimeAnimations[0].animation.framePerSecond;
  222. var currentFrame = runtimeAnimations[0].currentFrame;
  223. var adjustTime = frame - currentFrame;
  224. var delay = adjustTime * 1000 / (fps * this.speedRatio);
  225. if (this._localDelayOffset === null) {
  226. this._localDelayOffset = 0;
  227. }
  228. this._localDelayOffset -= delay;
  229. }
  230. for (var index = 0; index < runtimeAnimations.length; index++) {
  231. runtimeAnimations[index].goToFrame(frame);
  232. }
  233. }
  234. /**
  235. * Pause the animation
  236. */
  237. public pause(): void {
  238. if (this._paused) {
  239. return;
  240. }
  241. this._paused = true;
  242. }
  243. /**
  244. * Restart the animation
  245. */
  246. public restart(): void {
  247. this._paused = false;
  248. }
  249. private _raiseOnAnimationEnd() {
  250. if (this.onAnimationEnd) {
  251. this.onAnimationEnd();
  252. }
  253. this.onAnimationEndObservable.notifyObservers(this);
  254. }
  255. /**
  256. * Stop and delete the current animation
  257. * @param animationName defines a string used to only stop some of the runtime animations instead of all
  258. * @param targetMask - a function that determines if the animation should be stopped based on its target (all animations will be stopped if both this and animationName are empty)
  259. */
  260. public stop(animationName?: string, targetMask?: (target: any) => boolean): void {
  261. if (animationName || targetMask) {
  262. var idx = this._scene._activeAnimatables.indexOf(this);
  263. if (idx > -1) {
  264. var runtimeAnimations = this._runtimeAnimations;
  265. for (var index = runtimeAnimations.length - 1; index >= 0; index--) {
  266. const runtimeAnimation = runtimeAnimations[index];
  267. if (animationName && runtimeAnimation.animation.name != animationName) {
  268. continue;
  269. }
  270. if (targetMask && !targetMask(runtimeAnimation.target)) {
  271. continue;
  272. }
  273. runtimeAnimation.dispose();
  274. runtimeAnimations.splice(index, 1);
  275. }
  276. if (runtimeAnimations.length == 0) {
  277. this._scene._activeAnimatables.splice(idx, 1);
  278. this._raiseOnAnimationEnd();
  279. }
  280. }
  281. } else {
  282. var index = this._scene._activeAnimatables.indexOf(this);
  283. if (index > -1) {
  284. this._scene._activeAnimatables.splice(index, 1);
  285. var runtimeAnimations = this._runtimeAnimations;
  286. for (var index = 0; index < runtimeAnimations.length; index++) {
  287. runtimeAnimations[index].dispose();
  288. }
  289. this._raiseOnAnimationEnd();
  290. }
  291. }
  292. }
  293. /**
  294. * Wait asynchronously for the animation to end
  295. * @returns a promise which will be fullfilled when the animation ends
  296. */
  297. public waitAsync(): Promise<Animatable> {
  298. return new Promise((resolve, reject) => {
  299. this.onAnimationEndObservable.add(() => {
  300. resolve(this);
  301. }, undefined, undefined, this, true);
  302. });
  303. }
  304. /** @hidden */
  305. public _animate(delay: number): boolean {
  306. if (this._paused) {
  307. this.animationStarted = false;
  308. if (this._pausedDelay === null) {
  309. this._pausedDelay = delay;
  310. }
  311. return true;
  312. }
  313. if (this._localDelayOffset === null) {
  314. this._localDelayOffset = delay;
  315. this._pausedDelay = null;
  316. } else if (this._pausedDelay !== null) {
  317. this._localDelayOffset += delay - this._pausedDelay;
  318. this._pausedDelay = null;
  319. }
  320. if (this._weight === 0) { // We consider that an animation with a weight === 0 is "actively" paused
  321. return true;
  322. }
  323. // Animating
  324. var running = false;
  325. var runtimeAnimations = this._runtimeAnimations;
  326. var index: number;
  327. for (index = 0; index < runtimeAnimations.length; index++) {
  328. var animation = runtimeAnimations[index];
  329. var isRunning = animation.animate(delay - this._localDelayOffset, this.fromFrame,
  330. this.toFrame, this.loopAnimation, this._speedRatio, this._weight,
  331. () => {
  332. this.onAnimationLoopObservable.notifyObservers(this);
  333. if (this.onAnimationLoop) {
  334. this.onAnimationLoop();
  335. }
  336. }
  337. );
  338. running = running || isRunning;
  339. }
  340. this.animationStarted = running;
  341. if (!running) {
  342. if (this.disposeOnEnd) {
  343. // Remove from active animatables
  344. index = this._scene._activeAnimatables.indexOf(this);
  345. this._scene._activeAnimatables.splice(index, 1);
  346. // Dispose all runtime animations
  347. for (index = 0; index < runtimeAnimations.length; index++) {
  348. runtimeAnimations[index].dispose();
  349. }
  350. }
  351. this._raiseOnAnimationEnd();
  352. if (this.disposeOnEnd) {
  353. this.onAnimationEnd = null;
  354. this.onAnimationLoop = null;
  355. this.onAnimationLoopObservable.clear();
  356. this.onAnimationEndObservable.clear();
  357. }
  358. }
  359. return running;
  360. }
  361. }
  362. declare module "../scene" {
  363. export interface Scene {
  364. /** @hidden */
  365. _registerTargetForLateAnimationBinding(runtimeAnimation: RuntimeAnimation, originalValue: any): void;
  366. /** @hidden */
  367. _processLateAnimationBindingsForMatrices(holder: {
  368. totalWeight: number,
  369. animations: RuntimeAnimation[],
  370. originalValue: Matrix
  371. }): any;
  372. /** @hidden */
  373. _processLateAnimationBindingsForQuaternions(holder: {
  374. totalWeight: number,
  375. animations: RuntimeAnimation[],
  376. originalValue: Quaternion
  377. }, refQuaternion: Quaternion): Quaternion;
  378. /** @hidden */
  379. _processLateAnimationBindings(): void;
  380. /**
  381. * Will start the animation sequence of a given target
  382. * @param target defines the target
  383. * @param from defines from which frame should animation start
  384. * @param to defines until which frame should animation run.
  385. * @param weight defines the weight to apply to the animation (1.0 by default)
  386. * @param loop defines if the animation loops
  387. * @param speedRatio defines the speed in which to run the animation (1.0 by default)
  388. * @param onAnimationEnd defines the function to be executed when the animation ends
  389. * @param animatable defines an animatable object. If not provided a new one will be created from the given params
  390. * @param targetMask defines if the target should be animated if animations are present (this is called recursively on descendant animatables regardless of return value)
  391. * @param onAnimationLoop defines the callback to call when an animation loops
  392. * @returns the animatable object created for this animation
  393. */
  394. beginWeightedAnimation(target: any, from: number, to: number, weight: number, loop?: boolean, speedRatio?: number,
  395. onAnimationEnd?: () => void, animatable?: Animatable, targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable;
  396. /**
  397. * Will start the animation sequence of a given target
  398. * @param target defines the target
  399. * @param from defines from which frame should animation start
  400. * @param to defines until which frame should animation run.
  401. * @param loop defines if the animation loops
  402. * @param speedRatio defines the speed in which to run the animation (1.0 by default)
  403. * @param onAnimationEnd defines the function to be executed when the animation ends
  404. * @param animatable defines an animatable object. If not provided a new one will be created from the given params
  405. * @param stopCurrent defines if the current animations must be stopped first (true by default)
  406. * @param targetMask defines if the target should be animate if animations are present (this is called recursively on descendant animatables regardless of return value)
  407. * @param onAnimationLoop defines the callback to call when an animation loops
  408. * @returns the animatable object created for this animation
  409. */
  410. beginAnimation(target: any, from: number, to: number, loop?: boolean, speedRatio?: number,
  411. onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent?: boolean,
  412. targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable;
  413. /**
  414. * Will start the animation sequence of a given target and its hierarchy
  415. * @param target defines the target
  416. * @param directDescendantsOnly if true only direct descendants will be used, if false direct and also indirect (children of children, an so on in a recursive manner) descendants will be used.
  417. * @param from defines from which frame should animation start
  418. * @param to defines until which frame should animation run.
  419. * @param loop defines if the animation loops
  420. * @param speedRatio defines the speed in which to run the animation (1.0 by default)
  421. * @param onAnimationEnd defines the function to be executed when the animation ends
  422. * @param animatable defines an animatable object. If not provided a new one will be created from the given params
  423. * @param stopCurrent defines if the current animations must be stopped first (true by default)
  424. * @param targetMask defines if the target should be animated if animations are present (this is called recursively on descendant animatables regardless of return value)
  425. * @param onAnimationLoop defines the callback to call when an animation loops
  426. * @returns the list of created animatables
  427. */
  428. beginHierarchyAnimation(target: any, directDescendantsOnly: boolean, from: number, to: number, loop?: boolean, speedRatio?: number,
  429. onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent?: boolean,
  430. targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable[];
  431. /**
  432. * Begin a new animation on a given node
  433. * @param target defines the target where the animation will take place
  434. * @param animations defines the list of animations to start
  435. * @param from defines the initial value
  436. * @param to defines the final value
  437. * @param loop defines if you want animation to loop (off by default)
  438. * @param speedRatio defines the speed ratio to apply to all animations
  439. * @param onAnimationEnd defines the callback to call when an animation ends (will be called once per node)
  440. * @param onAnimationLoop defines the callback to call when an animation loops
  441. * @returns the list of created animatables
  442. */
  443. beginDirectAnimation(target: any, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, onAnimationLoop?: () => void): Animatable;
  444. /**
  445. * Begin a new animation on a given node and its hierarchy
  446. * @param target defines the root node where the animation will take place
  447. * @param directDescendantsOnly if true only direct descendants will be used, if false direct and also indirect (children of children, an so on in a recursive manner) descendants will be used.
  448. * @param animations defines the list of animations to start
  449. * @param from defines the initial value
  450. * @param to defines the final value
  451. * @param loop defines if you want animation to loop (off by default)
  452. * @param speedRatio defines the speed ratio to apply to all animations
  453. * @param onAnimationEnd defines the callback to call when an animation ends (will be called once per node)
  454. * @param onAnimationLoop defines the callback to call when an animation loops
  455. * @returns the list of animatables created for all nodes
  456. */
  457. beginDirectHierarchyAnimation(target: Node, directDescendantsOnly: boolean, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, onAnimationLoop?: () => void): Animatable[];
  458. /**
  459. * Gets the animatable associated with a specific target
  460. * @param target defines the target of the animatable
  461. * @returns the required animatable if found
  462. */
  463. getAnimatableByTarget(target: any): Nullable<Animatable>;
  464. /**
  465. * Gets all animatables associated with a given target
  466. * @param target defines the target to look animatables for
  467. * @returns an array of Animatables
  468. */
  469. getAllAnimatablesByTarget(target: any): Array<Animatable>;
  470. /**
  471. * Will stop the animation of the given target
  472. * @param target - the target
  473. * @param animationName - the name of the animation to stop (all animations will be stopped if both this and targetMask are empty)
  474. * @param targetMask - a function that determines if the animation should be stopped based on its target (all animations will be stopped if both this and animationName are empty)
  475. */
  476. stopAnimation(target: any, animationName?: string, targetMask?: (target: any) => boolean): void;
  477. /**
  478. * Stops and removes all animations that have been applied to the scene
  479. */
  480. stopAllAnimations(): void;
  481. }
  482. }
  483. Scene.prototype._animate = function(): void {
  484. if (!this.animationsEnabled || this._activeAnimatables.length === 0) {
  485. return;
  486. }
  487. // Getting time
  488. var now = PrecisionDate.Now;
  489. if (!this._animationTimeLast) {
  490. if (this._pendingData.length > 0) {
  491. return;
  492. }
  493. this._animationTimeLast = now;
  494. }
  495. var deltaTime = this.useConstantAnimationDeltaTime ? 16.0 : (now - this._animationTimeLast) * this.animationTimeScale;
  496. this._animationTime += deltaTime;
  497. this._animationTimeLast = now;
  498. for (var index = 0; index < this._activeAnimatables.length; index++) {
  499. this._activeAnimatables[index]._animate(this._animationTime);
  500. }
  501. // Late animation bindings
  502. this._processLateAnimationBindings();
  503. };
  504. Scene.prototype.beginWeightedAnimation = function(target: any, from: number, to: number, weight = 1.0, loop?: boolean, speedRatio: number = 1.0,
  505. onAnimationEnd?: () => void, animatable?: Animatable, targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable {
  506. let returnedAnimatable = this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, false, targetMask, onAnimationLoop);
  507. returnedAnimatable.weight = weight;
  508. return returnedAnimatable;
  509. };
  510. Scene.prototype.beginAnimation = function(target: any, from: number, to: number, loop?: boolean, speedRatio: number = 1.0,
  511. onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent = true,
  512. targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable {
  513. if (from > to && speedRatio > 0) {
  514. speedRatio *= -1;
  515. }
  516. if (stopCurrent) {
  517. this.stopAnimation(target, undefined, targetMask);
  518. }
  519. if (!animatable) {
  520. animatable = new Animatable(this, target, from, to, loop, speedRatio, onAnimationEnd, undefined, onAnimationLoop);
  521. }
  522. const shouldRunTargetAnimations = targetMask ? targetMask(target) : true;
  523. // Local animations
  524. if (target.animations && shouldRunTargetAnimations) {
  525. animatable.appendAnimations(target, target.animations);
  526. }
  527. // Children animations
  528. if (target.getAnimatables) {
  529. var animatables = target.getAnimatables();
  530. for (var index = 0; index < animatables.length; index++) {
  531. this.beginAnimation(animatables[index], from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask, onAnimationLoop);
  532. }
  533. }
  534. animatable.reset();
  535. return animatable;
  536. };
  537. Scene.prototype.beginHierarchyAnimation = function(target: any, directDescendantsOnly: boolean, from: number, to: number, loop?: boolean, speedRatio: number = 1.0,
  538. onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent = true,
  539. targetMask?: (target: any) => boolean, onAnimationLoop?: () => void): Animatable[] {
  540. let children = target.getDescendants(directDescendantsOnly);
  541. let result = [];
  542. result.push(this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask));
  543. for (var child of children) {
  544. result.push(this.beginAnimation(child, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask));
  545. }
  546. return result;
  547. };
  548. Scene.prototype.beginDirectAnimation = function(target: any, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, onAnimationLoop?: () => void): Animatable {
  549. if (speedRatio === undefined) {
  550. speedRatio = 1.0;
  551. }
  552. var animatable = new Animatable(this, target, from, to, loop, speedRatio, onAnimationEnd, animations, onAnimationLoop);
  553. return animatable;
  554. };
  555. Scene.prototype.beginDirectHierarchyAnimation = function(target: Node, directDescendantsOnly: boolean, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void, onAnimationLoop?: () => void): Animatable[] {
  556. let children = target.getDescendants(directDescendantsOnly);
  557. let result = [];
  558. result.push(this.beginDirectAnimation(target, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop));
  559. for (var child of children) {
  560. result.push(this.beginDirectAnimation(child, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop));
  561. }
  562. return result;
  563. };
  564. Scene.prototype.getAnimatableByTarget = function(target: any): Nullable<Animatable> {
  565. for (var index = 0; index < this._activeAnimatables.length; index++) {
  566. if (this._activeAnimatables[index].target === target) {
  567. return this._activeAnimatables[index];
  568. }
  569. }
  570. return null;
  571. };
  572. Scene.prototype.getAllAnimatablesByTarget = function(target: any): Array<Animatable> {
  573. let result = [];
  574. for (var index = 0; index < this._activeAnimatables.length; index++) {
  575. if (this._activeAnimatables[index].target === target) {
  576. result.push(this._activeAnimatables[index]);
  577. }
  578. }
  579. return result;
  580. };
  581. /**
  582. * Will stop the animation of the given target
  583. * @param target - the target
  584. * @param animationName - the name of the animation to stop (all animations will be stopped if both this and targetMask are empty)
  585. * @param targetMask - a function that determines if the animation should be stopped based on its target (all animations will be stopped if both this and animationName are empty)
  586. */
  587. Scene.prototype.stopAnimation = function(target: any, animationName?: string, targetMask?: (target: any) => boolean): void {
  588. var animatables = this.getAllAnimatablesByTarget(target);
  589. for (var animatable of animatables) {
  590. animatable.stop(animationName, targetMask);
  591. }
  592. };
  593. /**
  594. * Stops and removes all animations that have been applied to the scene
  595. */
  596. Scene.prototype.stopAllAnimations = function(): void {
  597. if (this._activeAnimatables) {
  598. for (let i = 0; i < this._activeAnimatables.length; i++) {
  599. this._activeAnimatables[i].stop();
  600. }
  601. this._activeAnimatables = [];
  602. }
  603. for (var group of this.animationGroups) {
  604. group.stop();
  605. }
  606. };
  607. Scene.prototype._registerTargetForLateAnimationBinding = function(runtimeAnimation: RuntimeAnimation, originalValue: any): void {
  608. let target = runtimeAnimation.target;
  609. this._registeredForLateAnimationBindings.pushNoDuplicate(target);
  610. if (!target._lateAnimationHolders) {
  611. target._lateAnimationHolders = {};
  612. }
  613. if (!target._lateAnimationHolders[runtimeAnimation.targetPath]) {
  614. target._lateAnimationHolders[runtimeAnimation.targetPath] = {
  615. totalWeight: 0,
  616. animations: [],
  617. originalValue: originalValue
  618. };
  619. }
  620. target._lateAnimationHolders[runtimeAnimation.targetPath].animations.push(runtimeAnimation);
  621. target._lateAnimationHolders[runtimeAnimation.targetPath].totalWeight += runtimeAnimation.weight;
  622. };
  623. Scene.prototype._processLateAnimationBindingsForMatrices = function(holder: {
  624. totalWeight: number,
  625. animations: RuntimeAnimation[],
  626. originalValue: Matrix
  627. }): any {
  628. let normalizer = 1.0;
  629. let finalPosition = Tmp.Vector3[0];
  630. let finalScaling = Tmp.Vector3[1];
  631. let finalQuaternion = Tmp.Quaternion[0];
  632. let startIndex = 0;
  633. let originalAnimation = holder.animations[0];
  634. let originalValue = holder.originalValue;
  635. var scale = 1;
  636. if (holder.totalWeight < 1.0) {
  637. // We need to mix the original value in
  638. originalValue.decompose(finalScaling, finalQuaternion, finalPosition);
  639. scale = 1.0 - holder.totalWeight;
  640. } else {
  641. startIndex = 1;
  642. // We need to normalize the weights
  643. normalizer = holder.totalWeight;
  644. originalAnimation.currentValue.decompose(finalScaling, finalQuaternion, finalPosition);
  645. scale = originalAnimation.weight / normalizer;
  646. if (scale == 1) {
  647. return originalAnimation.currentValue;
  648. }
  649. }
  650. finalScaling.scaleInPlace(scale);
  651. finalPosition.scaleInPlace(scale);
  652. finalQuaternion.scaleInPlace(scale);
  653. for (var animIndex = startIndex; animIndex < holder.animations.length; animIndex++) {
  654. var runtimeAnimation = holder.animations[animIndex];
  655. var scale = runtimeAnimation.weight / normalizer;
  656. let currentPosition = Tmp.Vector3[2];
  657. let currentScaling = Tmp.Vector3[3];
  658. let currentQuaternion = Tmp.Quaternion[1];
  659. runtimeAnimation.currentValue.decompose(currentScaling, currentQuaternion, currentPosition);
  660. currentScaling.scaleAndAddToRef(scale, finalScaling);
  661. currentQuaternion.scaleAndAddToRef(scale, finalQuaternion);
  662. currentPosition.scaleAndAddToRef(scale, finalPosition);
  663. }
  664. Matrix.ComposeToRef(finalScaling, finalQuaternion, finalPosition, originalAnimation._workValue);
  665. return originalAnimation._workValue;
  666. };
  667. Scene.prototype._processLateAnimationBindingsForQuaternions = function(holder: {
  668. totalWeight: number,
  669. animations: RuntimeAnimation[],
  670. originalValue: Quaternion
  671. }, refQuaternion: Quaternion): Quaternion {
  672. let originalAnimation = holder.animations[0];
  673. let originalValue = holder.originalValue;
  674. if (holder.animations.length === 1) {
  675. Quaternion.SlerpToRef(originalValue, originalAnimation.currentValue, Math.min(1.0, holder.totalWeight), refQuaternion);
  676. return refQuaternion;
  677. }
  678. let normalizer = 1.0;
  679. let quaternions: Array<Quaternion>;
  680. let weights: Array<number>;
  681. if (holder.totalWeight < 1.0) {
  682. let scale = 1.0 - holder.totalWeight;
  683. quaternions = [];
  684. weights = [];
  685. quaternions.push(originalValue);
  686. weights.push(scale);
  687. } else {
  688. if (holder.animations.length === 2) { // Slerp as soon as we can
  689. Quaternion.SlerpToRef(holder.animations[0].currentValue, holder.animations[1].currentValue, holder.animations[1].weight / holder.totalWeight, refQuaternion);
  690. return refQuaternion;
  691. }
  692. quaternions = [];
  693. weights = [];
  694. normalizer = holder.totalWeight;
  695. }
  696. for (var animIndex = 0; animIndex < holder.animations.length; animIndex++) {
  697. let runtimeAnimation = holder.animations[animIndex];
  698. quaternions.push(runtimeAnimation.currentValue);
  699. weights.push(runtimeAnimation.weight / normalizer);
  700. }
  701. // https://gamedev.stackexchange.com/questions/62354/method-for-interpolation-between-3-quaternions
  702. let cumulativeAmount = 0;
  703. let cumulativeQuaternion: Nullable<Quaternion> = null;
  704. for (var index = 0; index < quaternions.length;) {
  705. if (!cumulativeQuaternion) {
  706. Quaternion.SlerpToRef(quaternions[index], quaternions[index + 1], weights[index + 1] / (weights[index] + weights[index + 1]), refQuaternion);
  707. cumulativeQuaternion = refQuaternion;
  708. cumulativeAmount = weights[index] + weights[index + 1];
  709. index += 2;
  710. continue;
  711. }
  712. cumulativeAmount += weights[index];
  713. Quaternion.SlerpToRef(cumulativeQuaternion, quaternions[index], weights[index] / cumulativeAmount, cumulativeQuaternion);
  714. index++;
  715. }
  716. return cumulativeQuaternion!;
  717. };
  718. Scene.prototype._processLateAnimationBindings = function(): void {
  719. if (!this._registeredForLateAnimationBindings.length) {
  720. return;
  721. }
  722. for (var index = 0; index < this._registeredForLateAnimationBindings.length; index++) {
  723. var target = this._registeredForLateAnimationBindings.data[index];
  724. for (var path in target._lateAnimationHolders) {
  725. var holder = target._lateAnimationHolders[path];
  726. let originalAnimation: RuntimeAnimation = holder.animations[0];
  727. let originalValue = holder.originalValue;
  728. let matrixDecomposeMode = Animation.AllowMatrixDecomposeForInterpolation && originalValue.m; // ie. data is matrix
  729. let finalValue: any = target[path];
  730. if (matrixDecomposeMode) {
  731. finalValue = this._processLateAnimationBindingsForMatrices(holder);
  732. } else {
  733. let quaternionMode = originalValue.w !== undefined;
  734. if (quaternionMode) {
  735. finalValue = this._processLateAnimationBindingsForQuaternions(holder, finalValue || Quaternion.Identity());
  736. } else {
  737. let startIndex = 0;
  738. let normalizer = 1.0;
  739. if (holder.totalWeight < 1.0) {
  740. // We need to mix the original value in
  741. if (originalValue.scale) {
  742. finalValue = originalValue.scale(1.0 - holder.totalWeight);
  743. } else {
  744. finalValue = originalValue * (1.0 - holder.totalWeight);
  745. }
  746. } else {
  747. // We need to normalize the weights
  748. normalizer = holder.totalWeight;
  749. let scale = originalAnimation.weight / normalizer;
  750. if (scale !== 1) {
  751. if (originalAnimation.currentValue.scale) {
  752. finalValue = originalAnimation.currentValue.scale(scale);
  753. } else {
  754. finalValue = originalAnimation.currentValue * scale;
  755. }
  756. } else {
  757. finalValue = originalAnimation.currentValue;
  758. }
  759. startIndex = 1;
  760. }
  761. for (var animIndex = startIndex; animIndex < holder.animations.length; animIndex++) {
  762. var runtimeAnimation = holder.animations[animIndex];
  763. var scale = runtimeAnimation.weight / normalizer;
  764. if (runtimeAnimation.currentValue.scaleAndAddToRef) {
  765. runtimeAnimation.currentValue.scaleAndAddToRef(scale, finalValue);
  766. } else {
  767. finalValue += runtimeAnimation.currentValue * scale;
  768. }
  769. }
  770. }
  771. }
  772. target[path] = finalValue;
  773. }
  774. target._lateAnimationHolders = {};
  775. }
  776. this._registeredForLateAnimationBindings.reset();
  777. };
  778. declare module "../Bones/bone" {
  779. export interface Bone {
  780. /**
  781. * Copy an animation range from another bone
  782. * @param source defines the source bone
  783. * @param rangeName defines the range name to copy
  784. * @param frameOffset defines the frame offset
  785. * @param rescaleAsRequired defines if rescaling must be applied if required
  786. * @param skelDimensionsRatio defines the scaling ratio
  787. * @returns true if operation was successful
  788. */
  789. copyAnimationRange(source: Bone, rangeName: string, frameOffset: number, rescaleAsRequired: boolean, skelDimensionsRatio: Nullable<Vector3>): boolean;
  790. }
  791. }
  792. Bone.prototype.copyAnimationRange = function(source: Bone, rangeName: string, frameOffset: number, rescaleAsRequired = false, skelDimensionsRatio: Nullable<Vector3> = null): boolean {
  793. // all animation may be coming from a library skeleton, so may need to create animation
  794. if (this.animations.length === 0) {
  795. this.animations.push(new Animation(this.name, "_matrix", source.animations[0].framePerSecond, Animation.ANIMATIONTYPE_MATRIX, 0));
  796. this.animations[0].setKeys([]);
  797. }
  798. // get animation info / verify there is such a range from the source bone
  799. var sourceRange = source.animations[0].getRange(rangeName);
  800. if (!sourceRange) {
  801. return false;
  802. }
  803. var from = sourceRange.from;
  804. var to = sourceRange.to;
  805. var sourceKeys = source.animations[0].getKeys();
  806. // rescaling prep
  807. var sourceBoneLength = source.length;
  808. var sourceParent = source.getParent();
  809. var parent = this.getParent();
  810. var parentScalingReqd = rescaleAsRequired && sourceParent && sourceBoneLength && this.length && sourceBoneLength !== this.length;
  811. var parentRatio = parentScalingReqd && parent && sourceParent ? parent.length / sourceParent.length : 1;
  812. var dimensionsScalingReqd = rescaleAsRequired && !parent && skelDimensionsRatio && (skelDimensionsRatio.x !== 1 || skelDimensionsRatio.y !== 1 || skelDimensionsRatio.z !== 1);
  813. var destKeys = this.animations[0].getKeys();
  814. // loop vars declaration
  815. var orig: { frame: number, value: Matrix };
  816. var origTranslation: Vector3;
  817. var mat: Matrix;
  818. for (var key = 0, nKeys = sourceKeys.length; key < nKeys; key++) {
  819. orig = sourceKeys[key];
  820. if (orig.frame >= from && orig.frame <= to) {
  821. if (rescaleAsRequired) {
  822. mat = orig.value.clone();
  823. // scale based on parent ratio, when bone has parent
  824. if (parentScalingReqd) {
  825. origTranslation = mat.getTranslation();
  826. mat.setTranslation(origTranslation.scaleInPlace(parentRatio));
  827. // scale based on skeleton dimension ratio when root bone, and value is passed
  828. } else if (dimensionsScalingReqd && skelDimensionsRatio) {
  829. origTranslation = mat.getTranslation();
  830. mat.setTranslation(origTranslation.multiplyInPlace(skelDimensionsRatio));
  831. // use original when root bone, and no data for skelDimensionsRatio
  832. } else {
  833. mat = orig.value;
  834. }
  835. } else {
  836. mat = orig.value;
  837. }
  838. destKeys.push({ frame: orig.frame + frameOffset, value: mat });
  839. }
  840. }
  841. this.animations[0].createRange(rangeName, from + frameOffset, to + frameOffset);
  842. return true;
  843. };