babylon.runtimeAnimation.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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. /** @ignore */
  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. */
  139. public reset(): void {
  140. this._offsetsCache = {};
  141. this._highLimitsCache = {};
  142. this._currentFrame = 0;
  143. this._blendingFactor = 0;
  144. this._originalValue = null;
  145. }
  146. /**
  147. * Specifies if the runtime animation is stopped
  148. * @returns Boolean specifying if the runtime animation is stopped
  149. */
  150. public isStopped(): boolean {
  151. return this._stopped;
  152. }
  153. /**
  154. * Disposes of the runtime animation
  155. */
  156. public dispose(): void {
  157. let index = this._animation.runtimeAnimations.indexOf(this);
  158. if (index > -1) {
  159. this._animation.runtimeAnimations.splice(index, 1);
  160. }
  161. }
  162. /**
  163. * Interpolates the animation from the current frame
  164. * @param currentFrame The frame to interpolate the animation to
  165. * @param repeatCount The number of times that the animation should loop
  166. * @param loopMode The type of looping mode to use
  167. * @param offsetValue Animation offset value
  168. * @param highLimitValue The high limit value
  169. * @returns The interpolated value
  170. */
  171. private _interpolate(currentFrame: number, repeatCount: number, loopMode?: number, offsetValue?: any, highLimitValue?: any): any {
  172. if (loopMode === Animation.ANIMATIONLOOPMODE_CONSTANT && repeatCount > 0) {
  173. return highLimitValue.clone ? highLimitValue.clone() : highLimitValue;
  174. }
  175. this._currentFrame = currentFrame;
  176. return this._animation._interpolate(currentFrame, repeatCount, this._workValue, loopMode, offsetValue, highLimitValue);
  177. }
  178. /**
  179. * Affect the interpolated value to the target
  180. * @param currentValue defines the value computed by the animation
  181. * @param weight defines the weight to apply to this value
  182. */
  183. public setValue(currentValue: any, weight = 1.0): void {
  184. if (this._target instanceof Array) {
  185. for (const target of this._target) {
  186. this._setValue(target, currentValue, weight);
  187. }
  188. }
  189. else {
  190. this._setValue(this._target, currentValue, weight);
  191. }
  192. }
  193. /**
  194. * Sets the value of the runtime animation
  195. * @param target The target property of the runtime animation
  196. * @param currentValue The current value to use for the runtime animation
  197. * @param weight The weight to use for the runtime animation (Defaults to 1.0)
  198. */
  199. private _setValue(target: any, currentValue: any, weight = 1.0): void {
  200. // Set value
  201. var path: any;
  202. var destination: any;
  203. let targetPropertyPath = this._animation.targetPropertyPath
  204. if (targetPropertyPath.length > 1) {
  205. var property = target[targetPropertyPath[0]];
  206. for (var index = 1; index < targetPropertyPath.length - 1; index++) {
  207. property = property[targetPropertyPath[index]];
  208. }
  209. path = targetPropertyPath[targetPropertyPath.length - 1];
  210. destination = property;
  211. } else {
  212. path = targetPropertyPath[0];
  213. destination = target;
  214. }
  215. this._targetPath = path;
  216. this._activeTarget = destination;
  217. this._weight = weight;
  218. // Blending
  219. let enableBlending = target && target.animationPropertiesOverride ? target.animationPropertiesOverride.enableBlending : this._animation.enableBlending;
  220. let blendingSpeed = target && target.animationPropertiesOverride ? target.animationPropertiesOverride.blendingSpeed : this._animation.blendingSpeed;
  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. }
  231. if (weight !== -1.0) {
  232. if (!this._originalValue) {
  233. let originalValue: any;
  234. if (destination.getRestPose) { // For bones
  235. originalValue = destination.getRestPose();
  236. } else {
  237. originalValue = destination[path];
  238. }
  239. if (originalValue.clone) {
  240. this._originalValue = originalValue.clone();
  241. } else {
  242. this._originalValue = originalValue;
  243. }
  244. }
  245. }
  246. if (enableBlending && this._blendingFactor <= 1.0) {
  247. if (this._originalBlendValue.m) { // Matrix
  248. if (Animation.AllowMatrixDecomposeForInterpolation) {
  249. if (this._currentValue) {
  250. Matrix.DecomposeLerpToRef(this._originalBlendValue, currentValue, this._blendingFactor, this._currentValue);
  251. } else {
  252. this._currentValue = Matrix.DecomposeLerp(this._originalBlendValue, currentValue, this._blendingFactor);
  253. }
  254. } else {
  255. if (this._currentValue) {
  256. Matrix.LerpToRef(this._originalBlendValue, currentValue, this._blendingFactor, this._currentValue);
  257. } else {
  258. this._currentValue = Matrix.Lerp(this._originalBlendValue, currentValue, this._blendingFactor);
  259. }
  260. }
  261. } else {
  262. let constructor = this._originalBlendValue.constructor;
  263. if (constructor.Lerp) { // Lerp supported
  264. this._currentValue = constructor.Lerp(this._originalBlendValue, currentValue, this._blendingFactor);
  265. } else if (constructor.Slerp) { // Slerp supported
  266. this._currentValue = constructor.Slerp(this._originalBlendValue, currentValue, this._blendingFactor);
  267. } else if (this._originalBlendValue.toFixed) { // Number
  268. this._currentValue = this._originalBlendValue * (1.0 - this._blendingFactor) + this._blendingFactor * currentValue;
  269. } else { // Blending not supported
  270. this._currentValue = currentValue;
  271. }
  272. }
  273. this._blendingFactor += blendingSpeed;
  274. } else {
  275. this._currentValue = currentValue;
  276. }
  277. if (weight !== -1.0) {
  278. this._scene._registerTargetForLateAnimationBinding(this);
  279. } else {
  280. destination[path] = this._currentValue;
  281. }
  282. if (target.markAsDirty) {
  283. target.markAsDirty(this._animation.targetProperty);
  284. }
  285. }
  286. /**
  287. * Gets the loop pmode of the runtime animation
  288. * @returns Loop Mode
  289. */
  290. private _getCorrectLoopMode(): number | undefined {
  291. if ( this._target && this._target.animationPropertiesOverride) {
  292. return this._target.animationPropertiesOverride.loopMode;
  293. }
  294. return this._animation.loopMode;
  295. }
  296. /**
  297. * Move the current animation to a given frame
  298. * @param frame defines the frame to move to
  299. */
  300. public goToFrame(frame: number): void {
  301. let keys = this._animation.getKeys();
  302. if (frame < keys[0].frame) {
  303. frame = keys[0].frame;
  304. } else if (frame > keys[keys.length - 1].frame) {
  305. frame = keys[keys.length - 1].frame;
  306. }
  307. var currentValue = this._interpolate(frame, 0, this._getCorrectLoopMode());
  308. this.setValue(currentValue, -1);
  309. }
  310. /**
  311. * @ignore Internal use only
  312. */
  313. public _prepareForSpeedRatioChange(newSpeedRatio: number): void {
  314. let newRatio = this._previousDelay * (this._animation.framePerSecond * newSpeedRatio) / 1000.0;
  315. this._ratioOffset = this._previousRatio - newRatio;
  316. }
  317. /**
  318. * Execute the current animation
  319. * @param delay defines the delay to add to the current frame
  320. * @param from defines the lower bound of the animation range
  321. * @param to defines the upper bound of the animation range
  322. * @param loop defines if the current animation must loop
  323. * @param speedRatio defines the current speed ratio
  324. * @param weight defines the weight of the animation (default is -1 so no weight)
  325. * @returns a boolean indicating if the animation has ended
  326. */
  327. public animate(delay: number, from: number, to: number, loop: boolean, speedRatio: number, weight = -1.0): boolean {
  328. let targetPropertyPath = this._animation.targetPropertyPath
  329. if (!targetPropertyPath || targetPropertyPath.length < 1) {
  330. this._stopped = true;
  331. return false;
  332. }
  333. var returnValue = true;
  334. let keys = this._animation.getKeys();
  335. // Adding a start key at frame 0 if missing
  336. if (keys[0].frame !== 0) {
  337. var newKey = { frame: 0, value: keys[0].value };
  338. keys.splice(0, 0, newKey);
  339. }
  340. // Check limits
  341. if (from < keys[0].frame || from > keys[keys.length - 1].frame) {
  342. from = keys[0].frame;
  343. }
  344. if (to < keys[0].frame || to > keys[keys.length - 1].frame) {
  345. to = keys[keys.length - 1].frame;
  346. }
  347. //to and from cannot be the same key
  348. if(from === to) {
  349. if (from > keys[0].frame) {
  350. from--;
  351. } else if (to < keys[keys.length - 1].frame) {
  352. to++;
  353. }
  354. }
  355. // Compute ratio
  356. var range = to - from;
  357. var offsetValue;
  358. // ratio represents the frame delta between from and to
  359. var ratio = (delay * (this._animation.framePerSecond * speedRatio) / 1000.0) + this._ratioOffset;
  360. var highLimitValue = 0;
  361. this._previousDelay = delay;
  362. this._previousRatio = ratio;
  363. if (((to > from && ratio > range) || (from > to && ratio < range)) && !loop) { // If we are out of range and not looping get back to caller
  364. returnValue = false;
  365. highLimitValue = this._animation._getKeyValue(keys[keys.length - 1].value);
  366. } else {
  367. // Get max value if required
  368. if (this._getCorrectLoopMode() !== Animation.ANIMATIONLOOPMODE_CYCLE) {
  369. var keyOffset = to.toString() + from.toString();
  370. if (!this._offsetsCache[keyOffset]) {
  371. var fromValue = this._interpolate(from, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
  372. var toValue = this._interpolate(to, 0, Animation.ANIMATIONLOOPMODE_CYCLE);
  373. switch (this._animation.dataType) {
  374. // Float
  375. case Animation.ANIMATIONTYPE_FLOAT:
  376. this._offsetsCache[keyOffset] = toValue - fromValue;
  377. break;
  378. // Quaternion
  379. case Animation.ANIMATIONTYPE_QUATERNION:
  380. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  381. break;
  382. // Vector3
  383. case Animation.ANIMATIONTYPE_VECTOR3:
  384. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  385. // Vector2
  386. case Animation.ANIMATIONTYPE_VECTOR2:
  387. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  388. // Size
  389. case Animation.ANIMATIONTYPE_SIZE:
  390. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  391. // Color3
  392. case Animation.ANIMATIONTYPE_COLOR3:
  393. this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
  394. default:
  395. break;
  396. }
  397. this._highLimitsCache[keyOffset] = toValue;
  398. }
  399. highLimitValue = this._highLimitsCache[keyOffset];
  400. offsetValue = this._offsetsCache[keyOffset];
  401. }
  402. }
  403. if (offsetValue === undefined) {
  404. switch (this._animation.dataType) {
  405. // Float
  406. case Animation.ANIMATIONTYPE_FLOAT:
  407. offsetValue = 0;
  408. break;
  409. // Quaternion
  410. case Animation.ANIMATIONTYPE_QUATERNION:
  411. offsetValue = new Quaternion(0, 0, 0, 0);
  412. break;
  413. // Vector3
  414. case Animation.ANIMATIONTYPE_VECTOR3:
  415. offsetValue = Vector3.Zero();
  416. break;
  417. // Vector2
  418. case Animation.ANIMATIONTYPE_VECTOR2:
  419. offsetValue = Vector2.Zero();
  420. break;
  421. // Size
  422. case Animation.ANIMATIONTYPE_SIZE:
  423. offsetValue = Size.Zero();
  424. break;
  425. // Color3
  426. case Animation.ANIMATIONTYPE_COLOR3:
  427. offsetValue = Color3.Black();
  428. }
  429. }
  430. // Compute value
  431. var repeatCount = (ratio / range) >> 0;
  432. var currentFrame = returnValue ? from + ratio % range : to;
  433. // Need to normalize?
  434. if (this._host && this._host.syncRoot) {
  435. let syncRoot = this._host.syncRoot;
  436. let hostNormalizedFrame = (syncRoot.masterFrame - syncRoot.fromFrame) / (syncRoot.toFrame - syncRoot.fromFrame);
  437. currentFrame = from + (to - from) * hostNormalizedFrame;
  438. }
  439. var currentValue = this._interpolate(currentFrame, repeatCount, this._getCorrectLoopMode(), offsetValue, highLimitValue);
  440. // Set value
  441. this.setValue(currentValue, weight);
  442. // Check events
  443. let events = this._animation.getEvents();
  444. for (var index = 0; index < events.length; index++) {
  445. // Make sure current frame has passed event frame and that event frame is within the current range
  446. // Also, handle both forward and reverse animations
  447. if (
  448. (range > 0 && currentFrame >= events[index].frame && events[index].frame >= from) ||
  449. (range < 0 && currentFrame <= events[index].frame && events[index].frame <= from)
  450. ){
  451. var event = events[index];
  452. if (!event.isDone) {
  453. // If event should be done only once, remove it.
  454. if (event.onlyOnce) {
  455. events.splice(index, 1);
  456. index--;
  457. }
  458. event.isDone = true;
  459. event.action();
  460. } // Don't do anything if the event has already be done.
  461. } else if (events[index].isDone && !events[index].onlyOnce) {
  462. // reset event, the animation is looping
  463. events[index].isDone = false;
  464. }
  465. }
  466. if (!returnValue) {
  467. this._stopped = true;
  468. }
  469. return returnValue;
  470. }
  471. }
  472. }