PropertyBinding.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. /**
  2. *
  3. * A reference to a real property in the scene graph.
  4. *
  5. *
  6. * @author Ben Houston / http://clara.io/
  7. * @author David Sarno / http://lighthaus.us/
  8. * @author tschw
  9. */
  10. // Characters [].:/ are reserved for track binding syntax.
  11. var RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
  12. function Composite( targetGroup, path, optionalParsedPath ) {
  13. var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
  14. this._targetGroup = targetGroup;
  15. this._bindings = targetGroup.subscribe_( path, parsedPath );
  16. }
  17. Object.assign( Composite.prototype, {
  18. getValue: function ( array, offset ) {
  19. this.bind(); // bind all binding
  20. var firstValidIndex = this._targetGroup.nCachedObjects_,
  21. binding = this._bindings[ firstValidIndex ];
  22. // and only call .getValue on the first
  23. if ( binding !== undefined ) binding.getValue( array, offset );
  24. },
  25. setValue: function ( array, offset ) {
  26. var bindings = this._bindings;
  27. for ( var i = this._targetGroup.nCachedObjects_,
  28. n = bindings.length; i !== n; ++ i ) {
  29. bindings[ i ].setValue( array, offset );
  30. }
  31. },
  32. bind: function () {
  33. var bindings = this._bindings;
  34. for ( var i = this._targetGroup.nCachedObjects_,
  35. n = bindings.length; i !== n; ++ i ) {
  36. bindings[ i ].bind();
  37. }
  38. },
  39. unbind: function () {
  40. var bindings = this._bindings;
  41. for ( var i = this._targetGroup.nCachedObjects_,
  42. n = bindings.length; i !== n; ++ i ) {
  43. bindings[ i ].unbind();
  44. }
  45. }
  46. } );
  47. function PropertyBinding( rootNode, path, parsedPath ) {
  48. this.path = path;
  49. this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
  50. this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
  51. this.rootNode = rootNode;
  52. }
  53. Object.assign( PropertyBinding, {
  54. Composite: Composite,
  55. create: function ( root, path, parsedPath ) {
  56. if ( ! ( root && root.isAnimationObjectGroup ) ) {
  57. return new PropertyBinding( root, path, parsedPath );
  58. } else {
  59. return new PropertyBinding.Composite( root, path, parsedPath );
  60. }
  61. },
  62. /**
  63. * Replaces spaces with underscores and removes unsupported characters from
  64. * node names, to ensure compatibility with parseTrackName().
  65. *
  66. * @param {string} name Node name to be sanitized.
  67. * @return {string}
  68. */
  69. sanitizeNodeName: ( function () {
  70. var reservedRe = new RegExp( '[' + RESERVED_CHARS_RE + ']', 'g' );
  71. return function sanitizeNodeName ( name ) {
  72. return name.replace( /\s/g, '_' ).replace( reservedRe, '' );
  73. };
  74. }() ),
  75. parseTrackName: function () {
  76. // Attempts to allow node names from any language. ES5's `\w` regexp matches
  77. // only latin characters, and the unicode \p{L} is not yet supported. So
  78. // instead, we exclude reserved characters and match everything else.
  79. var wordChar = '[^' + RESERVED_CHARS_RE + ']';
  80. var wordCharOrDot = '[^' + RESERVED_CHARS_RE.replace( '\\.', '' ) + ']';
  81. // Parent directories, delimited by '/' or ':'. Currently unused, but must
  82. // be matched to parse the rest of the track name.
  83. var directoryRe = /((?:WC+[\/:])*)/.source.replace( 'WC', wordChar );
  84. // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.
  85. var nodeRe = /(WCOD+)?/.source.replace( 'WCOD', wordCharOrDot );
  86. // Object on target node, and accessor. May not contain reserved
  87. // characters. Accessor may contain any character except closing bracket.
  88. var objectRe = /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', wordChar );
  89. // Property and accessor. May not contain reserved characters. Accessor may
  90. // contain any non-bracket characters.
  91. var propertyRe = /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', wordChar );
  92. var trackRe = new RegExp( ''
  93. + '^'
  94. + directoryRe
  95. + nodeRe
  96. + objectRe
  97. + propertyRe
  98. + '$'
  99. );
  100. var supportedObjectNames = [ 'material', 'materials', 'bones' ];
  101. return function ( trackName ) {
  102. var matches = trackRe.exec( trackName );
  103. if ( ! matches ) {
  104. throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );
  105. }
  106. var results = {
  107. // directoryName: matches[ 1 ], // (tschw) currently unused
  108. nodeName: matches[ 2 ],
  109. objectName: matches[ 3 ],
  110. objectIndex: matches[ 4 ],
  111. propertyName: matches[ 5 ], // required
  112. propertyIndex: matches[ 6 ]
  113. };
  114. var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );
  115. if ( lastDot !== undefined && lastDot !== - 1 ) {
  116. var objectName = results.nodeName.substring( lastDot + 1 );
  117. // Object names must be checked against a whitelist. Otherwise, there
  118. // is no way to parse 'foo.bar.baz': 'baz' must be a property, but
  119. // 'bar' could be the objectName, or part of a nodeName (which can
  120. // include '.' characters).
  121. if ( supportedObjectNames.indexOf( objectName ) !== - 1 ) {
  122. results.nodeName = results.nodeName.substring( 0, lastDot );
  123. results.objectName = objectName;
  124. }
  125. }
  126. if ( results.propertyName === null || results.propertyName.length === 0 ) {
  127. throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );
  128. }
  129. return results;
  130. };
  131. }(),
  132. findNode: function ( root, nodeName ) {
  133. if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {
  134. return root;
  135. }
  136. // search into skeleton bones.
  137. if ( root.skeleton ) {
  138. var searchSkeleton = function ( skeleton ) {
  139. for ( var i = 0; i < skeleton.bones.length; i ++ ) {
  140. var bone = skeleton.bones[ i ];
  141. if ( bone.name === nodeName ) {
  142. return bone;
  143. }
  144. }
  145. return null;
  146. };
  147. var bone = searchSkeleton( root.skeleton );
  148. if ( bone ) {
  149. return bone;
  150. }
  151. }
  152. // search into node subtree.
  153. if ( root.children ) {
  154. var searchNodeSubtree = function ( children ) {
  155. for ( var i = 0; i < children.length; i ++ ) {
  156. var childNode = children[ i ];
  157. if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
  158. return childNode;
  159. }
  160. var result = searchNodeSubtree( childNode.children );
  161. if ( result ) return result;
  162. }
  163. return null;
  164. };
  165. var subTreeNode = searchNodeSubtree( root.children );
  166. if ( subTreeNode ) {
  167. return subTreeNode;
  168. }
  169. }
  170. return null;
  171. }
  172. } );
  173. Object.assign( PropertyBinding.prototype, { // prototype, continued
  174. // these are used to "bind" a nonexistent property
  175. _getValue_unavailable: function () {},
  176. _setValue_unavailable: function () {},
  177. BindingType: {
  178. Direct: 0,
  179. EntireArray: 1,
  180. ArrayElement: 2,
  181. HasFromToArray: 3
  182. },
  183. Versioning: {
  184. None: 0,
  185. NeedsUpdate: 1,
  186. MatrixWorldNeedsUpdate: 2
  187. },
  188. GetterByBindingType: [
  189. function getValue_direct( buffer, offset ) {
  190. buffer[ offset ] = this.node[ this.propertyName ];
  191. },
  192. function getValue_array( buffer, offset ) {
  193. var source = this.resolvedProperty;
  194. for ( var i = 0, n = source.length; i !== n; ++ i ) {
  195. buffer[ offset ++ ] = source[ i ];
  196. }
  197. },
  198. function getValue_arrayElement( buffer, offset ) {
  199. buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
  200. },
  201. function getValue_toArray( buffer, offset ) {
  202. this.resolvedProperty.toArray( buffer, offset );
  203. }
  204. ],
  205. SetterByBindingTypeAndVersioning: [
  206. [
  207. // Direct
  208. function setValue_direct( buffer, offset ) {
  209. this.targetObject[ this.propertyName ] = buffer[ offset ];
  210. },
  211. function setValue_direct_setNeedsUpdate( buffer, offset ) {
  212. this.targetObject[ this.propertyName ] = buffer[ offset ];
  213. this.targetObject.needsUpdate = true;
  214. },
  215. function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
  216. this.targetObject[ this.propertyName ] = buffer[ offset ];
  217. this.targetObject.matrixWorldNeedsUpdate = true;
  218. }
  219. ], [
  220. // EntireArray
  221. function setValue_array( buffer, offset ) {
  222. var dest = this.resolvedProperty;
  223. for ( var i = 0, n = dest.length; i !== n; ++ i ) {
  224. dest[ i ] = buffer[ offset ++ ];
  225. }
  226. },
  227. function setValue_array_setNeedsUpdate( buffer, offset ) {
  228. var dest = this.resolvedProperty;
  229. for ( var i = 0, n = dest.length; i !== n; ++ i ) {
  230. dest[ i ] = buffer[ offset ++ ];
  231. }
  232. this.targetObject.needsUpdate = true;
  233. },
  234. function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {
  235. var dest = this.resolvedProperty;
  236. for ( var i = 0, n = dest.length; i !== n; ++ i ) {
  237. dest[ i ] = buffer[ offset ++ ];
  238. }
  239. this.targetObject.matrixWorldNeedsUpdate = true;
  240. }
  241. ], [
  242. // ArrayElement
  243. function setValue_arrayElement( buffer, offset ) {
  244. this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
  245. },
  246. function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
  247. this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
  248. this.targetObject.needsUpdate = true;
  249. },
  250. function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
  251. this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
  252. this.targetObject.matrixWorldNeedsUpdate = true;
  253. }
  254. ], [
  255. // HasToFromArray
  256. function setValue_fromArray( buffer, offset ) {
  257. this.resolvedProperty.fromArray( buffer, offset );
  258. },
  259. function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
  260. this.resolvedProperty.fromArray( buffer, offset );
  261. this.targetObject.needsUpdate = true;
  262. },
  263. function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
  264. this.resolvedProperty.fromArray( buffer, offset );
  265. this.targetObject.matrixWorldNeedsUpdate = true;
  266. }
  267. ]
  268. ],
  269. getValue: function getValue_unbound( targetArray, offset ) {
  270. this.bind();
  271. this.getValue( targetArray, offset );
  272. // Note: This class uses a State pattern on a per-method basis:
  273. // 'bind' sets 'this.getValue' / 'setValue' and shadows the
  274. // prototype version of these methods with one that represents
  275. // the bound state. When the property is not found, the methods
  276. // become no-ops.
  277. },
  278. setValue: function getValue_unbound( sourceArray, offset ) {
  279. this.bind();
  280. this.setValue( sourceArray, offset );
  281. },
  282. // create getter / setter pair for a property in the scene graph
  283. bind: function () {
  284. var targetObject = this.node,
  285. parsedPath = this.parsedPath,
  286. objectName = parsedPath.objectName,
  287. propertyName = parsedPath.propertyName,
  288. propertyIndex = parsedPath.propertyIndex;
  289. if ( ! targetObject ) {
  290. targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode;
  291. this.node = targetObject;
  292. }
  293. // set fail state so we can just 'return' on error
  294. this.getValue = this._getValue_unavailable;
  295. this.setValue = this._setValue_unavailable;
  296. // ensure there is a value node
  297. if ( ! targetObject ) {
  298. console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' );
  299. return;
  300. }
  301. if ( objectName ) {
  302. var objectIndex = parsedPath.objectIndex;
  303. // special cases were we need to reach deeper into the hierarchy to get the face materials....
  304. switch ( objectName ) {
  305. case 'materials':
  306. if ( ! targetObject.material ) {
  307. console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this );
  308. return;
  309. }
  310. if ( ! targetObject.material.materials ) {
  311. console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this );
  312. return;
  313. }
  314. targetObject = targetObject.material.materials;
  315. break;
  316. case 'bones':
  317. if ( ! targetObject.skeleton ) {
  318. console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this );
  319. return;
  320. }
  321. // potential future optimization: skip this if propertyIndex is already an integer
  322. // and convert the integer string to a true integer.
  323. targetObject = targetObject.skeleton.bones;
  324. // support resolving morphTarget names into indices.
  325. for ( var i = 0; i < targetObject.length; i ++ ) {
  326. if ( targetObject[ i ].name === objectIndex ) {
  327. objectIndex = i;
  328. break;
  329. }
  330. }
  331. break;
  332. default:
  333. if ( targetObject[ objectName ] === undefined ) {
  334. console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this );
  335. return;
  336. }
  337. targetObject = targetObject[ objectName ];
  338. }
  339. if ( objectIndex !== undefined ) {
  340. if ( targetObject[ objectIndex ] === undefined ) {
  341. console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject );
  342. return;
  343. }
  344. targetObject = targetObject[ objectIndex ];
  345. }
  346. }
  347. // resolve property
  348. var nodeProperty = targetObject[ propertyName ];
  349. if ( nodeProperty === undefined ) {
  350. var nodeName = parsedPath.nodeName;
  351. console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName +
  352. '.' + propertyName + ' but it wasn\'t found.', targetObject );
  353. return;
  354. }
  355. // determine versioning scheme
  356. var versioning = this.Versioning.None;
  357. if ( targetObject.needsUpdate !== undefined ) { // material
  358. versioning = this.Versioning.NeedsUpdate;
  359. this.targetObject = targetObject;
  360. } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
  361. versioning = this.Versioning.MatrixWorldNeedsUpdate;
  362. this.targetObject = targetObject;
  363. }
  364. // determine how the property gets bound
  365. var bindingType = this.BindingType.Direct;
  366. if ( propertyIndex !== undefined ) {
  367. // access a sub element of the property array (only primitives are supported right now)
  368. if ( propertyName === "morphTargetInfluences" ) {
  369. // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
  370. // support resolving morphTarget names into indices.
  371. if ( ! targetObject.geometry ) {
  372. console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this );
  373. return;
  374. }
  375. if ( targetObject.geometry.isBufferGeometry ) {
  376. if ( ! targetObject.geometry.morphAttributes ) {
  377. console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this );
  378. return;
  379. }
  380. for ( var i = 0; i < this.node.geometry.morphAttributes.position.length; i ++ ) {
  381. if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) {
  382. propertyIndex = i;
  383. break;
  384. }
  385. }
  386. } else {
  387. if ( ! targetObject.geometry.morphTargets ) {
  388. console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this );
  389. return;
  390. }
  391. for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
  392. if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) {
  393. propertyIndex = i;
  394. break;
  395. }
  396. }
  397. }
  398. }
  399. bindingType = this.BindingType.ArrayElement;
  400. this.resolvedProperty = nodeProperty;
  401. this.propertyIndex = propertyIndex;
  402. } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
  403. // must use copy for Object3D.Euler/Quaternion
  404. bindingType = this.BindingType.HasFromToArray;
  405. this.resolvedProperty = nodeProperty;
  406. } else if ( Array.isArray( nodeProperty ) ) {
  407. bindingType = this.BindingType.EntireArray;
  408. this.resolvedProperty = nodeProperty;
  409. } else {
  410. this.propertyName = propertyName;
  411. }
  412. // select getter / setter
  413. this.getValue = this.GetterByBindingType[ bindingType ];
  414. this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
  415. },
  416. unbind: function () {
  417. this.node = null;
  418. // back to the prototype version of getValue / setValue
  419. // note: avoiding to mutate the shape of 'this' via 'delete'
  420. this.getValue = this._getValue_unbound;
  421. this.setValue = this._setValue_unbound;
  422. }
  423. } );
  424. //!\ DECLARE ALIAS AFTER assign prototype !
  425. Object.assign( PropertyBinding.prototype, {
  426. // initial state of these methods that calls 'bind'
  427. _getValue_unbound: PropertyBinding.prototype.getValue,
  428. _setValue_unbound: PropertyBinding.prototype.setValue,
  429. } );
  430. export { PropertyBinding };
粤ICP备19079148号