animatable.ts 46 KB

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