import clone from "../Core/clone.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import EasingFunction from "../Core/EasingFunction.js"; import getTimestamp from "../Core/getTimestamp.js"; import TimeConstants from "../Core/TimeConstants.js"; import { Tween as TweenJS } from "@tweenjs/tween.js"; /** * A tween is an animation that interpolates the properties of two objects using an {@link EasingFunction}. Create * one using {@link Scene#tweens} and {@link TweenCollection#add} and related add functions. * * @alias Tween * @constructor * * @private */ function Tween( tweens, tweenjs, startObject, stopObject, duration, delay, easingFunction, update, complete, cancel ) { this._tweens = tweens; this._tweenjs = tweenjs; this._startObject = clone(startObject); this._stopObject = clone(stopObject); this._duration = duration; this._delay = delay; this._easingFunction = easingFunction; this._update = update; this._complete = complete; /** * The callback to call if the tween is canceled either because {@link Tween#cancelTween} * was called or because the tween was removed from the collection. * * @type {TweenCollection.TweenCancelledCallback} */ this.cancel = cancel; /** * @private */ this.needsStart = true; } Object.defineProperties(Tween.prototype, { /** * An object with properties for initial values of the tween. The properties of this object are changed during the tween's animation. * @memberof Tween.prototype * * @type {object} * @readonly */ startObject: { get: function () { return this._startObject; }, }, /** * An object with properties for the final values of the tween. * @memberof Tween.prototype * * @type {object} * @readonly */ stopObject: { get: function () { return this._stopObject; }, }, /** * The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops. * @memberof Tween.prototype * * @type {number} * @readonly */ duration: { get: function () { return this._duration; }, }, /** * The delay, in seconds, before the tween starts animating. * @memberof Tween.prototype * * @type {number} * @readonly */ delay: { get: function () { return this._delay; }, }, /** * Determines the curve for animtion. * @memberof Tween.prototype * * @type {EasingFunction} * @readonly */ easingFunction: { get: function () { return this._easingFunction; }, }, /** * The callback to call at each animation update (usually tied to the a rendered frame). * @memberof Tween.prototype * * @type {TweenCollection.TweenUpdateCallback} * @readonly */ update: { get: function () { return this._update; }, }, /** * The callback to call when the tween finishes animating. * @memberof Tween.prototype * * @type {TweenCollection.TweenCompleteCallback} * @readonly */ complete: { get: function () { return this._complete; }, }, /** * @memberof Tween.prototype * * @private */ tweenjs: { get: function () { return this._tweenjs; }, }, }); /** * Cancels the tween calling the {@link Tween#cancel} callback if one exists. This * has no effect if the tween finished or was already canceled. */ Tween.prototype.cancelTween = function () { this._tweens.remove(this); }; /** * A collection of tweens for animating properties. Commonly accessed using {@link Scene#tweens}. * * @alias TweenCollection * @constructor * * @private */ function TweenCollection() { this._tweens = []; } Object.defineProperties(TweenCollection.prototype, { /** * The number of tweens in the collection. * @memberof TweenCollection.prototype * * @type {number} * @readonly */ length: { get: function () { return this._tweens.length; }, }, }); /** * Creates a tween for animating between two sets of properties. The tween starts animating at the next call to {@link TweenCollection#update}, which * is implicit when {@link Viewer} or {@link CesiumWidget} render the scene. * * @param {object} [options] Object with the following properties: * @param {object} options.startObject An object with properties for initial values of the tween. The properties of this object are changed during the tween's animation. * @param {object} options.stopObject An object with properties for the final values of the tween. * @param {number} options.duration The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops. * @param {number} [options.delay=0.0] The delay, in seconds, before the tween starts animating. * @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion. * @param {TweenCollection.TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame). * @param {TweenCollection.TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating. * @param {TweenCollection.TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection. * @returns {Tween} The tween. * * @exception {DeveloperError} options.duration must be positive. */ TweenCollection.prototype.add = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); if (!defined(options.startObject) || !defined(options.stopObject)) { throw new DeveloperError( "options.startObject and options.stopObject are required." ); } if (!defined(options.duration) || options.duration < 0.0) { throw new DeveloperError( "options.duration is required and must be positive." ); } //>>includeEnd('debug'); if (options.duration === 0.0) { if (defined(options.complete)) { options.complete(); } return new Tween(this); } const duration = options.duration / TimeConstants.SECONDS_PER_MILLISECOND; const delayInSeconds = defaultValue(options.delay, 0.0); const delay = delayInSeconds / TimeConstants.SECONDS_PER_MILLISECOND; const easingFunction = defaultValue( options.easingFunction, EasingFunction.LINEAR_NONE ); const value = options.startObject; const tweenjs = new TweenJS(value); tweenjs.to(clone(options.stopObject), duration); tweenjs.delay(delay); tweenjs.easing(easingFunction); if (defined(options.update)) { tweenjs.onUpdate(function () { options.update(value); }); } tweenjs.onComplete(defaultValue(options.complete, null)); tweenjs.repeat(defaultValue(options._repeat, 0.0)); const tween = new Tween( this, tweenjs, options.startObject, options.stopObject, options.duration, delayInSeconds, easingFunction, options.update, options.complete, options.cancel ); this._tweens.push(tween); return tween; }; /** * Creates a tween for animating a scalar property on the given object. The tween starts animating at the next call to {@link TweenCollection#update}, which * is implicit when {@link Viewer} or {@link CesiumWidget} render the scene. * * @param {object} [options] Object with the following properties: * @param {object} options.object The object containing the property to animate. * @param {string} options.property The name of the property to animate. * @param {number} options.startValue The initial value. * @param {number} options.stopValue The final value. * @param {number} [options.duration=3.0] The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops. * @param {number} [options.delay=0.0] The delay, in seconds, before the tween starts animating. * @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion. * @param {TweenCollection.TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame). * @param {TweenCollection.TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating. * @param {TweenCollection.TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection. * @returns {Tween} The tween. * * @exception {DeveloperError} options.object must have the specified property. * @exception {DeveloperError} options.duration must be positive. */ TweenCollection.prototype.addProperty = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const object = options.object; const property = options.property; const startValue = options.startValue; const stopValue = options.stopValue; //>>includeStart('debug', pragmas.debug); if (!defined(object) || !defined(options.property)) { throw new DeveloperError( "options.object and options.property are required." ); } if (!defined(object[property])) { throw new DeveloperError( "options.object must have the specified property." ); } if (!defined(startValue) || !defined(stopValue)) { throw new DeveloperError( "options.startValue and options.stopValue are required." ); } //>>includeEnd('debug'); function update(value) { object[property] = value.value; } return this.add({ startObject: { value: startValue, }, stopObject: { value: stopValue, }, duration: defaultValue(options.duration, 3.0), delay: options.delay, easingFunction: options.easingFunction, update: update, complete: options.complete, cancel: options.cancel, _repeat: options._repeat, }); }; /** * Creates a tween for animating the alpha of all color uniforms on a {@link Material}. The tween starts animating at the next call to {@link TweenCollection#update}, which * is implicit when {@link Viewer} or {@link CesiumWidget} render the scene. * * @param {object} [options] Object with the following properties: * @param {Material} options.material The material to animate. * @param {number} [options.startValue=0.0] The initial alpha value. * @param {number} [options.stopValue=1.0] The final alpha value. * @param {number} [options.duration=3.0] The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops. * @param {number} [options.delay=0.0] The delay, in seconds, before the tween starts animating. * @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion. * @param {TweenCollection.TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame). * @param {TweenCollection.TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating. * @param {TweenCollection.TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection. * @returns {Tween} The tween. * * @exception {DeveloperError} material has no properties with alpha components. * @exception {DeveloperError} options.duration must be positive. */ TweenCollection.prototype.addAlpha = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const material = options.material; //>>includeStart('debug', pragmas.debug); if (!defined(material)) { throw new DeveloperError("options.material is required."); } //>>includeEnd('debug'); const properties = []; for (const property in material.uniforms) { if ( material.uniforms.hasOwnProperty(property) && defined(material.uniforms[property]) && defined(material.uniforms[property].alpha) ) { properties.push(property); } } //>>includeStart('debug', pragmas.debug); if (properties.length === 0) { throw new DeveloperError( "material has no properties with alpha components." ); } //>>includeEnd('debug'); function update(value) { const length = properties.length; for (let i = 0; i < length; ++i) { material.uniforms[properties[i]].alpha = value.alpha; } } return this.add({ startObject: { alpha: defaultValue(options.startValue, 0.0), // Default to fade in }, stopObject: { alpha: defaultValue(options.stopValue, 1.0), }, duration: defaultValue(options.duration, 3.0), delay: options.delay, easingFunction: options.easingFunction, update: update, complete: options.complete, cancel: options.cancel, }); }; /** * Creates a tween for animating the offset uniform of a {@link Material}. The tween starts animating at the next call to {@link TweenCollection#update}, which * is implicit when {@link Viewer} or {@link CesiumWidget} render the scene. * * @param {object} [options] Object with the following properties: * @param {Material} options.material The material to animate. * @param {number} options.startValue The initial alpha value. * @param {number} options.stopValue The final alpha value. * @param {number} [options.duration=3.0] The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops. * @param {number} [options.delay=0.0] The delay, in seconds, before the tween starts animating. * @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion. * @param {TweenCollection.TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame). * @param {TweenCollection.TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection. * @returns {Tween} The tween. * * @exception {DeveloperError} material.uniforms must have an offset property. * @exception {DeveloperError} options.duration must be positive. */ TweenCollection.prototype.addOffsetIncrement = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const material = options.material; //>>includeStart('debug', pragmas.debug); if (!defined(material)) { throw new DeveloperError("material is required."); } if (!defined(material.uniforms.offset)) { throw new DeveloperError("material.uniforms must have an offset property."); } //>>includeEnd('debug'); const uniforms = material.uniforms; return this.addProperty({ object: uniforms, property: "offset", startValue: uniforms.offset, stopValue: uniforms.offset + 1, duration: options.duration, delay: options.delay, easingFunction: options.easingFunction, update: options.update, cancel: options.cancel, _repeat: Infinity, }); }; /** * Removes a tween from the collection. *
* This calls the {@link Tween#cancel} callback if the tween has one. *
* * @param {Tween} tween The tween to remove. * @returns {boolean}true
if the tween was removed; false
if the tween was not found in the collection.
*/
TweenCollection.prototype.remove = function (tween) {
if (!defined(tween)) {
return false;
}
const index = this._tweens.indexOf(tween);
if (index !== -1) {
tween.tweenjs.stop();
if (defined(tween.cancel)) {
tween.cancel();
}
this._tweens.splice(index, 1);
return true;
}
return false;
};
/**
* Removes all tweens from the collection.
* * This calls the {@link Tween#cancel} callback for each tween that has one. *
*/ TweenCollection.prototype.removeAll = function () { const tweens = this._tweens; for (let i = 0; i < tweens.length; ++i) { const tween = tweens[i]; tween.tweenjs.stop(); if (defined(tween.cancel)) { tween.cancel(); } } tweens.length = 0; }; /** * Determines whether this collection contains a given tween. * * @param {Tween} tween The tween to check for. * @returns {boolean}true
if this collection contains the tween, false
otherwise.
*/
TweenCollection.prototype.contains = function (tween) {
return defined(tween) && this._tweens.indexOf(tween) !== -1;
};
/**
* Returns the tween in the collection at the specified index. Indices are zero-based
* and increase as tweens are added. Removing a tween shifts all tweens after
* it to the left, changing their indices. This function is commonly used to iterate over
* all the tween in the collection.
*
* @param {number} index The zero-based index of the tween.
* @returns {Tween} The tween at the specified index.
*
* @example
* // Output the duration of all the tweens in the collection.
* const tweens = scene.tweens;
* const length = tweens.length;
* for (let i = 0; i < length; ++i) {
* console.log(tweens.get(i).duration);
* }
*/
TweenCollection.prototype.get = function (index) {
//>>includeStart('debug', pragmas.debug);
if (!defined(index)) {
throw new DeveloperError("index is required.");
}
//>>includeEnd('debug');
return this._tweens[index];
};
/**
* Updates the tweens in the collection to be at the provide time. When a tween finishes, it is removed
* from the collection.
*
* @param {number} [time=getTimestamp()] The time in seconds. By default tweens are synced to the system clock.
*/
TweenCollection.prototype.update = function (time) {
const tweens = this._tweens;
let i = 0;
time = defined(time)
? time / TimeConstants.SECONDS_PER_MILLISECOND
: getTimestamp();
while (i < tweens.length) {
const tween = tweens[i];
const tweenjs = tween.tweenjs;
if (tween.needsStart) {
tween.needsStart = false;
tweenjs.start(time);
} else if (tweenjs.update(time)) {
i++;
} else {
tweenjs.stop();
tweens.splice(i, 1);
}
}
};
/**
* A function that will execute when a tween completes.
* @callback TweenCollection.TweenCompleteCallback
*/
/**
* A function that will execute when a tween updates.
* @callback TweenCollection.TweenUpdateCallback
*/
/**
* A function that will execute when a tween is cancelled.
* @callback TweenCollection.TweenCancelledCallback
*/
export default TweenCollection;