AnimationObjectGroup.js 9.0 KB

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