animation.vwf.yaml 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. # Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
  2. # Secretary of Defense (Personnel & Readiness).
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  5. # in compliance with the License. You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software distributed under the License
  10. # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  11. # or implied. See the License for the specific language governing permissions and limitations under
  12. # the License.
  13. ## `animation.vwf` adds standard animation functions to a node.
  14. ##
  15. ## Animations play over a given `animationDuration` and at a given `animationRate` with respect to
  16. ## simulation time. `animationTime` tracks time as the animation plays. Setting `animationTime`
  17. ## moves the animation. The animation plays once to the end, or with `animationLoop` repeats
  18. ## indefinitely.
  19. ##
  20. ## Events are sent to indicate `animationStarted`, `animationStopped` and `animationLooped`. Changes
  21. ## to the animation time are announced with `animationTimeChanged.`
  22. ##
  23. ## A node may provide an `animationUpdate` method to calculate its own animation state, or it may
  24. ## use a specialized behavior that implements a standard animation.
  25. ##
  26. ## @name animation.vwf
  27. ## @namespace
  28. # The duration must be a non-negative number, times are always in the range [ `0`, `duration` ], and
  29. # the rate may be any number (although there is little protection against rates very near zero).
  30. #
  31. # 0 <= animationDuration
  32. # 0 <= animationTime <= animationDuration
  33. # -Infinity < animationRate < Infinity
  34. #
  35. # The animation play state and time index are maintained in `animationStartSIM` and
  36. # `animationPauseSIM`. `animationPauseSIM` is `null` while the animation is playing. The
  37. # distance from `animationStartSIM` to the current time (when playing) or `animationPauseSIM`
  38. # (when paused) gives the time index, after accounting for the play rate.
  39. #
  40. # animationTime = ( ( animationPauseSIM || time ) - ( animationStartSIM || time ) ) *
  41. # animationRate (positive rates)
  42. # animationTime = ( ( animationPauseSIM || time ) - ( animationStartSIM || time ) ) *
  43. # animationRate + animationDuration (negative rates)
  44. #
  45. # animationPlaying = this.animationStartSIM != null && this.animationPauseSIM == null
  46. #
  47. # `animationStartSIM` and `animationPauseSIM` are `null` before the animation is first played.
  48. # This state is interpreted as `animationPlaying` == `false` and `animationTime` == `0`.
  49. #
  50. # animationStartSIM == null => animationPauseSIM == null
  51. # animationStartSIM == null => animationStopSIM == null
  52. #
  53. # animationStartSIM != null => animationStartSIM <= time
  54. # animationPauseSIM != null => animationStartSIM <= animationPauseSIM <= time
  55. # animationStopSIM != null => animationStartSIM + animationDurationSIM == animationStopSIM
  56. #
  57. # animationDurationSIM == animationDuration / animationRate (positive rates)
  58. # animationDurationSIM == -animationDuration / animationRate (negative rates)
  59. #
  60. # Properties named with a`SIM` suffix refer to times or durations on the simulation timeline. All
  61. # other time-related properties refer to the animation timeline.
  62. ---
  63. properties:
  64. ## The current animation position in seconds.
  65. ##
  66. ## `animationTime` automatically moves with the simulation time while the animation is playing,
  67. ## tracking the change in time multiplied by `animationRate`. A negative `animationRate` will
  68. ## cause `animationTime` to move backwards. Setting `animationTime` updates the postion whether or
  69. ## not the animation is currently playing.
  70. animationTime:
  71. set: |
  72. if(this.animationStartTime == null) {
  73. this.animationStartTime = 0;
  74. }
  75. if(this.animationStopTime == null) {
  76. this.animationStopTime = this.animationDuration;
  77. }
  78. // Save copies to avoid repeated reads.
  79. var duration = this.animationStopTime - this.animationStartTime,
  80. rate = this.animationRate;
  81. // Range limit the incoming value.
  82. value = Math.min( Math.max( this.animationStartTime, value ), this.animationStopTime );
  83. // Keep paused if updating start/pause from null/null. Use t=0 instead of `this.time` so that
  84. // setting `node.animationTime` during initialization is consistent across multiple clients.
  85. if ( this.animationStartSIM == null ) {
  86. this.animationPauseSIM = 0;
  87. }
  88. // Calculate the start and stop times that makes the new time work.
  89. this.animationStartSIM =
  90. ( this.animationPauseSIM != null ? this.animationPauseSIM : this.time ) -
  91. ( rate >= 0 ? value - this.animationStartTime : value - duration ) / rate;
  92. this.animationStopSIM =
  93. this.animationStartSIM +
  94. ( rate >= 0 ? duration : -duration ) / rate;
  95. // Update the node and fire the changed event.
  96. if ( value !== this.animationTimeUpdated ) {
  97. this.animationTimeUpdated = value;
  98. this.animationUpdate( value, this.animationDuration );
  99. this.animationTimeChanged( value );
  100. } //@ sourceURL=animation.animationTime.set.vwf
  101. get: |
  102. // Save copies to avoid repeated reads.
  103. var startTime = this.animationStartTime;
  104. var stopTime = this.animationStopTime;
  105. var rate = this.animationRate;
  106. var animationPauseSIM = this.animationPauseSIM;
  107. var animationStartSIM = this.animationStartSIM;
  108. var time = this.time;
  109. // Calculate the time from the start and current/pause times.
  110. var value = (
  111. ( animationPauseSIM != null ? animationPauseSIM : time ) -
  112. ( animationStartSIM != null ? animationStartSIM : time )
  113. ) * rate + ( rate >= 0 ? startTime : stopTime );
  114. // Range limit the value.
  115. value = Math.min( Math.max( startTime, value ), stopTime );
  116. // If changed since last seen, update and fire the changed event.
  117. if ( value !== this.animationTimeUpdated ) {
  118. this.animationTimeUpdated = value;
  119. this.animationUpdate( value, this.animationDuration );
  120. this.animationTimeChanged( value );
  121. }
  122. return value; //@ sourceURL=animation.animationTime.get.vwf
  123. ## The length of the animation in seconds.
  124. ##
  125. ## `animationTime` will always be within the range [ `0`, `animationDuration` ]. Animations
  126. ## automatically stop playing once `animationTime` reaches `animationDuration` unless
  127. ## `animationLoop` is set. Animations with a negative `animationRate` start at
  128. ## `animationDuration` and stop at `0`.
  129. animationDuration: # TODO: allow changes while playing
  130. set: |
  131. var duration = value, rate = this.animationRate;
  132. this.animationDuration = duration;
  133. this.animationDurationSIM = ( rate >= 0 ? duration : -duration ) / rate;
  134. value: 1
  135. ## The animation playback rate.
  136. ##
  137. ## Set `animationRate` to a value greater than `1` to increase the rate. Set a value between `0`
  138. ## and `1` to decrease it. Negative rates will cause the animation to play backwards.
  139. animationRate: # TODO: allow changes while playing
  140. set: |
  141. var duration = this.animationDuration, rate = value;
  142. this.animationRate = rate;
  143. this.animationDurationSIM = ( rate >= 0 ? duration : -duration ) / rate;
  144. value: 1
  145. ## Automatically replay the animation from the start after reaching the end.
  146. animationLoop: false
  147. ## The animation's play/pause control.
  148. animationPlaying:
  149. set: |
  150. if(this.animationStartTime == null) {
  151. this.animationStartTime = 0;
  152. }
  153. if(this.animationStopTime == null) {
  154. this.animationStopTime = this.animationDuration;
  155. }
  156. if ( this.animationStartSIM != null && this.animationPauseSIM == null ) {
  157. if ( ! value ) {
  158. // Mark as paused at the current time.
  159. this.animationPauseSIM = this.time;
  160. // Send the `animationStopped` event if stopping at the end.
  161. if ( this.time == this.animationStopSIM ) {
  162. this.animationStopped();
  163. }
  164. }
  165. } else {
  166. if ( value ) {
  167. // Save copies to avoid repeated reads.
  168. var duration = this.animationStopTime - this.animationStartTime,
  169. rate = this.animationRate;
  170. // Start from the beginning if resuming from the end.
  171. if ( this.animationPauseSIM == this.animationStopSIM ) {
  172. this.animationPauseSIM = this.animationStartSIM;
  173. }
  174. // Recalculate the start and stop times to keep paused time unchanged, then resume.
  175. this.animationStartSIM =
  176. ( this.animationStartSIM != null ? this.animationStartSIM : this.time ) -
  177. ( this.animationPauseSIM != null ? this.animationPauseSIM : this.time ) +
  178. this.time;
  179. this.animationStopSIM =
  180. this.animationStartSIM +
  181. ( rate >= 0 ? duration : -duration ) / rate;
  182. this.animationPauseSIM = null;
  183. // Send the `animationStarted` event if starting from the beginning.
  184. if ( this.time == this.animationStartSIM ) {
  185. this.animationStarted();
  186. }
  187. // Start the animation worker.
  188. this.logger.debug( "scheduling animationTick" );
  189. this.animationTick();
  190. }
  191. } //@ sourceURL=animation.animationPlaying.set.vwf
  192. get: |
  193. return this.animationStartSIM != null && this.animationPauseSIM == null;
  194. ## The most recent value of `animationTime` recognized by this behavior.
  195. ##
  196. ## Each `animationTime` change causes `animationUpdate` to be called. `animationTimeUpdated`
  197. ## records the value of `animationTime` from the last time this occurred.
  198. ##
  199. ## `animationTimeUpdated` is for internal use. Do not set this property.
  200. animationTimeUpdated: null
  201. ## The simulation time corresponding to the start of the animation.
  202. ##
  203. ## `animationStartSIM` is `null` until the animation is first played. `animationPlaying` is
  204. ## `false` and `animationTime` is `0` while `animationStartSIM` is `null`.
  205. ##
  206. ## `animationStartSIM` is for internal use. Do not set this property.
  207. animationStartSIM: null
  208. ## The simulation time corresponding to the animation's pause position.
  209. ##
  210. ## `animationPauseSIM` is `null` while the animation is playing and before the animation is
  211. ## first played.
  212. ##
  213. ## `animationPauseSIM` is for internal use. Do not set this property.
  214. animationPauseSIM: null
  215. ## The simulation time corresponding to the end of the animation. The animation worker function
  216. ## loops or stops the animation when `time` reaches this value.
  217. ##
  218. ## `animationStopSIM` is for internal use. Do not set this property.
  219. animationStopSIM: null
  220. ## The animation's duration in simulation time, after considering `animationRate`.
  221. ##
  222. ## `animationDurationSIM` is for internal use. Do not set this property.
  223. animationDurationSIM: null
  224. ## The time that the animation should start at. Used with animationStopTime to play
  225. ## a subsection of the animation. Defaults to 0 when the animation starts to play
  226. ## if it is not assigned another value. 'animationStartTime' will always be within
  227. ## the range [ `0`, `animationDuration` ]
  228. animationStartTime:
  229. set: |
  230. this.animationStartTime = value ? Math.min( Math.max( 0, value ), this.animationDuration ) : value;
  231. value: null
  232. ## The time that the animation should stop at. Used with animationStartTime to play
  233. ## a subsection of the animation. Defaults to 'animationDuration' when the animation
  234. ## starts to play if it is not assigned another value. 'animationStopTime' will always
  235. ## be within the range [ `0`, `animationDuration` ]
  236. animationStopTime:
  237. set: |
  238. this.animationStopTime = value ? Math.min( Math.max( 0, value ), this.animationDuration ) : value;
  239. value: null
  240. ## The frame that the animation should start at. Setting this value automatically updates the "animationStartTime"
  241. ## to the "animationStartFrame" / "fps"
  242. animationStartFrame:
  243. set: |
  244. this.animationStartTime = value / this.animationFPS;
  245. get: |
  246. return Math.floor(this.animationStartTime * this.animationFPS);
  247. ## The frame that the animation should stop at. Setting this value automatically updates the "animationStopTime"
  248. ## to the "animationStopFrame" / "fps"
  249. animationStopFrame:
  250. set: |
  251. this.animationStopTime = value / this.animationFPS;
  252. get: |
  253. return Math.floor(this.animationStopTime * this.animationFPS);
  254. ## The frames per second of the animation loaded from the source model.
  255. animationFPS: 30
  256. ## The number of frames of the animation loaded from the source model.
  257. animationFrames:
  258. set: |
  259. this.animationDuration = value / this.animationFPS;
  260. get: |
  261. return Math.ceil(this.animationFPS * this.animationDuration);
  262. ## The current frame the animation is on. Equivalent to animationTime.
  263. animationFrame:
  264. set: |
  265. if(this.animationFPS) {
  266. this.animationTime = value / this.animationFPS;
  267. }
  268. get: |
  269. if(this.animationFPS) {
  270. return Math.floor(this.animationTime * this.animationFPS);
  271. }
  272. ## The animation update rate in ticks per second of simulation time.
  273. animationTPS: 60
  274. methods:
  275. # Play the animation from the start.
  276. animationPlay:
  277. parameters:
  278. - startTime
  279. - stopTime
  280. body: |
  281. if(!isNaN(stopTime)) {
  282. this.animationStopTime = stopTime;
  283. }
  284. if(!isNaN(startTime)) {
  285. this.animationStartTime = startTime;
  286. }
  287. this.animationPlaying = true;
  288. # Pause the animation at the current position.
  289. animationPause: |
  290. this.animationPlaying = false;
  291. # Resume the animation from the current position.
  292. animationResume: |
  293. this.animationPlaying = true;
  294. # Stop the animation and reset the position to the start.
  295. animationStop: |
  296. this.animationPlaying = false;
  297. this.animationTime = 0;
  298. ## Update the animation. If `animationTime` has reached the end, stop or loop depending on the
  299. ## `animationLoop` setting. Schedule the next tick if the animation did not stop.
  300. ##
  301. ## `animationTick` is for internal use. Do not call this method directly.
  302. animationTick: |
  303. if ( this.animationPlaying ) {
  304. // Read the time to recognize the current time and update.
  305. // TODO: move side effects out of the getter!!! (says Kevin)
  306. this.animationTime;
  307. // Loop or stop after reaching the end.
  308. if ( this.time === this.animationStopSIM ) {
  309. if ( this.animationLoop ) {
  310. this.animationLooped();
  311. this.animationTime = this.animationRate >= 0 ?
  312. this.animationStartTime : this.animationStopTime;
  313. } else {
  314. this.animationPlaying = false;
  315. }
  316. }
  317. // Schedule the next tick if still playing.
  318. if ( this.animationPlaying ) {
  319. if ( this.animationStopSIM - this.time > 1 / this.animationTPS ) {
  320. this.in( 1 / this.animationTPS ).animationTick(); // next interval
  321. } else {
  322. // TODO: When animationStopSIM is 0 (usually when a model does not actually have an
  323. // animation on it), we schedule a method call for a time in the past (at time 0).
  324. // That immediately calls animationTick again, but this.time does not equal
  325. // animationStopSIM as we would expect. So, it doesn't stop the animation and we get
  326. // caught in an infinite loop.
  327. // Ideally we should catch the case where animationStopSIM is 0 before this point.
  328. // But for now, we catch it here.
  329. if ( this.animationStopSIM > 0 ) {
  330. this.at( this.animationStopSIM ).animationTick(); // exactly at end
  331. } else {
  332. this.animationPlaying = false;
  333. }
  334. }
  335. } else {
  336. this.logger.debug( "canceling animationTick" );
  337. }
  338. } //@ sourceURL=animation.animationTick.vwf
  339. ## `animation.vwf` calls `animationUpdate` each time `animationTime` changes. Nodes that
  340. ## implement `animation.vwf` should provide an `animationUpdate` method to calculate the animation
  341. ## state appropriate for the node.
  342. ##
  343. ## Since this event is sent within the `animationTime` getter function, references to
  344. ## `this.animationTime` will return `undefined` due to reentrancy protections. Use the provided
  345. ## `time` parameter instead.
  346. ##
  347. ## @param {Number} time
  348. ## The animation's `animationTime`.
  349. ## @param {Number} duration
  350. ## The animation's `animationDuration`.
  351. animationUpdate:
  352. parameters:
  353. - time
  354. - duration
  355. events:
  356. # Sent when the animation starts playing from the beginning.
  357. animationStarted:
  358. # Sent when the animation reaches the end and `animationLoop` is not set.
  359. animationStopped:
  360. # Sent when the animation reaches the end and `animationLoop` is set.
  361. animationLooped:
  362. ## Sent each time `animationTime` changes.
  363. ##
  364. ## Since this event is sent within the `animationTime` getter function, references to
  365. ## `this.animationTime` will return `undefined` due to reentrancy protections. Use the provided
  366. ## `time` parameter instead.
  367. ##
  368. ## @param {Number} time
  369. ## The animation's `animationTime`.
  370. animationTimeChanged:
  371. parameters:
  372. - time
  373. scripts:
  374. - |
  375. this.initialize = function() {
  376. // Locate child nodes that extend or implement "http://vwf.example.com/animation/position.vwf"
  377. // to identify themselves as animation key positions.
  378. var positions = this.find( "./element(*,'http://vwf.example.com/animation/position.vwf')" );
  379. // Fill in missing `animationTime` properties, distributing evenly between the left and right
  380. // positions that define `animationTime`.
  381. // 1: [ - ] => [ 0 ]
  382. // 1: [ 0, - ] => [ 0, 1 ]
  383. // 1: [ -, 1 ] => [ 0, 1 ]
  384. // 1: [ 0, -, - ] => [ 0, 1/2, 1 ]
  385. // 1: [ -, -, 1 ] => [ 0, 1/2, 1 ]
  386. // 1: [ 0, - , -, 1 ] => [ 0, 1/3 , 2/3, 1 ]
  387. var leftTime, leftIndex;
  388. var rightTime, rightIndex = -Infinity;
  389. if ( positions.length > 0 ) {
  390. positions.sort(function(a, b) {
  391. return a.sequence - b.sequence;
  392. });
  393. if ( positions[0].animationTime === null ) {
  394. positions[0].animationTime = 0;
  395. }
  396. if ( positions[positions.length-1].animationTime === null ) {
  397. positions[positions.length-1].animationTime = this.animationDuration;
  398. }
  399. positions.forEach( function( position, index ) {
  400. if ( position.animationTime !== null ) {
  401. leftTime = position.animationTime;
  402. leftIndex = index;
  403. } else {
  404. if ( index > rightIndex ) {
  405. for ( rightIndex = index + 1; rightIndex < positions.length; rightIndex++ ) {
  406. if ( ( rightTime = /* assignment! */ positions[rightIndex].animationTime ) !== null ) {
  407. break;
  408. }
  409. }
  410. }
  411. position.animationTime = leftTime + ( rightTime - leftTime ) *
  412. ( index - leftIndex ) / ( rightIndex - leftIndex );
  413. }
  414. }, this );
  415. }
  416. } //@ sourceURL=http://vwf.example.com/animation.vwf/scripts~initialize