AnimationObjectGroup.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. /**
  2. *
  3. * A group of objects that receives a shared animation state.
  4. *
  5. * Usage:
  6. *
  7. * - Add objects you would otherwise pass as 'root' to the
  8. * constructor or the .clipAction method of AnimationMixer.
  9. *
  10. * - Instead pass this object as 'root'.
  11. *
  12. * - You can also add and remove objects later when the mixer
  13. * is running.
  14. *
  15. * Note:
  16. *
  17. * Objects of this class appear as one object to the mixer,
  18. * so cache control of the individual objects must be done
  19. * on the group.
  20. *
  21. * Limitation:
  22. *
  23. * - The animated properties must be compatible among the
  24. * all objects in the group.
  25. *
  26. * - A single property can either be controlled through a
  27. * target group or directly, but not both.
  28. *
  29. * @author tschw
  30. */
  31. THREE.AnimationObjectGroup = function( var_args ) {
  32. this.uuid = THREE.Math.generateUUID();
  33. // cached objects followed by the active ones
  34. this._objects = Array.prototype.slice.call( arguments );
  35. this.nCachedObjects_ = 0; // threshold
  36. // note: read by PropertyBinding.Composite
  37. var indices = {};
  38. this._indicesByUUID = indices; // for bookkeeping
  39. for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
  40. indices[ arguments[ i ].uuid ] = i;
  41. }
  42. this._paths = []; // inside: string
  43. this._parsedPaths = []; // inside: { we don't care, here }
  44. this._bindings = []; // inside: Array< PropertyBinding >
  45. this._bindingsIndicesByPath = {}; // inside: indices in these arrays
  46. var scope = this;
  47. this.stats = {
  48. objects: {
  49. get total() { return scope._objects.length; },
  50. get inUse() { return this.total - scope.nCachedObjects_; }
  51. },
  52. get bindingsPerObject() { return scope._bindings.length; }
  53. };
  54. };
  55. THREE.AnimationObjectGroup.prototype = {
  56. constructor: THREE.AnimationObjectGroup,
  57. add: function( var_args ) {
  58. var objects = this._objects,
  59. nObjects = objects.length,
  60. nCachedObjects = this.nCachedObjects_,
  61. indicesByUUID = this._indicesByUUID,
  62. paths = this._paths,
  63. parsedPaths = this._parsedPaths,
  64. bindings = this._bindings,
  65. nBindings = bindings.length;
  66. for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
  67. var object = arguments[ i ],
  68. uuid = object.uuid,
  69. index = indicesByUUID[ uuid ];
  70. if ( index === undefined ) {
  71. // unknown object -> add it to the ACTIVE region
  72. index = nObjects ++;
  73. indicesByUUID[ uuid ] = index;
  74. objects.push( object );
  75. // accounting is done, now do the same for all bindings
  76. for ( var j = 0, m = nBindings; j !== m; ++ j ) {
  77. bindings[ j ].push(
  78. new THREE.PropertyBinding(
  79. object, paths[ j ], parsedPaths[ j ] ) );
  80. }
  81. } else if ( index < nCachedObjects ) {
  82. var knownObject = objects[ index ];
  83. // move existing object to the ACTIVE region
  84. var firstActiveIndex = -- nCachedObjects,
  85. lastCachedObject = objects[ firstActiveIndex ];
  86. indicesByUUID[ lastCachedObject.uuid ] = index;
  87. objects[ index ] = lastCachedObject;
  88. indicesByUUID[ uuid ] = firstActiveIndex;
  89. objects[ firstActiveIndex ] = object;
  90. // accounting is done, now do the same for all bindings
  91. for ( var j = 0, m = nBindings; j !== m; ++ j ) {
  92. var bindingsForPath = bindings[ j ],
  93. lastCached = bindingsForPath[ firstActiveIndex ],
  94. binding = bindingsForPath[ index ];
  95. bindingsForPath[ index ] = lastCached;
  96. if ( binding === undefined ) {
  97. // since we do not bother to create new bindings
  98. // for objects that are cached, the binding may
  99. // or may not exist
  100. binding = new THREE.PropertyBinding(
  101. object, paths[ j ], parsedPaths[ j ] );
  102. }
  103. bindingsForPath[ firstActiveIndex ] = binding;
  104. }
  105. } else if ( objects[ index ] !== knownObject) {
  106. console.error( "Different objects with the same UUID " +
  107. "detected. Clean the caches or recreate your " +
  108. "infrastructure when reloading scenes..." );
  109. } // else the object is already where we want it to be
  110. } // for arguments
  111. this.nCachedObjects_ = nCachedObjects;
  112. },
  113. remove: function( var_args ) {
  114. var objects = this._objects,
  115. nObjects = objects.length,
  116. nCachedObjects = this.nCachedObjects_,
  117. indicesByUUID = this._indicesByUUID,
  118. bindings = this._bindings,
  119. nBindings = bindings.length;
  120. for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
  121. var object = arguments[ i ],
  122. uuid = object.uuid,
  123. index = indicesByUUID[ uuid ];
  124. if ( index !== undefined && index >= nCachedObjects ) {
  125. // move existing object into the CACHED region
  126. var lastCachedIndex = nCachedObjects ++,
  127. firstActiveObject = objects[ lastCachedIndex ];
  128. indicesByUUID[ firstActiveObject.uuid ] = index;
  129. objects[ index ] = firstActiveObject;
  130. indicesByUUID[ uuid ] = lastCachedIndex;
  131. objects[ lastCachedIndex ] = object;
  132. // accounting is done, now do the same for all bindings
  133. for ( var j = 0, m = nBindings; j !== m; ++ j ) {
  134. var bindingsForPath = bindings[ j ],
  135. firstActive = bindingsForPath[ lastCachedIndex ],
  136. binding = bindingsForPath[ index ];
  137. bindingsForPath[ index ] = firstActive;
  138. bindingsForPath[ lastCachedIndex ] = binding;
  139. }
  140. }
  141. } // for arguments
  142. this.nCachedObjects_ = nCachedObjects;
  143. },
  144. // remove & forget
  145. uncache: function( var_args ) {
  146. var objects = this._objects,
  147. nObjects = objects.length,
  148. nCachedObjects = this.nCachedObjects_,
  149. indicesByUUID = this._indicesByUUID,
  150. bindings = this._bindings,
  151. nBindings = bindings.length;
  152. for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
  153. var object = arguments[ i ],
  154. uuid = object.uuid,
  155. index = indicesByUUID[ uuid ];
  156. if ( index !== undefined ) {
  157. delete indicesByUUID[ uuid ];
  158. if ( index < nCachedObjects ) {
  159. // object is cached, shrink the CACHED region
  160. var firstActiveIndex = -- nCachedObjects,
  161. lastCachedObject = objects[ firstActiveIndex ],
  162. lastIndex = -- nObjects,
  163. lastObject = objects[ lastIndex ];
  164. // last cached object takes this object's place
  165. indicesByUUID[ lastCachedObject.uuid ] = index;
  166. objects[ index ] = lastCachedObject;
  167. // last object goes to the activated slot and pop
  168. indicesByUUID[ lastObject.uuid ] = firstActiveIndex;
  169. objects[ firstActiveIndex ] = lastObject;
  170. objects.pop();
  171. // accounting is done, now do the same for all bindings
  172. for ( var j = 0, m = nBindings; j !== m; ++ j ) {
  173. var bindingsForPath = bindings[ j ],
  174. lastCached = bindingsForPath[ firstActiveIndex ],
  175. last = bindingsForPath[ lastIndex ];
  176. bindingsForPath[ index ] = lastCached;
  177. bindingsForPath[ firstActiveIndex ] = last;
  178. bindingsForPath.pop();
  179. }
  180. } else {
  181. // object is active, just swap with the last and pop
  182. var lastIndex = -- nObjects,
  183. lastObject = objects[ lastIndex ];
  184. indicesByUUID[ lastObject.uuid ] = index;
  185. objects[ index ] = lastObject;
  186. objects.pop();
  187. // accounting is done, now do the same for all bindings
  188. for ( var j = 0, m = nBindings; j !== m; ++ j ) {
  189. var bindingsForPath = bindings[ j ];
  190. bindingsForPath[ index ] = bindingsForPath[ lastIndex ];
  191. bindingsForPath.pop();
  192. }
  193. } // cached or active
  194. } // if object is known
  195. } // for arguments
  196. this.nCachedObjects_ = nCachedObjects;
  197. },
  198. // Internal interface used by befriended PropertyBinding.Composite:
  199. subscribe_: function( path, parsedPath ) {
  200. // returns an array of bindings for the given path that is changed
  201. // according to the contained objects in the group
  202. var indicesByPath = this._bindingsIndicesByPath,
  203. index = indicesByPath[ path ],
  204. bindings = this._bindings;
  205. if ( index !== undefined ) return bindings[ index ];
  206. var paths = this._paths,
  207. parsedPaths = this._parsedPaths,
  208. objects = this._objects,
  209. nObjects = objects.length,
  210. nCachedObjects = this.nCachedObjects_,
  211. bindingsForPath = new Array( nObjects );
  212. index = bindings.length;
  213. indicesByPath[ path ] = index;
  214. paths.push( path );
  215. parsedPaths.push( parsedPath );
  216. bindings.push( bindingsForPath );
  217. for ( var i = nCachedObjects,
  218. n = objects.length; i !== n; ++ i ) {
  219. var object = objects[ i ];
  220. bindingsForPath[ i ] =
  221. new THREE.PropertyBinding( object, path, parsedPath );
  222. }
  223. return bindingsForPath;
  224. },
  225. unsubscribe_: function( path ) {
  226. // tells the group to forget about a property path and no longer
  227. // update the array previously obtained with 'subscribe_'
  228. var indicesByPath = this._bindingsIndicesByPath,
  229. index = indicesByPath[ path ];
  230. if ( index !== undefined ) {
  231. var paths = this._paths,
  232. parsedPaths = this._parsedPaths,
  233. bindings = this._bindings,
  234. lastBindingsIndex = bindings.length - 1,
  235. lastBindings = bindings[ lastBindingsIndex ],
  236. lastBindingsPath = path[ lastBindingsIndex ];
  237. indicesByPath[ lastBindingsPath ] = index;
  238. bindings[ index ] = lastBindings;
  239. bindings.pop();
  240. parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];
  241. parsedPaths.pop();
  242. paths[ index ] = paths[ lastBindingsIndex ];
  243. paths.pop();
  244. }
  245. }
  246. };
粤ICP备19079148号