import Cartesian3 from "../../Core/Cartesian3.js"; import Check from "../../Core/Check.js"; import defaultValue from "../../Core/defaultValue.js"; import defined from "../../Core/defined.js"; import DeveloperError from "../../Core/DeveloperError.js"; import Matrix4 from "../../Core/Matrix4.js"; import InstancingPipelineStage from "./InstancingPipelineStage.js"; import ModelMatrixUpdateStage from "./ModelMatrixUpdateStage.js"; import TranslationRotationScale from "../../Core/TranslationRotationScale.js"; import Quaternion from "../../Core/Quaternion.js"; /** * An in-memory representation of a node as part of the {@link ModelExperimentalSceneGraph}. * * * @param {Object} options An object containing the following options: * @param {ModelComponents.Node} options.node The corresponding node components from the 3D model * @param {Matrix4} options.transform The transform of this node, excluding transforms from the node's ancestors or children. * @param {Matrix4} options.transformToRoot The product of the transforms of all the node's ancestors, excluding the node's own transform. * @param {ModelExperimentalSceneGraph} options.sceneGraph The scene graph this node belongs to. * @param {Number[]} options.children The indices of the children of this node in the runtime nodes array of the scene graph. * * @alias ModelExperimentalNode * @constructor * * @private */ export default function ModelExperimentalNode(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options.node", options.node); Check.typeOf.object("options.transform", options.transform); Check.typeOf.object("options.transformToRoot", options.transformToRoot); Check.typeOf.object("options.sceneGraph", options.sceneGraph); Check.typeOf.object("options.children", options.children); //>>includeEnd('debug'); const sceneGraph = options.sceneGraph; const transform = options.transform; const transformToRoot = options.transformToRoot; const node = options.node; this._sceneGraph = sceneGraph; this._children = options.children; this._node = node; this._name = node.name; // Helps with debugging this._originalTransform = Matrix4.clone(transform, this._originalTransform); this._transform = Matrix4.clone(transform, this._transform); this._transformToRoot = Matrix4.clone(transformToRoot, this._transformToRoot); this._originalTransform = Matrix4.clone(transform, this._originalTransform); const computedTransform = Matrix4.multiply( transformToRoot, transform, new Matrix4() ); this._computedTransform = computedTransform; this._transformDirty = false; // Used for animation this._transformParameters = defined(node.matrix) ? undefined : new TranslationRotationScale(node.translation, node.rotation, node.scale); this._morphWeights = defined(node.morphWeights) ? node.morphWeights.slice() : []; // Will be set by the scene graph after the skins have been created this._runtimeSkin = undefined; this._computedJointMatrices = []; /** * Pipeline stages to apply across all the mesh primitives of this node. This * is an array of classes, each with a static method called * process() * * @type {Object[]} * @readonly * * @private */ this.pipelineStages = []; /** * The mesh primitives that belong to this node * * @type {ModelExperimentalPrimitive[]} * @readonly * * @private */ this.runtimePrimitives = []; /** * Update stages to apply to this primitive. * * @private */ this.updateStages = []; this.configurePipeline(); } Object.defineProperties(ModelExperimentalNode.prototype, { /** * The internal node this runtime node represents. * * @type {ModelComponents.Node} * @readonly * * @private */ node: { get: function () { return this._node; }, }, /** * The scene graph this node belongs to. * * @type {ModelExperimentalSceneGraph} * @readonly * * @private */ sceneGraph: { get: function () { return this._sceneGraph; }, }, /** * The indices of the children of this node in the scene graph. * * @type {Number[]} * @readonly */ children: { get: function () { return this._children; }, }, /** * The node's local space transform. This can be changed externally so animation * can be driven by another source, not just an animation in the model's asset. *

* For changes to take effect, this property must be assigned to; * setting individual elements of the matrix will not work. *

* * @memberof ModelExperimentalNode.prototype * @type {Matrix4} */ transform: { get: function () { return this._transform; }, set: function (value) { if (Matrix4.equals(this._transform, value)) { return; } this._transformDirty = true; this._transform = Matrix4.clone(value, this._transform); }, }, /** * The transforms of all the node's ancestors, not including this node's * transform. * * @see ModelExperimentalNode#computedTransform * * @memberof ModelExperimentalNode.prototype * @type {Matrix4} * @readonly */ transformToRoot: { get: function () { return this._transformToRoot; }, }, /** * A transform from the node's local space to the model's scene graph space. * This is the product of transformToRoot * transform. * * @memberof ModelExperimentalNode.prototype * @type {Matrix4} * @readonly */ computedTransform: { get: function () { return this._computedTransform; }, }, /** * The node's original transform, as specified in the model. Does not include transformations from the node's ancestors. * * @memberof ModelExperimentalNode.prototype * @type {Matrix4} * @readonly */ originalTransform: { get: function () { return this._originalTransform; }, }, /** * The node's local space translation. This is used internally to allow * animations in the model's asset to affect the node's properties. * * If the node's transformation was originally described using a matrix * in the model, then this will return undefined. * * @memberof ModelExperimentalNode.prototype * @type {Cartesian3} * * @exception {DeveloperError} The translation of a node cannot be set if it was defined using a matrix in the model. * * @private */ translation: { get: function () { return defined(this._transformParameters) ? this._transformParameters.translation : undefined; }, set: function (value) { const transformParameters = this._transformParameters; //>>includeStart('debug', pragmas.debug); if (!defined(transformParameters)) { throw new DeveloperError( "The translation of a node cannot be set if it was defined using a matrix in the model." ); } //>>includeEnd('debug'); const currentTranslation = transformParameters.translation; if (Cartesian3.equals(currentTranslation, value)) { return; } transformParameters.translation = Cartesian3.clone( value, transformParameters.translation ); updateTransformFromParameters(this, transformParameters); }, }, /** * The node's local space rotation. This is used internally to allow * animations in the model's asset to affect the node's properties. * * If the node's transformation was originally described using a matrix * in the model, then this will return undefined. * * @memberof ModelExperimentalNode.prototype * @type {Quaternion} * * @exception {DeveloperError} The rotation of a node cannot be set if it was defined using a matrix in the model. * * @private */ rotation: { get: function () { return defined(this._transformParameters) ? this._transformParameters.rotation : undefined; }, set: function (value) { const transformParameters = this._transformParameters; //>>includeStart('debug', pragmas.debug); if (!defined(transformParameters)) { throw new DeveloperError( "The rotation of a node cannot be set if it was defined using a matrix in the model." ); } //>>includeEnd('debug'); const currentRotation = transformParameters.rotation; if (Quaternion.equals(currentRotation, value)) { return; } transformParameters.rotation = Quaternion.clone( value, transformParameters.rotation ); updateTransformFromParameters(this, transformParameters); }, }, /** * The node's local space scale. This is used internally to allow * animations in the model's asset to affect the node's properties. * * If the node's transformation was originally described using a matrix * in the model, then this will return undefined. * * @memberof ModelExperimentalNode.prototype * @type {Cartesian3} * * @exception {DeveloperError} The scale of a node cannot be set if it was defined using a matrix in the model. * @private */ scale: { get: function () { return defined(this._transformParameters) ? this._transformParameters.scale : undefined; }, set: function (value) { const transformParameters = this._transformParameters; //>>includeStart('debug', pragmas.debug); if (!defined(transformParameters)) { throw new DeveloperError( "The scale of a node cannot be set if it was defined using a matrix in the model." ); } //>>includeEnd('debug'); const currentScale = transformParameters.scale; if (Cartesian3.equals(currentScale, value)) { return; } transformParameters.scale = Cartesian3.clone( value, transformParameters.scale ); updateTransformFromParameters(this, transformParameters); }, }, /** * The node's morph weights. This is used internally to allow animations * in the model's asset to affect the node's properties. * * @memberof ModelExperimentalNode.prototype * @type {Number[]} * * @private */ morphWeights: { get: function () { return this._morphWeights; }, set: function (value) { const valueLength = value.length; //>>includeStart('debug', pragmas.debug); if (this._morphWeights.length !== valueLength) { throw new DeveloperError( "value must have the same length as the original weights array." ); } //>>includeEnd('debug'); for (let i = 0; i < valueLength; i++) { this._morphWeights[i] = value[i]; } }, }, /** * The skin applied to this node, if it exists. * * @memberof ModelExperimentalNode.prototype * @type {ModelExperimentalSkin} * @readonly */ runtimeSkin: { get: function () { return this._runtimeSkin; }, }, /** * The computed joint matrices of this node, derived from its skin. * * @memberof ModelExperimentalNode.prototype * @type {Matrix4[]} * @readonly */ computedJointMatrices: { get: function () { return this._computedJointMatrices; }, }, }); function updateTransformFromParameters(runtimeNode, transformParameters) { runtimeNode._transformDirty = true; runtimeNode._transform = Matrix4.fromTranslationRotationScale( transformParameters, runtimeNode._transform ); } /** * Returns the child with the given index. * * @param {Number} index The index of the child. * * @returns {ModelExperimentalNode} * * @example * // Iterate through all children of a runtime node. * for (let i = 0; i < runtimeNode.children.length; i++) * { * const childNode = runtimeNode.getChild(i); * } */ ModelExperimentalNode.prototype.getChild = function (index) { //>>includeStart('debug', pragmas.debug); Check.typeOf.number("index", index); if (index < 0 || index >= this.children.length) { throw new DeveloperError( "index must be greater than or equal to 0 and less than the number of children." ); } //>>includeEnd('debug'); return this.sceneGraph.runtimeNodes[this.children[index]]; }; /** * Configure the node pipeline stages. If the pipeline needs to be re-run, call * this method again to ensure the correct sequence of pipeline stages are * used. * * @private */ ModelExperimentalNode.prototype.configurePipeline = function () { const node = this.node; const pipelineStages = this.pipelineStages; pipelineStages.length = 0; const updateStages = this.updateStages; updateStages.length = 0; if (defined(node.instances)) { pipelineStages.push(InstancingPipelineStage); } updateStages.push(ModelMatrixUpdateStage); }; /** * Updates the computed transform used for rendering and instancing. * * @private */ ModelExperimentalNode.prototype.updateComputedTransform = function () { this._computedTransform = Matrix4.multiply( this._transformToRoot, this._transform, this._computedTransform ); }; /** * Updates the joint matrices for this node, where each matrix is computed as * computedJointMatrix = nodeWorldTransform^(-1) * skinJointMatrix. * * @private */ ModelExperimentalNode.prototype.updateJointMatrices = function () { const runtimeSkin = this._runtimeSkin; if (!defined(runtimeSkin)) { return; } runtimeSkin.updateJointMatrices(); const computedJointMatrices = this._computedJointMatrices; const skinJointMatrices = runtimeSkin.jointMatrices; const length = skinJointMatrices.length; for (let i = 0; i < length; i++) { if (!defined(computedJointMatrices[i])) { computedJointMatrices[i] = new Matrix4(); } const nodeWorldTransform = Matrix4.multiplyTransformation( this.transformToRoot, this.transform, computedJointMatrices[i] ); const inverseNodeWorldTransform = Matrix4.inverseTransformation( nodeWorldTransform, computedJointMatrices[i] ); computedJointMatrices[i] = Matrix4.multiplyTransformation( inverseNodeWorldTransform, skinJointMatrices[i], computedJointMatrices[i] ); } };