ModelAnimation.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import defaultValue from "../../Core/defaultValue.js";
  2. import defined from "../../Core/defined.js";
  3. import Event from "../../Core/Event.js";
  4. import JulianDate from "../../Core/JulianDate.js";
  5. import ModelAnimationLoop from "../ModelAnimationLoop.js";
  6. import ModelAnimationState from "../ModelAnimationState.js";
  7. import ModelAnimationChannel from "./ModelAnimationChannel.js";
  8. /**
  9. * <div class="notice">
  10. * Create animations by calling {@link ModelAnimationCollection#add}. Do not call the constructor directly.
  11. * </div>
  12. *
  13. * An active animation derived from a glTF asset. An active animation is an
  14. * animation that is either currently playing or scheduled to be played due to
  15. * being added to a model's {@link ModelAnimationCollection}. An active animation
  16. * is an instance of an animation; for example, there can be multiple active
  17. * animations for the same glTF animation, each with a different start time.
  18. *
  19. * @alias ModelAnimation
  20. * @internalConstructor
  21. * @class
  22. *
  23. * @see ModelAnimationCollection#add
  24. */
  25. function ModelAnimation(model, animation, options) {
  26. this._animation = animation;
  27. this._name = animation.name;
  28. this._runtimeChannels = undefined;
  29. this._startTime = JulianDate.clone(options.startTime);
  30. this._delay = defaultValue(options.delay, 0.0); // in seconds
  31. this._stopTime = JulianDate.clone(options.stopTime);
  32. /**
  33. * When <code>true</code>, the animation is removed after it stops playing.
  34. * This is slightly more efficient that not removing it, but if, for example,
  35. * time is reversed, the animation is not played again.
  36. *
  37. * @type {boolean}
  38. * @default false
  39. */
  40. this.removeOnStop = defaultValue(options.removeOnStop, false);
  41. this._multiplier = defaultValue(options.multiplier, 1.0);
  42. this._reverse = defaultValue(options.reverse, false);
  43. this._loop = defaultValue(options.loop, ModelAnimationLoop.NONE);
  44. this._animationTime = options.animationTime;
  45. this._prevAnimationDelta = undefined;
  46. /**
  47. * The event fired when this animation is started. This can be used, for
  48. * example, to play a sound or start a particle system, when the animation starts.
  49. * <p>
  50. * This event is fired at the end of the frame after the scene is rendered.
  51. * </p>
  52. *
  53. * @type {Event}
  54. * @default new Event()
  55. *
  56. * @example
  57. * animation.start.addEventListener(function(model, animation) {
  58. * console.log(`Animation started: ${animation.name}`);
  59. * });
  60. */
  61. this.start = new Event();
  62. /**
  63. * The event fired when on each frame when this animation is updated. The
  64. * current time of the animation, relative to the glTF animation time span, is
  65. * passed to the event, which allows, for example, starting new animations at a
  66. * specific time relative to a playing animation.
  67. * <p>
  68. * This event is fired at the end of the frame after the scene is rendered.
  69. * </p>
  70. *
  71. * @type {Event}
  72. * @default new Event()
  73. *
  74. * @example
  75. * animation.update.addEventListener(function(model, animation, time) {
  76. * console.log(`Animation updated: ${animation.name}. glTF animation time: ${time}`);
  77. * });
  78. */
  79. this.update = new Event();
  80. /**
  81. * The event fired when this animation is stopped. This can be used, for
  82. * example, to play a sound or start a particle system, when the animation stops.
  83. * <p>
  84. * This event is fired at the end of the frame after the scene is rendered.
  85. * </p>
  86. *
  87. * @type {Event}
  88. * @default new Event()
  89. *
  90. * @example
  91. * animation.stop.addEventListener(function(model, animation) {
  92. * console.log(`Animation stopped: ${animation.name}`);
  93. * });
  94. */
  95. this.stop = new Event();
  96. this._state = ModelAnimationState.STOPPED;
  97. // Set during animation update
  98. this._computedStartTime = undefined;
  99. this._duration = undefined;
  100. // To avoid allocations in ModelAnimationCollection.update
  101. const that = this;
  102. this._raiseStartEvent = function () {
  103. that.start.raiseEvent(model, that);
  104. };
  105. this._updateEventTime = 0.0;
  106. this._raiseUpdateEvent = function () {
  107. that.update.raiseEvent(model, that, that._updateEventTime);
  108. };
  109. this._raiseStopEvent = function () {
  110. that.stop.raiseEvent(model, that);
  111. };
  112. this._model = model;
  113. this._localStartTime = undefined;
  114. this._localStopTime = undefined;
  115. initialize(this);
  116. }
  117. Object.defineProperties(ModelAnimation.prototype, {
  118. /**
  119. * The glTF animation.
  120. *
  121. * @memberof ModelAnimation.prototype
  122. *
  123. * @type {ModelComponents.Animation}
  124. * @readonly
  125. *
  126. * @private
  127. */
  128. animation: {
  129. get: function () {
  130. return this._animation;
  131. },
  132. },
  133. /**
  134. * The name that identifies this animation in the model, if it exists.
  135. *
  136. * @memberof ModelAnimation.prototype
  137. *
  138. * @type {string}
  139. * @readonly
  140. */
  141. name: {
  142. get: function () {
  143. return this._name;
  144. },
  145. },
  146. /**
  147. * The runtime animation channels for this animation.
  148. *
  149. * @memberof ModelAnimation.prototype
  150. *
  151. * @type {ModelAnimationChannel[]}
  152. * @readonly
  153. *
  154. * @private
  155. */
  156. runtimeChannels: {
  157. get: function () {
  158. return this._runtimeChannels;
  159. },
  160. },
  161. /**
  162. * The {@link Model} that owns this animation.
  163. *
  164. * @memberof ModelAnimation.prototype
  165. *
  166. * @type {Model}
  167. * @readonly
  168. *
  169. * @private
  170. */
  171. model: {
  172. get: function () {
  173. return this._model;
  174. },
  175. },
  176. /**
  177. * The starting point of the animation in local animation time. This is the minimum
  178. * time value across all of the keyframes belonging to this animation.
  179. *
  180. * @memberof ModelAnimation.prototype
  181. *
  182. * @type {number}
  183. * @readonly
  184. *
  185. * @private
  186. */
  187. localStartTime: {
  188. get: function () {
  189. return this._localStartTime;
  190. },
  191. },
  192. /**
  193. * The stopping point of the animation in local animation time. This is the maximum
  194. * time value across all of the keyframes belonging to this animation.
  195. *
  196. * @memberof ModelAnimation.prototype
  197. *
  198. * @type {number}
  199. * @readonly
  200. *
  201. * @private
  202. */
  203. localStopTime: {
  204. get: function () {
  205. return this._localStopTime;
  206. },
  207. },
  208. /**
  209. * The scene time to start playing this animation. When this is <code>undefined</code>,
  210. * the animation starts at the next frame.
  211. *
  212. * @memberof ModelAnimation.prototype
  213. *
  214. * @type {JulianDate}
  215. * @readonly
  216. *
  217. * @default undefined
  218. */
  219. startTime: {
  220. get: function () {
  221. return this._startTime;
  222. },
  223. },
  224. /**
  225. * The delay, in seconds, from {@link ModelAnimation#startTime} to start playing.
  226. *
  227. * @memberof ModelAnimation.prototype
  228. *
  229. * @type {number}
  230. * @readonly
  231. *
  232. * @default undefined
  233. */
  234. delay: {
  235. get: function () {
  236. return this._delay;
  237. },
  238. },
  239. /**
  240. * The scene time to stop playing this animation. When this is <code>undefined</code>,
  241. * the animation is played for its full duration and perhaps repeated depending on
  242. * {@link ModelAnimation#loop}.
  243. *
  244. * @memberof ModelAnimation.prototype
  245. *
  246. * @type {JulianDate}
  247. * @readonly
  248. *
  249. * @default undefined
  250. */
  251. stopTime: {
  252. get: function () {
  253. return this._stopTime;
  254. },
  255. },
  256. /**
  257. * Values greater than <code>1.0</code> increase the speed that the animation is played relative
  258. * to the scene clock speed; values less than <code>1.0</code> decrease the speed. A value of
  259. * <code>1.0</code> plays the animation at the speed in the glTF animation mapped to the scene
  260. * clock speed. For example, if the scene is played at 2x real-time, a two-second glTF animation
  261. * will play in one second even if <code>multiplier</code> is <code>1.0</code>.
  262. *
  263. * @memberof ModelAnimation.prototype
  264. *
  265. * @type {number}
  266. * @readonly
  267. *
  268. * @default 1.0
  269. */
  270. multiplier: {
  271. get: function () {
  272. return this._multiplier;
  273. },
  274. },
  275. /**
  276. * When <code>true</code>, the animation is played in reverse.
  277. *
  278. * @memberof ModelAnimation.prototype
  279. *
  280. * @type {boolean}
  281. * @readonly
  282. *
  283. * @default false
  284. */
  285. reverse: {
  286. get: function () {
  287. return this._reverse;
  288. },
  289. },
  290. /**
  291. * Determines if and how the animation is looped.
  292. *
  293. * @memberof ModelAnimation.prototype
  294. *
  295. * @type {ModelAnimationLoop}
  296. * @readonly
  297. *
  298. * @default {@link ModelAnimationLoop.NONE}
  299. */
  300. loop: {
  301. get: function () {
  302. return this._loop;
  303. },
  304. },
  305. /**
  306. * If this is defined, it will be used to compute the local animation time
  307. * instead of the scene's time.
  308. *
  309. * @memberof ModelAnimation.prototype
  310. *
  311. * @type {ModelAnimation.AnimationTimeCallback}
  312. * @default undefined
  313. */
  314. animationTime: {
  315. get: function () {
  316. return this._animationTime;
  317. },
  318. },
  319. });
  320. function initialize(runtimeAnimation) {
  321. let localStartTime = Number.MAX_VALUE;
  322. let localStopTime = -Number.MAX_VALUE;
  323. const sceneGraph = runtimeAnimation._model.sceneGraph;
  324. const animation = runtimeAnimation._animation;
  325. const channels = animation.channels;
  326. const length = channels.length;
  327. const runtimeChannels = [];
  328. for (let i = 0; i < length; i++) {
  329. const channel = channels[i];
  330. const target = channel.target;
  331. // Ignore this channel if the target is invalid, i.e. if the node
  332. // it references doesn't exist.
  333. if (!defined(target)) {
  334. continue;
  335. }
  336. const nodeIndex = target.node.index;
  337. const runtimeNode = sceneGraph._runtimeNodes[nodeIndex];
  338. const runtimeChannel = new ModelAnimationChannel({
  339. channel: channel,
  340. runtimeAnimation: runtimeAnimation,
  341. runtimeNode: runtimeNode,
  342. });
  343. const times = channel.sampler.input;
  344. localStartTime = Math.min(localStartTime, times[0]);
  345. localStopTime = Math.max(localStopTime, times[times.length - 1]);
  346. runtimeChannels.push(runtimeChannel);
  347. }
  348. runtimeAnimation._runtimeChannels = runtimeChannels;
  349. runtimeAnimation._localStartTime = localStartTime;
  350. runtimeAnimation._localStopTime = localStopTime;
  351. }
  352. /**
  353. * Evaluate all animation channels to advance this animation.
  354. *
  355. * @param {number} time The local animation time.
  356. *
  357. * @private
  358. */
  359. ModelAnimation.prototype.animate = function (time) {
  360. const runtimeChannels = this._runtimeChannels;
  361. const length = runtimeChannels.length;
  362. for (let i = 0; i < length; i++) {
  363. runtimeChannels[i].animate(time);
  364. }
  365. };
  366. /**
  367. * A function used to compute the local animation time for a ModelAnimation.
  368. * @callback ModelAnimation.AnimationTimeCallback
  369. *
  370. * @param {number} duration The animation's original duration in seconds.
  371. * @param {number} seconds The seconds since the animation started, in scene time.
  372. * @returns {number} Returns the local animation time.
  373. *
  374. * @example
  375. * // Use real time for model animation (assuming animateWhilePaused was set to true)
  376. * function animationTime(duration) {
  377. * return Date.now() / 1000 / duration;
  378. * }
  379. *
  380. * @example
  381. * // Offset the phase of the animation, so it starts halfway through its cycle.
  382. * function animationTime(duration, seconds) {
  383. * return seconds / duration + 0.5;
  384. * }
  385. */
  386. export default ModelAnimation;