babylon.runtimeAnimation.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. module BABYLON {
  2. /**
  3. * Defines a runtime animation
  4. */
  5. export class RuntimeAnimation {
  6. private _events = new Array<AnimationEvent>();
  7. /**
  8. * The current frame of the runtime animation
  9. */
  10. private _currentFrame: number = 0;
  11. /**
  12. * The animation used by the runtime animation
  13. */
  14. private _animation: Animation;
  15. /**
  16. * The target of the runtime animation
  17. */
  18. private _target: any;
  19. /**
  20. * The initiating animatable
  21. */
  22. private _host: Animatable;
  23. /**
  24. * The original value of the runtime animation
  25. */
  26. private _originalValue = new Array<any>();
  27. /**
  28. * The original blend value of the runtime animation
  29. */
  30. private _originalBlendValue: any;
  31. /**
  32. * The offsets cache of the runtime animation
  33. */
  34. private _offsetsCache: {[key: string]: any} = {};
  35. /**
  36. * The high limits cache of the runtime animation
  37. */
  38. private _highLimitsCache: {[key: string]: any} = {};
  39. /**
  40. * Specifies if the runtime animation has been stopped
  41. */
  42. private _stopped = false;
  43. /**
  44. * The blending factor of the runtime animation
  45. */
  46. private _blendingFactor = 0;
  47. /**
  48. * The BabylonJS scene
  49. */
  50. private _scene: Scene;
  51. /**
  52. * The current value of the runtime animation
  53. */
  54. private _currentValue: any;
  55. /** @hidden */
  56. public _workValue: any;
  57. /**
  58. * The active target of the runtime animation
  59. */
  60. private _activeTarget: any;
  61. /**
  62. * The target path of the runtime animation
  63. */
  64. private _targetPath: string = "";
  65. /**
  66. * The weight of the runtime animation
  67. */
  68. private _weight = 1.0;
  69. /**
  70. * The ratio offset of the runtime animation
  71. */
  72. private _ratioOffset = 0;
  73. /**
  74. * The previous delay of the runtime animation
  75. */
  76. private _previousDelay: number = 0;
  77. /**
  78. * The previous ratio of the runtime animation
  79. */
  80. private _previousRatio: number = 0;
  81. /**
  82. * Gets the current frame of the runtime animation
  83. */
  84. public get currentFrame(): number {
  85. return this._currentFrame;
  86. }
  87. /**
  88. * Gets the weight of the runtime animation
  89. */
  90. public get weight(): number {
  91. return this._weight;
  92. }
  93. /**
  94. * Gets the current value of the runtime animation
  95. */
  96. public get currentValue(): any {
  97. return this._currentValue;
  98. }
  99. /**
  100. * Gets the target path of the runtime animation
  101. */
  102. public get targetPath(): string {
  103. return this._targetPath;
  104. }
  105. /**
  106. * Gets the actual target of the runtime animation
  107. */
  108. public get target(): any {
  109. return this._activeTarget;
  110. }
  111. /**
  112. * Create a new RuntimeAnimation object
  113. * @param target defines the target of the animation
  114. * @param animation defines the source animation object
  115. * @param scene defines the hosting scene
  116. * @param host defines the initiating Animatable
  117. */
  118. public constructor(target: any, animation: Animation, scene: Scene, host: Animatable) {
  119. this._animation = animation;
  120. this._target = target;
  121. this._scene = scene;
  122. this._host = host;
  123. animation._runtimeAnimations.push(this);
  124. // Cloning events locally
  125. var events = animation.getEvents();
  126. if (events && events.length > 0) {
  127. events.forEach((e) => {
  128. this._events.push(e._clone());
  129. });
  130. }
  131. }
  132. /**
  133. * Gets the animation from the runtime animation
  134. */
  135. public get animation(): Animation {
  136. return this._animation;
  137. }
  138. /**
  139. * Resets the runtime animation to the beginning
  140. * @param restoreOriginal defines whether to restore the target property to the original value
  141. */
  142. public reset(restoreOriginal = false): void {
  143. if (restoreOriginal) {
  144. if (this._target instanceof Array) {
  145. var index = 0;
  146. for (const target of this._target) {
  147. if (this._originalValue[index] !== undefined) {
  148. this._setValue(target, this._originalValue[index], -1);
  149. }
  150. index++;
  151. }
  152. }
  153. else {
  154. if (this._originalValue[0] !== undefined) {
  155. this._setValue(this._target, this._originalValue[0], -1);
  156. }
  157. }
  158. }
  159. this._offsetsCache = {};
  160. this._highLimitsCache = {};
  161. this._currentFrame = 0;
  162. this._blendingFactor = 0;
  163. this._originalValue = new Array<any>();
  164. // Events
  165. for (var index = 0; index < this._events.length; index++) {
  166. this._events[index].isDone = false;
  167. }
  168. }
  169. /**
  170. * Specifies if the runtime animation is stopped
  171. * @returns Boolean specifying if the runtime animation is stopped
  172. */
  173. public isStopped(): boolean {
  174. return this._stopped;
  175. }
  176. /**
  177. * Disposes of the runtime animation
  178. */
  179. public dispose(): void {
  180. let index = this._animation.runtimeAnimations.indexOf(this);
  181. if (index > -1) {
  182. this._animation.runtimeAnimations.splice(index, 1);
  183. }
  184. }
  185. /**
  186. * Interpolates the animation from the current frame
  187. * @param currentFrame The frame to interpolate the animation to
  188. * @param repeatCount The number of times that the animation should loop
  189. * @param loopMode The type of looping mode to use
  190. * @param offsetValue Animation offset value
  191. * @param highLimitValue The high limit value
  192. * @returns The interpolated value
  193. */
  194. private _interpolate(currentFrame: number, repeatCount: number, loopMode?: number, offsetValue?: any, highLimitValue?: any): any {
  195. this._currentFrame = currentFrame;
  196. if (this._animation.dataType === Animation.ANIMATIONTYPE_MATRIX && !this._workValue) {
  197. this._workValue = Matrix.Zero();
  198. }
  199. return this._animation._interpolate(currentFrame, repeatCount, this._workValue, loopMode, offsetValue, highLimitValue);
  200. }
  201. /**
  202. * Apply the interpolated value to the target
  203. * @param currentValue defines the value computed by the animation
  204. * @param weight defines the weight to apply to this value (Defaults to 1.0)
  205. */
  206. public setValue(currentValue: any, weight = 1.0): void {
  207. if (this._target instanceof Array) {
  208. var index = 0;
  209. for (const target of this._target) {
  210. this._setValue(target, currentValue, weight, index);
  211. index++;
  212. }
  213. }
  214. else {
  215. this._setValue(this._target, currentValue, weight);
  216. }
  217. }
  218. private _setValue(target: any, currentValue: any, weight: number, targetIndex = 0): void {
  219. // Set value
  220. var path: any;
  221. var destination: any;
  222. let targetPropertyPath = this._animation.targetPropertyPath
  223. if (targetPropertyPath.length > 1) {
  224. var property = target[targetPropertyPath[0]];
  225. for (var index = 1; index < targetPropertyPath.length - 1; index++) {
  226. property = property[targetPropertyPath[index]];
  227. }
  228. path = targetPropertyPath[targetPropertyPath.length - 1];
  229. destination = property;
  230. } else {
  231. path = targetPropertyPath[0];
  232. destination = target;
  233. }
  234. this._targetPath = path;
  235. this._activeTarget = destination;
  236. this._weight = weight;
  237. if (this._originalValue[targetIndex] === undefined) {
  238. let originalValue: any;
  239. if (destination.getRestPose && path === "_matrix") { // For bones
  240. originalValue = destination.getRestPose();
  241. } else {
  242. originalValue = destination[path];
  243. }
  244. if (originalValue && originalValue.clone) {
  245. this._originalValue[targetIndex] = originalValue.clone();
  246. } else {
  247. this._originalValue[targetIndex] = originalValue;
  248. }
  249. }
  250. // Blending
  251. const enableBlending = target && target.animationPropertiesOverride ? target.animationPropertiesOverride.enableBlending : this._animation.enableBlending;
  252. if (enableBlending && this._blendingFactor <= 1.0) {
  253. if (!this._originalBlendValue) {
  254. let originalValue = destination[path];
  255. if (originalValue.clone) {
  256. this._originalBlendValue = originalValue.clone();
  257. } else {
  258. this._originalBlendValue = originalValue;
  259. }
  260. }
  261. if (this._originalBlendValue.m) { // Matrix
  262. if (Animation.AllowMatrixDecomposeForInterpolation) {
  263. if (this._currentValue) {
  264. Matrix.DecomposeLerpToRef(this._originalBlendValue, currentValue, this._blendingFactor, this._currentValue);
  265. } else {
  266. this._currentValue = Matrix.DecomposeLerp(this._originalBlendValue, currentValue, this._blendingFactor);
  267. }
  268. } else {
  269. if (this._currentValue) {
  270. Matrix.LerpToRef(this._originalBlendValue, currentValue, this._blendingFactor, this._currentValue);
  271. } else {
  272. this._currentValue = Matrix.Lerp(this._originalBlendValue, currentValue, this._blendingFactor);
  273. }
  274. }
  275. } else {
  276. this._currentValue = Animation._UniversalLerp(this._originalBlendValue, currentValue, this._blendingFactor);
  277. }
  278. const blendingSpeed = target && target.animationPropertiesOverride ? target.animationPropertiesOverride.blendingSpeed : this._animation.blendingSpeed;
  279. this._blendingFactor += blendingSpeed;
  280. } else {
  281. this._currentValue = currentValue;
  282. }
  283. if (weight !== -1.0) {
  284. this._scene._registerTargetForLateAnimationBinding(this, this._originalValue[targetIndex]);
  285. } else {
  286. destination[path] = this._currentValue;
  287. }
  288. if (target.markAsDirty) {
  289. target.markAsDirty(this._animation.targetProperty);
  290. }
  291. }
  292. /**
  293. * Gets the loop pmode of the runtime animation
  294. * @returns Loop Mode
  295. */
  296. private _getCorrectLoopMode(): number | undefined {
  297. if ( this._target && this._target.animationPropertiesOverride) {
  298. return this._target.animationPropertiesOverride.loopMode;
  299. }
  300. return this._animation.loopMode;
  301. }
  302. /**
  303. * Move the current animation to a given frame
  304. * @param frame defines the frame to move to
  305. */
  306. public goToFrame(frame: number): void {
  307. let keys = this._animation.getKeys();
  308. if (frame < keys[0].frame) {
  309. frame = keys[0].frame;
  310. } else if (frame > keys[keys.length - 1].frame) {
  311. frame = keys[keys.length - 1].frame;
  312. }
  313. var currentValue = this._interpolate(frame, 0, this._getCorrectLoopMode());
  314. this.setValue(currentValue, -1);
  315. }
  316. /**
  317. * @hidden Internal use only
  318. */
  319. public _prepareForSpeedRatioChange(newSpeedRatio: number): void {
  320. let newRatio = this._previousDelay * (this._animation.framePerSecond * newSpeedRatio) / 1000.0;
  321. this._ratioOffset = this._previousRatio - newRatio;
  322. }
  323. /**
  324. * Execute the current animation
  325. * @param delay defines the delay to add to the current frame
  326. * @param from defines the lower bound of the animation range
  327. * @param to defines the upper bound of the animation range
  328. * @param loop defines if the current animation must loop
  329. * @param speedRatio defines the current speed ratio
  330. * @param weight defines the weight of the animation (default is -1 so no weight)
  331. * @returns a boolean indicating if the animation has ended
  332. */
  333. public animate(delay: number, from: number, to: number, loop: boolean, speedRatio: number, weight = -1.0): boolean {
  334. let targetPropertyPath = this._animation.targetPropertyPath
  335. if (!targetPropertyPath || targetPropertyPath.length < 1) {
  336. this._stopped = true;
  337. return false;
  338. }
  339. var returnValue = true;
  340. let keys = this._animation.getKeys();
  341. // Adding a start key at frame 0 if missing
  342. if (keys[0].frame !== 0) {
  343. var newKey = { frame: 0, value: keys[0].value };
  344. keys.splice(0, 0, newKey);
  345. }
  346. // Check limits
  347. if (from < keys[0].frame || from > keys[keys.length - 1].frame) {
  348. from = keys[0].frame;
  349. }
  350. if (to < keys[0].frame || to > keys[keys.length - 1].frame) {
  351. to = keys[keys.length - 1].frame;
  352. }
  353. //to and from cannot be the same key
  354. if(from === to) {
  355. if (from > keys[0].frame) {
  356. from--;
  357. } else if (to < keys[keys.length - 1].frame) {
  358. to++;
  359. }
  360. }
  361. // Compute ratio
  362. var range = to - from;
  363. var offsetValue;
  364. // ratio represents the frame delta between from and to
  365. var ratio = (delay * (this._animation.framePerSecond * speedRatio) / 1000.0) + this._ratioOffset;
  366. var highLimitValue = 0;
  367. this._previousDelay = delay;
  368. this._previousRatio = ratio;
  369. if (((to > from && ratio >= range) || (from > to && ratio <= range)) && !loop) { // If we are out of range and not looping get back to caller
  370. returnValue = false;
  371. highLimitValue = this._animation._getKeyValue(keys[keys.length - 1].value);
  372. } else {
  373. // Get max value if required
  374. if (this._getCorrectLoopMode() !== Animation.ANIMATIONLOOPMODE_CYCLE) {
  375. var keyOffset = to.toString() + from.toString();
  376. if (!this._offsetsCache[keyOffset]) {
  377. var fromValue = this._interpolate(from, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
  378. var toValue = this._interpolate(to, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
  379. switch (this._animation.dataType) {
  380. // Float
  381. case Animation.ANIMATIONTYPE_FLOAT:
  382. this._offsetsCache[keyOffset] = toValue - fromValue;
  383. break;
  384. // Quaternion
  385. case Animation.ANIMATIONTYPE_QUATERNION:
  386. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  387. break;
  388. // Vector3
  389. case Animation.ANIMATIONTYPE_VECTOR3:
  390. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  391. // Vector2
  392. case Animation.ANIMATIONTYPE_VECTOR2:
  393. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  394. // Size
  395. case Animation.ANIMATIONTYPE_SIZE:
  396. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  397. // Color3
  398. case Animation.ANIMATIONTYPE_COLOR3:
  399. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  400. default:
  401. break;
  402. }
  403. this._highLimitsCache[keyOffset] = toValue;
  404. }
  405. highLimitValue = this._highLimitsCache[keyOffset];
  406. offsetValue = this._offsetsCache[keyOffset];
  407. }
  408. }
  409. if (offsetValue === undefined) {
  410. switch (this._animation.dataType) {
  411. // Float
  412. case Animation.ANIMATIONTYPE_FLOAT:
  413. offsetValue = 0;
  414. break;
  415. // Quaternion
  416. case Animation.ANIMATIONTYPE_QUATERNION:
  417. offsetValue = new Quaternion(0, 0, 0, 0);
  418. break;
  419. // Vector3
  420. case Animation.ANIMATIONTYPE_VECTOR3:
  421. offsetValue = Vector3.Zero();
  422. break;
  423. // Vector2
  424. case Animation.ANIMATIONTYPE_VECTOR2:
  425. offsetValue = Vector2.Zero();
  426. break;
  427. // Size
  428. case Animation.ANIMATIONTYPE_SIZE:
  429. offsetValue = Size.Zero();
  430. break;
  431. // Color3
  432. case Animation.ANIMATIONTYPE_COLOR3:
  433. offsetValue = Color3.Black();
  434. }
  435. }
  436. // Compute value
  437. var repeatCount = (ratio / range) >> 0;
  438. var currentFrame = returnValue ? from + ratio % range : to;
  439. // Need to normalize?
  440. if (this._host && this._host.syncRoot) {
  441. let syncRoot = this._host.syncRoot;
  442. let hostNormalizedFrame = (syncRoot.masterFrame - syncRoot.fromFrame) / (syncRoot.toFrame - syncRoot.fromFrame);
  443. currentFrame = from + (to - from) * hostNormalizedFrame;
  444. }
  445. // Reset events if looping
  446. let events = this._events;
  447. if (range > 0 && this.currentFrame > currentFrame ||
  448. range < 0 && this.currentFrame < currentFrame) {
  449. // Need to reset animation events
  450. for (var index = 0; index < events.length; index++) {
  451. if (!events[index].onlyOnce) {
  452. // reset event, the animation is looping
  453. events[index].isDone = false;
  454. }
  455. }
  456. }
  457. var currentValue = this._interpolate(currentFrame, repeatCount, this._getCorrectLoopMode(), offsetValue, highLimitValue);
  458. // Set value
  459. this.setValue(currentValue, weight);
  460. // Check events
  461. for (var index = 0; index < events.length; index++) {
  462. // Make sure current frame has passed event frame and that event frame is within the current range
  463. // Also, handle both forward and reverse animations
  464. if (
  465. (range > 0 && currentFrame >= events[index].frame && events[index].frame >= from) ||
  466. (range < 0 && currentFrame <= events[index].frame && events[index].frame <= from)
  467. ){
  468. var event = events[index];
  469. if (!event.isDone) {
  470. // If event should be done only once, remove it.
  471. if (event.onlyOnce) {
  472. events.splice(index, 1);
  473. index--;
  474. }
  475. event.isDone = true;
  476. event.action();
  477. } // Don't do anything if the event has already be done.
  478. }
  479. }
  480. if (!returnValue) {
  481. this._stopped = true;
  482. }
  483. return returnValue;
  484. }
  485. }
  486. }