babylon.runtimeAnimation.ts 22 KB

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