Skip to content

Commit

Permalink
Switch to animationTime as a function
Browse files Browse the repository at this point in the history
  • Loading branch information
markw65 committed Apr 28, 2022
1 parent 248581d commit 06dc7bc
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 74 deletions.
12 changes: 3 additions & 9 deletions Apps/Sandcastle/gallery/Manually Controlled Animation.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.timeline.zoomTo(start, stop);

const wheelRadius = 0.52; //in meters.
const circumference = Math.PI * wheelRadius * 2;
// Create a path for our vehicle by lerping between two positions.
const position = new Cesium.SampledPositionProperty();
const distance = new Cesium.SampledProperty(Number);
Expand Down Expand Up @@ -135,8 +133,10 @@
vehiclePrimitive.readyPromise.then(function (model) {
model.activeAnimations.addAll({
loop: Cesium.ModelAnimationLoop.REPEAT,
animationTime: function (duration) {
return distance.getValue(viewer.clock.currentTime) / duration;
},
});
model.activeAnimations.manualAnimation = true;
const rot = new Cesium.Matrix3();
viewer.scene.preUpdate.addEventListener(function () {
const time = viewer.clock.currentTime;
Expand All @@ -150,12 +150,6 @@
rot
);
Cesium.Matrix4.fromRotationTranslation(rot, pos, model.modelMatrix);
const dist = distance.getValue(time);
const animations = model.activeAnimations;
const length = animations.length;
for (let i = 0; i < length; ++i) {
animations.get(i).animationTime = dist;
}
});
});
viewer.trackedEntity = vehicleLabel;
Expand Down
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

##### Additions :tada:

- Added `ModelAnimationCollection.manualAnimation` and `ModelAnimation.animationTime` to allow explicit control over a model's animations.
- Added `ModelAnimationCollection.animateWhilePaused` and `ModelAnimation.animationTime` to allow explicit control over a model's animations.

- `KmlDataSource` now exposes the `camera` and `canvas` properties, which are used to provide information about the state of the Viewer when making network requests for a [Link](https://developers.google.com/kml/documentation/kmlreference#link). Passing these values in the constructor is now optional.
- Added `GeoJsonSource.process` to support adding features without removing existing entities, similar to `CzmlDataSource.process`. [#9275](https://github.com/CesiumGS/cesium/issues/9275)
Expand Down
28 changes: 25 additions & 3 deletions Source/Scene/ModelAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ function ModelAnimation(options, model, runtimeAnimation) {
this._loop = defaultValue(options.loop, ModelAnimationLoop.NONE);

/**
* If this is defined, it will be used as the local animation time,
* rather than deriving the local animation time from the current time
* and the above parameters.
* If this is defined, it will be used to compute the local animation
* time.
*
* @type {ModelAnimation.AnimationTime}
* @default undefined
*/
this.animationTime = options.animationTime;
this._prevAnimationTime = undefined;
Expand Down Expand Up @@ -238,4 +240,24 @@ Object.defineProperties(ModelAnimation.prototype, {
},
},
});
/**
* A function used to compute the local animation time for a ModelAnimation.
* @callback ModelAnimation.AnimationTime
*
* @param {Number} duration The animation's duration in seconds.
* @returns {Number} The fractional part of the return value gives
* the fraction of the animation that has been completed.
* If the animation's loop option is set to REPEAT the
* integer part of the result is ignored; if its set to
* MIRROR_REPEAT the animation runs backwards when the
* integer part is odd, and forwards when its even.
* Otherwise the animation will be pinned to its start
* for negative values, and the end for values greater
* than 1.0
*
* @example
* function animationTime(duration) {
* return Date.now() / 1000 / duration;
* }
*/
export default ModelAnimation;
111 changes: 55 additions & 56 deletions Source/Scene/ModelAnimationCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ function ModelAnimationCollection(model) {
this.animationRemoved = new Event();

/**
* When false, all the animations are time based. When true, each animation can choose to
* override the local animation time.
* When true, the animation loop runs even when paused. Whether animation takes
* place will depend on the animationTime functions attached to the model's
* animations. The default functions are based on scene time, so models using
* the default will not animate regardless of this setting.
*/
this.manualAnimation = false;
this.animateWhilePaused = false;

this._model = model;
this._scheduledAnimations = [];
Expand Down Expand Up @@ -99,6 +101,7 @@ function add(collection, index, options) {
* @param {Number} [options.multiplier=1.0] Values greater than <code>1.0</code> increase the speed that the animation is played relative to the scene clock speed; values less than <code>1.0</code> decrease the speed.
* @param {Boolean} [options.reverse=false] When <code>true</code>, the animation is played in reverse.
* @param {ModelAnimationLoop} [options.loop=ModelAnimationLoop.NONE] Determines if and how the animation is looped.
* @param {ModelAnimation.AnimationTime} [options.animationTime=undefined] Determines the local animation time.
* @returns {ModelAnimation} The animation that was added to the collection.
*
* @exception {DeveloperError} Animations are not loaded. Wait for the {@link Model#readyPromise} to resolve.
Expand Down Expand Up @@ -210,6 +213,7 @@ ModelAnimationCollection.prototype.add = function (options) {
* @param {Number} [options.multiplier=1.0] Values greater than <code>1.0</code> increase the speed that the animations play relative to the scene clock speed; values less than <code>1.0</code> decrease the speed.
* @param {Boolean} [options.reverse=false] When <code>true</code>, the animations are played in reverse.
* @param {ModelAnimationLoop} [options.loop=ModelAnimationLoop.NONE] Determines if and how the animations are looped.
* @param {ModelAnimation.AnimationTime} [options.animationTime=undefined] Determines the local animation time.
* @returns {ModelAnimation[]} An array of {@link ModelAnimation} objects, one for each animation added to the collection. If there are no glTF animations, the array is empty.
*
* @exception {DeveloperError} Animations are not loaded. Wait for the {@link Model#readyPromise} to resolve.
Expand Down Expand Up @@ -373,10 +377,9 @@ ModelAnimationCollection.prototype.update = function (frameState) {
}

if (
!this.manualAnimation &&
!this.animateWhilePaused &&
JulianDate.equals(frameState.time, this._previousTime)
) {
// Animations are all time-dependent so do not animate when paused or picking
return false;
}
this._previousTime = JulianDate.clone(frameState.time, this._previousTime);
Expand All @@ -389,70 +392,66 @@ ModelAnimationCollection.prototype.update = function (frameState) {
const scheduledAnimation = scheduledAnimations[i];
const runtimeAnimation = scheduledAnimation._runtimeAnimation;

if (!defined(scheduledAnimation._computedStartTime)) {
scheduledAnimation._computedStartTime = JulianDate.addSeconds(
defaultValue(scheduledAnimation.startTime, sceneTime),
scheduledAnimation.delay,
new JulianDate()
);
}

if (!defined(scheduledAnimation._duration)) {
scheduledAnimation._duration =
runtimeAnimation.stopTime * (1.0 / scheduledAnimation.multiplier);
}

const startTime = scheduledAnimation._computedStartTime;
const duration = scheduledAnimation._duration;
let delta;
let play;
if (this.manualAnimation && defined(scheduledAnimation.animationTime)) {
const stopTime = scheduledAnimation.stopTime;

const pastStartTime = JulianDate.lessThanOrEquals(startTime, sceneTime);
const pastStopTime =
defined(stopTime) && JulianDate.greaterThan(sceneTime, stopTime);

// [0.0, 1.0] normalized local animation time
let delta =
duration !== 0.0
? scheduledAnimation.animationTime
? scheduledAnimation.animationTime(duration)
: JulianDate.secondsDifference(
pastStopTime ? stopTime : sceneTime,
startTime
) / duration
: 0.0;
if (delta === scheduledAnimation._prevAnimationTime) {
// No change, so normally nothing to do. But if the previous
// time we exactly hit the stop time, and this time we are
// pastStopTime (and there's no animationTime callback)
// we need to keep going, in order to actually stop the
// animation.
if (
scheduledAnimation.animationTime ===
scheduledAnimation._prevAnimationTime
!pastStopTime ||
scheduledAnimation._state === ModelAnimationState.STOPPED
) {
continue;
}
scheduledAnimation._prevAnimationTime = scheduledAnimation.animationTime;
delta =
duration !== 0.0 ? scheduledAnimation.animationTime / duration : 0.0;

play = true;
} else {
if (!defined(scheduledAnimation._computedStartTime)) {
scheduledAnimation._computedStartTime = JulianDate.addSeconds(
defaultValue(scheduledAnimation.startTime, sceneTime),
scheduledAnimation.delay,
new JulianDate()
);
}

const startTime = scheduledAnimation._computedStartTime;
const stopTime = scheduledAnimation.stopTime;

// [0.0, 1.0] normalized local animation time
delta =
duration !== 0.0
? JulianDate.secondsDifference(sceneTime, startTime) / duration
: 0.0;

// Clamp delta to stop time, if defined.
if (
duration !== 0.0 &&
defined(stopTime) &&
JulianDate.greaterThan(sceneTime, stopTime)
) {
delta = JulianDate.secondsDifference(stopTime, startTime) / duration;
}
}
scheduledAnimation._prevAnimationTime = delta;

const pastStartTime = delta >= 0.0;
// Play animation if
// * we are after the start time or the animation is being repeated, and
// * before the end of the animation's duration or the animation is being repeated, and
// * we did not reach a user-provided stop time.

// Play animation if
// * we are after the start time or the animation is being repeated, and
// * before the end of the animation's duration or the animation is being repeated, and
// * we did not reach a user-provided stop time.
const repeat =
scheduledAnimation.loop === ModelAnimationLoop.REPEAT ||
scheduledAnimation.loop === ModelAnimationLoop.MIRRORED_REPEAT;

const repeat =
scheduledAnimation.loop === ModelAnimationLoop.REPEAT ||
scheduledAnimation.loop === ModelAnimationLoop.MIRRORED_REPEAT;
const play =
(pastStartTime || (repeat && !defined(scheduledAnimation.startTime))) &&
(delta <= 1.0 || repeat) &&
!pastStopTime;

play =
(pastStartTime || (repeat && !defined(scheduledAnimation.startTime))) &&
(delta <= 1.0 || repeat) &&
(!defined(stopTime) ||
JulianDate.lessThanOrEquals(sceneTime, stopTime));
}
// If it IS, or WAS, animating...
if (play || scheduledAnimation._state === ModelAnimationState.ANIMATING) {
// STOPPED -> ANIMATING state transition?
Expand All @@ -471,7 +470,7 @@ ModelAnimationCollection.prototype.update = function (frameState) {
) {
const floor = Math.floor(delta);
const fract = delta - floor;
// When even use (1.0 - fract) to mirror repeat
// When odd use (1.0 - fract) to mirror repeat
delta = floor % 2 === 1.0 ? 1.0 - fract : fract;
}

Expand Down
11 changes: 6 additions & 5 deletions Specs/Scene/ModelSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1915,21 +1915,23 @@ describe(
new Date("January 1, 2014 12:00:00 UTC")
);
const animations = animBoxesModel.activeAnimations;
let animationTime = 0;
const a = animations.add({
name: "animation_1",
animationTime: 0,
animationTime: function (duration) {
return animationTime / duration;
},
});

const spyUpdate = jasmine.createSpy("listener");
a.update.addEventListener(spyUpdate);

animations.manualAnimation = true;
animBoxesModel.show = true;
scene.renderForSpecs(time);
a.animationTime = 0.5;
animationTime = 0.5;
scene.renderForSpecs(JulianDate.addSeconds(time, 1.0, new JulianDate()));
scene.renderForSpecs(JulianDate.addSeconds(time, 2.0, new JulianDate()));
a.animationTime = 1.7;
animationTime = 1.7;
scene.renderForSpecs(JulianDate.addSeconds(time, 3.0, new JulianDate()));

expect(spyUpdate.calls.count()).toEqual(3);
Expand All @@ -1947,7 +1949,6 @@ describe(
);
expect(animations.remove(a)).toEqual(true);
animBoxesModel.show = false;
animations.manualAnimation = false;
});

it("animates with a multiplier", function () {
Expand Down

0 comments on commit 06dc7bc

Please sign in to comment.