babylon.primitiveCollisionManager.ts 28 KB

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