babylon.primitiveCollisionManager.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. module BABYLON {
  2. export abstract class PirimitiveCollisionManagerBase {
  3. constructor(owner: Canvas2D) {
  4. this._owner = owner;
  5. }
  6. abstract addActor(actor: Prim2DBase, deep: boolean): ActorInfo;
  7. abstract removeActor(actor: Prim2DBase);
  8. abstract update();
  9. abstract get leftBorderIntersectedActors(): ObservableStringDictionary<Prim2DBase>;
  10. abstract get bottomBorderIntersectedActors(): ObservableStringDictionary<Prim2DBase>;
  11. abstract get rightBorderIntersectedActors(): ObservableStringDictionary<Prim2DBase>;
  12. abstract get topBorderIntersectedActors(): ObservableStringDictionary<Prim2DBase>;
  13. abstract get intersectedActors(): ObservableStringDictionary<{ a: Prim2DBase, b: Prim2DBase }>;
  14. protected _owner: Canvas2D;
  15. }
  16. export class ActorInfo {
  17. constructor(owner: BasicPrimitiviceCollisionManager, actor: Prim2DBase, deep: boolean) {
  18. this.owner = owner;
  19. this.prim = actor;
  20. this.flags = 0;
  21. this.presentInClusters = new StringDictionary<ClusterInfo>();
  22. this.intersectWith = new ObservableStringDictionary<ActorInfo>(false);
  23. this.setFlags((deep ? ActorInfo.flagDeep : 0) | ActorInfo.flagDirty);
  24. let bi = (deep ? actor.boundingInfo : actor.levelBoundingInfo);
  25. // Dirty Actor if its WorldAABB changed
  26. bi.worldAABBDirtyObservable.add((e, d) => {
  27. this.owner.actorDirty(this);
  28. });
  29. // Dirty Actor if it's getting enabled/disabled
  30. actor.propertyChanged.add((e, d) => {
  31. if (d.mask === -1) {
  32. return;
  33. }
  34. this.setFlagsValue(ActorInfo.flagEnabled, e.newValue === true);
  35. this.owner.actorDirty(this);
  36. }, Prim2DBase.isVisibleProperty.flagId);
  37. }
  38. setFlags(flags: number) {
  39. this.flags |= flags;
  40. }
  41. clearFlags(flags: number) {
  42. this.flags &= ~flags;
  43. }
  44. isAllFlagsSet(flags: number) {
  45. return (this.flags & flags) === flags;
  46. }
  47. isSomeFlagsSet(flags: number) {
  48. return (this.flags & flags) !== 0;
  49. }
  50. setFlagsValue(flags: number, value: boolean) {
  51. if (value) {
  52. this.flags |= flags;
  53. } else {
  54. this.flags &= ~flags;
  55. }
  56. }
  57. get worldAABB(): Vector4 {
  58. return (this.isSomeFlagsSet(ActorInfo.flagDeep) ? this.prim.boundingInfo : this.prim.levelBoundingInfo).worldAABB;
  59. }
  60. get isEnabled(): boolean {
  61. return this.isSomeFlagsSet(ActorInfo.flagEnabled);
  62. }
  63. get isDeep(): boolean {
  64. return this.isSomeFlagsSet(ActorInfo.flagDeep);
  65. }
  66. get isDirty(): boolean {
  67. return this.isSomeFlagsSet(ActorInfo.flagDirty);
  68. }
  69. get isRemoved(): boolean {
  70. return this.isSomeFlagsSet(ActorInfo.flagRemoved);
  71. }
  72. prim: Prim2DBase;
  73. flags: number;
  74. owner: BasicPrimitiviceCollisionManager;
  75. presentInClusters: StringDictionary<ClusterInfo>;
  76. intersectWith: ObservableStringDictionary<ActorInfo>;
  77. public static flagDeep = 0x0001; // set if the actor boundingInfo must be used instead of the levelBoundingInfo
  78. public static flagEnabled = 0x0002; // set if the actor is enabled and should be considered for intersection tests
  79. public static flagDirty = 0x0004; // set if the actor's AABB is dirty
  80. public static flagRemoved = 0x0008; // set if the actor was removed from the PCM
  81. }
  82. class ClusterInfo {
  83. constructor() {
  84. this.actors = new StringDictionary<ActorInfo>();
  85. }
  86. clear() {
  87. this.actors.clear();
  88. }
  89. actors: StringDictionary<ActorInfo>;
  90. }
  91. export class BasicPrimitiviceCollisionManager extends PirimitiveCollisionManagerBase {
  92. constructor(owner: Canvas2D, enableBorders: boolean) {
  93. super(owner);
  94. this._actors = new StringDictionary<ActorInfo>();
  95. this._dirtyActors = new StringDictionary<ActorInfo>();
  96. this._clusters = null;
  97. this._maxActorByCluster = 0;
  98. this._AABBRenderPrim = null;
  99. this._canvasSize = Size.Zero();
  100. this._ClusterRenderPrim = null;
  101. this._debugTextBackground = null;
  102. this._clusterDirty = true;
  103. this._clusterSize = new Size(2, 2);
  104. this._clusterStep = Vector2.Zero();
  105. this._lastClusterResizeCounter = 0;
  106. this._freeClusters = new Array<ClusterInfo>();
  107. this._enableBorder = enableBorders;
  108. this._debugUpdateOpCount = new PerfCounter();
  109. this._debugUpdateTime = new PerfCounter();
  110. this._intersectedActors = new ObservableStringDictionary<{ a: Prim2DBase; b: Prim2DBase }>(false);
  111. this._borderIntersecteddActors = new Array<ObservableStringDictionary<Prim2DBase>>(4);
  112. for (let j = 0; j < 4; j++) {
  113. this._borderIntersecteddActors[j] = new ObservableStringDictionary<Prim2DBase>(false);
  114. }
  115. let flagId = Canvas2D.actualSizeProperty.flagId;
  116. if (!BasicPrimitiviceCollisionManager.WAABBCorners) {
  117. BasicPrimitiviceCollisionManager.WAABBCorners = new Array<Vector2>(4);
  118. for (let i = 0; i < 4; i++) {
  119. BasicPrimitiviceCollisionManager.WAABBCorners[i] = Vector2.Zero();
  120. }
  121. BasicPrimitiviceCollisionManager.WAABBCornersCluster = new Array<Vector2>(4);
  122. for (let i = 0; i < 4; i++) {
  123. BasicPrimitiviceCollisionManager.WAABBCornersCluster[i] = Vector2.Zero();
  124. }
  125. }
  126. owner.propertyChanged.add((e: PropertyChangedInfo, d) => {
  127. if (d.mask === -1) {
  128. return;
  129. }
  130. this._clusterDirty = true;
  131. console.log("canvas size changed");
  132. }, flagId);
  133. this.debugRenderAABB = true;
  134. this.debugRenderClusters = true;
  135. this.debugStats = true;
  136. }
  137. addActor(actor: Prim2DBase, deep: boolean): ActorInfo {
  138. return this._actors.getOrAddWithFactory(actor.uid, () => {
  139. let ai = new ActorInfo(this, actor, deep);
  140. this.actorDirty(ai);
  141. return ai;
  142. });
  143. }
  144. removeActor(actor: Prim2DBase) {
  145. let ai = this._actors.getAndRemove(actor.uid);
  146. ai.setFlags(ActorInfo.flagRemoved);
  147. this.actorDirty(ai);
  148. }
  149. actorDirty(actor: ActorInfo) {
  150. actor.setFlags(ActorInfo.flagDirty);
  151. this._dirtyActors.add(actor.prim.uid, actor);
  152. }
  153. update() {
  154. this._canvasSize.copyFrom(this._owner.actualSize);
  155. // Should we update the WireFrame2D Primitive that displays the WorldAABB ?
  156. if (this.debugRenderAABB) {
  157. if (this._dirtyActors.count > 0) {
  158. this._updateAABBDisplay();
  159. }
  160. }
  161. let cw = this._clusterSize.width;
  162. let ch = this._clusterSize.height;
  163. // Check for Cluster resize
  164. if (((this._clusterSize.width < 16 && this._clusterSize.height < 16 && this._maxActorByCluster >= 10) ||
  165. (this._clusterSize.width > 2 && this._clusterSize.height > 2 && this._maxActorByCluster <= 7)) &&
  166. this._lastClusterResizeCounter > 100) {
  167. if (this._maxActorByCluster >= 10) {
  168. ++cw;
  169. ++ch;
  170. } else {
  171. --cw;
  172. --ch;
  173. }
  174. console.log(`Change cluster size to ${cw}:${ch}, max actor ${this._maxActorByCluster}`);
  175. this._clusterDirty = true;
  176. }
  177. // Should we update the WireFrame2D Primitive that displays the clusters
  178. if (this.debugRenderClusters && this._clusterDirty) {
  179. this._updateClusterDisplay(cw, ch);
  180. }
  181. let updateStats = this.debugStats && (this._dirtyActors.count > 0 || this._clusterDirty);
  182. this._debugUpdateTime.beginMonitoring();
  183. // If the Cluster Size changed: rebuild it and add all actors. Otherwise add only new (dirty) actors
  184. if (this._clusterDirty) {
  185. this._initializeCluster(cw, ch);
  186. this._rebuildAllActors();
  187. } else {
  188. this._rebuildDirtyActors();
  189. ++this._lastClusterResizeCounter;
  190. }
  191. // Proceed to the collision detection between primitives
  192. this._collisionDetection();
  193. this._debugUpdateTime.endMonitoring();
  194. if (updateStats) {
  195. this._updateDebugStats();
  196. }
  197. // Reset the dirty actor list: everything is processed
  198. this._dirtyActors.clear();
  199. }
  200. public debugRenderAABB;
  201. public debugRenderClusters;
  202. public debugStats;
  203. get intersectedActors(): ObservableStringDictionary<{ a: Prim2DBase; b: Prim2DBase }> {
  204. return this._intersectedActors;
  205. }
  206. get leftBorderIntersectedActors(): ObservableStringDictionary<Prim2DBase> {
  207. return this._borderIntersecteddActors[0];
  208. }
  209. get bottomBorderIntersectedActors(): ObservableStringDictionary<Prim2DBase> {
  210. return this._borderIntersecteddActors[1];
  211. }
  212. get rightBorderIntersectedActors(): ObservableStringDictionary<Prim2DBase> {
  213. return this._borderIntersecteddActors[2];
  214. }
  215. get topBorderIntersectedActors(): ObservableStringDictionary<Prim2DBase> {
  216. return this._borderIntersecteddActors[3];
  217. }
  218. private _initializeCluster(countW: number, countH: number) {
  219. // Check for free
  220. if (this._clusters) {
  221. for (let w = 0; w < this._clusterSize.height; w++) {
  222. for (let h = 0; h < this._clusterSize.width; h++) {
  223. this._freeClusterInfo(this._clusters[w][h]);
  224. }
  225. }
  226. }
  227. // Allocate
  228. this._clusterSize.copyFromFloats(countW, countH);
  229. this._clusters = [];
  230. for (let w = 0; w < this._clusterSize.height; w++) {
  231. this._clusters[w] = [];
  232. for (let h = 0; h < this._clusterSize.width; h++) {
  233. let ci = this._allocClusterInfo();
  234. this._clusters[w][h] = ci;
  235. }
  236. }
  237. this._clusterStep.copyFromFloats(this._owner.actualWidth / countW, this._owner.actualHeight / countH);
  238. this._maxActorByCluster = 0;
  239. this._lastClusterResizeCounter = 0;
  240. this._clusterDirty = false;
  241. }
  242. private _rebuildAllActors() {
  243. this._actors.forEach((k, ai) => {
  244. this._processActor(ai);
  245. });
  246. }
  247. private _rebuildDirtyActors() {
  248. this._dirtyActors.forEach((k, ai) => {
  249. this._processActor(ai);
  250. });
  251. }
  252. static WAABBCorners: Array<Vector2> = null;
  253. static WAABBCornersCluster: Array<Vector2> = null;
  254. private _processActor(actor: ActorInfo) {
  255. // Check if the actor is being disabled or removed
  256. if (!actor.isEnabled || actor.isRemoved) {
  257. actor.presentInClusters.forEach((k, ci) => {
  258. ci.actors.remove(actor.prim.uid);
  259. });
  260. actor.presentInClusters.clear();
  261. return;
  262. }
  263. let wab = actor.worldAABB;
  264. // Build the worldAABB corners
  265. let wac = BasicPrimitiviceCollisionManager.WAABBCorners;
  266. wac[0].copyFromFloats(wab.x, wab.y); // Bottom/Left
  267. wac[1].copyFromFloats(wab.z, wab.y); // Bottom/Right
  268. wac[2].copyFromFloats(wab.z, wab.w); // Top/Right
  269. wac[3].copyFromFloats(wab.x, wab.w); // Top/Left
  270. let cs = this._clusterStep;
  271. let wacc = BasicPrimitiviceCollisionManager.WAABBCornersCluster;
  272. for (let i = 0; i < 4; i++) {
  273. let p = wac[i];
  274. let cx = (p.x - (p.x % cs.x)) / cs.x;
  275. let cy = (p.y - (p.y % cs.y)) / cs.y;
  276. wacc[i].copyFromFloats(Math.floor(cx), Math.floor(cy));
  277. }
  278. let opCount = 0;
  279. let totalClusters = 0;
  280. let newCI = new Array<ClusterInfo>();
  281. let sx = Math.max(0, wacc[0].x); // Start Cluster X
  282. let sy = Math.max(0, wacc[0].y); // Start Cluster Y
  283. let ex = Math.min(this._clusterSize.width - 1, wacc[2].x); // End Cluster X
  284. let ey = Math.min(this._clusterSize.height - 1, wacc[2].y); // End Cluster Y
  285. if (this._enableBorder) {
  286. if (wac[0].x < 0) {
  287. this._borderIntersecteddActors[0].add(actor.prim.uid, actor.prim);
  288. } else {
  289. this._borderIntersecteddActors[0].remove(actor.prim.uid);
  290. }
  291. if (wac[0].y < 0) {
  292. this._borderIntersecteddActors[1].add(actor.prim.uid, actor.prim);
  293. } else {
  294. this._borderIntersecteddActors[1].remove(actor.prim.uid);
  295. }
  296. if (wac[2].x >= this._canvasSize.width) {
  297. this._borderIntersecteddActors[2].add(actor.prim.uid, actor.prim);
  298. } else {
  299. this._borderIntersecteddActors[2].remove(actor.prim.uid);
  300. }
  301. if (wac[2].y >= this._canvasSize.height) {
  302. this._borderIntersecteddActors[3].add(actor.prim.uid, actor.prim);
  303. } else {
  304. this._borderIntersecteddActors[3].remove(actor.prim.uid);
  305. }
  306. }
  307. for (var y = sy; y <= ey; y++) {
  308. for (let x = sx; x <= ex; x++) {
  309. let k = `${x}:${y}`;
  310. let cx = x, cy = y;
  311. let ci = actor.presentInClusters.getOrAddWithFactory(k,
  312. (k) => {
  313. let nci = this._getCluster(cx, cy);
  314. nci.actors.add(actor.prim.uid, actor);
  315. this._maxActorByCluster = Math.max(this._maxActorByCluster, nci.actors.count);
  316. ++opCount;
  317. ++totalClusters;
  318. return nci;
  319. });
  320. newCI.push(ci);
  321. }
  322. }
  323. // Check if there were no change
  324. if (opCount === 0 && actor.presentInClusters.count === totalClusters) {
  325. return;
  326. }
  327. // Build the array of the cluster where the actor is no longer in
  328. let clusterToRemove = new Array<string>();
  329. actor.presentInClusters.forEach((k, ci) => {
  330. if (newCI.indexOf(ci) === -1) {
  331. clusterToRemove.push(k);
  332. // remove the primitive from the Cluster Info object
  333. ci.actors.remove(actor.prim.uid);
  334. }
  335. });
  336. // Remove these clusters from the actor's dictionary
  337. for (let key of clusterToRemove) {
  338. actor.presentInClusters.remove(key);
  339. }
  340. }
  341. private static CandidatesActors = new StringDictionary<ActorInfo>();
  342. private static PreviousIntersections = new StringDictionary<ActorInfo>();
  343. // The algorithm is simple, we have previously partitioned the Actors in the Clusters: each actor has a list of the Cluster(s) it's inside.
  344. // Then for a given Actor that is dirty we evaluate the intersection with all the other actors present in the same Cluster(s)
  345. // So it's basically O(n²), BUT only inside a Cluster and only for dirty Actors.
  346. private _collisionDetection() {
  347. let hash = BasicPrimitiviceCollisionManager.CandidatesActors;
  348. let prev = BasicPrimitiviceCollisionManager.PreviousIntersections;
  349. let opCount = 0;
  350. this._dirtyActors.forEach((k1, ai1) => {
  351. ++opCount;
  352. // Build the list of candidates
  353. hash.clear();
  354. ai1.presentInClusters.forEach((k, ci) => {
  355. ++opCount;
  356. ci.actors.forEach((k, v) => hash.add(k, v));
  357. });
  358. let wab1 = ai1.worldAABB;
  359. // Save the previous intersections
  360. prev.clear();
  361. prev.copyFrom(ai1.intersectWith);
  362. ai1.intersectWith.clear();
  363. // For each candidate
  364. hash.forEach((k2, ai2) => {
  365. ++opCount;
  366. // Check if we're testing against itself
  367. if (k1 === k2) {
  368. return;
  369. }
  370. let wab2 = ai2.worldAABB;
  371. if (wab2.z >= wab1.x && wab2.x <= wab1.z && wab2.w >= wab1.y && wab2.y <= wab1.w) {
  372. if (ai1.prim.intersectOtherPrim(ai2.prim)) {
  373. ++opCount;
  374. ai1.intersectWith.add(k2, ai2);
  375. if (k1 < k2) {
  376. this._intersectedActors.add(`${k1};${k2}`, { a: ai1.prim, b: ai2.prim });
  377. } else {
  378. this._intersectedActors.add(`${k2};${k1}`, { a: ai2.prim, b: ai1.prim });
  379. }
  380. }
  381. }
  382. });
  383. // Check and remove the associations that no longer exist in the main intersection list
  384. prev.forEach((k, ai) => {
  385. if (!ai1.intersectWith.contains(k)) {
  386. ++opCount;
  387. this._intersectedActors.remove(`${k<k1 ? k : k1};${k<k1 ? k1 : k}`);
  388. }
  389. });
  390. });
  391. this._debugUpdateOpCount.fetchNewFrame();
  392. this._debugUpdateOpCount.addCount(opCount, true);
  393. }
  394. private _getCluster(x: number, y: number): ClusterInfo {
  395. return this._clusters[x][y];
  396. }
  397. private _updateDebugStats() {
  398. let format = (v: number) => (Math.round(v*100)/100).toString();
  399. let txt = `Primitive Collision Stats\n` +
  400. ` - PCM Execution Time: ${format(this._debugUpdateTime.lastSecAverage)}ms\n` +
  401. ` - Operation Count: ${format(this._debugUpdateOpCount.current)}, (avg:${format(this._debugUpdateOpCount.lastSecAverage)}, t:${format(this._debugUpdateOpCount.total)})\n` +
  402. ` - Max Actor per Cluster: ${this._maxActorByCluster}\n` +
  403. ` - Intersections count: ${this.intersectedActors.count}`;
  404. if (!this._debugTextBackground) {
  405. this._debugTextBackground = new Rectangle2D({
  406. id: "###DEBUG PMC STATS###", parent: this._owner, marginAlignment: "h: left, v: top", fill: "#C0404080", padding: "top: 10, left: 10, right: 10, bottom: 10", roundRadius: 10, children: [
  407. new Text2D(txt, { id: "###DEBUG PMC TEXT###", fontName: "12pt Lucida Console" })
  408. ]
  409. });
  410. } else {
  411. let text2d = this._debugTextBackground.children[0] as Text2D;
  412. text2d.text = txt;
  413. }
  414. }
  415. private _updateAABBDisplay() {
  416. let g = new WireFrameGroup2D("main", new Color4(0.5, 0.8, 1.0, 1.0));
  417. let v = Vector2.Zero();
  418. this._actors.forEach((k, ai) => {
  419. if (ai.isEnabled) {
  420. let ab = ai.worldAABB;
  421. v.x = ab.x;
  422. v.y = ab.y;
  423. g.startLineStrip(v);
  424. v.x = ab.z;
  425. g.pushVertex(v);
  426. v.y = ab.w;
  427. g.pushVertex(v);
  428. v.x = ab.x;
  429. g.pushVertex(v);
  430. v.y = ab.y;
  431. g.endLineStrip(v);
  432. }
  433. });
  434. if (!this._AABBRenderPrim) {
  435. this._AABBRenderPrim = new WireFrame2D([g], { parent: this._owner, alignToPixel: true, id: "###DEBUG PCM AABB###" });
  436. } else {
  437. this._AABBRenderPrim.wireFrameGroups.set("main", g);
  438. this._AABBRenderPrim.wireFrameGroupsDirty();
  439. }
  440. }
  441. private _updateClusterDisplay(cw: number, ch: number) {
  442. let g = new WireFrameGroup2D("main", new Color4(0.8, 0.1, 0.5, 1.0));
  443. let v1 = Vector2.Zero();
  444. let v2 = Vector2.Zero();
  445. // Vertical lines
  446. let step = (this._owner.actualWidth-1) / cw;
  447. v1.y = 0;
  448. v2.y = this._owner.actualHeight;
  449. for (let x = 0; x <= cw; x++) {
  450. g.pushVertex(v1);
  451. g.pushVertex(v2);
  452. v1.x += step;
  453. v2.x += step;
  454. }
  455. // Horizontal lines
  456. step = (this._owner.actualHeight-1) / ch;
  457. v1.x = v1.y = v2.y = 0;
  458. v2.x = this._owner.actualWidth;
  459. for (let y = 0; y <= ch; y++) {
  460. g.pushVertex(v1);
  461. g.pushVertex(v2);
  462. v1.y += step;
  463. v2.y += step;
  464. }
  465. if (!this._ClusterRenderPrim) {
  466. this._ClusterRenderPrim = new WireFrame2D([g], { parent: this._owner, alignToPixel: true, id: "###DEBUG PCM Clusters###" });
  467. } else {
  468. this._ClusterRenderPrim.wireFrameGroups.set("main", g);
  469. this._ClusterRenderPrim.wireFrameGroupsDirty();
  470. }
  471. }
  472. // Basically: we don't want to spend our time playing with the GC each time the Cluster Array is rebuilt, so we keep a list of available
  473. // ClusterInfo object and we have two method to allocate/free them. This way we always deal with the same objects.
  474. // The free array never shrink, always grows...For the better...and the worst!
  475. private _allocClusterInfo(): ClusterInfo {
  476. if (this._freeClusters.length === 0) {
  477. for (let i = 0; i < 8; i++) {
  478. this._freeClusters.push(new ClusterInfo());
  479. }
  480. }
  481. return this._freeClusters.pop();
  482. }
  483. private _freeClusterInfo(ci: ClusterInfo) {
  484. ci.clear();
  485. this._freeClusters.push(ci);
  486. }
  487. private _canvasSize: Size;
  488. private _clusterDirty: boolean;
  489. private _clusterSize: Size;
  490. private _clusterStep: Vector2;
  491. private _clusters: ClusterInfo[][];
  492. private _maxActorByCluster: number;
  493. private _lastClusterResizeCounter: number;
  494. private _actors: StringDictionary<ActorInfo>;
  495. private _dirtyActors: StringDictionary<ActorInfo>;
  496. private _freeClusters: Array<ClusterInfo>;
  497. private _enableBorder: boolean;
  498. private _intersectedActors: ObservableStringDictionary<{ a: Prim2DBase; b: Prim2DBase }>;
  499. private _borderIntersecteddActors: ObservableStringDictionary<Prim2DBase>[];
  500. private _debugUpdateOpCount: PerfCounter;
  501. private _debugUpdateTime: PerfCounter;
  502. private _AABBRenderPrim: WireFrame2D;
  503. private _ClusterRenderPrim: WireFrame2D;
  504. private _debugTextBackground: Rectangle2D;
  505. }
  506. }