babylon.animatable.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. module BABYLON {
  2. /**
  3. * Class used to store an actual running animation
  4. */
  5. export class Animatable {
  6. private _localDelayOffset: Nullable<number> = null;
  7. private _pausedDelay: Nullable<number> = null;
  8. private _runtimeAnimations = new Array<RuntimeAnimation>();
  9. private _paused = false;
  10. private _scene: Scene;
  11. private _speedRatio = 1;
  12. private _weight = -1.0;
  13. private _syncRoot: Animatable;
  14. /**
  15. * Gets or sets a boolean indicating if the animatable must be disposed and removed at the end of the animation.
  16. * This will only apply for non looping animation (default is true)
  17. */
  18. public disposeOnEnd = true;
  19. /**
  20. * Gets a boolean indicating if the animation has started
  21. */
  22. public animationStarted = false;
  23. /**
  24. * Observer raised when the animation ends
  25. */
  26. public onAnimationEndObservable = new Observable<Animatable>();
  27. /**
  28. * Gets the root Animatable used to synchronize and normalize animations
  29. */
  30. public get syncRoot(): Animatable {
  31. return this._syncRoot;
  32. }
  33. /**
  34. * Gets the current frame of the first RuntimeAnimation
  35. * Used to synchronize Animatables
  36. */
  37. public get masterFrame(): number {
  38. if (this._runtimeAnimations.length === 0) {
  39. return 0;
  40. }
  41. return this._runtimeAnimations[0].currentFrame;
  42. }
  43. /**
  44. * Gets or sets the animatable weight (-1.0 by default meaning not weighted)
  45. */
  46. public get weight(): number {
  47. return this._weight;
  48. }
  49. public set weight(value: number) {
  50. if (value === -1) { // -1 is ok and means no weight
  51. this._weight = -1;
  52. return;
  53. }
  54. // Else weight must be in [0, 1] range
  55. this._weight = Math.min(Math.max(value, 0), 1.0);
  56. }
  57. /**
  58. * Gets or sets the speed ratio to apply to the animatable (1.0 by default)
  59. */
  60. public get speedRatio(): number {
  61. return this._speedRatio;
  62. }
  63. public set speedRatio(value: number) {
  64. for (var index = 0; index < this._runtimeAnimations.length; index++) {
  65. var animation = this._runtimeAnimations[index];
  66. animation._prepareForSpeedRatioChange(value);
  67. }
  68. this._speedRatio = value;
  69. }
  70. /**
  71. * Creates a new Animatable
  72. * @param scene defines the hosting scene
  73. * @param target defines the target object
  74. * @param fromFrame defines the starting frame number (default is 0)
  75. * @param toFrame defines the ending frame number (default is 100)
  76. * @param loopAnimation defines if the animation must loop (default is false)
  77. * @param speedRatio defines the factor to apply to animation speed (default is 1)
  78. * @param onAnimationEnd defines a callback to call when animation ends if it is not looping
  79. * @param animations defines a group of animation to add to the new Animatable
  80. */
  81. constructor(scene: Scene,
  82. /** defines the target object */
  83. public target: any,
  84. /** defines the starting frame number (default is 0) */
  85. public fromFrame: number = 0,
  86. /** defines the ending frame number (default is 100) */
  87. public toFrame: number = 100,
  88. /** defines if the animation must loop (default is false) */
  89. public loopAnimation: boolean = false,
  90. speedRatio: number = 1.0,
  91. /** defines a callback to call when animation ends if it is not looping */
  92. public onAnimationEnd?: Nullable<() => void>,
  93. animations?: Animation[]) {
  94. this._scene = scene;
  95. if (animations) {
  96. this.appendAnimations(target, animations);
  97. }
  98. this._speedRatio = speedRatio;
  99. scene._activeAnimatables.push(this);
  100. }
  101. // Methods
  102. /**
  103. * Synchronize and normalize current Animatable with a source Animatable
  104. * This is useful when using animation weights and when animations are not of the same length
  105. * @param root defines the root Animatable to synchronize with
  106. * @returns the current Animatable
  107. */
  108. public syncWith(root: Animatable): Animatable {
  109. this._syncRoot = root;
  110. if (root) {
  111. // Make sure this animatable will animate after the root
  112. let index = this._scene._activeAnimatables.indexOf(this);
  113. if (index > -1) {
  114. this._scene._activeAnimatables.splice(index, 1);
  115. this._scene._activeAnimatables.push(this);
  116. }
  117. }
  118. return this;
  119. }
  120. /**
  121. * Gets the list of runtime animations
  122. * @returns an array of RuntimeAnimation
  123. */
  124. public getAnimations(): RuntimeAnimation[] {
  125. return this._runtimeAnimations;
  126. }
  127. /**
  128. * Adds more animations to the current animatable
  129. * @param target defines the target of the animations
  130. * @param animations defines the new animations to add
  131. */
  132. public appendAnimations(target: any, animations: Animation[]): void {
  133. for (var index = 0; index < animations.length; index++) {
  134. var animation = animations[index];
  135. this._runtimeAnimations.push(new RuntimeAnimation(target, animation, this._scene, this));
  136. }
  137. }
  138. /**
  139. * Gets the source animation for a specific property
  140. * @param property defines the propertyu to look for
  141. * @returns null or the source animation for the given property
  142. */
  143. public getAnimationByTargetProperty(property: string): Nullable<Animation> {
  144. var runtimeAnimations = this._runtimeAnimations;
  145. for (var index = 0; index < runtimeAnimations.length; index++) {
  146. if (runtimeAnimations[index].animation.targetProperty === property) {
  147. return runtimeAnimations[index].animation;
  148. }
  149. }
  150. return null;
  151. }
  152. /**
  153. * Gets the runtime animation for a specific property
  154. * @param property defines the propertyu to look for
  155. * @returns null or the runtime animation for the given property
  156. */
  157. public getRuntimeAnimationByTargetProperty(property: string): Nullable<RuntimeAnimation> {
  158. var runtimeAnimations = this._runtimeAnimations;
  159. for (var index = 0; index < runtimeAnimations.length; index++) {
  160. if (runtimeAnimations[index].animation.targetProperty === property) {
  161. return runtimeAnimations[index];
  162. }
  163. }
  164. return null;
  165. }
  166. /**
  167. * Resets the animatable to its original state
  168. */
  169. public reset(): void {
  170. var runtimeAnimations = this._runtimeAnimations;
  171. for (var index = 0; index < runtimeAnimations.length; index++) {
  172. runtimeAnimations[index].reset(true);
  173. }
  174. this._localDelayOffset = null;
  175. this._pausedDelay = null;
  176. }
  177. /**
  178. * Allows the animatable to blend with current running animations
  179. * @see http://doc.babylonjs.com/babylon101/animations#animation-blending
  180. * @param blendingSpeed defines the blending speed to use
  181. */
  182. public enableBlending(blendingSpeed: number): void {
  183. var runtimeAnimations = this._runtimeAnimations;
  184. for (var index = 0; index < runtimeAnimations.length; index++) {
  185. runtimeAnimations[index].animation.enableBlending = true;
  186. runtimeAnimations[index].animation.blendingSpeed = blendingSpeed;
  187. }
  188. }
  189. /**
  190. * Disable animation blending
  191. * @see http://doc.babylonjs.com/babylon101/animations#animation-blending
  192. */
  193. public disableBlending(): void {
  194. var runtimeAnimations = this._runtimeAnimations;
  195. for (var index = 0; index < runtimeAnimations.length; index++) {
  196. runtimeAnimations[index].animation.enableBlending = false;
  197. }
  198. }
  199. /**
  200. * Jump directly to a given frame
  201. * @param frame defines the frame to jump to
  202. */
  203. public goToFrame(frame: number): void {
  204. var runtimeAnimations = this._runtimeAnimations;
  205. if (runtimeAnimations[0]) {
  206. var fps = runtimeAnimations[0].animation.framePerSecond;
  207. var currentFrame = runtimeAnimations[0].currentFrame;
  208. var adjustTime = frame - currentFrame;
  209. var delay = adjustTime * 1000 / (fps * this.speedRatio);
  210. if (this._localDelayOffset === null) {
  211. this._localDelayOffset = 0;
  212. }
  213. this._localDelayOffset -= delay;
  214. }
  215. for (var index = 0; index < runtimeAnimations.length; index++) {
  216. runtimeAnimations[index].goToFrame(frame);
  217. }
  218. }
  219. /**
  220. * Pause the animation
  221. */
  222. public pause(): void {
  223. if (this._paused) {
  224. return;
  225. }
  226. this._paused = true;
  227. }
  228. /**
  229. * Restart the animation
  230. */
  231. public restart(): void {
  232. this._paused = false;
  233. }
  234. private _raiseOnAnimationEnd() {
  235. if (this.onAnimationEnd) {
  236. this.onAnimationEnd();
  237. }
  238. this.onAnimationEndObservable.notifyObservers(this);
  239. }
  240. /**
  241. * Stop and delete the current animation
  242. * @param animationName defines a string used to only stop some of the runtime animations instead of all
  243. * @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)
  244. */
  245. public stop(animationName?: string, targetMask?: (target: any) => boolean): void {
  246. if (animationName || targetMask) {
  247. var idx = this._scene._activeAnimatables.indexOf(this);
  248. if (idx > -1) {
  249. var runtimeAnimations = this._runtimeAnimations;
  250. for (var index = runtimeAnimations.length - 1; index >= 0; index--) {
  251. const runtimeAnimation = runtimeAnimations[index];
  252. if (animationName && runtimeAnimation.animation.name != animationName) {
  253. continue;
  254. }
  255. if (targetMask && !targetMask(runtimeAnimation.target)) {
  256. continue;
  257. }
  258. runtimeAnimation.dispose();
  259. runtimeAnimations.splice(index, 1);
  260. }
  261. if (runtimeAnimations.length == 0) {
  262. this._scene._activeAnimatables.splice(idx, 1);
  263. this._raiseOnAnimationEnd();
  264. }
  265. }
  266. } else {
  267. var index = this._scene._activeAnimatables.indexOf(this);
  268. if (index > -1) {
  269. this._scene._activeAnimatables.splice(index, 1);
  270. var runtimeAnimations = this._runtimeAnimations;
  271. for (var index = 0; index < runtimeAnimations.length; index++) {
  272. runtimeAnimations[index].dispose();
  273. }
  274. this._raiseOnAnimationEnd();
  275. }
  276. }
  277. }
  278. /**
  279. * Wait asynchronously for the animation to end
  280. * @returns a promise which will be fullfilled when the animation ends
  281. */
  282. public waitAsync(): Promise<Animatable> {
  283. return new Promise((resolve, reject) => {
  284. this.onAnimationEndObservable.add(() => {
  285. resolve(this);
  286. }, undefined, undefined, this, true);
  287. });
  288. }
  289. /** @hidden */
  290. public _animate(delay: number): boolean {
  291. if (this._paused) {
  292. this.animationStarted = false;
  293. if (this._pausedDelay === null) {
  294. this._pausedDelay = delay;
  295. }
  296. return true;
  297. }
  298. if (this._localDelayOffset === null) {
  299. this._localDelayOffset = delay;
  300. this._pausedDelay = null;
  301. } else if (this._pausedDelay !== null) {
  302. this._localDelayOffset += delay - this._pausedDelay;
  303. this._pausedDelay = null;
  304. }
  305. if (this._weight === 0) { // We consider that an animation with a weight === 0 is "actively" paused
  306. return true;
  307. }
  308. // Animating
  309. var running = false;
  310. var runtimeAnimations = this._runtimeAnimations;
  311. var index: number;
  312. for (index = 0; index < runtimeAnimations.length; index++) {
  313. var animation = runtimeAnimations[index];
  314. var isRunning = animation.animate(delay - this._localDelayOffset, this.fromFrame, this.toFrame, this.loopAnimation, this._speedRatio, this._weight);
  315. running = running || isRunning;
  316. }
  317. this.animationStarted = running;
  318. if (!running) {
  319. if (this.disposeOnEnd) {
  320. // Remove from active animatables
  321. index = this._scene._activeAnimatables.indexOf(this);
  322. this._scene._activeAnimatables.splice(index, 1);
  323. // Dispose all runtime animations
  324. for (index = 0; index < runtimeAnimations.length; index++) {
  325. runtimeAnimations[index].dispose();
  326. }
  327. }
  328. this._raiseOnAnimationEnd();
  329. if (this.disposeOnEnd) {
  330. this.onAnimationEnd = null;
  331. this.onAnimationEndObservable.clear();
  332. }
  333. }
  334. return running;
  335. }
  336. }
  337. }