|
@@ -8,226 +8,268 @@
|
|
|
* @author tschw
|
|
|
*/
|
|
|
|
|
|
-function Composite ( targetGroup, path, optionalParsedPath ) {
|
|
|
+function PropertyBinding( rootNode, path, parsedPath ) {
|
|
|
|
|
|
- var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
|
|
|
+ this.path = path;
|
|
|
+ this.parsedPath = parsedPath ||
|
|
|
+ PropertyBinding.parseTrackName( path );
|
|
|
|
|
|
- this._targetGroup = targetGroup;
|
|
|
- this._bindings = targetGroup.subscribe_( path, parsedPath );
|
|
|
+ this.node = PropertyBinding.findNode(
|
|
|
+ rootNode, this.parsedPath.nodeName ) || rootNode;
|
|
|
+
|
|
|
+ this.rootNode = rootNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
-Object.assign( Composite.prototype, {
|
|
|
+PropertyBinding.prototype = {
|
|
|
|
|
|
- constructor: Composite,
|
|
|
+ constructor: PropertyBinding,
|
|
|
|
|
|
- getValue: function( array, offset ) {
|
|
|
+ getValue: function getValue_unbound( targetArray, offset ) {
|
|
|
|
|
|
- this.bind(); // bind all binding
|
|
|
+ this.bind();
|
|
|
+ this.getValue( targetArray, offset );
|
|
|
|
|
|
- var firstValidIndex = this._targetGroup.nCachedObjects_,
|
|
|
- binding = this._bindings[ firstValidIndex ];
|
|
|
+ // Note: This class uses a State pattern on a per-method basis:
|
|
|
+ // 'bind' sets 'this.getValue' / 'setValue' and shadows the
|
|
|
+ // prototype version of these methods with one that represents
|
|
|
+ // the bound state. When the property is not found, the methods
|
|
|
+ // become no-ops.
|
|
|
|
|
|
- // and only call .getValue on the first
|
|
|
- if ( binding !== undefined ) binding.getValue( array, offset );
|
|
|
+ },
|
|
|
+
|
|
|
+ setValue: function getValue_unbound( sourceArray, offset ) {
|
|
|
+
|
|
|
+ this.bind();
|
|
|
+ this.setValue( sourceArray, offset );
|
|
|
|
|
|
},
|
|
|
|
|
|
- setValue: function( array, offset ) {
|
|
|
+ // create getter / setter pair for a property in the scene graph
|
|
|
+ bind: function() {
|
|
|
|
|
|
- var bindings = this._bindings;
|
|
|
+ var targetObject = this.node,
|
|
|
+ parsedPath = this.parsedPath,
|
|
|
|
|
|
- for ( var i = this._targetGroup.nCachedObjects_,
|
|
|
- n = bindings.length; i !== n; ++ i ) {
|
|
|
+ objectName = parsedPath.objectName,
|
|
|
+ propertyName = parsedPath.propertyName,
|
|
|
+ propertyIndex = parsedPath.propertyIndex;
|
|
|
|
|
|
- bindings[ i ].setValue( array, offset );
|
|
|
+ if ( ! targetObject ) {
|
|
|
|
|
|
- }
|
|
|
+ targetObject = PropertyBinding.findNode(
|
|
|
+ this.rootNode, parsedPath.nodeName ) || this.rootNode;
|
|
|
|
|
|
- },
|
|
|
+ this.node = targetObject;
|
|
|
|
|
|
- bind: function() {
|
|
|
+ }
|
|
|
|
|
|
- var bindings = this._bindings;
|
|
|
+ // set fail state so we can just 'return' on error
|
|
|
+ this.getValue = this._getValue_unavailable;
|
|
|
+ this.setValue = this._setValue_unavailable;
|
|
|
|
|
|
- for ( var i = this._targetGroup.nCachedObjects_,
|
|
|
- n = bindings.length; i !== n; ++ i ) {
|
|
|
+ // ensure there is a value node
|
|
|
+ if ( ! targetObject ) {
|
|
|
|
|
|
- bindings[ i ].bind();
|
|
|
+ console.error( " trying to update node for track: " + this.path + " but it wasn't found." );
|
|
|
+ return;
|
|
|
|
|
|
}
|
|
|
|
|
|
- },
|
|
|
+ if ( objectName ) {
|
|
|
|
|
|
- unbind: function() {
|
|
|
+ var objectIndex = parsedPath.objectIndex;
|
|
|
|
|
|
- var bindings = this._bindings;
|
|
|
+ // special cases were we need to reach deeper into the hierarchy to get the face materials....
|
|
|
+ switch ( objectName ) {
|
|
|
|
|
|
- for ( var i = this._targetGroup.nCachedObjects_,
|
|
|
- n = bindings.length; i !== n; ++ i ) {
|
|
|
+ case 'materials':
|
|
|
|
|
|
- bindings[ i ].unbind();
|
|
|
+ if ( ! targetObject.material ) {
|
|
|
|
|
|
- }
|
|
|
+ console.error( ' can not bind to material as node does not have a material', this );
|
|
|
+ return;
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
-} );
|
|
|
+ if ( ! targetObject.material.materials ) {
|
|
|
|
|
|
+ console.error( ' can not bind to material.materials as node.material does not have a materials array', this );
|
|
|
+ return;
|
|
|
|
|
|
-function PropertyBinding( rootNode, path, parsedPath ) {
|
|
|
+ }
|
|
|
|
|
|
- this.path = path;
|
|
|
- this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
|
|
|
+ targetObject = targetObject.material.materials;
|
|
|
|
|
|
- this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
|
|
|
+ break;
|
|
|
|
|
|
- this.rootNode = rootNode;
|
|
|
+ case 'bones':
|
|
|
|
|
|
-}
|
|
|
+ if ( ! targetObject.skeleton ) {
|
|
|
|
|
|
-Object.assign( PropertyBinding, {
|
|
|
+ console.error( ' can not bind to bones as node does not have a skeleton', this );
|
|
|
+ return;
|
|
|
|
|
|
- Composite: Composite,
|
|
|
+ }
|
|
|
|
|
|
- create: function( root, path, parsedPath ) {
|
|
|
+ // potential future optimization: skip this if propertyIndex is already an integer
|
|
|
+ // and convert the integer string to a true integer.
|
|
|
|
|
|
- if ( ! ( root && root.isAnimationObjectGroup ) ) {
|
|
|
+ targetObject = targetObject.skeleton.bones;
|
|
|
|
|
|
- return new PropertyBinding( root, path, parsedPath );
|
|
|
+ // support resolving morphTarget names into indices.
|
|
|
+ for ( var i = 0; i < targetObject.length; i ++ ) {
|
|
|
|
|
|
- } else {
|
|
|
+ if ( targetObject[ i ].name === objectIndex ) {
|
|
|
|
|
|
- return new PropertyBinding.Composite( root, path, parsedPath );
|
|
|
+ objectIndex = i;
|
|
|
+ break;
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- },
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+
|
|
|
+ if ( targetObject[ objectName ] === undefined ) {
|
|
|
+
|
|
|
+ console.error( ' can not bind to objectName of node, undefined', this );
|
|
|
+ return;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ targetObject = targetObject[ objectName ];
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
- parseTrackName: function( trackName ) {
|
|
|
|
|
|
- // matches strings in the form of:
|
|
|
- // nodeName.property
|
|
|
- // nodeName.property[accessor]
|
|
|
- // nodeName.material.property[accessor]
|
|
|
- // uuid.property[accessor]
|
|
|
- // uuid.objectName[objectIndex].propertyName[propertyIndex]
|
|
|
- // parentName/nodeName.property
|
|
|
- // parentName/parentName/nodeName.property[index]
|
|
|
- // .bone[Armature.DEF_cog].position
|
|
|
- // scene:helium_balloon_model:helium_balloon_model.position
|
|
|
- // created and tested via https://regex101.com/#javascript
|
|
|
+ if ( objectIndex !== undefined ) {
|
|
|
+
|
|
|
+ if ( targetObject[ objectIndex ] === undefined ) {
|
|
|
+
|
|
|
+ console.error( " trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
|
|
|
+ return;
|
|
|
|
|
|
- var re = /^((?:[\w-]+[\/:])*)([\w-]+)?(?:\.([\w-]+)(?:\[(.+)\])?)?\.([\w-]+)(?:\[(.+)\])?$/;
|
|
|
- var matches = re.exec( trackName );
|
|
|
+ }
|
|
|
|
|
|
- if ( ! matches ) {
|
|
|
+ targetObject = targetObject[ objectIndex ];
|
|
|
|
|
|
- throw new Error( "cannot parse trackName at all: " + trackName );
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- var results = {
|
|
|
- // directoryName: matches[ 1 ], // (tschw) currently unused
|
|
|
- nodeName: matches[ 2 ], // allowed to be null, specified root node.
|
|
|
- objectName: matches[ 3 ],
|
|
|
- objectIndex: matches[ 4 ],
|
|
|
- propertyName: matches[ 5 ],
|
|
|
- propertyIndex: matches[ 6 ] // allowed to be null, specifies that the whole property is set.
|
|
|
- };
|
|
|
+ // resolve property
|
|
|
+ var nodeProperty = targetObject[ propertyName ];
|
|
|
|
|
|
- if ( results.propertyName === null || results.propertyName.length === 0 ) {
|
|
|
+ if ( nodeProperty === undefined ) {
|
|
|
+
|
|
|
+ var nodeName = parsedPath.nodeName;
|
|
|
|
|
|
- throw new Error( "can not parse propertyName from trackName: " + trackName );
|
|
|
+ console.error( " trying to update property for track: " + nodeName +
|
|
|
+ '.' + propertyName + " but it wasn't found.", targetObject );
|
|
|
+ return;
|
|
|
|
|
|
}
|
|
|
|
|
|
- return results;
|
|
|
+ // determine versioning scheme
|
|
|
+ var versioning = this.Versioning.None;
|
|
|
|
|
|
- },
|
|
|
+ if ( targetObject.needsUpdate !== undefined ) { // material
|
|
|
|
|
|
- findNode: function( root, nodeName ) {
|
|
|
+ versioning = this.Versioning.NeedsUpdate;
|
|
|
+ this.targetObject = targetObject;
|
|
|
|
|
|
- if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) {
|
|
|
+ } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
|
|
|
|
|
|
- return root;
|
|
|
+ versioning = this.Versioning.MatrixWorldNeedsUpdate;
|
|
|
+ this.targetObject = targetObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
- // search into skeleton bones.
|
|
|
- if ( root.skeleton ) {
|
|
|
-
|
|
|
- var searchSkeleton = function( skeleton ) {
|
|
|
+ // determine how the property gets bound
|
|
|
+ var bindingType = this.BindingType.Direct;
|
|
|
|
|
|
- for( var i = 0; i < skeleton.bones.length; i ++ ) {
|
|
|
+ if ( propertyIndex !== undefined ) {
|
|
|
+ // access a sub element of the property array (only primitives are supported right now)
|
|
|
|
|
|
- var bone = skeleton.bones[ i ];
|
|
|
+ if ( propertyName === "morphTargetInfluences" ) {
|
|
|
+ // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
|
|
|
|
|
|
- if ( bone.name === nodeName ) {
|
|
|
+ // support resolving morphTarget names into indices.
|
|
|
+ if ( ! targetObject.geometry ) {
|
|
|
|
|
|
- return bone;
|
|
|
+ console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
|
|
|
+ return;
|
|
|
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- return null;
|
|
|
+ if ( ! targetObject.geometry.morphTargets ) {
|
|
|
+
|
|
|
+ console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );
|
|
|
+ return;
|
|
|
|
|
|
- };
|
|
|
+ }
|
|
|
|
|
|
- var bone = searchSkeleton( root.skeleton );
|
|
|
+ for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
|
|
|
|
|
|
- if ( bone ) {
|
|
|
+ if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) {
|
|
|
|
|
|
- return bone;
|
|
|
+ propertyIndex = i;
|
|
|
+ break;
|
|
|
|
|
|
- }
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- // search into node subtree.
|
|
|
- if ( root.children ) {
|
|
|
+ }
|
|
|
|
|
|
- var searchNodeSubtree = function( children ) {
|
|
|
+ }
|
|
|
|
|
|
- for( var i = 0; i < children.length; i ++ ) {
|
|
|
+ bindingType = this.BindingType.ArrayElement;
|
|
|
|
|
|
- var childNode = children[ i ];
|
|
|
+ this.resolvedProperty = nodeProperty;
|
|
|
+ this.propertyIndex = propertyIndex;
|
|
|
|
|
|
- if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
|
|
|
+ } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
|
|
|
+ // must use copy for Object3D.Euler/Quaternion
|
|
|
|
|
|
- return childNode;
|
|
|
+ bindingType = this.BindingType.HasFromToArray;
|
|
|
|
|
|
- }
|
|
|
+ this.resolvedProperty = nodeProperty;
|
|
|
|
|
|
- var result = searchNodeSubtree( childNode.children );
|
|
|
+ } else if ( Array.isArray( nodeProperty ) ) {
|
|
|
|
|
|
- if ( result ) return result;
|
|
|
+ bindingType = this.BindingType.EntireArray;
|
|
|
|
|
|
- }
|
|
|
+ this.resolvedProperty = nodeProperty;
|
|
|
|
|
|
- return null;
|
|
|
+ } else {
|
|
|
|
|
|
- };
|
|
|
+ this.propertyName = propertyName;
|
|
|
|
|
|
- var subTreeNode = searchNodeSubtree( root.children );
|
|
|
+ }
|
|
|
|
|
|
- if ( subTreeNode ) {
|
|
|
+ // select getter / setter
|
|
|
+ this.getValue = this.GetterByBindingType[ bindingType ];
|
|
|
+ this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
|
|
|
|
|
|
- return subTreeNode;
|
|
|
+ },
|
|
|
|
|
|
- }
|
|
|
+ unbind: function() {
|
|
|
|
|
|
- }
|
|
|
+ this.node = null;
|
|
|
|
|
|
- return null;
|
|
|
+ // back to the prototype version of getValue / setValue
|
|
|
+ // note: avoiding to mutate the shape of 'this' via 'delete'
|
|
|
+ this.getValue = this._getValue_unbound;
|
|
|
+ this.setValue = this._setValue_unbound;
|
|
|
|
|
|
}
|
|
|
|
|
|
-} );
|
|
|
+};
|
|
|
|
|
|
Object.assign( PropertyBinding.prototype, { // prototype, continued
|
|
|
|
|
|
- constructor: PropertyBinding,
|
|
|
-
|
|
|
// these are used to "bind" a nonexistent property
|
|
|
_getValue_unavailable: function() {},
|
|
|
_setValue_unavailable: function() {},
|
|
@@ -402,250 +444,209 @@ Object.assign( PropertyBinding.prototype, { // prototype, continued
|
|
|
|
|
|
]
|
|
|
|
|
|
- ],
|
|
|
+ ]
|
|
|
|
|
|
- getValue: function getValue_unbound( targetArray, offset ) {
|
|
|
+} );
|
|
|
|
|
|
- this.bind();
|
|
|
- this.getValue( targetArray, offset );
|
|
|
+PropertyBinding.Composite =
|
|
|
+ function( targetGroup, path, optionalParsedPath ) {
|
|
|
|
|
|
- // Note: This class uses a State pattern on a per-method basis:
|
|
|
- // 'bind' sets 'this.getValue' / 'setValue' and shadows the
|
|
|
- // prototype version of these methods with one that represents
|
|
|
- // the bound state. When the property is not found, the methods
|
|
|
- // become no-ops.
|
|
|
-
|
|
|
- },
|
|
|
+ var parsedPath = optionalParsedPath ||
|
|
|
+ PropertyBinding.parseTrackName( path );
|
|
|
|
|
|
- setValue: function getValue_unbound( sourceArray, offset ) {
|
|
|
+ this._targetGroup = targetGroup;
|
|
|
+ this._bindings = targetGroup.subscribe_( path, parsedPath );
|
|
|
|
|
|
- this.bind();
|
|
|
- this.setValue( sourceArray, offset );
|
|
|
+};
|
|
|
|
|
|
- },
|
|
|
+PropertyBinding.Composite.prototype = {
|
|
|
|
|
|
- // create getter / setter pair for a property in the scene graph
|
|
|
- bind: function() {
|
|
|
+ constructor: PropertyBinding.Composite,
|
|
|
|
|
|
- var targetObject = this.node,
|
|
|
- parsedPath = this.parsedPath,
|
|
|
+ getValue: function( array, offset ) {
|
|
|
|
|
|
- objectName = parsedPath.objectName,
|
|
|
- propertyName = parsedPath.propertyName,
|
|
|
- propertyIndex = parsedPath.propertyIndex;
|
|
|
+ this.bind(); // bind all binding
|
|
|
|
|
|
- if ( ! targetObject ) {
|
|
|
+ var firstValidIndex = this._targetGroup.nCachedObjects_,
|
|
|
+ binding = this._bindings[ firstValidIndex ];
|
|
|
|
|
|
- targetObject = PropertyBinding.findNode(
|
|
|
- this.rootNode, parsedPath.nodeName ) || this.rootNode;
|
|
|
+ // and only call .getValue on the first
|
|
|
+ if ( binding !== undefined ) binding.getValue( array, offset );
|
|
|
|
|
|
- this.node = targetObject;
|
|
|
+ },
|
|
|
|
|
|
- }
|
|
|
+ setValue: function( array, offset ) {
|
|
|
|
|
|
- // set fail state so we can just 'return' on error
|
|
|
- this.getValue = this._getValue_unavailable;
|
|
|
- this.setValue = this._setValue_unavailable;
|
|
|
+ var bindings = this._bindings;
|
|
|
|
|
|
- // ensure there is a value node
|
|
|
- if ( ! targetObject ) {
|
|
|
+ for ( var i = this._targetGroup.nCachedObjects_,
|
|
|
+ n = bindings.length; i !== n; ++ i ) {
|
|
|
|
|
|
- console.error( " trying to update node for track: " + this.path + " but it wasn't found." );
|
|
|
- return;
|
|
|
+ bindings[ i ].setValue( array, offset );
|
|
|
|
|
|
}
|
|
|
|
|
|
- if ( objectName ) {
|
|
|
+ },
|
|
|
|
|
|
- var objectIndex = parsedPath.objectIndex;
|
|
|
+ bind: function() {
|
|
|
|
|
|
- // special cases were we need to reach deeper into the hierarchy to get the face materials....
|
|
|
- switch ( objectName ) {
|
|
|
+ var bindings = this._bindings;
|
|
|
|
|
|
- case 'materials':
|
|
|
+ for ( var i = this._targetGroup.nCachedObjects_,
|
|
|
+ n = bindings.length; i !== n; ++ i ) {
|
|
|
|
|
|
- if ( ! targetObject.material ) {
|
|
|
+ bindings[ i ].bind();
|
|
|
|
|
|
- console.error( ' can not bind to material as node does not have a material', this );
|
|
|
- return;
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ },
|
|
|
|
|
|
- if ( ! targetObject.material.materials ) {
|
|
|
+ unbind: function() {
|
|
|
|
|
|
- console.error( ' can not bind to material.materials as node.material does not have a materials array', this );
|
|
|
- return;
|
|
|
+ var bindings = this._bindings;
|
|
|
|
|
|
- }
|
|
|
+ for ( var i = this._targetGroup.nCachedObjects_,
|
|
|
+ n = bindings.length; i !== n; ++ i ) {
|
|
|
|
|
|
- targetObject = targetObject.material.materials;
|
|
|
+ bindings[ i ].unbind();
|
|
|
|
|
|
- break;
|
|
|
+ }
|
|
|
|
|
|
- case 'bones':
|
|
|
+ }
|
|
|
|
|
|
- if ( ! targetObject.skeleton ) {
|
|
|
+};
|
|
|
|
|
|
- console.error( ' can not bind to bones as node does not have a skeleton', this );
|
|
|
- return;
|
|
|
+PropertyBinding.create = function( root, path, parsedPath ) {
|
|
|
|
|
|
- }
|
|
|
+ if ( ! ( root && root.isAnimationObjectGroup ) ) {
|
|
|
|
|
|
- // potential future optimization: skip this if propertyIndex is already an integer
|
|
|
- // and convert the integer string to a true integer.
|
|
|
+ return new PropertyBinding( root, path, parsedPath );
|
|
|
|
|
|
- targetObject = targetObject.skeleton.bones;
|
|
|
+ } else {
|
|
|
|
|
|
- // support resolving morphTarget names into indices.
|
|
|
- for ( var i = 0; i < targetObject.length; i ++ ) {
|
|
|
+ return new PropertyBinding.Composite( root, path, parsedPath );
|
|
|
|
|
|
- if ( targetObject[ i ].name === objectIndex ) {
|
|
|
+ }
|
|
|
|
|
|
- objectIndex = i;
|
|
|
- break;
|
|
|
+};
|
|
|
|
|
|
- }
|
|
|
+PropertyBinding.parseTrackName = function( trackName ) {
|
|
|
|
|
|
- }
|
|
|
+ // matches strings in the form of:
|
|
|
+ // nodeName.property
|
|
|
+ // nodeName.property[accessor]
|
|
|
+ // nodeName.material.property[accessor]
|
|
|
+ // uuid.property[accessor]
|
|
|
+ // uuid.objectName[objectIndex].propertyName[propertyIndex]
|
|
|
+ // parentName/nodeName.property
|
|
|
+ // parentName/parentName/nodeName.property[index]
|
|
|
+ // .bone[Armature.DEF_cog].position
|
|
|
+ // scene:helium_balloon_model:helium_balloon_model.position
|
|
|
+ // created and tested via https://regex101.com/#javascript
|
|
|
|
|
|
- break;
|
|
|
+ var re = /^((?:[\w-]+[\/:])*)([\w-]+)?(?:\.([\w-]+)(?:\[(.+)\])?)?\.([\w-]+)(?:\[(.+)\])?$/;
|
|
|
+ var matches = re.exec( trackName );
|
|
|
|
|
|
- default:
|
|
|
+ if ( ! matches ) {
|
|
|
|
|
|
- if ( targetObject[ objectName ] === undefined ) {
|
|
|
-
|
|
|
- console.error( ' can not bind to objectName of node, undefined', this );
|
|
|
- return;
|
|
|
+ throw new Error( "cannot parse trackName at all: " + trackName );
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- targetObject = targetObject[ objectName ];
|
|
|
+ var results = {
|
|
|
+ // directoryName: matches[ 1 ], // (tschw) currently unused
|
|
|
+ nodeName: matches[ 2 ], // allowed to be null, specified root node.
|
|
|
+ objectName: matches[ 3 ],
|
|
|
+ objectIndex: matches[ 4 ],
|
|
|
+ propertyName: matches[ 5 ],
|
|
|
+ propertyIndex: matches[ 6 ] // allowed to be null, specifies that the whole property is set.
|
|
|
+ };
|
|
|
|
|
|
- }
|
|
|
+ if ( results.propertyName === null || results.propertyName.length === 0 ) {
|
|
|
|
|
|
+ throw new Error( "can not parse propertyName from trackName: " + trackName );
|
|
|
|
|
|
- if ( objectIndex !== undefined ) {
|
|
|
+ }
|
|
|
|
|
|
- if ( targetObject[ objectIndex ] === undefined ) {
|
|
|
+ return results;
|
|
|
|
|
|
- console.error( " trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
|
|
|
- return;
|
|
|
+};
|
|
|
|
|
|
- }
|
|
|
+PropertyBinding.findNode = function( root, nodeName ) {
|
|
|
|
|
|
- targetObject = targetObject[ objectIndex ];
|
|
|
+ if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) {
|
|
|
|
|
|
- }
|
|
|
+ return root;
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- // resolve property
|
|
|
- var nodeProperty = targetObject[ propertyName ];
|
|
|
+ // search into skeleton bones.
|
|
|
+ if ( root.skeleton ) {
|
|
|
|
|
|
- if ( nodeProperty === undefined ) {
|
|
|
+ var searchSkeleton = function( skeleton ) {
|
|
|
|
|
|
- var nodeName = parsedPath.nodeName;
|
|
|
+ for( var i = 0; i < skeleton.bones.length; i ++ ) {
|
|
|
|
|
|
- console.error( " trying to update property for track: " + nodeName +
|
|
|
- '.' + propertyName + " but it wasn't found.", targetObject );
|
|
|
- return;
|
|
|
+ var bone = skeleton.bones[ i ];
|
|
|
|
|
|
- }
|
|
|
+ if ( bone.name === nodeName ) {
|
|
|
|
|
|
- // determine versioning scheme
|
|
|
- var versioning = this.Versioning.None;
|
|
|
+ return bone;
|
|
|
|
|
|
- if ( targetObject.needsUpdate !== undefined ) { // material
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- versioning = this.Versioning.NeedsUpdate;
|
|
|
- this.targetObject = targetObject;
|
|
|
+ return null;
|
|
|
|
|
|
- } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
|
|
|
+ };
|
|
|
|
|
|
- versioning = this.Versioning.MatrixWorldNeedsUpdate;
|
|
|
- this.targetObject = targetObject;
|
|
|
+ var bone = searchSkeleton( root.skeleton );
|
|
|
|
|
|
- }
|
|
|
+ if ( bone ) {
|
|
|
|
|
|
- // determine how the property gets bound
|
|
|
- var bindingType = this.BindingType.Direct;
|
|
|
+ return bone;
|
|
|
|
|
|
- if ( propertyIndex !== undefined ) {
|
|
|
- // access a sub element of the property array (only primitives are supported right now)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if ( propertyName === "morphTargetInfluences" ) {
|
|
|
- // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
|
|
|
+ // search into node subtree.
|
|
|
+ if ( root.children ) {
|
|
|
|
|
|
- // support resolving morphTarget names into indices.
|
|
|
- if ( ! targetObject.geometry ) {
|
|
|
+ var searchNodeSubtree = function( children ) {
|
|
|
|
|
|
- console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
|
|
|
- return;
|
|
|
+ for( var i = 0; i < children.length; i ++ ) {
|
|
|
|
|
|
- }
|
|
|
+ var childNode = children[ i ];
|
|
|
|
|
|
- if ( ! targetObject.geometry.morphTargets ) {
|
|
|
+ if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
|
|
|
|
|
|
- console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );
|
|
|
- return;
|
|
|
+ return childNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
- for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
|
|
|
-
|
|
|
- if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) {
|
|
|
-
|
|
|
- propertyIndex = i;
|
|
|
- break;
|
|
|
-
|
|
|
- }
|
|
|
+ var result = searchNodeSubtree( childNode.children );
|
|
|
|
|
|
- }
|
|
|
+ if ( result ) return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
- bindingType = this.BindingType.ArrayElement;
|
|
|
-
|
|
|
- this.resolvedProperty = nodeProperty;
|
|
|
- this.propertyIndex = propertyIndex;
|
|
|
-
|
|
|
- } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
|
|
|
- // must use copy for Object3D.Euler/Quaternion
|
|
|
-
|
|
|
- bindingType = this.BindingType.HasFromToArray;
|
|
|
-
|
|
|
- this.resolvedProperty = nodeProperty;
|
|
|
+ return null;
|
|
|
|
|
|
- } else if ( Array.isArray( nodeProperty ) ) {
|
|
|
-
|
|
|
- bindingType = this.BindingType.EntireArray;
|
|
|
+ };
|
|
|
|
|
|
- this.resolvedProperty = nodeProperty;
|
|
|
+ var subTreeNode = searchNodeSubtree( root.children );
|
|
|
|
|
|
- } else {
|
|
|
+ if ( subTreeNode ) {
|
|
|
|
|
|
- this.propertyName = propertyName;
|
|
|
+ return subTreeNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
- // select getter / setter
|
|
|
- this.getValue = this.GetterByBindingType[ bindingType ];
|
|
|
- this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
|
|
|
-
|
|
|
- },
|
|
|
-
|
|
|
- unbind: function() {
|
|
|
-
|
|
|
- this.node = null;
|
|
|
-
|
|
|
- // back to the prototype version of getValue / setValue
|
|
|
- // note: avoiding to mutate the shape of 'this' via 'delete'
|
|
|
- this.getValue = this._getValue_unbound;
|
|
|
- this.setValue = this._setValue_unbound;
|
|
|
-
|
|
|
}
|
|
|
|
|
|
-} );
|
|
|
+ return null;
|
|
|
+
|
|
|
+};
|
|
|
|
|
|
|
|
|
export { PropertyBinding };
|