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

M
Mr.doob 已提交
105
	parseTrackName: function ( trackName ) {
T
tschw 已提交
106

107 108 109 110 111 112 113 114 115 116 117
		// matches strings in the form of:
		//    nodeName.property
		//    nodeName.property[accessor]
		//    nodeName.material.property[accessor]
		//    uuid.property[accessor]
		//    uuid.objectName[objectIndex].propertyName[propertyIndex]
		//    parentName/nodeName.property
		//    parentName/parentName/nodeName.property[index]
		//    .bone[Armature.DEF_cog].position
		//    scene:helium_balloon_model:helium_balloon_model.position
		// created and tested via https://regex101.com/#javascript
T
tschw 已提交
118

119 120
		var re = /^((?:[\w-]+[\/:])*)([\w-]+)?(?:\.([\w-]+)(?:\[(.+)\])?)?\.([\w-]+)(?:\[(.+)\])?$/;
		var matches = re.exec( trackName );
121

122
		if ( ! matches ) {
T
tschw 已提交
123

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

126
		}
127

128 129 130 131 132 133 134 135
		var results = {
			// directoryName: matches[ 1 ], // (tschw) currently unused
			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.
		};
T
tschw 已提交
136

137
		if ( results.propertyName === null || results.propertyName.length === 0 ) {
T
tschw 已提交
138

139
			throw new Error( "can not parse propertyName from trackName: " + trackName );
T
tschw 已提交
140

141
		}
142

143
		return results;
144

145
	},
146

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

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

151
			return root;
152 153 154

		}

155 156
		// search into skeleton bones.
		if ( root.skeleton ) {
T
tschw 已提交
157

M
Mr.doob 已提交
158
			var searchSkeleton = function ( skeleton ) {
159

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

162
					var bone = skeleton.bones[ i ];
T
tschw 已提交
163

164
					if ( bone.name === nodeName ) {
T
tschw 已提交
165

166
						return bone;
T
tschw 已提交
167

168
					}
M
Mr.doob 已提交
169

170
				}
171

172
				return null;
T
tschw 已提交
173

174
			};
T
tschw 已提交
175

176
			var bone = searchSkeleton( root.skeleton );
T
tschw 已提交
177

178
			if ( bone ) {
T
tschw 已提交
179

180
				return bone;
T
tschw 已提交
181

182
			}
M
Mr.doob 已提交
183

184
		}
185

186 187
		// search into node subtree.
		if ( root.children ) {
188

M
Mr.doob 已提交
189
			var searchNodeSubtree = function ( children ) {
190

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

193
					var childNode = children[ i ];
194

195
					if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
196

197
						return childNode;
T
tschw 已提交
198

199
					}
T
tschw 已提交
200

201
					var result = searchNodeSubtree( childNode.children );
T
tschw 已提交
202

203
					if ( result ) return result;
204

205
				}
206

207
				return null;
208

209
			};
210

211
			var subTreeNode = searchNodeSubtree( root.children );
212

213
			if ( subTreeNode ) {
214

215
				return subTreeNode;
216

217 218 219 220 221
			}

		}

		return null;
222

T
tschw 已提交
223
	}
224

225
} );
226

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

T
tschw 已提交
229
	// these are used to "bind" a nonexistent property
M
Mr.doob 已提交
230 231
	_getValue_unavailable: function () {},
	_setValue_unavailable: function () {},
232

T
tschw 已提交
233 234
	BindingType: {
		Direct: 0,
T
tschw 已提交
235 236 237
		EntireArray: 1,
		ArrayElement: 2,
		HasFromToArray: 3
T
tschw 已提交
238
	},
239

T
tschw 已提交
240 241 242 243 244
	Versioning: {
		None: 0,
		NeedsUpdate: 1,
		MatrixWorldNeedsUpdate: 2
	},
245

T
tschw 已提交
246
	GetterByBindingType: [
247

T
tschw 已提交
248
		function getValue_direct( buffer, offset ) {
249

T
tschw 已提交
250
			buffer[ offset ] = this.node[ this.propertyName ];
251

T
tschw 已提交
252
		},
253

T
tschw 已提交
254 255
		function getValue_array( buffer, offset ) {

256
			var source = this.resolvedProperty;
T
tschw 已提交
257 258 259 260 261 262 263 264 265

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

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

			}

		},

T
tschw 已提交
266
		function getValue_arrayElement( buffer, offset ) {
267

T
tschw 已提交
268
			buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
269

T
tschw 已提交
270
		},
271

T
tschw 已提交
272
		function getValue_toArray( buffer, offset ) {
273

T
tschw 已提交
274
			this.resolvedProperty.toArray( buffer, offset );
275

T
tschw 已提交
276
		}
277

T
tschw 已提交
278
	],
279

T
tschw 已提交
280
	SetterByBindingTypeAndVersioning: [
281

T
tschw 已提交
282 283
		[
			// Direct
284

T
tschw 已提交
285
			function setValue_direct( buffer, offset ) {
286

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

T
tschw 已提交
289
			},
290

T
tschw 已提交
291
			function setValue_direct_setNeedsUpdate( buffer, offset ) {
292

T
tschw 已提交
293 294 295 296 297 298 299 300 301
				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 已提交
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345

			}

		], [

			// 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;
346 347 348

			}

T
tschw 已提交
349
		], [
350

T
tschw 已提交
351
			// ArrayElement
352

T
tschw 已提交
353
			function setValue_arrayElement( buffer, offset ) {
354

T
tschw 已提交
355
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
356

T
tschw 已提交
357
			},
358

T
tschw 已提交
359
			function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
360

T
tschw 已提交
361 362
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
				this.targetObject.needsUpdate = true;
363

T
tschw 已提交
364
			},
365

T
tschw 已提交
366
			function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
367

T
tschw 已提交
368 369
				this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
				this.targetObject.matrixWorldNeedsUpdate = true;
370

T
tschw 已提交
371
			}
B
Ben Houston 已提交
372

T
tschw 已提交
373
		], [
374

T
tschw 已提交
375
			// HasToFromArray
376

T
tschw 已提交
377
			function setValue_fromArray( buffer, offset ) {
378

T
tschw 已提交
379
				this.resolvedProperty.fromArray( buffer, offset );
380

T
tschw 已提交
381
			},
382

T
tschw 已提交
383
			function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
384

T
tschw 已提交
385 386
				this.resolvedProperty.fromArray( buffer, offset );
				this.targetObject.needsUpdate = true;
387

T
tschw 已提交
388
			},
389

T
tschw 已提交
390 391 392 393
			function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {

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

395
			}
B
Ben Houston 已提交
396

T
tschw 已提交
397 398
		]

399
	],
T
tschw 已提交
400

401
	getValue: function getValue_unbound( targetArray, offset ) {
T
tschw 已提交
402

403 404
		this.bind();
		this.getValue( targetArray, offset );
T
tschw 已提交
405

406 407 408 409 410
		// 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 已提交
411

412
	},
T
tschw 已提交
413

414
	setValue: function getValue_unbound( sourceArray, offset ) {
T
tschw 已提交
415

416 417
		this.bind();
		this.setValue( sourceArray, offset );
T
tschw 已提交
418

419
	},
T
tschw 已提交
420

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

424 425
		var targetObject = this.node,
			parsedPath = this.parsedPath,
T
tschw 已提交
426

427 428 429
			objectName = parsedPath.objectName,
			propertyName = parsedPath.propertyName,
			propertyIndex = parsedPath.propertyIndex;
T
tschw 已提交
430

431
		if ( ! targetObject ) {
T
tschw 已提交
432

433 434
			targetObject = PropertyBinding.findNode(
					this.rootNode, parsedPath.nodeName ) || this.rootNode;
T
tschw 已提交
435

436
			this.node = targetObject;
T
tschw 已提交
437

438
		}
T
tschw 已提交
439

440 441 442
		// set fail state so we can just 'return' on error
		this.getValue = this._getValue_unavailable;
		this.setValue = this._setValue_unavailable;
T
tschw 已提交
443

444 445
		// ensure there is a value node
		if ( ! targetObject ) {
T
tschw 已提交
446

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

450
		}
T
tschw 已提交
451

452
		if ( objectName ) {
T
tschw 已提交
453

454
			var objectIndex = parsedPath.objectIndex;
T
tschw 已提交
455

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

459
				case 'materials':
T
tschw 已提交
460

461
					if ( ! targetObject.material ) {
462

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

466
					}
467

468
					if ( ! targetObject.material.materials ) {
469

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

473
					}
T
tschw 已提交
474

475
					targetObject = targetObject.material.materials;
476

477
					break;
478

479
				case 'bones':
480

481
					if ( ! targetObject.skeleton ) {
482

483 484
						console.error( '  can not bind to bones as node does not have a skeleton', this );
						return;
485

486
					}
487

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

491
					targetObject = targetObject.skeleton.bones;
T
tschw 已提交
492

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

496
						if ( targetObject[ i ].name === objectIndex ) {
497

498 499
							objectIndex = i;
							break;
500

501
						}
502

503
					}
504

505
					break;
506

507
				default:
508

509
					if ( targetObject[ objectName ] === undefined ) {
510

511 512
						console.error( '  can not bind to objectName of node, undefined', this );
						return;
513

514
					}
515

516
					targetObject = targetObject[ objectName ];
517

518
			}
519

520

521
			if ( objectIndex !== undefined ) {
522

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

525 526
					console.error( "  trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
					return;
527

528
				}
529

530
				targetObject = targetObject[ objectIndex ];
531

532
			}
533

534
		}
535

536 537
		// resolve property
		var nodeProperty = targetObject[ propertyName ];
538

539
		if ( nodeProperty === undefined ) {
540

541
			var nodeName = parsedPath.nodeName;
542

543 544 545
			console.error( "  trying to update property for track: " + nodeName +
				'.' + propertyName + " but it wasn't found.", targetObject );
			return;
546

547
		}
548

549 550
		// determine versioning scheme
		var versioning = this.Versioning.None;
551

552
		if ( targetObject.needsUpdate !== undefined ) { // material
553

554 555
			versioning = this.Versioning.NeedsUpdate;
			this.targetObject = targetObject;
556

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

559 560
			versioning = this.Versioning.MatrixWorldNeedsUpdate;
			this.targetObject = targetObject;
561 562 563

		}

564 565 566 567
		// determine how the property gets bound
		var bindingType = this.BindingType.Direct;

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

569 570 571
			// access a sub element of the property array (only primitives are supported right now)

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

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

575 576
				// support resolving morphTarget names into indices.
				if ( ! targetObject.geometry ) {
577

578 579
					console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry', this );
					return;
580

581
				}
582

583
				if ( ! targetObject.geometry.morphTargets ) {
584

585 586
					console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );
					return;
587

T
tschw 已提交
588
				}
589

590 591 592 593 594 595 596 597
				for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {

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

						propertyIndex = i;
						break;

					}
598

599
				}
600

T
tschw 已提交
601
			}
602

603
			bindingType = this.BindingType.ArrayElement;
604

605 606 607 608
			this.resolvedProperty = nodeProperty;
			this.propertyIndex = propertyIndex;

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

610 611 612 613 614
			// must use copy for Object3D.Euler/Quaternion

			bindingType = this.BindingType.HasFromToArray;

			this.resolvedProperty = nodeProperty;
615

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

618
			bindingType = this.BindingType.EntireArray;
619

620 621 622
			this.resolvedProperty = nodeProperty;

		} else {
623

624
			this.propertyName = propertyName;
625 626 627

		}

628 629 630 631 632 633
		// select getter / setter
		this.getValue = this.GetterByBindingType[ bindingType ];
		this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];

	},

M
Mr.doob 已提交
634
	unbind: function () {
635 636 637 638 639 640 641

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

643
	}
T
tschw 已提交
644

645
} );
R
Rich Harris 已提交
646

T
Tristan VALCKE 已提交
647 648 649 650 651 652 653 654
//!\ 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 已提交
655

656
export { PropertyBinding };