babylon.runtimeAnimation.ts 20 KB

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