babylon.runtimeAnimation.ts 21 KB

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