AnimationObjectGroup.js 9.0 KB

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