import ClockRange from "./ClockRange.js"; import ClockStep from "./ClockStep.js"; import defaultValue from "./defaultValue.js"; import defined from "./defined.js"; import DeveloperError from "./DeveloperError.js"; import Event from "./Event.js"; import getTimestamp from "./getTimestamp.js"; import JulianDate from "./JulianDate.js"; /** * A simple clock for keeping track of simulated time. * * @alias Clock * @constructor * * @param {Object} [options] Object with the following properties: * @param {JulianDate} [options.startTime] The start time of the clock. * @param {JulianDate} [options.stopTime] The stop time of the clock. * @param {JulianDate} [options.currentTime] The current time. * @param {Number} [options.multiplier=1.0] Determines how much time advances when {@link Clock#tick} is called, negative values allow for advancing backwards. * @param {ClockStep} [options.clockStep=ClockStep.SYSTEM_CLOCK_MULTIPLIER] Determines if calls to {@link Clock#tick} are frame dependent or system clock dependent. * @param {ClockRange} [options.clockRange=ClockRange.UNBOUNDED] Determines how the clock should behave when {@link Clock#startTime} or {@link Clock#stopTime} is reached. * @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. * @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. * * @exception {DeveloperError} startTime must come before stopTime. * * * @example * // Create a clock that loops on Christmas day 2013 and runs in real-time. * var clock = new Cesium.Clock({ * startTime : Cesium.JulianDate.fromIso8601("2013-12-25"), * currentTime : Cesium.JulianDate.fromIso8601("2013-12-25"), * stopTime : Cesium.JulianDate.fromIso8601("2013-12-26"), * clockRange : Cesium.ClockRange.LOOP_STOP, * clockStep : Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER * }); * * @see ClockStep * @see ClockRange * @see JulianDate */ function Clock(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var currentTime = options.currentTime; var startTime = options.startTime; var stopTime = options.stopTime; if (!defined(currentTime)) { // if not specified, current time is the start time, // or if that is not specified, 1 day before the stop time, // or if that is not specified, then now. if (defined(startTime)) { currentTime = JulianDate.clone(startTime); } else if (defined(stopTime)) { currentTime = JulianDate.addDays(stopTime, -1.0, new JulianDate()); } else { currentTime = JulianDate.now(); } } else { currentTime = JulianDate.clone(currentTime); } if (!defined(startTime)) { // if not specified, start time is the current time // (as determined above) startTime = JulianDate.clone(currentTime); } else { startTime = JulianDate.clone(startTime); } if (!defined(stopTime)) { // if not specified, stop time is 1 day after the start time // (as determined above) stopTime = JulianDate.addDays(startTime, 1.0, new JulianDate()); } else { stopTime = JulianDate.clone(stopTime); } //>>includeStart('debug', pragmas.debug); if (JulianDate.greaterThan(startTime, stopTime)) { throw new DeveloperError("startTime must come before stopTime."); } //>>includeEnd('debug'); /** * The start time of the clock. * @type {JulianDate} */ this.startTime = startTime; /** * The stop time of the clock. * @type {JulianDate} */ this.stopTime = stopTime; /** * Determines how the clock should behave when * {@link Clock#startTime} or {@link Clock#stopTime} * is reached. * @type {ClockRange} * @default {@link ClockRange.UNBOUNDED} */ this.clockRange = defaultValue(options.clockRange, ClockRange.UNBOUNDED); /** * Indicates whether {@link Clock#tick} can advance time. This could be false if data is being buffered, * for example. The clock will only advance time when both * {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true. * @type {Boolean} * @default true */ this.canAnimate = defaultValue(options.canAnimate, true); /** * An {@link Event} that is fired whenever {@link Clock#tick} is called. * @type {Event} */ this.onTick = new Event(); /** * An {@link Event} that is fired whenever {@link Clock#stopTime} is reached. * @type {Event} */ this.onStop = new Event(); this._currentTime = undefined; this._multiplier = undefined; this._clockStep = undefined; this._shouldAnimate = undefined; this._lastSystemTime = getTimestamp(); // set values using the property setters to // make values consistent. this.currentTime = currentTime; this.multiplier = defaultValue(options.multiplier, 1.0); this.shouldAnimate = defaultValue(options.shouldAnimate, false); this.clockStep = defaultValue( options.clockStep, ClockStep.SYSTEM_CLOCK_MULTIPLIER ); } Object.defineProperties(Clock.prototype, { /** * The current time. * Changing this property will change * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}. * @memberof Clock.prototype * @type {JulianDate} */ currentTime: { get: function () { return this._currentTime; }, set: function (value) { if (JulianDate.equals(this._currentTime, value)) { return; } if (this._clockStep === ClockStep.SYSTEM_CLOCK) { this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER; } this._currentTime = value; }, }, /** * Gets or sets how much time advances when {@link Clock#tick} is called. Negative values allow for advancing backwards. * If {@link Clock#clockStep} is set to {@link ClockStep.TICK_DEPENDENT}, this is the number of seconds to advance. * If {@link Clock#clockStep} is set to {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}, this value is multiplied by the * elapsed system time since the last call to {@link Clock#tick}. * Changing this property will change * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}. * @memberof Clock.prototype * @type {Number} * @default 1.0 */ multiplier: { get: function () { return this._multiplier; }, set: function (value) { if (this._multiplier === value) { return; } if (this._clockStep === ClockStep.SYSTEM_CLOCK) { this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER; } this._multiplier = value; }, }, /** * Determines if calls to {@link Clock#tick} are frame dependent or system clock dependent. * Changing this property to {@link ClockStep.SYSTEM_CLOCK} will set * {@link Clock#multiplier} to 1.0, {@link Clock#shouldAnimate} to true, and * {@link Clock#currentTime} to the current system clock time. * @memberof Clock.prototype * @type ClockStep * @default {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER} */ clockStep: { get: function () { return this._clockStep; }, set: function (value) { if (value === ClockStep.SYSTEM_CLOCK) { this._multiplier = 1.0; this._shouldAnimate = true; this._currentTime = JulianDate.now(); } this._clockStep = value; }, }, /** * Indicates whether {@link Clock#tick} should attempt to advance time. * The clock will only advance time when both * {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true. * Changing this property will change * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}. * @memberof Clock.prototype * @type {Boolean} * @default false */ shouldAnimate: { get: function () { return this._shouldAnimate; }, set: function (value) { if (this._shouldAnimate === value) { return; } if (this._clockStep === ClockStep.SYSTEM_CLOCK) { this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER; } this._shouldAnimate = value; }, }, }); /** * Advances the clock from the current time based on the current configuration options. * tick should be called every frame, regardless of whether animation is taking place * or not. To control animation, use the {@link Clock#shouldAnimate} property. * * @returns {JulianDate} The new value of the {@link Clock#currentTime} property. */ Clock.prototype.tick = function () { var currentSystemTime = getTimestamp(); var currentTime = JulianDate.clone(this._currentTime); if (this.canAnimate && this._shouldAnimate) { var clockStep = this._clockStep; if (clockStep === ClockStep.SYSTEM_CLOCK) { currentTime = JulianDate.now(currentTime); } else { var multiplier = this._multiplier; if (clockStep === ClockStep.TICK_DEPENDENT) { currentTime = JulianDate.addSeconds( currentTime, multiplier, currentTime ); } else { var milliseconds = currentSystemTime - this._lastSystemTime; currentTime = JulianDate.addSeconds( currentTime, multiplier * (milliseconds / 1000.0), currentTime ); } var clockRange = this.clockRange; var startTime = this.startTime; var stopTime = this.stopTime; if (clockRange === ClockRange.CLAMPED) { if (JulianDate.lessThan(currentTime, startTime)) { currentTime = JulianDate.clone(startTime, currentTime); } else if (JulianDate.greaterThan(currentTime, stopTime)) { currentTime = JulianDate.clone(stopTime, currentTime); this.onStop.raiseEvent(this); } } else if (clockRange === ClockRange.LOOP_STOP) { if (JulianDate.lessThan(currentTime, startTime)) { currentTime = JulianDate.clone(startTime, currentTime); } while (JulianDate.greaterThan(currentTime, stopTime)) { currentTime = JulianDate.addSeconds( startTime, JulianDate.secondsDifference(currentTime, stopTime), currentTime ); this.onStop.raiseEvent(this); } } } } this._currentTime = currentTime; this._lastSystemTime = currentSystemTime; this.onTick.raiseEvent(this); return currentTime; }; export default Clock;