babylon.runtimeAnimation.ts 23 KB

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