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

CZML model node transformations #3316

Merged
merged 24 commits into from
Dec 18, 2015
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c3903fc
initial implementation of model transformations in czml
gbeatty Jun 18, 2015
7a65133
Fixed ModelVisualizer and tests
gbeatty Jun 19, 2015
818529e
initial working czml implementation for model transformations
gbeatty Jun 21, 2015
895c914
changed ModelGraphics nodeTransformations from a raw property to a re…
gbeatty Jun 21, 2015
9b48563
remove automatic model animation when a model is loaded via czml
gbeatty Jun 22, 2015
4462be8
include the model node's default transformation matrix when adding ad…
gbeatty Jun 22, 2015
f005d20
Update CZML node transformation names
gbeatty Aug 25, 2015
06ad0e8
code formatting cleanup
gbeatty Sep 14, 2015
9359a01
Added a runAnimations property to ModelGraphics.js to allow the user …
gbeatty Sep 14, 2015
22f6ef3
Merge branch 'master' into czmlModelAnimations
gbeatty Sep 29, 2015
f2e0d0e
Merge remote-tracking branch 'origin/master' into gbeatty-czmlModelAn…
shunter Dec 3, 2015
8e4f397
Further work on node transformations.
shunter Dec 8, 2015
46b8234
Merge remote-tracking branch 'origin/master' into czmlModelNodeTransf…
shunter Dec 8, 2015
7a321e1
cleanup
shunter Dec 8, 2015
7755e22
cleanup
shunter Dec 8, 2015
8d38198
load runAnimations from CZML.
shunter Dec 8, 2015
4a3b0b7
Add tests for PropertyBag.
shunter Dec 9, 2015
781952f
Rename NodeTransformation to TranslationRotationScale and move to Cor…
shunter Dec 11, 2015
a90ddbc
clean up module paths.
shunter Dec 11, 2015
8ea6732
originalNodeMatrixHash needs to be per-model
shunter Dec 11, 2015
21ee2ae
Add sandcastle examples
shunter Dec 15, 2015
5bbf684
Updates after review
mramato Dec 18, 2015
ac342c1
Merge remote-tracking branch 'origin/master' into czmlModelNodeTransf…
mramato Dec 18, 2015
3147d89
Update CHANGES.
mramato Dec 18, 2015
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
63 changes: 63 additions & 0 deletions Source/DataSources/CzmlDataSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ define([
'./ImageMaterialProperty',
'./LabelGraphics',
'./ModelGraphics',
'./NodeTransformationProperty',
'./PathGraphics',
'./PointGraphics',
'./PolygonGraphics',
'./PolylineGlowMaterialProperty',
'./PolylineGraphics',
'./PolylineOutlineMaterialProperty',
'./PositionPropertyArray',
'./PropertyBag',
'./RectangleGraphics',
'./ReferenceProperty',
'./Rotation',
Expand Down Expand Up @@ -120,13 +122,15 @@ define([
ImageMaterialProperty,
LabelGraphics,
ModelGraphics,
NodeTransformationProperty,
PathGraphics,
PointGraphics,
PolygonGraphics,
PolylineGlowMaterialProperty,
PolylineGraphics,
PolylineOutlineMaterialProperty,
PositionPropertyArray,
PropertyBag,
RectangleGraphics,
ReferenceProperty,
Rotation,
Expand Down Expand Up @@ -1169,6 +1173,65 @@ define([
processPacketData(Number, model, 'minimumPixelSize', modelData.minimumPixelSize, interval, sourceUri, entityCollection);
processPacketData(Boolean, model, 'incrementallyLoadTextures', modelData.incrementallyLoadTextures, interval, sourceUri, entityCollection);
processPacketData(Uri, model, 'uri', modelData.gltf, interval, sourceUri, entityCollection);
processPacketData(Boolean, model, 'runAnimations', modelData.runAnimations, interval, sourceUri, entityCollection);

var nodeTransformationsData = modelData.nodeTransformations;
if (defined(nodeTransformationsData)) {
if (isArray(nodeTransformationsData)) {
for (var i = 0, len = nodeTransformationsData.length; i < len; i++) {
processNodeTransformations(model, nodeTransformationsData[i], interval, sourceUri, entityCollection);
}
} else {
processNodeTransformations(model, nodeTransformationsData, interval, sourceUri, entityCollection);
}
}
}

function processNodeTransformations(model, nodeTransformationsData, constrainedInterval, sourceUri, entityCollection) {
var combinedInterval;
var packetInterval = nodeTransformationsData.interval;
if (defined(packetInterval)) {
iso8601Scratch.iso8601 = packetInterval;
combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
if (defined(constrainedInterval)) {
combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
}
} else if (defined(constrainedInterval)) {
combinedInterval = constrainedInterval;
}

var nodeTransformations = model.nodeTransformations;
var nodeNames = Object.keys(nodeTransformationsData);
for (var i = 0, len = nodeNames.length; i < len; ++i) {
var nodeName = nodeNames[i];

if (nodeName === 'interval') {
continue;
}

var nodeTransformationData = nodeTransformationsData[nodeName];

if (!defined(nodeTransformationData)) {
continue;
}

if (!defined(nodeTransformations)) {
model.nodeTransformations = nodeTransformations = new PropertyBag();
}

if (!nodeTransformations.hasProperty(nodeName)) {
nodeTransformations.addProperty(nodeName);
}

var nodeTransformation = nodeTransformations[nodeName];
if (!defined(nodeTransformation)) {
nodeTransformations[nodeName] = nodeTransformation = new NodeTransformationProperty();
}

processPacketData(Cartesian3, nodeTransformation, 'scale', nodeTransformationData.scale, combinedInterval, sourceUri, entityCollection);
processPacketData(Cartesian3, nodeTransformation, 'translation', nodeTransformationData.translation, combinedInterval, sourceUri, entityCollection);
processPacketData(Quaternion, nodeTransformation, 'rotation', nodeTransformationData.rotation, combinedInterval, sourceUri, entityCollection);
}
}

function processPath(entity, packet, entityCollection, sourceUri) {
Expand Down
56 changes: 52 additions & 4 deletions Source/DataSources/ModelGraphics.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,28 @@ define([
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createPropertyDescriptor'
'./createPropertyDescriptor',
'./NodeTransformationProperty',
'./PropertyBag'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createPropertyDescriptor) {
createPropertyDescriptor,
NodeTransformationProperty,
PropertyBag) {
"use strict";

function createNodeTransformationProperty(value) {
return new NodeTransformationProperty(value);
}

function createNodeTransformationPropertyBag(value) {
return new PropertyBag(value, createNodeTransformationProperty);
}

/**
* A 3D model based on {@link https://github.com/KhronosGroup/glTF|glTF}, the runtime asset format for WebGL, OpenGL ES, and OpenGL.
* The position and orientation of the model is determined by the containing {@link Entity}.
Expand All @@ -33,6 +45,8 @@ define([
* @param {Property} [options.minimumPixelSize=0.0] A numeric Property specifying the approximate minimum pixel size of the model regardless of zoom.
* @param {Property} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize.
* @param {Property} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
* @param {Property} [options.runAnimations=true] A boolean Property specifying if glTF animations specified in the model should be started.
* @param {Property} [options.nodeTransformations] An object, where keys are names of nodes, and values are {@link NodeTransformation} Properties describing the transformation to apply to that node.
*
* @see {@link http://cesiumjs.org/2014/03/03/Cesium-3D-Models-Tutorial/|3D Models Tutorial}
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle 3D Models Demo}
Expand All @@ -50,6 +64,10 @@ define([
this._incrementallyLoadTexturesSubscription = undefined;
this._uri = undefined;
this._uriSubscription = undefined;
this._runAnimations = undefined;
this._runAnimationsSubscription = undefined;
this._nodeTransformations = undefined;
this._nodeTransformationsSubscription = undefined;
this._definitionChanged = new Event();

this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
Expand Down Expand Up @@ -99,7 +117,7 @@ define([

/**
* Gets or sets the numeric Property specifying the maximum scale
* size of a model. This property is used as an upper limit for
* size of a model. This property is used as an upper limit for
* {@link ModelGraphics#minimumPixelSize}.
* @memberof ModelGraphics.prototype
* @type {Property}
Expand All @@ -119,7 +137,23 @@ define([
* @memberof ModelGraphics.prototype
* @type {Property}
*/
uri : createPropertyDescriptor('uri')
uri : createPropertyDescriptor('uri'),

/**
* Gets or sets the boolean Property specifying if glTF animations should be run.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default true
*/
runAnimations : createPropertyDescriptor('runAnimations'),

/**
* Gets or sets the set of node transformations to apply to this model. This is represented as an {@link PropertyBag}, where keys are
* names of nodes, and values are {@link NodeTransformation} Properties describing the transformation to apply to that node.
* @memberof ModelGraphics.prototype
* @type {PropertyBag}
*/
nodeTransformations : createPropertyDescriptor('nodeTransformations', undefined, createNodeTransformationPropertyBag)
});

/**
Expand All @@ -138,6 +172,9 @@ define([
result.maximumScale = this.maximumScale;
result.incrementallyLoadTextures = this.incrementallyLoadTextures;
result.uri = this.uri;
result.runAnimations = this.runAnimations;
result.nodeTransformations = this.nodeTransformations;

return result;
};

Expand All @@ -160,6 +197,17 @@ define([
this.maximumScale = defaultValue(this.maximumScale, source.maximumScale);
this.incrementallyLoadTextures = defaultValue(this.incrementallyLoadTextures, source.incrementallyLoadTextures);
this.uri = defaultValue(this.uri, source.uri);
this.runAnimations = defaultValue(this.runAnimations, source.runAnimations);

var sourceNodeTransformations = source.nodeTransformations;
if (defined(sourceNodeTransformations)) {
var targetNodeTransformations = this.nodeTransformations;
if (defined(targetNodeTransformations)) {
targetNodeTransformations.merge(sourceNodeTransformations);
} else {
this.nodeTransformations = new PropertyBag(sourceNodeTransformations, createNodeTransformationProperty);
}
}
};

return ModelGraphics;
Expand Down
59 changes: 53 additions & 6 deletions Source/DataSources/ModelVisualizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@
define([
'../Core/AssociativeArray',
'../Core/BoundingSphere',
'../Core/Cartesian3',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Matrix4',
'../Scene/Model',
'../Scene/ModelAnimationLoop',
'./BoundingSphereState',
'./NodeTransformation',
'./Property'
], function(
AssociativeArray,
BoundingSphere,
Cartesian3,
defined,
destroyObject,
DeveloperError,
Matrix4,
Model,
ModelAnimationLoop,
BoundingSphereState,
NodeTransformation,
Property) {
"use strict";

Expand All @@ -30,6 +30,8 @@ define([
var defaultIncrementallyLoadTextures = true;

var modelMatrixScratch = new Matrix4();
var nodeTransformationScratch = new NodeTransformation();
var nodeMatrixScratch = new Matrix4();

/**
* A {@link Visualizer} which maps {@link Entity#model} to a {@link Model}.
Expand Down Expand Up @@ -57,6 +59,7 @@ define([
this._modelHash = {};
this._entitiesToVisualize = new AssociativeArray();
this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
this._originalNodeMatrixHash = {};
};

/**
Expand All @@ -76,6 +79,7 @@ define([
var entities = this._entitiesToVisualize.values;
var modelHash = this._modelHash;
var primitives = this._primitives;
var originalNodeMatrixHash = this._originalNodeMatrixHash;

for (var i = 0, len = entities.length; i < len; i++) {
var entity = entities[i];
Expand Down Expand Up @@ -117,7 +121,8 @@ define([

modelData = {
modelPrimitive : model,
uri : uri
uri : uri,
animationsRunning : false
};
modelHash[entity.id] = modelData;
}
Expand All @@ -127,7 +132,51 @@ define([
model.minimumPixelSize = Property.getValueOrDefault(modelGraphics._minimumPixelSize, time, defaultMinimumPixelSize);
model.maximumScale = Property.getValueOrUndefined(modelGraphics._maximumScale, time);
model.modelMatrix = Matrix4.clone(modelMatrix, model.modelMatrix);

if (model.ready) {
var runAnimations = Property.getValueOrDefault(modelGraphics._runAnimations, time, true);
if (modelData.animationsRunning !== runAnimations) {
if (runAnimations) {
model.activeAnimations.addAll({
loop : ModelAnimationLoop.REPEAT
});
} else {
model.activeAnimations.removeAll();
}
modelData.animationsRunning = runAnimations;
}

// Apply node transformations
var nodeTransformations = modelGraphics._nodeTransformations;
if (defined(nodeTransformations)) {
var nodeNames = nodeTransformations._propertyNames;
for (var nodeIndex = 0, nodeLength = nodeNames.length; nodeIndex < nodeLength; ++nodeIndex) {
var nodeName = nodeNames[nodeIndex];

var nodeTransformationProperty = nodeTransformations[nodeName];
if (!defined(nodeTransformationProperty)) {
continue;
}

var modelNode = model.getNode(nodeName);
if (!defined(modelNode)) {
continue;
}

var originalNodeMatrix = originalNodeMatrixHash[nodeName];
if (!defined(originalNodeMatrix)) {
originalNodeMatrix = modelNode.matrix.clone();
originalNodeMatrixHash[nodeName] = originalNodeMatrix;
}

var nodeTransformation = nodeTransformationProperty.getValue(time, nodeTransformationScratch);
var transformationMatrix = nodeTransformation.toMatrix(nodeMatrixScratch);
modelNode.matrix = Matrix4.multiply(originalNodeMatrix, transformationMatrix, transformationMatrix);
}
}
}
}

return true;
};

Expand Down Expand Up @@ -236,9 +285,7 @@ define([
}

function onModelReady(model) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function is empty and can just be removed now.

model.activeAnimations.addAll({
loop : ModelAnimationLoop.REPEAT
});

}

function onModelError(error) {
Expand Down
61 changes: 61 additions & 0 deletions Source/DataSources/NodeTransformation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*global define*/
define([
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/Matrix4',
'../Core/Quaternion'
], function(
Cartesian3,
defaultValue,
Matrix4,
Quaternion) {
"use strict";

var defaultScale = new Cartesian3(1.0, 1.0, 1.0);
var defaultTranslation = Cartesian3.ZERO;
var defaultRotation = Quaternion.IDENTITY;

/**
* A set of transformations to apply to a particular node in a {@link Model}.
* @alias NodeTransformation
* @constructor
*
* @param {Cartesian3} [scale=new Cartesian3(1.0, 1.0, 1.0)] A {@link Cartesian3} specifying the (x, y, z) scaling to apply to the node.
* @param {Cartesian3} [translation=Cartesian3.ZERO] A {@link Cartesian3} specifying the (x, y, z) translation to apply to the node.
* @param {Quaternion.IDENTITY} [rotation=Quaternion.IDENTITY] A {@link Quaternion} specifying the (x, y, z, w) rotation to apply to the node.
*/
var NodeTransformation = function(scale, translation, rotation) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand why this is in DataSources, but it certainly looks like something I would except to be in Core. Are we sure that we're never going to need something like this at the lower level?

Copy link
Contributor

Choose a reason for hiding this comment

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

Core is probably a better choice. Probably name it Transform, AffineTransform, or TranslationRotationScale.

Throughout, the parameters should also be in that order: translation, rotation, scale. TRS, as it is called, is common in graphics and what we say in glTF.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, renamed to TranslationRotationScale and moved to Core.

/**
* The (x, y, z) scaling to apply to the node.
* @type {Cartesian3}
* @default new Cartesian3(1.0, 1.0, 1.0)
*/
this.scale = Cartesian3.clone(defaultValue(scale, defaultScale));

/**
* The (x, y, z) translation to apply to the node.
* @type {Cartesian3}
* @default Cartesian3.ZERO
*/
this.translation = Cartesian3.clone(defaultValue(translation, defaultTranslation));

/**
* The (x, y, z, w) rotation to apply to the node.
* @type {Quaternion}
* @default Quaternion.IDENTITY
*/
this.rotation = Quaternion.clone(defaultValue(rotation, defaultRotation));
};

/**
* Combine this transformation into a single {@link Matrix4}.
*
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided.
*/
NodeTransformation.prototype.toMatrix = function(result) {
return Matrix4.fromTranslationQuaternionRotationScale(this.translation, this.rotation, this.scale, result);
};

return NodeTransformation;
});
Loading