PropertyBinding.js 14 KB

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