PropertyBinding.js 14.9 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 Composite( targetGroup, path, optionalParsedPath ) {
12

13
	var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
14

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

M
Mr.doob 已提交
18
}
19

20
Object.assign( Composite.prototype, {
21

M
Mr.doob 已提交
22
	getValue: function ( array, offset ) {
23

24
		this.bind(); // bind all binding
25

26 27
		var firstValidIndex = this._targetGroup.nCachedObjects_,
			binding = this._bindings[ firstValidIndex ];
B
Ben Houston 已提交
28

29 30
		// and only call .getValue on the first
		if ( binding !== undefined ) binding.getValue( array, offset );
31

32
	},
B
Ben Houston 已提交
33

M
Mr.doob 已提交
34
	setValue: function ( array, offset ) {
35

36
		var bindings = this._bindings;
37

38 39
		for ( var i = this._targetGroup.nCachedObjects_,
				  n = bindings.length; i !== n; ++ i ) {
T
tschw 已提交
40

41
			bindings[ i ].setValue( array, offset );
T
tschw 已提交
42

43
		}
44

45
	},
T
tschw 已提交
46

M
Mr.doob 已提交
47
	bind: function () {
T
tschw 已提交
48

49
		var bindings = this._bindings;
50

51 52
		for ( var i = this._targetGroup.nCachedObjects_,
				  n = bindings.length; i !== n; ++ i ) {
53

54
			bindings[ i ].bind();
T
tschw 已提交
55

56
		}
T
tschw 已提交
57

58
	},
T
tschw 已提交
59

M
Mr.doob 已提交
60
	unbind: function () {
T
tschw 已提交
61

62
		var bindings = this._bindings;
T
tschw 已提交
63

64 65
		for ( var i = this._targetGroup.nCachedObjects_,
				  n = bindings.length; i !== n; ++ i ) {
T
tschw 已提交
66

67
			bindings[ i ].unbind();
T
tschw 已提交
68

69
		}
T
tschw 已提交
70

71
	}
T
tschw 已提交
72

73
} );
T
tschw 已提交
74 75


76
function PropertyBinding( rootNode, path, parsedPath ) {
T
tschw 已提交
77

78 79
	this.path = path;
	this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
T
tschw 已提交
80

81
	this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
T
tschw 已提交
82

83
	this.rootNode = rootNode;
T
tschw 已提交
84

85
}
T
tschw 已提交
86

87
Object.assign( PropertyBinding, {
T
tschw 已提交
88

89
	Composite: Composite,
T
tschw 已提交
90

M
Mr.doob 已提交
91
	create: function ( root, path, parsedPath ) {
T
tschw 已提交
92

93
		if ( ! ( root && root.isAnimationObjectGroup ) ) {
T
tschw 已提交
94

95
			return new PropertyBinding( root, path, parsedPath );
T
tschw 已提交
96

97
		} else {
T
tschw 已提交
98

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

101
		}
T
tschw 已提交
102

103
	},
T
tschw 已提交
104

105
	parseTrackName: function () {
T
tschw 已提交
106

107 108 109
		// Parent directories, delimited by '/' or ':'. Currently unused, but must
		// be matched to parse the rest of the track name.
		var directoryRe = /((?:[\w-]+[\/:])*)/;
T
tschw 已提交
110

111 112
		// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.
		var nodeRe = /([\w-\.]+)?/;
113

D
Don McCurdy 已提交
114 115
		// Object on target node, and accessor. Name may contain only word
		// characters. Accessor may contain any character except closing bracket.
116
		var objectRe = /(?:\.([\w-]+)(?:\[(.+)\])?)?/;
T
tschw 已提交
117

118 119 120
		// Property and accessor. May contain only word characters. Accessor may
		// contain any non-bracket characters.
		var propertyRe = /\.([\w-]+)(?:\[(.+)\])?/;
121

122 123 124 125 126 127 128 129
		var trackRe = new RegExp(''
			+ '^'
			+ directoryRe.source
			+ nodeRe.source
			+ objectRe.source
			+ propertyRe.source
			+ '$'
		);
130

131
		var supportedObjectNames = [ 'material', 'materials', 'bones' ];
T
tschw 已提交
132

133
		return function ( trackName ) {
T
tschw 已提交
134

135
				var matches = trackRe.exec( trackName );
T
tschw 已提交
136

137
				if ( ! matches ) {
138

139
					throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );
140

141 142 143 144
				}

				var results = {
					// directoryName: matches[ 1 ], // (tschw) currently unused
145
					nodeName: matches[ 2 ],
146 147
					objectName: matches[ 3 ],
					objectIndex: matches[ 4 ],
148 149
					propertyName: matches[ 5 ],     // required
					propertyIndex: matches[ 6 ]
150 151 152 153 154 155 156 157
				};

				var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );

				if ( lastDot !== undefined && lastDot !== -1 ) {

					var objectName = results.nodeName.substring( lastDot + 1 );

D
Don McCurdy 已提交
158 159 160 161
					// Object names must be checked against a whitelist. Otherwise, there
					// is no way to parse 'foo.bar.baz': 'baz' must be a property, but
					// 'bar' could be the objectName, or part of a nodeName (which can
					// include '.' characters).
162 163
					if ( supportedObjectNames.indexOf( objectName ) !== -1 ) {

164
						results.nodeName = results.nodeName.substring( 0, lastDot );
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
						results.objectName = objectName;

					}

				}

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

					throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );

				}

				return results;

			};

	}(),
182

M
Mr.doob 已提交
183
	findNode: function ( root, nodeName ) {
184

M
Mr.doob 已提交
185
		if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {
186

187
			return root;
188 189 190

		}

191 192
		// search into skeleton bones.
		if ( root.skeleton ) {
T
tschw 已提交
193

M
Mr.doob 已提交
194
			var searchSkeleton = function ( skeleton ) {
195

M
Mr.doob 已提交
196
				for ( var i = 0; i < skeleton.bones.length; i ++ ) {
T
tschw 已提交
197

198
					var bone = skeleton.bones[ i ];
T
tschw 已提交
199

200
					if ( bone.name === nodeName ) {
T
tschw 已提交
201

202
						return bone;
T
tschw 已提交
203

204
					}
M
Mr.doob 已提交
205

206
				}
207

208
				return null;
T
tschw 已提交
209

210
			};
T
tschw 已提交
211

212
			var bone = searchSkeleton( root.skeleton );
T
tschw 已提交
213

214
			if ( bone ) {
T
tschw 已提交
215

216
				return bone;
T
tschw 已提交
217

218
			}
M
Mr.doob 已提交
219

220
		}
221

222 223
		// search into node subtree.
		if ( root.children ) {
224

M
Mr.doob 已提交
225
			var searchNodeSubtree = function ( children ) {
226

M
Mr.doob 已提交
227
				for ( var i = 0; i < children.length; i ++ ) {
228

229
					var childNode = children[ i ];
230

231
					if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
232

233
						return childNode;
T
tschw 已提交
234

235
					}
T
tschw 已提交
236

237
					var result = searchNodeSubtree( childNode.children );
T
tschw 已提交
238

239
					if ( result ) return result;
240

241
				}
242

243
				return null;
244

245
			};
246

247
			var subTreeNode = searchNodeSubtree( root.children );
248

249
			if ( subTreeNode ) {
250

251
				return subTreeNode;
252

253 254 255 256 257
			}

		}

		return null;
258

T
tschw 已提交
259
	}
260

261
} );
262

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

T
tschw 已提交
265
	// these are used to "bind" a nonexistent property
M
Mr.doob 已提交
266 267
	_getValue_unavailable: function () {},
	_setValue_unavailable: function () {},
268

T
tschw 已提交
269 270
	BindingType: {
		Direct: 0,
T
tschw 已提交
271 272 273
		EntireArray: 1,
		ArrayElement: 2,
		HasFromToArray: 3
T
tschw 已提交
274
	},
275

T
tschw 已提交
276 277 278 279 280
	Versioning: {
		None: 0,
		NeedsUpdate: 1,
		MatrixWorldNeedsUpdate: 2
	},
281

T
tschw 已提交
282
	GetterByBindingType: [
283

T
tschw 已提交
284
		function getValue_direct( buffer, offset ) {
285

T
tschw 已提交
286
			buffer[ offset ] = this.node[ this.propertyName ];
287

T
tschw 已提交
288
		},
289

T
tschw 已提交
290 291
		function getValue_array( buffer, offset ) {

292
			var source = this.resolvedProperty;
T
tschw 已提交
293 294 295 296 297 298 299 300 301

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

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

			}

		},

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

T
tschw 已提交
304
			buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
305

T
tschw 已提交
306
		},
307

T
tschw 已提交
308
		function getValue_toArray( buffer, offset ) {
309

T
tschw 已提交
310
			this.resolvedProperty.toArray( buffer, offset );
311

T
tschw 已提交
312
		}
313

T
tschw 已提交
314
	],
315

T
tschw 已提交
316
	SetterByBindingTypeAndVersioning: [
317

T
tschw 已提交
318 319
		[
			// Direct
320

T
tschw 已提交
321
			function setValue_direct( buffer, offset ) {
322

T
tschw 已提交
323
				this.node[ this.propertyName ] = buffer[ offset ];
324

T
tschw 已提交
325
			},
326

T
tschw 已提交
327
			function setValue_direct_setNeedsUpdate( buffer, offset ) {
328

T
tschw 已提交
329 330 331 332 333 334 335 336 337
				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 已提交
338 339 340 341 342 343 344 345 346 347 348 349 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

			}

		], [

			// 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;
382 383 384

			}

T
tschw 已提交
385
		], [
386

T
tschw 已提交
387
			// ArrayElement
388

T
tschw 已提交
389
			function setValue_arrayElement( buffer, offset ) {
390

T
tschw 已提交
391
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
392

T
tschw 已提交
393
			},
394

T
tschw 已提交
395
			function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
396

T
tschw 已提交
397 398
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
				this.targetObject.needsUpdate = true;
399

T
tschw 已提交
400
			},
401

T
tschw 已提交
402
			function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
403

T
tschw 已提交
404 405
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
				this.targetObject.matrixWorldNeedsUpdate = true;
406

T
tschw 已提交
407
			}
B
Ben Houston 已提交
408

T
tschw 已提交
409
		], [
410

T
tschw 已提交
411
			// HasToFromArray
412

T
tschw 已提交
413
			function setValue_fromArray( buffer, offset ) {
414

T
tschw 已提交
415
				this.resolvedProperty.fromArray( buffer, offset );
416

T
tschw 已提交
417
			},
418

T
tschw 已提交
419
			function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
420

T
tschw 已提交
421 422
				this.resolvedProperty.fromArray( buffer, offset );
				this.targetObject.needsUpdate = true;
423

T
tschw 已提交
424
			},
425

T
tschw 已提交
426 427 428 429
			function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {

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

431
			}
B
Ben Houston 已提交
432

T
tschw 已提交
433 434
		]

435
	],
T
tschw 已提交
436

437
	getValue: function getValue_unbound( targetArray, offset ) {
T
tschw 已提交
438

439 440
		this.bind();
		this.getValue( targetArray, offset );
T
tschw 已提交
441

442 443 444 445 446
		// 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.
T
tschw 已提交
447

448
	},
T
tschw 已提交
449

450
	setValue: function getValue_unbound( sourceArray, offset ) {
T
tschw 已提交
451

452 453
		this.bind();
		this.setValue( sourceArray, offset );
T
tschw 已提交
454

455
	},
T
tschw 已提交
456

457
	// create getter / setter pair for a property in the scene graph
M
Mr.doob 已提交
458
	bind: function () {
T
tschw 已提交
459

460 461
		var targetObject = this.node,
			parsedPath = this.parsedPath,
T
tschw 已提交
462

463 464 465
			objectName = parsedPath.objectName,
			propertyName = parsedPath.propertyName,
			propertyIndex = parsedPath.propertyIndex;
T
tschw 已提交
466

467
		if ( ! targetObject ) {
T
tschw 已提交
468

469 470
			targetObject = PropertyBinding.findNode(
					this.rootNode, parsedPath.nodeName ) || this.rootNode;
T
tschw 已提交
471

472
			this.node = targetObject;
T
tschw 已提交
473

474
		}
T
tschw 已提交
475

476 477 478
		// set fail state so we can just 'return' on error
		this.getValue = this._getValue_unavailable;
		this.setValue = this._setValue_unavailable;
T
tschw 已提交
479

480 481
		// ensure there is a value node
		if ( ! targetObject ) {
T
tschw 已提交
482

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

486
		}
T
tschw 已提交
487

488
		if ( objectName ) {
T
tschw 已提交
489

490
			var objectIndex = parsedPath.objectIndex;
T
tschw 已提交
491

492 493
			// special cases were we need to reach deeper into the hierarchy to get the face materials....
			switch ( objectName ) {
T
tschw 已提交
494

495
				case 'materials':
T
tschw 已提交
496

497
					if ( ! targetObject.material ) {
498

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

502
					}
503

504
					if ( ! targetObject.material.materials ) {
505

506 507
						console.error( '  can not bind to material.materials as node.material does not have a materials array', this );
						return;
T
tschw 已提交
508

509
					}
T
tschw 已提交
510

511
					targetObject = targetObject.material.materials;
512

513
					break;
514

515
				case 'bones':
516

517
					if ( ! targetObject.skeleton ) {
518

519 520
						console.error( '  can not bind to bones as node does not have a skeleton', this );
						return;
521

522
					}
523

524 525
					// potential future optimization: skip this if propertyIndex is already an integer
					// and convert the integer string to a true integer.
T
tschw 已提交
526

527
					targetObject = targetObject.skeleton.bones;
T
tschw 已提交
528

529 530
					// support resolving morphTarget names into indices.
					for ( var i = 0; i < targetObject.length; i ++ ) {
T
tschw 已提交
531

532
						if ( targetObject[ i ].name === objectIndex ) {
533

534 535
							objectIndex = i;
							break;
536

537
						}
538

539
					}
540

541
					break;
542

543
				default:
544

545
					if ( targetObject[ objectName ] === undefined ) {
546

547 548
						console.error( '  can not bind to objectName of node, undefined', this );
						return;
549

550
					}
551

552
					targetObject = targetObject[ objectName ];
553

554
			}
555

556

557
			if ( objectIndex !== undefined ) {
558

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

561 562
					console.error( "  trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
					return;
563

564
				}
565

566
				targetObject = targetObject[ objectIndex ];
567

568
			}
569

570
		}
571

572 573
		// resolve property
		var nodeProperty = targetObject[ propertyName ];
574

575
		if ( nodeProperty === undefined ) {
576

577
			var nodeName = parsedPath.nodeName;
578

579 580 581
			console.error( "  trying to update property for track: " + nodeName +
				'.' + propertyName + " but it wasn't found.", targetObject );
			return;
582

583
		}
584

585 586
		// determine versioning scheme
		var versioning = this.Versioning.None;
587

588
		if ( targetObject.needsUpdate !== undefined ) { // material
589

590 591
			versioning = this.Versioning.NeedsUpdate;
			this.targetObject = targetObject;
592

593
		} else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
M
Mr.doob 已提交
594

595 596
			versioning = this.Versioning.MatrixWorldNeedsUpdate;
			this.targetObject = targetObject;
597 598 599

		}

600 601 602 603
		// determine how the property gets bound
		var bindingType = this.BindingType.Direct;

		if ( propertyIndex !== undefined ) {
M
Mr.doob 已提交
604

605 606 607
			// access a sub element of the property array (only primitives are supported right now)

			if ( propertyName === "morphTargetInfluences" ) {
M
Mr.doob 已提交
608

609
				// potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
610

611 612
				// support resolving morphTarget names into indices.
				if ( ! targetObject.geometry ) {
613

614 615
					console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
					return;
616

617
				}
618

619
				if ( targetObject.geometry.isBufferGeometry ) {
620

621
					if ( ! targetObject.geometry.morphAttributes ) {
622

623 624 625 626 627 628
						console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry.morphAttributes', this );
						return;

					}

					for ( var i = 0; i < this.node.geometry.morphAttributes.position.length; i ++ ) {
629

630 631 632 633 634 635 636 637
						if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) {

							propertyIndex = i;
							break;

						}

					}
638 639


640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
				} else {

					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;

						}
657 658

					}
659

660
				}
661

T
tschw 已提交
662
			}
663

664
			bindingType = this.BindingType.ArrayElement;
665

666 667 668 669
			this.resolvedProperty = nodeProperty;
			this.propertyIndex = propertyIndex;

		} else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
M
Mr.doob 已提交
670

671 672 673 674 675
			// must use copy for Object3D.Euler/Quaternion

			bindingType = this.BindingType.HasFromToArray;

			this.resolvedProperty = nodeProperty;
676

677
		} else if ( Array.isArray( nodeProperty ) ) {
M
Mr.doob 已提交
678

679
			bindingType = this.BindingType.EntireArray;
680

681 682 683
			this.resolvedProperty = nodeProperty;

		} else {
684

685
			this.propertyName = propertyName;
686 687 688

		}

689 690 691 692 693 694
		// select getter / setter
		this.getValue = this.GetterByBindingType[ bindingType ];
		this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];

	},

M
Mr.doob 已提交
695
	unbind: function () {
696 697 698 699 700 701 702

		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;
703

704
	}
T
tschw 已提交
705

706
} );
R
Rich Harris 已提交
707

T
Tristan VALCKE 已提交
708 709 710 711 712 713 714 715
//!\ DECLARE ALIAS AFTER assign prototype !
Object.assign( PropertyBinding.prototype, {

	// initial state of these methods that calls 'bind'
	_getValue_unbound: PropertyBinding.prototype.getValue,
	_setValue_unbound: PropertyBinding.prototype.setValue,

} );
R
Rich Harris 已提交
716

717
export { PropertyBinding };