PropertyBinding.js 12.4 KB
Newer Older
1 2
/**
 *
3 4 5
 * A reference to a real property in the scene graph.
 *
 *
6 7
 * @author Ben Houston / http://clara.io/
 * @author David Sarno / http://lighthaus.us/
8
 * @author tschw
9 10
 */

T
tschw 已提交
11
THREE.PropertyBinding = function ( rootNode, path, parsedPath ) {
12

13
	this.path = path;
T
tschw 已提交
14 15
	this.parsedPath = parsedPath ||
			THREE.PropertyBinding.parseTrackName( path );
16

T
tschw 已提交
17 18
	this.node = THREE.PropertyBinding.findNode(
			rootNode, this.parsedPath.nodeName ) || rootNode;
19

T
tschw 已提交
20
	this.rootNode = rootNode;
21

22 23
};

24
THREE.PropertyBinding.prototype = {
25

26
	constructor: THREE.PropertyBinding,
27

28 29 30 31
	getValue: function getValue_unbound( targetArray, offset ) {

		this.bind();
		this.getValue( targetArray, offset );
32

33 34 35 36 37
		// 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.
38

39 40
	},

41
	setValue: function getValue_unbound( sourceArray, offset ) {
B
Ben Houston 已提交
42

43 44
		this.bind();
		this.setValue( sourceArray, offset );
45

46
	},
B
Ben Houston 已提交
47

48 49
	// create getter / setter pair for a property in the scene graph
	bind: function() {
50

T
tschw 已提交
51 52 53 54 55 56
		var targetObject = this.node,
			parsedPath = this.parsedPath,

			objectName = parsedPath.objectName,
			propertyName = parsedPath.propertyName,
			propertyIndex = parsedPath.propertyIndex;
B
Ben Houston 已提交
57

58
		if ( ! targetObject ) {
59

T
tschw 已提交
60 61
			targetObject = THREE.PropertyBinding.findNode(
					this.rootNode, parsedPath.nodeName ) || this.rootNode;
62

63
			this.node = targetObject;
64

65
		}
66

67 68 69
		// set fail state so we can just 'return' on error
		this.getValue = this._getValue_unavailable;
		this.setValue = this._setValue_unavailable;
70

71
 		// ensure there is a value node
T
tschw 已提交
72 73
		if ( ! targetObject ) {

74
			console.error( "  trying to update node for track: " + this.path + " but it wasn't found." );
75
			return;
T
tschw 已提交
76

77
		}
78

T
tschw 已提交
79
		if( objectName ) {
80

T
tschw 已提交
81 82 83 84 85 86 87 88 89 90 91
			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;
92

93 94
					}

T
tschw 已提交
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
					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 ];

145
			}
146

T
tschw 已提交
147 148 149 150 151

			if ( objectIndex !== undefined ) {

				if( targetObject[ objectIndex ] === undefined ) {

152
					console.error( "  trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
153
					return;
T
tschw 已提交
154

155
				}
156

T
tschw 已提交
157 158
				targetObject = targetObject[ objectIndex ];

159 160
			}

161
		}
162

T
tschw 已提交
163 164 165 166 167 168 169 170 171
		// 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 );
172
			return;
T
tschw 已提交
173

174
		}
175

176
		// determine versioning scheme
T
tschw 已提交
177
		var versioning = this.Versioning.None;
178

T
tschw 已提交
179
		if ( targetObject.needsUpdate !== undefined ) { // material
180

T
tschw 已提交
181 182
			versioning = this.Versioning.NeedsUpdate;
			this.targetObject = targetObject;
183

T
tschw 已提交
184
		} else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
185

T
tschw 已提交
186 187
			versioning = this.Versioning.MatrixWorldNeedsUpdate;
			this.targetObject = targetObject;
188 189 190

		}

T
tschw 已提交
191 192 193 194 195
		// 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)
196

T
tschw 已提交
197
			if ( propertyName === "morphTargetInfluences" ) {
B
Ben Houston 已提交
198
				// potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
199

200
				// support resolving morphTarget names into indices.
T
tschw 已提交
201 202
				if ( ! targetObject.geometry ) {

203
					console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
T
tschw 已提交
204 205
					return;

206
				}
T
tschw 已提交
207 208 209

				if ( ! targetObject.geometry.morphTargets ) {

210
					console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );
T
tschw 已提交
211 212
					return;

213
				}
214

T
tschw 已提交
215 216 217 218 219
				for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {

					if ( targetObject.geometry.morphTargets[i].name === propertyIndex ) {

						propertyIndex = i;
220
						break;
T
tschw 已提交
221

222
					}
T
tschw 已提交
223

224
				}
T
tschw 已提交
225

226
			}
227

T
tschw 已提交
228
			bindingType = this.BindingType.ArrayElement;
229

T
tschw 已提交
230 231
			this.resolvedProperty = nodeProperty;
			this.propertyIndex = propertyIndex;
232

T
tschw 已提交
233 234
		} else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
			// must use copy for Object3D.Euler/Quaternion
235

T
tschw 已提交
236
			bindingType = this.BindingType.HasFromToArray;
237

T
tschw 已提交
238
			this.resolvedProperty = nodeProperty;
239

T
tschw 已提交
240
		} else {
241

T
tschw 已提交
242
			this.propertyName = propertyName;
243

T
tschw 已提交
244
		}
245

T
tschw 已提交
246 247 248
		// select getter / setter
		this.getValue = this.GetterByBindingType[ bindingType ];
		this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
249

T
tschw 已提交
250
	},
251

T
tschw 已提交
252
	unbind: function() {
253

T
tschw 已提交
254
		this.node = null;
255

T
tschw 已提交
256 257 258 259
		// 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;
260

T
tschw 已提交
261
	}
262

T
tschw 已提交
263
};
264

T
tschw 已提交
265
Object.assign( THREE.PropertyBinding.prototype, { // prototype, continued
266

T
tschw 已提交
267 268 269
	// these are used to "bind" a nonexistent property
	_getValue_unavailable: function() {},
	_setValue_unavailable: function() {},
270

T
tschw 已提交
271 272 273
	// initial state of these methods that calls 'bind'
	_getValue_unbound: THREE.PropertyBinding.prototype.getValue,
	_setValue_unbound: THREE.PropertyBinding.prototype.setValue,
274

T
tschw 已提交
275 276 277 278 279
	BindingType: {
		Direct: 0,
		ArrayElement: 1,
		HasFromToArray: 2
	},
280

T
tschw 已提交
281 282 283 284 285
	Versioning: {
		None: 0,
		NeedsUpdate: 1,
		MatrixWorldNeedsUpdate: 2
	},
286

T
tschw 已提交
287
	GetterByBindingType: [
288

T
tschw 已提交
289
		function getValue_direct( buffer, offset ) {
290

T
tschw 已提交
291
			buffer[ offset ] = this.node[ this.propertyName ];
292

T
tschw 已提交
293
		},
294

T
tschw 已提交
295
		function getValue_arrayElement( buffer, offset ) {
296

T
tschw 已提交
297
			buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
298

T
tschw 已提交
299
		},
300

T
tschw 已提交
301
		function getValue_toArray( buffer, offset ) {
302

T
tschw 已提交
303
			this.resolvedProperty.toArray( buffer, offset );
304

T
tschw 已提交
305
		}
306

T
tschw 已提交
307
	],
308

T
tschw 已提交
309
	SetterByBindingTypeAndVersioning: [
310

T
tschw 已提交
311 312
		[
			// Direct
313

T
tschw 已提交
314
			function setValue_direct( buffer, offset ) {
315

T
tschw 已提交
316
				this.node[ this.propertyName ] = buffer[ offset ];
317

T
tschw 已提交
318
			},
319

T
tschw 已提交
320
			function setValue_direct_setNeedsUpdate( buffer, offset ) {
321

T
tschw 已提交
322 323 324 325 326 327 328 329 330
				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;
331 332 333

			}

T
tschw 已提交
334
		], [
335

T
tschw 已提交
336
			// ArrayElement
337

T
tschw 已提交
338
			function setValue_arrayElement( buffer, offset ) {
339

T
tschw 已提交
340
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
341

T
tschw 已提交
342
			},
343

T
tschw 已提交
344
			function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
345

T
tschw 已提交
346 347
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
				this.targetObject.needsUpdate = true;
348

T
tschw 已提交
349
			},
350

T
tschw 已提交
351
			function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
352

T
tschw 已提交
353 354
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
				this.targetObject.matrixWorldNeedsUpdate = true;
355

T
tschw 已提交
356
			}
B
Ben Houston 已提交
357

T
tschw 已提交
358
		], [
359

T
tschw 已提交
360
			// HasToFromArray
361

T
tschw 已提交
362
			function setValue_fromArray( buffer, offset ) {
363

T
tschw 已提交
364
				this.resolvedProperty.fromArray( buffer, offset );
365

T
tschw 已提交
366
			},
367

T
tschw 已提交
368
			function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
369

T
tschw 已提交
370 371
				this.resolvedProperty.fromArray( buffer, offset );
				this.targetObject.needsUpdate = true;
372

T
tschw 已提交
373
			},
374

T
tschw 已提交
375 376 377 378
			function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {

				this.resolvedProperty.fromArray( buffer, offset );
				this.targetObject.matrixWorldNeedsUpdate = true;
379

380
			}
B
Ben Houston 已提交
381

T
tschw 已提交
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
		]

	]

} );

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();

437
		}
438 439 440 441 442

	},

	unbind: function() {

T
tschw 已提交
443
		var bindings = this._bindings;
444

T
tschw 已提交
445 446 447 448 449 450
		for ( var i = this._targetGroup.nCachedObjects_,
				n = bindings.length; i !== n; ++ i ) {

			bindings[ i ].unbind();

		}
451

452 453 454 455
	}

};

T
tschw 已提交
456
THREE.PropertyBinding.create = function( root, path, parsedPath ) {
457

T
tschw 已提交
458
	if ( ! ( root instanceof THREE.AnimationObjectGroup ) ) {
459

T
tschw 已提交
460
		return new THREE.PropertyBinding( root, path, parsedPath );
461

T
tschw 已提交
462 463 464 465 466 467 468
	} else {

		return new THREE.PropertyBinding.Composite( root, path, parsedPath );

	}

};
469

470
THREE.PropertyBinding.parseTrackName = function( trackName ) {
471 472

	// matches strings in the form of:
473 474
	//    nodeName.property
	//    nodeName.property[accessor]
475
	//    nodeName.material.property[accessor]
476
	//    uuid.property[accessor]
477
	//    uuid.objectName[objectIndex].propertyName[propertyIndex]
478 479
	//    parentName/nodeName.property
	//    parentName/parentName/nodeName.property[index]
480
	//	  .bone[Armature.DEF_cog].position
481 482
	// created and tested via https://regex101.com/#javascript

483
	var re = /^(([\w]+\/)*)([\w-\d]+)?(\.([\w]+)(\[([\w\d\[\]\_. ]+)\])?)?(\.([\w.]+)(\[([\w\d\[\]\_. ]+)\])?)$/;
484 485
	var matches = re.exec(trackName);

T
tschw 已提交
486
	if( ! matches ) {
487 488 489 490 491 492 493
		throw new Error( "cannot parse trackName at all: " + trackName );
	}

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

494
	var results = {
T
tschw 已提交
495
		// directoryName: matches[1], // (tschw) currently unused
B
Ben Houston 已提交
496
		nodeName: matches[3], 	// allowed to be null, specified root node.
497 498 499 500
		objectName: matches[5],
		objectIndex: matches[7],
		propertyName: matches[9],
		propertyIndex: matches[11]	// allowed to be null, specifies that the whole property is set.
501 502
	};

T
tschw 已提交
503
	if( results.propertyName === null || results.propertyName.length === 0 ) {
504 505 506 507
		throw new Error( "can not parse propertyName from trackName: " + trackName );
	}

	return results;
508 509 510

};

511
THREE.PropertyBinding.findNode = function( root, nodeName ) {
512

T
tschw 已提交
513
	if( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) {
514

T
tschw 已提交
515
		return root;
516

T
tschw 已提交
517
	}
518

T
tschw 已提交
519 520
	// search into skeleton bones.
	if( root.skeleton ) {
521

T
tschw 已提交
522
		var searchSkeleton = function( skeleton ) {
523

T
tschw 已提交
524
			for( var i = 0; i < skeleton.bones.length; i ++ ) {
525

T
tschw 已提交
526
				var bone = skeleton.bones[i];
527

T
tschw 已提交
528
				if( bone.name === nodeName ) {
529

T
tschw 已提交
530
					return bone;
531

T
tschw 已提交
532 533
				}
			}
534

T
tschw 已提交
535
			return null;
536

T
tschw 已提交
537
		};
538

T
tschw 已提交
539
		var bone = searchSkeleton( root.skeleton );
540

T
tschw 已提交
541
		if( bone ) {
M
Mr.doob 已提交
542

T
tschw 已提交
543
			return bone;
544 545

		}
T
tschw 已提交
546
	}
547

T
tschw 已提交
548 549
	// search into node subtree.
	if( root.children ) {
550

T
tschw 已提交
551
		var searchNodeSubtree = function( children ) {
552

T
tschw 已提交
553
			for( var i = 0; i < children.length; i ++ ) {
554

T
tschw 已提交
555
				var childNode = children[i];
556

T
tschw 已提交
557
				if( childNode.name === nodeName || childNode.uuid === nodeName ) {
558

T
tschw 已提交
559
					return childNode;
560

T
tschw 已提交
561
				}
562

T
tschw 已提交
563
				var result = searchNodeSubtree( childNode.children );
564

T
tschw 已提交
565
				if( result ) return result;
566

T
tschw 已提交
567
			}
568

569
			return null;
570

T
tschw 已提交
571
		};
572 573 574

		var subTreeNode = searchNodeSubtree( root.children );

T
tschw 已提交
575
		if( subTreeNode ) {
576 577 578 579 580 581 582 583

			return subTreeNode;

		}

	}

	return null;
T
tschw 已提交
584

585
}