PropertyBinding.js 15.5 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

D
Don McCurdy 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117
	/**
	 * Replaces spaces with underscores and removes unsupported characters from
	 * node names, to ensure compatibility with parseTrackName().
	 *
	 * @param  {string} name Node name to be sanitized.
	 * @return {string}
	 */
	sanitizeNodeName: function ( name ) {

		return name.replace( /\s/g, '_' ).replace( /[^\w-]/g, '' );

	},

118
	parseTrackName: function () {
T
tschw 已提交
119

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

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

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

131 132 133
		// Property and accessor. May contain only word characters. Accessor may
		// contain any non-bracket characters.
		var propertyRe = /\.([\w-]+)(?:\[(.+)\])?/;
134

135 136 137 138 139 140 141 142
		var trackRe = new RegExp(''
			+ '^'
			+ directoryRe.source
			+ nodeRe.source
			+ objectRe.source
			+ propertyRe.source
			+ '$'
		);
143

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

146
		return function ( trackName ) {
T
tschw 已提交
147

148
				var matches = trackRe.exec( trackName );
T
tschw 已提交
149

150
				if ( ! matches ) {
151

152
					throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );
153

154 155 156 157
				}

				var results = {
					// directoryName: matches[ 1 ], // (tschw) currently unused
158
					nodeName: matches[ 2 ],
159 160
					objectName: matches[ 3 ],
					objectIndex: matches[ 4 ],
161 162
					propertyName: matches[ 5 ],     // required
					propertyIndex: matches[ 6 ]
163 164 165 166 167 168 169 170
				};

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

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

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

D
Don McCurdy 已提交
171 172 173 174
					// 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).
175 176
					if ( supportedObjectNames.indexOf( objectName ) !== -1 ) {

177
						results.nodeName = results.nodeName.substring( 0, lastDot );
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
						results.objectName = objectName;

					}

				}

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

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

				}

				return results;

			};

	}(),
195

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

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

200
			return root;
201 202 203

		}

204 205
		// search into skeleton bones.
		if ( root.skeleton ) {
T
tschw 已提交
206

M
Mr.doob 已提交
207
			var searchSkeleton = function ( skeleton ) {
208

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

211
					var bone = skeleton.bones[ i ];
T
tschw 已提交
212

213
					if ( bone.name === nodeName ) {
T
tschw 已提交
214

215
						return bone;
T
tschw 已提交
216

217
					}
M
Mr.doob 已提交
218

219
				}
220

221
				return null;
T
tschw 已提交
222

223
			};
T
tschw 已提交
224

225
			var bone = searchSkeleton( root.skeleton );
T
tschw 已提交
226

227
			if ( bone ) {
T
tschw 已提交
228

229
				return bone;
T
tschw 已提交
230

231
			}
M
Mr.doob 已提交
232

233
		}
234

235 236
		// search into node subtree.
		if ( root.children ) {
237

M
Mr.doob 已提交
238
			var searchNodeSubtree = function ( children ) {
239

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

242
					var childNode = children[ i ];
243

244
					if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
245

246
						return childNode;
T
tschw 已提交
247

248
					}
T
tschw 已提交
249

250
					var result = searchNodeSubtree( childNode.children );
T
tschw 已提交
251

252
					if ( result ) return result;
253

254
				}
255

256
				return null;
257

258
			};
259

260
			var subTreeNode = searchNodeSubtree( root.children );
261

262
			if ( subTreeNode ) {
263

264
				return subTreeNode;
265

266 267 268 269 270
			}

		}

		return null;
271

T
tschw 已提交
272
	}
273

274
} );
275

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

T
tschw 已提交
278
	// these are used to "bind" a nonexistent property
M
Mr.doob 已提交
279 280
	_getValue_unavailable: function () {},
	_setValue_unavailable: function () {},
281

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

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

T
tschw 已提交
295
	GetterByBindingType: [
296

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

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

T
tschw 已提交
301
		},
302

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

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

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

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

			}

		},

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

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

T
tschw 已提交
319
		},
320

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

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

T
tschw 已提交
325
		}
326

T
tschw 已提交
327
	],
328

T
tschw 已提交
329
	SetterByBindingTypeAndVersioning: [
330

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

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

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

T
tschw 已提交
338
			},
339

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

T
tschw 已提交
342 343 344 345 346 347 348 349 350
				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 已提交
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 394

			}

		], [

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

			}

T
tschw 已提交
398
		], [
399

T
tschw 已提交
400
			// ArrayElement
401

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

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

T
tschw 已提交
406
			},
407

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

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

T
tschw 已提交
413
			},
414

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

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

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

T
tschw 已提交
422
		], [
423

T
tschw 已提交
424
			// HasToFromArray
425

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

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

T
tschw 已提交
430
			},
431

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

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

T
tschw 已提交
437
			},
438

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

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

444
			}
B
Ben Houston 已提交
445

T
tschw 已提交
446 447
		]

448
	],
T
tschw 已提交
449

450
	getValue: function getValue_unbound( targetArray, offset ) {
T
tschw 已提交
451

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

455 456 457 458 459
		// 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 已提交
460

461
	},
T
tschw 已提交
462

463
	setValue: function getValue_unbound( sourceArray, offset ) {
T
tschw 已提交
464

465 466
		this.bind();
		this.setValue( sourceArray, offset );
T
tschw 已提交
467

468
	},
T
tschw 已提交
469

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

473 474
		var targetObject = this.node,
			parsedPath = this.parsedPath,
T
tschw 已提交
475

476 477 478
			objectName = parsedPath.objectName,
			propertyName = parsedPath.propertyName,
			propertyIndex = parsedPath.propertyIndex;
T
tschw 已提交
479

480
		if ( ! targetObject ) {
T
tschw 已提交
481

482 483
			targetObject = PropertyBinding.findNode(
					this.rootNode, parsedPath.nodeName ) || this.rootNode;
T
tschw 已提交
484

485
			this.node = targetObject;
T
tschw 已提交
486

487
		}
T
tschw 已提交
488

489 490 491
		// set fail state so we can just 'return' on error
		this.getValue = this._getValue_unavailable;
		this.setValue = this._setValue_unavailable;
T
tschw 已提交
492

493 494
		// ensure there is a value node
		if ( ! targetObject ) {
T
tschw 已提交
495

M
Mugen87 已提交
496
			console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' );
497
			return;
T
tschw 已提交
498

499
		}
T
tschw 已提交
500

501
		if ( objectName ) {
T
tschw 已提交
502

503
			var objectIndex = parsedPath.objectIndex;
T
tschw 已提交
504

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

508
				case 'materials':
T
tschw 已提交
509

510
					if ( ! targetObject.material ) {
511

M
Mugen87 已提交
512
						console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this );
513
						return;
514

515
					}
516

517
					if ( ! targetObject.material.materials ) {
518

M
Mugen87 已提交
519
						console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this );
520
						return;
T
tschw 已提交
521

522
					}
T
tschw 已提交
523

524
					targetObject = targetObject.material.materials;
525

526
					break;
527

528
				case 'bones':
529

530
					if ( ! targetObject.skeleton ) {
531

M
Mugen87 已提交
532
						console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this );
533
						return;
534

535
					}
536

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

540
					targetObject = targetObject.skeleton.bones;
T
tschw 已提交
541

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

545
						if ( targetObject[ i ].name === objectIndex ) {
546

547 548
							objectIndex = i;
							break;
549

550
						}
551

552
					}
553

554
					break;
555

556
				default:
557

558
					if ( targetObject[ objectName ] === undefined ) {
559

M
Mugen87 已提交
560
						console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this );
561
						return;
562

563
					}
564

565
					targetObject = targetObject[ objectName ];
566

567
			}
568

569

570
			if ( objectIndex !== undefined ) {
571

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

M
Mugen87 已提交
574
					console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject );
575
					return;
576

577
				}
578

579
				targetObject = targetObject[ objectIndex ];
580

581
			}
582

583
		}
584

585 586
		// resolve property
		var nodeProperty = targetObject[ propertyName ];
587

588
		if ( nodeProperty === undefined ) {
589

590
			var nodeName = parsedPath.nodeName;
591

M
Mugen87 已提交
592 593
			console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName +
				'.' + propertyName + ' but it wasn\'t found.', targetObject );
594
			return;
595

596
		}
597

598 599
		// determine versioning scheme
		var versioning = this.Versioning.None;
600

601
		if ( targetObject.needsUpdate !== undefined ) { // material
602

603 604
			versioning = this.Versioning.NeedsUpdate;
			this.targetObject = targetObject;
605

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

608 609
			versioning = this.Versioning.MatrixWorldNeedsUpdate;
			this.targetObject = targetObject;
610 611 612

		}

613 614 615 616
		// determine how the property gets bound
		var bindingType = this.BindingType.Direct;

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

618 619 620
			// access a sub element of the property array (only primitives are supported right now)

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

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

624 625
				// support resolving morphTarget names into indices.
				if ( ! targetObject.geometry ) {
626

M
Mugen87 已提交
627
					console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this );
628
					return;
629

630
				}
631

632
				if ( targetObject.geometry.isBufferGeometry ) {
633

634
					if ( ! targetObject.geometry.morphAttributes ) {
635

M
Mugen87 已提交
636
						console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this );
637 638 639 640 641
						return;

					}

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

643 644 645 646 647 648 649 650
						if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) {

							propertyIndex = i;
							break;

						}

					}
651 652


653 654 655 656
				} else {

					if ( ! targetObject.geometry.morphTargets ) {

M
Mugen87 已提交
657
						console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this );
658 659 660 661 662 663 664 665 666 667 668 669
						return;

					}

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

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

							propertyIndex = i;
							break;

						}
670 671

					}
672

673
				}
674

T
tschw 已提交
675
			}
676

677
			bindingType = this.BindingType.ArrayElement;
678

679 680 681 682
			this.resolvedProperty = nodeProperty;
			this.propertyIndex = propertyIndex;

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

684 685 686 687 688
			// must use copy for Object3D.Euler/Quaternion

			bindingType = this.BindingType.HasFromToArray;

			this.resolvedProperty = nodeProperty;
689

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

692
			bindingType = this.BindingType.EntireArray;
693

694 695 696
			this.resolvedProperty = nodeProperty;

		} else {
697

698
			this.propertyName = propertyName;
699 700 701

		}

702 703 704 705 706 707
		// select getter / setter
		this.getValue = this.GetterByBindingType[ bindingType ];
		this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];

	},

M
Mr.doob 已提交
708
	unbind: function () {
709 710 711 712 713 714 715

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

717
	}
T
tschw 已提交
718

719
} );
R
Rich Harris 已提交
720

T
Tristan VALCKE 已提交
721 722 723 724 725 726 727 728
//!\ 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 已提交
729

730
export { PropertyBinding };