@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
317 lines (285 loc) • 10.6 kB
JavaScript
import ClockRange from "./ClockRange.js";
import ClockStep from "./ClockStep.js";
import Frozen from "./Frozen.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.
* const 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 = options ?? Frozen.EMPTY_OBJECT;
let currentTime = options.currentTime;
let startTime = options.startTime;
let 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 = 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 = 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 = options.multiplier ?? 1.0;
this.shouldAnimate = options.shouldAnimate ?? false;
this.clockStep = 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 () {
const currentSystemTime = getTimestamp();
let currentTime = JulianDate.clone(this._currentTime);
if (this.canAnimate && this._shouldAnimate) {
const clockStep = this._clockStep;
if (clockStep === ClockStep.SYSTEM_CLOCK) {
currentTime = JulianDate.now(currentTime);
} else {
const multiplier = this._multiplier;
if (clockStep === ClockStep.TICK_DEPENDENT) {
currentTime = JulianDate.addSeconds(
currentTime,
multiplier,
currentTime,
);
} else {
const milliseconds = currentSystemTime - this._lastSystemTime;
currentTime = JulianDate.addSeconds(
currentTime,
multiplier * (milliseconds / 1000.0),
currentTime,
);
}
const clockRange = this.clockRange;
const startTime = this.startTime;
const 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;