Clock.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import ClockRange from "./ClockRange.js";
  2. import ClockStep from "./ClockStep.js";
  3. import defaultValue from "./defaultValue.js";
  4. import defined from "./defined.js";
  5. import DeveloperError from "./DeveloperError.js";
  6. import Event from "./Event.js";
  7. import getTimestamp from "./getTimestamp.js";
  8. import JulianDate from "./JulianDate.js";
  9. /**
  10. * A simple clock for keeping track of simulated time.
  11. *
  12. * @alias Clock
  13. * @constructor
  14. *
  15. * @param {Object} [options] Object with the following properties:
  16. * @param {JulianDate} [options.startTime] The start time of the clock.
  17. * @param {JulianDate} [options.stopTime] The stop time of the clock.
  18. * @param {JulianDate} [options.currentTime] The current time.
  19. * @param {Number} [options.multiplier=1.0] Determines how much time advances when {@link Clock#tick} is called, negative values allow for advancing backwards.
  20. * @param {ClockStep} [options.clockStep=ClockStep.SYSTEM_CLOCK_MULTIPLIER] Determines if calls to {@link Clock#tick} are frame dependent or system clock dependent.
  21. * @param {ClockRange} [options.clockRange=ClockRange.UNBOUNDED] Determines how the clock should behave when {@link Clock#startTime} or {@link Clock#stopTime} is reached.
  22. * @param {Boolean} [options.canAnimate=true] Indicates whether {@link Clock#tick} can advance time. This could be false if data is being buffered, for example. The clock will only tick when both {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
  23. * @param {Boolean} [options.shouldAnimate=false] Indicates whether {@link Clock#tick} should attempt to advance time. The clock will only tick when both {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
  24. *
  25. * @exception {DeveloperError} startTime must come before stopTime.
  26. *
  27. *
  28. * @example
  29. * // Create a clock that loops on Christmas day 2013 and runs in real-time.
  30. * const clock = new Cesium.Clock({
  31. * startTime : Cesium.JulianDate.fromIso8601("2013-12-25"),
  32. * currentTime : Cesium.JulianDate.fromIso8601("2013-12-25"),
  33. * stopTime : Cesium.JulianDate.fromIso8601("2013-12-26"),
  34. * clockRange : Cesium.ClockRange.LOOP_STOP,
  35. * clockStep : Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER
  36. * });
  37. *
  38. * @see ClockStep
  39. * @see ClockRange
  40. * @see JulianDate
  41. */
  42. function Clock(options) {
  43. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  44. let currentTime = options.currentTime;
  45. let startTime = options.startTime;
  46. let stopTime = options.stopTime;
  47. if (!defined(currentTime)) {
  48. // if not specified, current time is the start time,
  49. // or if that is not specified, 1 day before the stop time,
  50. // or if that is not specified, then now.
  51. if (defined(startTime)) {
  52. currentTime = JulianDate.clone(startTime);
  53. } else if (defined(stopTime)) {
  54. currentTime = JulianDate.addDays(stopTime, -1.0, new JulianDate());
  55. } else {
  56. currentTime = JulianDate.now();
  57. }
  58. } else {
  59. currentTime = JulianDate.clone(currentTime);
  60. }
  61. if (!defined(startTime)) {
  62. // if not specified, start time is the current time
  63. // (as determined above)
  64. startTime = JulianDate.clone(currentTime);
  65. } else {
  66. startTime = JulianDate.clone(startTime);
  67. }
  68. if (!defined(stopTime)) {
  69. // if not specified, stop time is 1 day after the start time
  70. // (as determined above)
  71. stopTime = JulianDate.addDays(startTime, 1.0, new JulianDate());
  72. } else {
  73. stopTime = JulianDate.clone(stopTime);
  74. }
  75. //>>includeStart('debug', pragmas.debug);
  76. if (JulianDate.greaterThan(startTime, stopTime)) {
  77. throw new DeveloperError("startTime must come before stopTime.");
  78. }
  79. //>>includeEnd('debug');
  80. /**
  81. * The start time of the clock.
  82. * @type {JulianDate}
  83. */
  84. this.startTime = startTime;
  85. /**
  86. * The stop time of the clock.
  87. * @type {JulianDate}
  88. */
  89. this.stopTime = stopTime;
  90. /**
  91. * Determines how the clock should behave when
  92. * {@link Clock#startTime} or {@link Clock#stopTime}
  93. * is reached.
  94. * @type {ClockRange}
  95. * @default {@link ClockRange.UNBOUNDED}
  96. */
  97. this.clockRange = defaultValue(options.clockRange, ClockRange.UNBOUNDED);
  98. /**
  99. * Indicates whether {@link Clock#tick} can advance time. This could be false if data is being buffered,
  100. * for example. The clock will only advance time when both
  101. * {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
  102. * @type {Boolean}
  103. * @default true
  104. */
  105. this.canAnimate = defaultValue(options.canAnimate, true);
  106. /**
  107. * An {@link Event} that is fired whenever {@link Clock#tick} is called.
  108. * @type {Event}
  109. */
  110. this.onTick = new Event();
  111. /**
  112. * An {@link Event} that is fired whenever {@link Clock#stopTime} is reached.
  113. * @type {Event}
  114. */
  115. this.onStop = new Event();
  116. this._currentTime = undefined;
  117. this._multiplier = undefined;
  118. this._clockStep = undefined;
  119. this._shouldAnimate = undefined;
  120. this._lastSystemTime = getTimestamp();
  121. // set values using the property setters to
  122. // make values consistent.
  123. this.currentTime = currentTime;
  124. this.multiplier = defaultValue(options.multiplier, 1.0);
  125. this.shouldAnimate = defaultValue(options.shouldAnimate, false);
  126. this.clockStep = defaultValue(
  127. options.clockStep,
  128. ClockStep.SYSTEM_CLOCK_MULTIPLIER
  129. );
  130. }
  131. Object.defineProperties(Clock.prototype, {
  132. /**
  133. * The current time.
  134. * Changing this property will change
  135. * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
  136. * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
  137. * @memberof Clock.prototype
  138. * @type {JulianDate}
  139. */
  140. currentTime: {
  141. get: function () {
  142. return this._currentTime;
  143. },
  144. set: function (value) {
  145. if (JulianDate.equals(this._currentTime, value)) {
  146. return;
  147. }
  148. if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
  149. this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
  150. }
  151. this._currentTime = value;
  152. },
  153. },
  154. /**
  155. * Gets or sets how much time advances when {@link Clock#tick} is called. Negative values allow for advancing backwards.
  156. * If {@link Clock#clockStep} is set to {@link ClockStep.TICK_DEPENDENT}, this is the number of seconds to advance.
  157. * If {@link Clock#clockStep} is set to {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}, this value is multiplied by the
  158. * elapsed system time since the last call to {@link Clock#tick}.
  159. * Changing this property will change
  160. * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
  161. * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
  162. * @memberof Clock.prototype
  163. * @type {Number}
  164. * @default 1.0
  165. */
  166. multiplier: {
  167. get: function () {
  168. return this._multiplier;
  169. },
  170. set: function (value) {
  171. if (this._multiplier === value) {
  172. return;
  173. }
  174. if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
  175. this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
  176. }
  177. this._multiplier = value;
  178. },
  179. },
  180. /**
  181. * Determines if calls to {@link Clock#tick} are frame dependent or system clock dependent.
  182. * Changing this property to {@link ClockStep.SYSTEM_CLOCK} will set
  183. * {@link Clock#multiplier} to 1.0, {@link Clock#shouldAnimate} to true, and
  184. * {@link Clock#currentTime} to the current system clock time.
  185. * @memberof Clock.prototype
  186. * @type ClockStep
  187. * @default {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}
  188. */
  189. clockStep: {
  190. get: function () {
  191. return this._clockStep;
  192. },
  193. set: function (value) {
  194. if (value === ClockStep.SYSTEM_CLOCK) {
  195. this._multiplier = 1.0;
  196. this._shouldAnimate = true;
  197. this._currentTime = JulianDate.now();
  198. }
  199. this._clockStep = value;
  200. },
  201. },
  202. /**
  203. * Indicates whether {@link Clock#tick} should attempt to advance time.
  204. * The clock will only advance time when both
  205. * {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
  206. * Changing this property will change
  207. * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
  208. * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
  209. * @memberof Clock.prototype
  210. * @type {Boolean}
  211. * @default false
  212. */
  213. shouldAnimate: {
  214. get: function () {
  215. return this._shouldAnimate;
  216. },
  217. set: function (value) {
  218. if (this._shouldAnimate === value) {
  219. return;
  220. }
  221. if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
  222. this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
  223. }
  224. this._shouldAnimate = value;
  225. },
  226. },
  227. });
  228. /**
  229. * Advances the clock from the current time based on the current configuration options.
  230. * tick should be called every frame, regardless of whether animation is taking place
  231. * or not. To control animation, use the {@link Clock#shouldAnimate} property.
  232. *
  233. * @returns {JulianDate} The new value of the {@link Clock#currentTime} property.
  234. */
  235. Clock.prototype.tick = function () {
  236. const currentSystemTime = getTimestamp();
  237. let currentTime = JulianDate.clone(this._currentTime);
  238. if (this.canAnimate && this._shouldAnimate) {
  239. const clockStep = this._clockStep;
  240. if (clockStep === ClockStep.SYSTEM_CLOCK) {
  241. currentTime = JulianDate.now(currentTime);
  242. } else {
  243. const multiplier = this._multiplier;
  244. if (clockStep === ClockStep.TICK_DEPENDENT) {
  245. currentTime = JulianDate.addSeconds(
  246. currentTime,
  247. multiplier,
  248. currentTime
  249. );
  250. } else {
  251. const milliseconds = currentSystemTime - this._lastSystemTime;
  252. currentTime = JulianDate.addSeconds(
  253. currentTime,
  254. multiplier * (milliseconds / 1000.0),
  255. currentTime
  256. );
  257. }
  258. const clockRange = this.clockRange;
  259. const startTime = this.startTime;
  260. const stopTime = this.stopTime;
  261. if (clockRange === ClockRange.CLAMPED) {
  262. if (JulianDate.lessThan(currentTime, startTime)) {
  263. currentTime = JulianDate.clone(startTime, currentTime);
  264. } else if (JulianDate.greaterThan(currentTime, stopTime)) {
  265. currentTime = JulianDate.clone(stopTime, currentTime);
  266. this.onStop.raiseEvent(this);
  267. }
  268. } else if (clockRange === ClockRange.LOOP_STOP) {
  269. if (JulianDate.lessThan(currentTime, startTime)) {
  270. currentTime = JulianDate.clone(startTime, currentTime);
  271. }
  272. while (JulianDate.greaterThan(currentTime, stopTime)) {
  273. currentTime = JulianDate.addSeconds(
  274. startTime,
  275. JulianDate.secondsDifference(currentTime, stopTime),
  276. currentTime
  277. );
  278. this.onStop.raiseEvent(this);
  279. }
  280. }
  281. }
  282. }
  283. this._currentTime = currentTime;
  284. this._lastSystemTime = currentSystemTime;
  285. this.onTick.raiseEvent(this);
  286. return currentTime;
  287. };
  288. export default Clock;