123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648 |
- /**
- *
- * A reference to a real property in the scene graph.
- *
- *
- * @author Ben Houston / http://clara.io/
- * @author David Sarno / http://lighthaus.us/
- * @author tschw
- */
- THREE.PropertyBinding = function ( rootNode, path, parsedPath ) {
- this.path = path;
- this.parsedPath = parsedPath ||
- THREE.PropertyBinding.parseTrackName( path );
- this.node = THREE.PropertyBinding.findNode(
- rootNode, this.parsedPath.nodeName ) || rootNode;
- this.rootNode = rootNode;
- };
- THREE.PropertyBinding.prototype = {
- constructor: THREE.PropertyBinding,
- getValue: function getValue_unbound( targetArray, offset ) {
- this.bind();
- this.getValue( targetArray, offset );
- // 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.
- },
- setValue: function getValue_unbound( sourceArray, offset ) {
- this.bind();
- this.setValue( sourceArray, offset );
- },
- // create getter / setter pair for a property in the scene graph
- bind: function() {
- var targetObject = this.node,
- parsedPath = this.parsedPath,
- objectName = parsedPath.objectName,
- propertyName = parsedPath.propertyName,
- propertyIndex = parsedPath.propertyIndex;
- if ( ! targetObject ) {
- targetObject = THREE.PropertyBinding.findNode(
- this.rootNode, parsedPath.nodeName ) || this.rootNode;
- this.node = targetObject;
- }
- // set fail state so we can just 'return' on error
- this.getValue = this._getValue_unavailable;
- this.setValue = this._setValue_unavailable;
- // ensure there is a value node
- if ( ! targetObject ) {
- console.error( " trying to update node for track: " + this.path + " but it wasn't found." );
- return;
- }
- if( objectName ) {
- var objectIndex = parsedPath.objectIndex;
- // special cases were we need to reach deeper into the hierarchy to get the face materials....
- switch ( objectName ) {
- case 'materials':
- 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;
- }
- targetObject = targetObject.material.materials;
- break;
- case 'bones':
- if( ! targetObject.skeleton ) {
- console.error( ' can not bind to bones as node does not have a skeleton', this );
- return;
- }
- // potential future optimization: skip this if propertyIndex is already an integer
- // and convert the integer string to a true integer.
- targetObject = targetObject.skeleton.bones;
- // support resolving morphTarget names into indices.
- for ( var i = 0; i < targetObject.length; i ++ ) {
- if ( targetObject[i].name === objectIndex ) {
- objectIndex = i;
- break;
- }
- }
- break;
- default:
- if ( targetObject[ objectName ] === undefined ) {
- console.error( ' can not bind to objectName of node, undefined', this );
- return;
- }
- targetObject = targetObject[ objectName ];
- }
- if ( objectIndex !== undefined ) {
- if( targetObject[ objectIndex ] === undefined ) {
- console.error( " trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
- return;
- }
- targetObject = targetObject[ objectIndex ];
- }
- }
- // resolve property
- var nodeProperty = targetObject[ propertyName ];
- if ( ! nodeProperty ) {
- var nodeName = parsedPath.nodeName;
- console.error( " trying to update property for track: " + nodeName +
- '.' + propertyName + " but it wasn't found.", targetObject );
- return;
- }
- // determine versioning scheme
- var versioning = this.Versioning.None;
- if ( targetObject.needsUpdate !== undefined ) { // material
- versioning = this.Versioning.NeedsUpdate;
- this.targetObject = targetObject;
- } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
- versioning = this.Versioning.MatrixWorldNeedsUpdate;
- this.targetObject = targetObject;
- }
- // determine how the property gets bound
- var bindingType = this.BindingType.Direct;
- 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.
- // support resolving morphTarget names into indices.
- if ( ! targetObject.geometry ) {
- console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
- return;
- }
- if ( ! targetObject.geometry.morphTargets ) {
- console.error( ' can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );
- return;
- }
- for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
- if ( targetObject.geometry.morphTargets[i].name === propertyIndex ) {
- propertyIndex = i;
- break;
- }
- }
- }
- 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;
- } else if ( nodeProperty.length !== undefined ) {
- bindingType = this.BindingType.EntireArray;
- this.resolvedProperty = nodeProperty;
- } else {
- this.propertyName = propertyName;
- }
- // 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;
- }
- };
- Object.assign( THREE.PropertyBinding.prototype, { // prototype, continued
- // these are used to "bind" a nonexistent property
- _getValue_unavailable: function() {},
- _setValue_unavailable: function() {},
- // initial state of these methods that calls 'bind'
- _getValue_unbound: THREE.PropertyBinding.prototype.getValue,
- _setValue_unbound: THREE.PropertyBinding.prototype.setValue,
- BindingType: {
- Direct: 0,
- EntireArray: 1,
- ArrayElement: 2,
- HasFromToArray: 3
- },
- Versioning: {
- None: 0,
- NeedsUpdate: 1,
- MatrixWorldNeedsUpdate: 2
- },
- GetterByBindingType: [
- function getValue_direct( buffer, offset ) {
- buffer[ offset ] = this.node[ this.propertyName ];
- },
- function getValue_array( buffer, offset ) {
- var source = this.resolvedProperty;
- for ( var i = 0, n = source.length; i !== n; ++ i ) {
- buffer[ offset ++ ] = source[ i ];
- }
- },
- function getValue_arrayElement( buffer, offset ) {
- buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
- },
- function getValue_toArray( buffer, offset ) {
- this.resolvedProperty.toArray( buffer, offset );
- }
- ],
- SetterByBindingTypeAndVersioning: [
- [
- // Direct
- function setValue_direct( buffer, offset ) {
- this.node[ this.propertyName ] = buffer[ offset ];
- },
- function setValue_direct_setNeedsUpdate( buffer, offset ) {
- this.node[ this.propertyName ] = buffer[ offset ];
- this.targetObject.needsUpdate = true;
- },
- function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
- this.node[ this.propertyName ] = buffer[ offset ];
- this.targetObject.matrixWorldNeedsUpdate = true;
- }
- ], [
- // EntireArray
- function setValue_array( buffer, offset ) {
- var dest = this.resolvedProperty;
- for ( var i = 0, n = dest.length; i !== n; ++ i ) {
- dest[ i ] = buffer[ offset ++ ];
- }
- },
- function setValue_array_setNeedsUpdate( buffer, offset ) {
- var dest = this.resolvedProperty;
- for ( var i = 0, n = dest.length; i !== n; ++ i ) {
- dest[ i ] = buffer[ offset ++ ];
- }
- this.targetObject.needsUpdate = true;
- },
- function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {
- var dest = this.resolvedProperty;
- for ( var i = 0, n = dest.length; i !== n; ++ i ) {
- dest[ i ] = buffer[ offset ++ ];
- }
- this.targetObject.matrixWorldNeedsUpdate = true;
- }
- ], [
- // ArrayElement
- function setValue_arrayElement( buffer, offset ) {
- this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
- },
- function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
- this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
- this.targetObject.needsUpdate = true;
- },
- function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
- this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
- this.targetObject.matrixWorldNeedsUpdate = true;
- }
- ], [
- // HasToFromArray
- function setValue_fromArray( buffer, offset ) {
- this.resolvedProperty.fromArray( buffer, offset );
- },
- function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
- this.resolvedProperty.fromArray( buffer, offset );
- this.targetObject.needsUpdate = true;
- },
- function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
- this.resolvedProperty.fromArray( buffer, offset );
- this.targetObject.matrixWorldNeedsUpdate = true;
- }
- ]
- ]
- } );
- THREE.PropertyBinding.Composite =
- function( targetGroup, path, optionalParsedPath ) {
- var parsedPath = optionalParsedPath ||
- THREE.PropertyBinding.parseTrackName( path );
- this._targetGroup = targetGroup;
- this._bindings = targetGroup.subscribe_( path, parsedPath );
- };
- THREE.PropertyBinding.Composite.prototype = {
- constructor: THREE.PropertyBinding.Composite,
- getValue: function( array, offset ) {
- this.bind(); // bind all binding
- var firstValidIndex = this._targetGroup.nCachedObjects_,
- binding = this._bindings[ firstValidIndex ];
- // and only call .getValue on the first
- if ( binding !== undefined ) binding.getValue( array, offset );
- },
- setValue: function( array, offset ) {
- var bindings = this._bindings;
- for ( var i = this._targetGroup.nCachedObjects_,
- n = bindings.length; i !== n; ++ i ) {
- bindings[ i ].setValue( array, offset );
- }
- },
- bind: function() {
- var bindings = this._bindings;
- for ( var i = this._targetGroup.nCachedObjects_,
- n = bindings.length; i !== n; ++ i ) {
- bindings[ i ].bind();
- }
- },
- unbind: function() {
- var bindings = this._bindings;
- for ( var i = this._targetGroup.nCachedObjects_,
- n = bindings.length; i !== n; ++ i ) {
- bindings[ i ].unbind();
- }
- }
- };
- THREE.PropertyBinding.create = function( root, path, parsedPath ) {
- if ( ! ( root instanceof THREE.AnimationObjectGroup ) ) {
- return new THREE.PropertyBinding( root, path, parsedPath );
- } else {
- return new THREE.PropertyBinding.Composite( root, path, parsedPath );
- }
- };
- THREE.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
- // created and tested via https://regex101.com/#javascript
- var re = /^(([\w]+\/)*)([\w-\d]+)?(\.([\w]+)(\[([\w\d\[\]\_. ]+)\])?)?(\.([\w.]+)(\[([\w\d\[\]\_. ]+)\])?)$/;
- var matches = re.exec(trackName);
- if( ! matches ) {
- throw new Error( "cannot parse trackName at all: " + trackName );
- }
- if (matches.index === re.lastIndex) {
- re.lastIndex++;
- }
- var results = {
- // directoryName: matches[1], // (tschw) currently unused
- nodeName: matches[3], // allowed to be null, specified root node.
- objectName: matches[5],
- objectIndex: matches[7],
- propertyName: matches[9],
- propertyIndex: matches[11] // 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 );
- }
- return results;
- };
- THREE.PropertyBinding.findNode = function( root, nodeName ) {
- if( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) {
- return root;
- }
- // search into skeleton bones.
- if( root.skeleton ) {
- var searchSkeleton = function( skeleton ) {
- for( var i = 0; i < skeleton.bones.length; i ++ ) {
- var bone = skeleton.bones[i];
- if( bone.name === nodeName ) {
- return bone;
- }
- }
- return null;
- };
- var bone = searchSkeleton( root.skeleton );
- if( bone ) {
- return bone;
- }
- }
- // search into node subtree.
- if( root.children ) {
- var searchNodeSubtree = function( children ) {
- for( var i = 0; i < children.length; i ++ ) {
- var childNode = children[i];
- if( childNode.name === nodeName || childNode.uuid === nodeName ) {
- return childNode;
- }
- var result = searchNodeSubtree( childNode.children );
- if( result ) return result;
- }
- return null;
- };
- var subTreeNode = searchNodeSubtree( root.children );
- if( subTreeNode ) {
- return subTreeNode;
- }
- }
- return null;
- }
|