runtimeAnimation.ts 22 KB

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