PropertyBinding.js 15 KB

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