PropertyBinding.js 13.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
 */

M
Mr.doob 已提交
11
function PropertyBinding( rootNode, path, parsedPath ) {
12

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

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

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

M
Mr.doob 已提交
22
}
23

R
Rich Harris 已提交
24
PropertyBinding.prototype = {
25

R
Rich Harris 已提交
26
	constructor: 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

R
Rich Harris 已提交
60
			targetObject = PropertyBinding.findNode(
T
tschw 已提交
61
					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

79
		if ( objectName ) {
80

T
tschw 已提交
81 82 83 84 85 86 87
			var objectIndex = parsedPath.objectIndex;

			// special cases were we need to reach deeper into the hierarchy to get the face materials....
			switch ( objectName ) {

				case 'materials':

88
					if ( ! targetObject.material ) {
T
tschw 已提交
89 90 91

						console.error( '  can not bind to material as node does not have a material', this );
						return;
92

93 94
					}

95
					if ( ! targetObject.material.materials ) {
T
tschw 已提交
96 97 98 99 100 101 102 103 104 105 106 107

						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':

108
					if ( ! targetObject.skeleton ) {
T
tschw 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122

						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 ++ ) {

123
						if ( targetObject[ i ].name === objectIndex ) {
T
tschw 已提交
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

							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

			if ( objectIndex !== undefined ) {

150
				if ( targetObject[ objectIndex ] === undefined ) {
T
tschw 已提交
151

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
		// resolve property
		var nodeProperty = targetObject[ propertyName ];

166
		if ( nodeProperty === undefined ) {
T
tschw 已提交
167 168 169 170 171

			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
				for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {

217
					if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) {
T
tschw 已提交
218 219

						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

240
		} else if ( nodeProperty.length !== undefined ) {
T
tschw 已提交
241 242 243 244 245

			bindingType = this.BindingType.EntireArray;

			this.resolvedProperty = nodeProperty;

T
tschw 已提交
246
		} else {
247

T
tschw 已提交
248
			this.propertyName = propertyName;
249

T
tschw 已提交
250
		}
251

T
tschw 已提交
252 253 254
		// select getter / setter
		this.getValue = this.GetterByBindingType[ bindingType ];
		this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
255

T
tschw 已提交
256
	},
257

T
tschw 已提交
258
	unbind: function() {
259

T
tschw 已提交
260
		this.node = null;
261

T
tschw 已提交
262 263 264 265
		// 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;
266

T
tschw 已提交
267
	}
268

T
tschw 已提交
269
};
270

R
Rich Harris 已提交
271
Object.assign( PropertyBinding.prototype, { // prototype, continued
272

T
tschw 已提交
273 274 275
	// these are used to "bind" a nonexistent property
	_getValue_unavailable: function() {},
	_setValue_unavailable: function() {},
276

T
tschw 已提交
277
	// initial state of these methods that calls 'bind'
R
Rich Harris 已提交
278 279
	_getValue_unbound: PropertyBinding.prototype.getValue,
	_setValue_unbound: PropertyBinding.prototype.setValue,
280

T
tschw 已提交
281 282
	BindingType: {
		Direct: 0,
T
tschw 已提交
283 284 285
		EntireArray: 1,
		ArrayElement: 2,
		HasFromToArray: 3
T
tschw 已提交
286
	},
287

T
tschw 已提交
288 289 290 291 292
	Versioning: {
		None: 0,
		NeedsUpdate: 1,
		MatrixWorldNeedsUpdate: 2
	},
293

T
tschw 已提交
294
	GetterByBindingType: [
295

T
tschw 已提交
296
		function getValue_direct( buffer, offset ) {
297

T
tschw 已提交
298
			buffer[ offset ] = this.node[ this.propertyName ];
299

T
tschw 已提交
300
		},
301

T
tschw 已提交
302 303
		function getValue_array( buffer, offset ) {

304
			var source = this.resolvedProperty;
T
tschw 已提交
305 306 307 308 309 310 311 312 313

			for ( var i = 0, n = source.length; i !== n; ++ i ) {

				buffer[ offset ++ ] = source[ i ];

			}

		},

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

T
tschw 已提交
316
			buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
317

T
tschw 已提交
318
		},
319

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

T
tschw 已提交
322
			this.resolvedProperty.toArray( buffer, offset );
323

T
tschw 已提交
324
		}
325

T
tschw 已提交
326
	],
327

T
tschw 已提交
328
	SetterByBindingTypeAndVersioning: [
329

T
tschw 已提交
330 331
		[
			// Direct
332

T
tschw 已提交
333
			function setValue_direct( buffer, offset ) {
334

T
tschw 已提交
335
				this.node[ this.propertyName ] = buffer[ offset ];
336

T
tschw 已提交
337
			},
338

T
tschw 已提交
339
			function setValue_direct_setNeedsUpdate( buffer, offset ) {
340

T
tschw 已提交
341 342 343 344 345 346 347 348 349
				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;
T
tschw 已提交
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393

			}

		], [

			// 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;
394 395 396

			}

T
tschw 已提交
397
		], [
398

T
tschw 已提交
399
			// ArrayElement
400

T
tschw 已提交
401
			function setValue_arrayElement( buffer, offset ) {
402

T
tschw 已提交
403
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
404

T
tschw 已提交
405
			},
406

T
tschw 已提交
407
			function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
408

T
tschw 已提交
409 410
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
				this.targetObject.needsUpdate = true;
411

T
tschw 已提交
412
			},
413

T
tschw 已提交
414
			function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
415

T
tschw 已提交
416 417
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
				this.targetObject.matrixWorldNeedsUpdate = true;
418

T
tschw 已提交
419
			}
B
Ben Houston 已提交
420

T
tschw 已提交
421
		], [
422

T
tschw 已提交
423
			// HasToFromArray
424

T
tschw 已提交
425
			function setValue_fromArray( buffer, offset ) {
426

T
tschw 已提交
427
				this.resolvedProperty.fromArray( buffer, offset );
428

T
tschw 已提交
429
			},
430

T
tschw 已提交
431
			function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
432

T
tschw 已提交
433 434
				this.resolvedProperty.fromArray( buffer, offset );
				this.targetObject.needsUpdate = true;
435

T
tschw 已提交
436
			},
437

T
tschw 已提交
438 439 440 441
			function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {

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

443
			}
B
Ben Houston 已提交
444

T
tschw 已提交
445 446 447 448 449 450
		]

	]

} );

R
Rich Harris 已提交
451
PropertyBinding.Composite =
T
tschw 已提交
452 453 454
		function( targetGroup, path, optionalParsedPath ) {

	var parsedPath = optionalParsedPath ||
R
Rich Harris 已提交
455
			PropertyBinding.parseTrackName( path );
T
tschw 已提交
456 457 458 459 460 461

	this._targetGroup = targetGroup;
	this._bindings = targetGroup.subscribe_( path, parsedPath );

};

R
Rich Harris 已提交
462
PropertyBinding.Composite.prototype = {
T
tschw 已提交
463

R
Rich Harris 已提交
464
	constructor: PropertyBinding.Composite,
T
tschw 已提交
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499

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

500
		}
501 502 503 504 505

	},

	unbind: function() {

T
tschw 已提交
506
		var bindings = this._bindings;
507

T
tschw 已提交
508 509 510 511 512 513
		for ( var i = this._targetGroup.nCachedObjects_,
				n = bindings.length; i !== n; ++ i ) {

			bindings[ i ].unbind();

		}
514

515 516 517 518
	}

};

R
Rich Harris 已提交
519
PropertyBinding.create = function( root, path, parsedPath ) {
520

R
Rich Harris 已提交
521
	if ( ! ( (root && root.isAnimationObjectGroup) ) ) {
522

R
Rich Harris 已提交
523
		return new PropertyBinding( root, path, parsedPath );
524

T
tschw 已提交
525 526
	} else {

R
Rich Harris 已提交
527
		return new PropertyBinding.Composite( root, path, parsedPath );
T
tschw 已提交
528 529 530 531

	}

};
532

R
Rich Harris 已提交
533
PropertyBinding.parseTrackName = function( trackName ) {
534 535

	// matches strings in the form of:
536 537
	//    nodeName.property
	//    nodeName.property[accessor]
538
	//    nodeName.material.property[accessor]
539
	//    uuid.property[accessor]
540
	//    uuid.objectName[objectIndex].propertyName[propertyIndex]
541 542
	//    parentName/nodeName.property
	//    parentName/parentName/nodeName.property[index]
543
	//	  .bone[Armature.DEF_cog].position
544 545
	// created and tested via https://regex101.com/#javascript

546
	var re = /^((?:\w+\/)*)(\w+)?(?:\.(\w+)(?:\[(.+)\])?)?\.(\w+)(?:\[(.+)\])?$/;
547 548 549
	var matches = re.exec( trackName );

	if ( ! matches ) {
550 551

		throw new Error( "cannot parse trackName at all: " + trackName );
552

553 554
	}

555
	var results = {
556
		// directoryName: matches[ 1 ], // (tschw) currently unused
557 558 559 560 561
		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.
562 563
	};

564 565
	if ( results.propertyName === null || results.propertyName.length === 0 ) {

566
		throw new Error( "can not parse propertyName from trackName: " + trackName );
567

568 569 570
	}

	return results;
571 572 573

};

R
Rich Harris 已提交
574
PropertyBinding.findNode = function( root, nodeName ) {
575

576
	if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) {
577

T
tschw 已提交
578
		return root;
579

T
tschw 已提交
580
	}
581

T
tschw 已提交
582
	// search into skeleton bones.
583
	if ( root.skeleton ) {
584

T
tschw 已提交
585
		var searchSkeleton = function( skeleton ) {
586

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

589
				var bone = skeleton.bones[ i ];
590

591
				if ( bone.name === nodeName ) {
592

T
tschw 已提交
593
					return bone;
594

T
tschw 已提交
595 596
				}
			}
597

T
tschw 已提交
598
			return null;
599

T
tschw 已提交
600
		};
601

T
tschw 已提交
602
		var bone = searchSkeleton( root.skeleton );
603

604
		if ( bone ) {
M
Mr.doob 已提交
605

T
tschw 已提交
606
			return bone;
607 608

		}
T
tschw 已提交
609
	}
610

T
tschw 已提交
611
	// search into node subtree.
612
	if ( root.children ) {
613

T
tschw 已提交
614
		var searchNodeSubtree = function( children ) {
615

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

618
				var childNode = children[ i ];
619

620
				if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
621

T
tschw 已提交
622
					return childNode;
623

T
tschw 已提交
624
				}
625

T
tschw 已提交
626
				var result = searchNodeSubtree( childNode.children );
627

628
				if ( result ) return result;
629

T
tschw 已提交
630
			}
631

632
			return null;
633

T
tschw 已提交
634
		};
635 636 637

		var subTreeNode = searchNodeSubtree( root.children );

638
		if ( subTreeNode ) {
639 640 641 642 643 644 645 646

			return subTreeNode;

		}

	}

	return null;
T
tschw 已提交
647

M
Michael Herzog 已提交
648
};
R
Rich Harris 已提交
649 650


651
export { PropertyBinding };