PropertyBinding.js 10.0 KB
Newer Older
1 2 3 4 5 6 7 8
/**
 *
 * A track bound to a real value in the scene graph.
 * 
 * @author Ben Houston / http://clara.io/
 * @author David Sarno / http://lighthaus.us/
 */

9
THREE.PropertyBinding = function ( rootNode, trackName ) {
10 11 12 13

	this.rootNode = rootNode;
	this.trackName = trackName;

14
	var parseResults = THREE.PropertyBinding.parseTrackName( trackName );
15

B
Ben Houston 已提交
16
	//console.log( parseResults );
17
	this.directoryName = parseResults.directoryName;
18
	this.nodeName = parseResults.nodeName;
B
Ben Houston 已提交
19 20
	this.objectName = parseResults.objectName;
	this.objectIndex = parseResults.objectIndex;
21 22
	this.propertyName = parseResults.propertyName;
	this.propertyIndex = parseResults.propertyIndex;
23

24
	this.node = THREE.PropertyBinding.findNode( rootNode, this.nodeName );
25

26 27
	this.cumulativeValue = null;
	this.cumulativeWeight = 0;
28 29
};

30
THREE.PropertyBinding.prototype = {
31

32
	constructor: THREE.PropertyBinding,
33

34
	reset: function() {
35

36 37
		this.cumulativeValue = null;
		this.cumulativeWeight = 0;
38

39 40 41 42
	},

	accumulate: function( value, weight ) {
		
43
		var lerp = THREE.AnimationUtils.getLerpFunc( value, true );
44

45
		this.accumulate = function( value, weight ) {
46

47 48
			if( this.cumulativeWeight === 0 ) {

49 50 51
				if( this.cumulativeValue === null ) {
					this.cumulativeValue = THREE.AnimationUtils.clone( value );
				}
52
				this.cumulativeWeight = weight;
53 54
				//console.log( this );
	
55 56
			}
			else {
57

58 59 60
				var lerpAlpha = weight / ( this.cumulativeWeight + weight );
				this.cumulativeValue = lerp( this.cumulativeValue, value, lerpAlpha );
				this.cumulativeWeight += weight;
61
				//console.log( this );
62

63
			}
64 65
		}

66
		this.accumulate( value, weight );
67

68
	},
69 70 71

	apply: function() {

B
Ben Houston 已提交
72
		// for speed capture the setter pattern as a closure (sort of a memoization pattern: https://en.wikipedia.org/wiki/Memoization)
73 74
		if( ! this.internalApply ) {

B
Ben Houston 已提交
75
			 //console.log( "PropertyBinding", this );
76

77 78
			 var equalsFunc = THREE.AnimationUtils.getEqualsFunc( this.cumulativeValue );

79 80 81 82 83 84 85 86
			 var targetObject = this.node;

	 		// ensure there is a value node
			if( ! targetObject ) {
				console.error( "  trying to update node for track: " + this.trackName + " but it wasn't found." );
				return;
			}

87 88 89 90 91 92
			if( this.objectName ) {
				// special case were we need to reach deeper into the hierarchy to get the face materials....
				if( this.objectName === "materials" ) {
					if( ! targetObject.material ) {
						console.error( '  can not bind to material as node does not have a material', this );
						return;				
93
					}
94 95
					if( ! targetObject.material.materials ) {
						console.error( '  can not bind to material.materials as node.material does not have a materials array', this );
96
						return;				
97
					}
98
					targetObject = targetObject.material.materials;
99
				}
100 101 102 103 104
				else if( this.objectName === "bones" ) {
					if( ! targetObject.skeleton ) {
						console.error( '  can not bind to bones as node does not have a skeleton', this );
					}
					targetObject = targetObject.skeleton.bones;
105

106 107 108
					// TODO/OPTIMIZE, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
					
					// support resolving morphTarget names into indices.
B
Ben Houston 已提交
109
					//console.log( "  resolving bone name: ", this.objectIndex );
110 111
					for( var i = 0; i < this.node.skeleton.bones.length; i ++ ) {
						if( this.node.skeleton.bones[i].name === this.objectIndex ) {
B
Ben Houston 已提交
112
							//console.log( "  resolved to index: ", i );
113 114 115 116
							this.objectIndex = i;
							break;
						}
					}
117
				}
118 119 120 121 122 123 124
				else {

					if( targetObject[ this.objectName ] === undefined ) {
						console.error( '  can not bind to objectName of node, undefined', this );			
						return;
					}
					targetObject = targetObject[ this.objectName ];
125
				}
126 127 128 129 130 131 132 133
				
				if( this.objectIndex !== undefined ) {
					if( targetObject[ this.objectIndex ] === undefined ) {
						console.error( "  trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
						return;				
					}

					targetObject = targetObject[ this.objectIndex ];
134
				}
135

136
			}
137 138 139 140 141 142

	 		// special case mappings
	 		var nodeProperty = targetObject[ this.propertyName ];
			if( ! nodeProperty ) {
				console.error( "  trying to update property for track: " + this.nodeName + '.' + this.propertyName + " but it wasn't found.", targetObject );				
				return;
143 144 145 146
			}

			// access a sub element of the property array (only primitives are supported right now)
			if( this.propertyIndex !== undefined ) {
147 148

				if( this.propertyName === "morphTargetInfluences" ) {
149 150
					// TODO/OPTIMIZE, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
					
151
					// support resolving morphTarget names into indices.
152
					//console.log( "  resolving morphTargetInfluence name: ", this.propertyIndex );
B
Ben Houston 已提交
153
					if( ! this.node.geometry ) {
154 155
						console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry', this );				
					}
B
Ben Houston 已提交
156
					if( ! this.node.geometry.morphTargets ) {
157 158 159 160 161 162
						console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );				
					}
					
					for( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
						if( this.node.geometry.morphTargets[i].name === this.propertyIndex ) {
							//console.log( "  resolved to index: ", i );
163 164 165 166 167
							this.propertyIndex = i;
							break;
						}
					}
				}
168

169 170
				//console.log( '  update property array ' + this.propertyName + '[' + this.propertyIndex + '] via assignment.' );				
				this.internalApply = function() {
171 172 173 174 175
					if( ! equalsFunc( nodeProperty[ this.propertyIndex ], this.cumulativeValue ) ) {
						nodeProperty[ this.propertyIndex ] = this.cumulativeValue;
						return true;
					}
					return false;
176 177 178 179 180 181
				};
			}
			// must use copy for Object3D.Euler/Quaternion		
			else if( nodeProperty.copy ) {
				//console.log( '  update property ' + this.name + '.' + this.propertyName + ' via a set() function.' );				
				this.internalApply = function() {
182 183 184 185 186
					if( ! equalsFunc( nodeProperty, this.cumulativeValue ) ) {
						nodeProperty.copy( this.cumulativeValue );
						return true;
					}
					return false;
187 188 189 190
				}
			}
			// otherwise just set the property directly on the node (do not use nodeProperty as it may not be a reference object)
			else {
191

192 193
				//console.log( '  update property ' + this.name + '.' + this.propertyName + ' via assignment.' );				
				this.internalApply = function() {
194 195 196 197 198
					if( ! equalsFunc( targetObject[ this.propertyName ], this.cumulativeValue ) ) {
						targetObject[ this.propertyName ] = this.cumulativeValue;	
						return true;
					}
					return false;
199 200
				}
			}
201

202 203 204 205 206 207 208 209 210 211 212 213 214
			// trigger node dirty			
			if( targetObject.needsUpdate !== undefined ) { // material
				//console.log( '  triggering material as dirty' );
				this.triggerDirty = function() {
					this.node.needsUpdate = true;
				}
			}			
			else if( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
				//console.log( '  triggering node as dirty' );
				this.triggerDirty = function() {
					targetObject.matrixWorldNeedsUpdate = true;
				}
			}
215 216 217

		}

218
		// early exit if there is nothing to apply.
B
Ben Houston 已提交
219 220 221
		if( this.cumulativeWeight > 0 ) {
		
			var valueChanged = this.internalApply();
222

B
Ben Houston 已提交
223 224 225
			if( valueChanged && this.triggerDirty ) {
				this.triggerDirty();
			}
226

227 228 229
			// reset accumulator
			this.cumulativeValue = null;
			this.cumulativeWeight = 0;
B
Ben Houston 已提交
230

231
		}
232 233 234 235 236 237 238 239 240 241 242
	},

	get: function() {

		throw new Error( "TODO" );

	}

};


243
THREE.PropertyBinding.parseTrackName = function( trackName ) {
244 245

	// matches strings in the form of:
246 247
	//    nodeName.property
	//    nodeName.property[accessor]
248
	//    nodeName.material.property[accessor]
249
	//    uuid.property[accessor]
250
	//    uuid.objectName[objectIndex].propertyName[propertyIndex]
251 252
	//    parentName/nodeName.property
	//    parentName/parentName/nodeName.property[index]
253
	//	  .bone[Armature.DEF_cog].position
254 255
	// created and tested via https://regex101.com/#javascript

256
	var re = /^(([\w]+\/)*)([\w-\d]+)?(\.([\w]+)(\[([\w\d\[\]\_. ]+)\])?)?(\.([\w.]+)(\[([\w\d\[\]\_. ]+)\])?)$/; 
257 258 259 260 261 262 263 264 265 266
	var matches = re.exec(trackName);

	if( ! matches ) {
		throw new Error( "cannot parse trackName at all: " + trackName );
	}

    if (matches.index === re.lastIndex) {
        re.lastIndex++;
    }

267
	var results = {
268
		directoryName: matches[1],
B
Ben Houston 已提交
269
		nodeName: matches[3], 	// allowed to be null, specified root node.
270 271 272 273
		objectName: matches[5],
		objectIndex: matches[7],
		propertyName: matches[9],
		propertyIndex: matches[11]	// allowed to be null, specifies that the whole property is set.
274 275
	};

276
	//console.log( "PropertyBinding.parseTrackName", trackName, results, matches );
277

278 279 280 281 282
	if( results.propertyName === null || results.propertyName.length === 0 ) {
		throw new Error( "can not parse propertyName from trackName: " + trackName );
	}

	return results;
283 284 285 286

};

// TODO: Cache this at some point
287
THREE.PropertyBinding.findNode = function( root, nodeName ) {
288

289
	//console.log( 'AnimationUtils.findNode( ' + root.name + ', nodeName: ' + nodeName + ')');
290
	
291
	if( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) {
292

293
		//console.log( '  root.' );
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
		return root;

	}

	// (2) 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 ) {

B
Ben Houston 已提交
309
					return bone;
310 311 312 313 314 315 316 317

				}
			}

			return null;

		};

B
Ben Houston 已提交
318
		var bone = searchSkeleton( root.skeleton );
319

B
Ben Houston 已提交
320
		if( bone ) {
321

322
			//console.log( '  bone: ' + bone.name + '.' );
B
Ben Houston 已提交
323
			return bone;
324 325 326 327 328 329 330 331 332 333 334 335 336

		}
	}

	// (3) search into node subtree.
	if( root.children ) {

		var searchNodeSubtree = function( children ) {

			for( var i = 0; i < children.length; i ++ ) {

				var childNode = children[i];

337
				if( childNode.name === nodeName || childNode.uuid === nodeName ) {
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356

					return childNode;

				}

				var result = searchNodeSubtree( childNode.children );

				if( result ) return result;

			}

			return null;	

		};

		var subTreeNode = searchNodeSubtree( root.children );

		if( subTreeNode ) {

357
			//console.log( '  node: ' + subTreeNode.name + '.' );
358 359 360 361 362 363
			return subTreeNode;

		}

	}

364
	//console.log( "   <null>.  No node found for name: " + nodeName );
365 366 367

	return null;
}