Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Realistic vehicle wheel speed Sandcastle example #7361

Merged
merged 23 commits into from
Jan 16, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions Apps/Sandcastle/gallery/Animation Speed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<meta name="description" content="Control glTF animation speed based on the entity's velocity.">
<meta name="cesium-sandcastle-labels" content="Showcases">
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script type="text/javascript" src="../../../ThirdParty/requirejs-2.1.20/require.js"></script>
<script type="text/javascript">
require.config({
baseUrl : '../../../Source',
waitSeconds : 60
});
</script>
</head>
<body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html">
<style>
@import url(../templates/bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar"></div>
<script id="cesium_sandcastle_script">
function startup(Cesium) {
'use strict';
//Sandcastle_Begin
var viewer = new Cesium.Viewer('cesiumContainer', {
shouldAnimate: true
});

//Make sure viewer is at the desired time.
var start = Cesium.JulianDate.fromDate(new Date(2018, 11, 12));
var totalSeconds = 30;
var stop = Cesium.JulianDate.addSeconds(start, totalSeconds, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; //Loop at the end
viewer.timeline.zoomTo(start, stop);

// Create a path from a set of positions for our vehicle.
var position = new Cesium.SampledPositionProperty();
// These numbers were obtained by starting with 3 Cartesian3 locations, and then using
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
// Cesium.Cartesian3.lerp to create more positions between them. They were constructed
// so that the first few are close together and the last two are far apart to show
// how the animation picks up as the vehicle's speed changes.
var locations = [
new Cesium.Cartesian3(-2379510.08905552,-4665419.64840452,3628182.20006795),
new Cesium.Cartesian3(-2379521.766634856, -4665446.787560957, 3628139.926700882),
new Cesium.Cartesian3(-2379533.4442141918, -4665473.926717391, 3628097.653333814),
new Cesium.Cartesian3(-2379545.121793528, -4665501.065873828, 3628055.379966746),
new Cesium.Cartesian3(-2379556.799372864, -4665528.205030263, 3628013.106599678),
new Cesium.Cartesian3(-2379568.4769522,-4665555.3441867,3627970.83323261),
new Cesium.Cartesian3(-2379603.7074103747, -4665623.48990283, 3627860.82704567)
];

var stepSize = totalSeconds / (locations.length - 1);

for (var i = 0; i < locations.length; ++i) {
var location = locations[i];
var time = Cesium.JulianDate.addSeconds(start, i * stepSize, new Cesium.JulianDate());
position.addSample(time, location);
}

// A velocity vector property will give us the entity's speed and direction at any given time.
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
var velocityVectorProperty = new Cesium.VelocityVectorProperty(position, false);
var velocityVector = new Cesium.Cartesian3();

// Add our vehicle model.
var vehicleEntity = viewer.entities.add({
position : position,
orientation : new Cesium.VelocityOrientationProperty(position), // Automatically set the vehicle's orientation to the direction it's facing
model : {
uri : '../../../../Apps/SampleData/models/GroundVehicle/GroundVehicle.glb',
minimumPixelSize : 128,
maximumScale : 20000
}
});
viewer.trackedEntity = vehicleEntity;

// Set the animation speed to a callback property that gets evaluated only when it's used.
vehicleEntity.model.animationsMultiplier = new Cesium.CallbackProperty(function(time, result) {
velocityVectorProperty.getValue(time, velocityVector);

var rotationsPerSecond = rotationSpeedFromVelocity(Cesium.Cartesian3.magnitude(velocityVector), 0.52);
// Make the animation play at that speed, given that this glTF wheel animation makes almost 2 rotations per second.
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
var multiplier = (rotationsPerSecond * 1.1) / 2;
// animationMultiplier can't be 0.
multiplier = Math.max(Math.abs(multiplier), 0.001);
// If it gets too fast it doesn't look great so we create a maximum here.
multiplier = Math.min(multiplier, 15);
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved

return multiplier;
}, false);

function rotationSpeedFromVelocity(velocity, wheelRadius) {
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
// velocity is how fast the vehicle is moving in meters per second.
// wheelRadius in meters.
var circumference = Math.PI * wheelRadius * 2;
return velocity / circumference;
}
//Sandcastle_End
Sandcastle.finishedLoading();
}
if (typeof Cesium !== 'undefined') {
startup(Cesium);
} else if (typeof require === 'function') {
require(['Cesium'], startup);
}
</script>
</body>
</html>
Binary file added Apps/Sandcastle/gallery/Animation Speed.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Change Log
* The `specularEnvironmentMaps` and `sphericalHarmonicCoefficients` properties of `Model` and `Cesium3DTileset` can be used to override the values from the scene for specific models and tilesets.
* The `luminanceAtZenith` property of `Model` and `Cesium3DTileset` adjusts the luminance of the procedural image-based lighting.
* Double click away from an entity to un-track it [#7285](https://github.com/AnalyticalGraphicsInc/cesium/pull/7285)
* Added a new `animationsMultiplier` property on `ModelGraphics` to control the playback speed of glTF animations. [#7361](https://github.com/AnalyticalGraphicsInc/cesium/pull/7361).

##### Fixes :wrench:
* Fixed 3D Tiles visibility checking when running multiple passes within the same frame. [#7289](https://github.com/AnalyticalGraphicsInc/cesium/pull/7289)
Expand Down
11 changes: 11 additions & 0 deletions Source/DataSources/ModelGraphics.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ define([
this._uriSubscription = undefined;
this._runAnimations = undefined;
this._clampAnimations = undefined;
this._animationsMultiplier = undefined;
this._runAnimationsSubscription = undefined;
this._nodeTransformations = undefined;
this._nodeTransformationsSubscription = undefined;
Expand Down Expand Up @@ -190,6 +191,14 @@ define([
*/
runAnimations : createPropertyDescriptor('runAnimations'),

/**
* Gets or sets the numeric Property specifying the relative speed to play the glTF animations.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default 1.0
*/
animationsMultiplier : createPropertyDescriptor('animationsMultiplier'),

/**
* Gets or sets the boolean Property specifying if glTF animations should hold the last pose for time durations with no keyframes.
* @memberof ModelGraphics.prototype
Expand Down Expand Up @@ -304,6 +313,7 @@ define([
result.uri = this.uri;
result.runAnimations = this.runAnimations;
result.clampAnimations = this.clampAnimations;
result.animationsMultiplier = this.animationsMultiplier;
result.nodeTransformations = this.nodeTransformations;
result.heightReference = this._heightReference;
result.distanceDisplayCondition = this.distanceDisplayCondition;
Expand Down Expand Up @@ -341,6 +351,7 @@ define([
this.uri = defaultValue(this.uri, source.uri);
this.runAnimations = defaultValue(this.runAnimations, source.runAnimations);
this.clampAnimations = defaultValue(this.clampAnimations, source.clampAnimations);
this.animationsMultiplier = defaultValue(this.animationsMultiplier, source.animationsMultiplier);
this.heightReference = defaultValue(this.heightReference, source.heightReference);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
this.silhouetteColor = defaultValue(this.silhouetteColor, source.silhouetteColor);
Expand Down
15 changes: 14 additions & 1 deletion Source/DataSources/ModelVisualizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ define([
var defaultColorBlendMode = ColorBlendMode.HIGHLIGHT;
var defaultColorBlendAmount = 0.5;
var defaultImageBasedLightingFactor = new Cartesian2(1.0, 1.0);
var defaultAnimationMultiplier = 1.0;

var modelMatrixScratch = new Matrix4();
var nodeMatrixScratch = new Matrix4();
Expand Down Expand Up @@ -138,7 +139,8 @@ define([
animationsRunning : false,
nodeTransformationsScratch : {},
originalNodeMatrixHash : {},
loadFail : false
loadFail : false,
multiplier : defaultAnimationMultiplier
};
modelHash[entity.id] = modelData;

Expand All @@ -165,6 +167,7 @@ define([

if (model.ready) {
var runAnimations = Property.getValueOrDefault(modelGraphics._runAnimations, time, true);
var multiplier = Property.getValueOrDefault(modelGraphics._animationsMultiplier, time, defaultAnimationMultiplier);
if (modelData.animationsRunning !== runAnimations) {
if (runAnimations) {
model.activeAnimations.addAll({
Expand All @@ -176,6 +179,16 @@ define([
modelData.animationsRunning = runAnimations;
}

// Update the multiplier property
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only use inline comments for things that are non-obvious. I don't think this comment is needed.

if (modelData.animationsRunning && modelData.multiplier !== multiplier) {
var animationLength = model.activeAnimations.length;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is an activeAnimation can an animation become inactive? And if that happens will it's multiplier setting become out of date?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activeAnimations is an instance of ModelAnimationCollection, which keeps track of all currently playing animations. Theoretically yes if you remove an animation, and add it back in its multiplier value will be out of sync. There's no way to do this for individual animations at the Entity level though. So I'd say the solution is, if/when we implement that, to set modelData.multiplier to undefined every time an animation is added/removed, (in the same way that ModelAnimationCollection currently keeps track of when the multiplier changes to recompute duration and other things).

Or we could just loop over all animations and re-set the multiplier every frame the way it sets all other properties on model every frame.

for (var j = 0; j < animationLength; ++j) {
var animation = model.activeAnimations.get(j);
animation.multiplier = multiplier;
}
modelData.multiplier = multiplier;
}

// Apply node transformations
var nodeTransformations = Property.getValueOrUndefined(modelGraphics._nodeTransformations, time, modelData.nodeTransformationsScratch);
if (defined(nodeTransformations)) {
Expand Down
12 changes: 11 additions & 1 deletion Source/Scene/ModelAnimation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
define([
'../Core/Check',
'../Core/defaultValue',
'../Core/defineProperties',
'../Core/defined',
Expand All @@ -8,6 +9,7 @@ define([
'./ModelAnimationLoop',
'./ModelAnimationState'
], function(
Check,
defaultValue,
defineProperties,
defined,
Expand Down Expand Up @@ -118,6 +120,7 @@ define([
// Set during animation update
this._computedStartTime = undefined;
this._duration = undefined;
this._multiplierChanged = false;

// To avoid allocations in ModelAnimationCollection.update
var that = this;
Expand Down Expand Up @@ -208,13 +211,20 @@ define([
* @memberof ModelAnimation.prototype
*
* @type {Number}
* @readonly
*
* @default 1.0
*/
multiplier : {
get : function() {
return this._multiplier;
},
set : function(multiplier) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThan('multiplier', multiplier, 0);
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
//>>includeEnd('debug');

this._multiplier = multiplier;
this._multiplierChanged = true;
}
},

Expand Down
4 changes: 3 additions & 1 deletion Source/Scene/ModelAnimationCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ define([

this._model = model;
this._scheduledAnimations = [];
this._multiplierChanged = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property doesn't seem to be used anywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is. See #7361 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have multiplierChanged on ModelAnimation and I see that that's being used. But this is on ModelAnimationCollection and I don't see that being set or accessed anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, you're right. My bad!

this._previousTime = undefined;
}

Expand Down Expand Up @@ -397,8 +398,9 @@ define([
scheduledAnimation._computedStartTime = JulianDate.addSeconds(defaultValue(scheduledAnimation.startTime, sceneTime), scheduledAnimation.delay, new JulianDate());
}

if (!defined(scheduledAnimation._duration)) {
if (!defined(scheduledAnimation._duration) || scheduledAnimation._multiplierChanged) {
scheduledAnimation._duration = runtimeAnimation.stopTime * (1.0 / scheduledAnimation.multiplier);
scheduledAnimation._multiplierChanged = false;
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
}

var startTime = scheduledAnimation._computedStartTime;
Expand Down
11 changes: 11 additions & 0 deletions Specs/DataSources/ModelGraphicsSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ defineSuite([
incrementallyLoadTextures : false,
runAnimations : false,
clampAnimations : false,
animationsMultiplier : 1.5,
shadows : ShadowMode.DISABLED,
heightReference : HeightReference.CLAMP_TO_GROUND,
distanceDisplayCondition : new DistanceDisplayCondition(),
Expand Down Expand Up @@ -80,6 +81,7 @@ defineSuite([
expect(model.lightColor).toBeInstanceOf(ConstantProperty);
expect(model.runAnimations).toBeInstanceOf(ConstantProperty);
expect(model.clampAnimations).toBeInstanceOf(ConstantProperty);
expect(model.animationsMultiplier).toBeInstanceOf(ConstantProperty);

expect(model.nodeTransformations).toBeInstanceOf(PropertyBag);

Expand All @@ -102,6 +104,7 @@ defineSuite([
expect(model.lightColor.getValue()).toEqual(options.lightColor);
expect(model.runAnimations.getValue()).toEqual(options.runAnimations);
expect(model.clampAnimations.getValue()).toEqual(options.clampAnimations);
expect(model.animationsMultiplier.getValue()).toEqual(options.animationsMultiplier);

var actualNodeTransformations = model.nodeTransformations.getValue(new JulianDate());
var expectedNodeTransformations = options.nodeTransformations;
Expand Down Expand Up @@ -133,6 +136,7 @@ defineSuite([
source.lightColor = new ConstantProperty(new Color(1.0, 1.0, 0.0, 1.0));
source.runAnimations = new ConstantProperty(true);
source.clampAnimations = new ConstantProperty(true);
source.animationsMultiplier = new ConstantProperty(1.5);
source.nodeTransformations = {
node1 : new NodeTransformationProperty({
translation : Cartesian3.UNIT_Y,
Expand Down Expand Up @@ -166,6 +170,7 @@ defineSuite([
expect(target.lightColor).toBe(source.lightColor);
expect(target.runAnimations).toBe(source.runAnimations);
expect(target.clampAnimations).toBe(source.clampAnimations);
expect(target.animationsMultiplier).toBe(source.animationsMultiplier);
expect(target.nodeTransformations).toEqual(source.nodeTransformations);
});

Expand All @@ -190,6 +195,7 @@ defineSuite([
source.lightColor = new ConstantProperty(new Color(1.0, 1.0, 0.0, 1.0));
source.runAnimations = new ConstantProperty(true);
source.clampAnimations = new ConstantProperty(true);
source.animationsMultiplier = new ConstantProperty(1.5);
source.nodeTransformations = {
transform : new NodeTransformationProperty()
};
Expand All @@ -213,6 +219,7 @@ defineSuite([
var lightColor = new ConstantProperty(new Color(1.0, 1.0, 0.0, 1.0));
var runAnimations = new ConstantProperty(true);
var clampAnimations = new ConstantProperty(true);
var animationsMultiplier = new ConstantProperty(2.0);
var nodeTransformations = new PropertyBag({
transform : new NodeTransformationProperty()
});
Expand All @@ -237,6 +244,7 @@ defineSuite([
target.lightColor = lightColor;
target.runAnimations = runAnimations;
target.clampAnimations = clampAnimations;
target.animationsMultiplier = animationsMultiplier;
target.nodeTransformations = nodeTransformations;

target.merge(source);
Expand All @@ -260,6 +268,7 @@ defineSuite([
expect(target.lightColor).toBe(lightColor);
expect(target.runAnimations).toBe(runAnimations);
expect(target.clampAnimations).toBe(clampAnimations);
expect(target.animationsMultiplier).toBe(animationsMultiplier);
expect(target.nodeTransformations).toBe(nodeTransformations);
});

Expand All @@ -284,6 +293,7 @@ defineSuite([
source.lightColor = new ConstantProperty(new Color(1.0, 1.0, 0.0, 1.0));
source.runAnimations = new ConstantProperty(true);
source.clampAnimations = new ConstantProperty(true);
source.animationsMultiplier = new ConstantProperty(1.5);
source.nodeTransformations = {
node1 : new NodeTransformationProperty(),
node2 : new NodeTransformationProperty()
Expand All @@ -309,6 +319,7 @@ defineSuite([
expect(result.lightColor).toBe(source.lightColor);
expect(result.runAnimations).toBe(source.runAnimations);
expect(result.clampAnimations).toBe(source.clampAnimations);
expect(result.animationsMultiplier).toBe(source.animationsMultiplier);
expect(result.nodeTransformations).toEqual(source.nodeTransformations);
});

Expand Down
Loading