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

M
Mugen87 已提交
135
		var trackRe = new RegExp( ''
136 137 138 139 140 141 142
			+ '^'
			+ 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

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

M
Mugen87 已提交
150
			if ( ! matches ) {
151

M
Mugen87 已提交
152
				throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );
153

M
Mugen87 已提交
154
			}
155

M
Mugen87 已提交
156 157 158 159 160 161 162 163
			var results = {
				// directoryName: matches[ 1 ], // (tschw) currently unused
				nodeName: matches[ 2 ],
				objectName: matches[ 3 ],
				objectIndex: matches[ 4 ],
				propertyName: matches[ 5 ], // required
				propertyIndex: matches[ 6 ]
			};
164

M
Mugen87 已提交
165
			var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );
166

M
Mugen87 已提交
167
			if ( lastDot !== undefined && lastDot !== - 1 ) {
168

M
Mugen87 已提交
169
				var objectName = results.nodeName.substring( lastDot + 1 );
170

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

M
Mugen87 已提交
177 178
					results.nodeName = results.nodeName.substring( 0, lastDot );
					results.objectName = objectName;
179 180 181

				}

M
Mugen87 已提交
182
			}
183

M
Mugen87 已提交
184
			if ( results.propertyName === null || results.propertyName.length === 0 ) {
185

M
Mugen87 已提交
186
				throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );
187

M
Mugen87 已提交
188
			}
189

M
Mugen87 已提交
190 191 192
			return results;

		};
193 194

	}(),
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

336
				this.targetObject[ this.propertyName ] = buffer[ offset ];
337

T
tschw 已提交
338
			},
339

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

342
				this.targetObject[ this.propertyName ] = buffer[ offset ];
T
tschw 已提交
343 344 345 346 347 348
				this.targetObject.needsUpdate = true;

			},

			function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {

349
				this.targetObject[ this.propertyName ] = buffer[ offset ];
T
tschw 已提交
350
				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

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

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

486
		}
T
tschw 已提交
487

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

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

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

498
		}
T
tschw 已提交
499

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

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

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

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

509
					if ( ! targetObject.material ) {
510

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

514
					}
515

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

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

521
					}
T
tschw 已提交
522

523
					targetObject = targetObject.material.materials;
524

525
					break;
526

527
				case 'bones':
528

529
					if ( ! targetObject.skeleton ) {
530

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

534
					}
535

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

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

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

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

546 547
							objectIndex = i;
							break;
548

549
						}
550

551
					}
552

553
					break;
554

555
				default:
556

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

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

562
					}
563

564
					targetObject = targetObject[ objectName ];
565

566
			}
567

568

569
			if ( objectIndex !== undefined ) {
570

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

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

576
				}
577

578
				targetObject = targetObject[ objectIndex ];
579

580
			}
581

582
		}
583

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

587
		if ( nodeProperty === undefined ) {
588

589
			var nodeName = parsedPath.nodeName;
590

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

595
		}
596

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

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

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

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

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

		}

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

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

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

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

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

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

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

629
				}
630

631
				if ( targetObject.geometry.isBufferGeometry ) {
632

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

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

					}

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

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

							propertyIndex = i;
							break;

						}

					}
650 651


652 653 654 655
				} else {

					if ( ! targetObject.geometry.morphTargets ) {

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

					}

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

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

							propertyIndex = i;
							break;

						}
669 670

					}
671

672
				}
673

T
tschw 已提交
674
			}
675

676
			bindingType = this.BindingType.ArrayElement;
677

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

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

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

			bindingType = this.BindingType.HasFromToArray;

			this.resolvedProperty = nodeProperty;
688

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

691
			bindingType = this.BindingType.EntireArray;
692

693 694 695
			this.resolvedProperty = nodeProperty;

		} else {
696

697
			this.propertyName = propertyName;
698 699 700

		}

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

	},

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

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

716
	}
T
tschw 已提交
717

718
} );
R
Rich Harris 已提交
719

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

729
export { PropertyBinding };