@@ -41,7 +41,7 @@ export default class HashGrid implements CollisionManager {
4141 private hashMap : number [ ] [ ] = [ ] ;
4242 private gameLeftX : number = 0 ;
4343 private gameTopY : number = 0
44- private collisionPairsSeen = new Uint32Array ( MAX_ENTITY_COUNT * MAX_ENTITY_COUNT / 32 )
44+ private collisionPairsSeen = new Uint32Array ( MAX_ENTITY_COUNT * ( MAX_ENTITY_COUNT - 1 ) / 2 / 32 )
4545
4646 public constructor ( game : GameServer ) {
4747 this . game = game ;
@@ -50,7 +50,7 @@ export default class HashGrid implements CollisionManager {
5050 public preTick ( tick : number ) : void {
5151 const widthInCells = ( this . game . arena . width + ( CELL_SIZE - 1 ) ) >> CELL_SHIFT ;
5252 const heightInCells = ( this . game . arena . height + ( CELL_SIZE - 1 ) ) >> CELL_SHIFT ;
53- this . hashMul = widthInCells >> CELL_SHIFT ;
53+ this . hashMul = widthInCells ;
5454 this . hashMap = Array ( widthInCells * heightInCells ) ;
5555 this . queryIdMap . fill ( 0 ) ;
5656 this . lastQueryId = 0 ;
@@ -65,7 +65,7 @@ export default class HashGrid implements CollisionManager {
6565 }
6666
6767 public insert ( entity : ObjectEntity ) {
68- if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot insert entity outside of tick" ) ;
68+ if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot insert() entity outside of tick" ) ;
6969 const { sides, size, width } = entity . physicsData . values ;
7070 const { x, y } = entity . positionData . values ;
7171 const isLine = sides === 2 ;
@@ -98,7 +98,7 @@ export default class HashGrid implements CollisionManager {
9898 halfWidth : number ,
9999 halfHeight : number
100100 ) : PackedEntitySet {
101- if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot insert entity outside of tick" ) ;
101+ if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot retrieve() entity outside of tick" ) ;
102102 const result = this . resultSet ;
103103 result . clear ( ) ;
104104
@@ -140,7 +140,7 @@ export default class HashGrid implements CollisionManager {
140140 halfHeight : number ,
141141 predicate : ( entity : ObjectEntity ) => boolean
142142 ) : ObjectEntity | null {
143- if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot insert entity outside of tick" ) ;
143+ if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot getFirstMatch() outside of tick" ) ;
144144
145145 const startX = ( centerX - halfWidth - this . gameLeftX ) >> CELL_SHIFT ;
146146 const startY = ( centerY - halfHeight - this . gameTopY ) >> CELL_SHIFT ;
@@ -180,9 +180,9 @@ export default class HashGrid implements CollisionManager {
180180
181181 // No longer used
182182 public retrieveEntitiesByEntity ( entity : ObjectEntity ) : PackedEntitySet {
183- if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot insert entity outside of tick" ) ;
183+ if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot retrieveEntitiesByEntity() outside of tick" ) ;
184184 const { sides, size, width } = entity . physicsData . values ;
185- const { x, y } = entity . positionData ;
185+ const { x, y } = entity . positionData . values ;
186186 const isLine = sides === 2 ;
187187 const halfWidth = isLine ? size / 2 : size ;
188188 const halfHeight = isLine ? width / 2 : size ;
@@ -192,7 +192,7 @@ export default class HashGrid implements CollisionManager {
192192 public forEachCollisionPair (
193193 callback : ( entityA : ObjectEntity , entityB : ObjectEntity ) => void
194194 ) : void {
195- if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot insert entity outside of tick" ) ;
195+ if ( this . isLocked ) throw new Error ( "HashGrid is locked! Cannot forEachCollisionPair() entity outside of tick" ) ;
196196
197197 const collisionsSeen = this . collisionPairsSeen ;
198198 collisionsSeen . fill ( 0 ) ;
@@ -213,27 +213,20 @@ export default class HashGrid implements CollisionManager {
213213 const entityB = this . game . entities . inner [ eidB ] as ObjectEntity ;
214214 if ( ! entityB || entityB . hash === 0 ) continue ;
215215
216- if ( eidA < eidB ) {
217- // Prevent extra-cell duplicates
218- const pairHash = ( eidA << MAX_ENTITY_ID_BITS ) | eidB ;
219- const pairHashIndex = pairHash >>> 5 ;
220- const pairHashBit = 1 << ( pairHash & 31 ) ;
221- if ( ( collisionsSeen [ pairHashIndex ] & pairHashBit ) !== 0 ) continue ;
222- collisionsSeen [ pairHashIndex ] |= pairHashBit ;
223-
224- // Ensure (x, y) -> x.id < y.id
225- callback ( entityA , entityB ) ;
226- } else {
227- // Prevent extra-cell duplicates
228- const pairHash = ( eidB << MAX_ENTITY_ID_BITS ) | eidA ;
229- const pairHashIndex = pairHash >>> 5 ;
230- const pairHashBit = 1 << ( pairHash & 31 ) ;
231- if ( ( collisionsSeen [ pairHashIndex ] & pairHashBit ) !== 0 ) continue ;
232- collisionsSeen [ pairHashIndex ] |= pairHashBit ;
233-
234- // Ensure (x, y) -> x.id < y.id
235- callback ( entityB , entityA ) ;
236- }
216+ // Ensure eidA < eidB for triangular matrix indexing
217+ const [ idA , idB ] = eidA < eidB ? [ eidA , eidB ] : [ eidB , eidA ] ;
218+ const [ entA , entB ] = eidA < eidB ? [ entityA , entityB ] : [ entityB , entityA ] ;
219+
220+ // Triangular matrix index: row * (row - 1) / 2 + col, where row > col
221+ const triangularIndex = Math . floor ( idB * ( idB - 1 ) / 2 ) + idA ;
222+ const arrayIndex = triangularIndex >>> 5 ;
223+ const bitIndex = triangularIndex & 31 ;
224+ const bitMask = 1 << bitIndex ;
225+
226+ if ( ( collisionsSeen [ arrayIndex ] & bitMask ) !== 0 ) continue ;
227+ collisionsSeen [ arrayIndex ] |= bitMask ;
228+
229+ callback ( entA , entB ) ;
237230 }
238231 }
239232 }
0 commit comments