1 (* Copyright (C) DooM 2D:Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 {$modeswitch nestedprocvars}
25 GridDefaultTileSize
= 32;
28 GridQueryCB
= function (obj
: TObject
; tag
: Integer): Boolean is nested
; // return `true` to stop
33 TBodyProxy
= class(TObject
)
35 mX
, mY
, mWidth
, mHeight
: Integer; // aabb
36 mQueryMark
: DWord
; // was this object visited at this query?
40 prevLink
: TBodyProxy
; // only for used
41 nextLink
: TBodyProxy
; // either used or free
44 procedure setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer);
47 constructor Create (aGrid
: TBodyGrid
; aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer);
48 destructor Destroy (); override;
50 property x
: Integer read mX
;
51 property y
: Integer read mY
;
52 property width
: Integer read mWidth
;
53 property height
: Integer read mHeight
;
54 property obj
: TObject read mObj
;
55 property tag
: Integer read mTag
;
56 property grid
: TBodyGrid read mGrid
;
59 PGridCell
= ^TGridCell
;
62 next
: Integer; // in this cell; index in mCells
65 GridInternalCB
= function (grida
: Integer): Boolean is nested
; // return `true` to stop
67 TBodyGrid
= class(TObject
)
70 mMinX
, mMinY
: Integer; // so grids can start at any origin
71 mWidth
, mHeight
: Integer; // in tiles
72 mGrid
: array of Integer; // mWidth*mHeight, index in mCells
73 mCells
: array of TGridCell
; // cell pool
74 mFreeCell
: Integer; // first free cell index or -1
77 mProxyFree
: TBodyProxy
; // free
78 mProxyList
: TBodyProxy
; // used
79 mProxyCount
: Integer; // total allocated
82 function allocCell
: Integer;
83 procedure freeCell (idx
: Integer); // `next` is simply overwritten
85 function allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer): TBodyProxy
;
86 procedure freeProxy (body
: TBodyProxy
);
88 procedure insert (body
: TBodyProxy
);
89 procedure remove (body
: TBodyProxy
);
91 function forGridRect (x
, y
, w
, h
: Integer; cb
: GridInternalCB
): Boolean;
94 constructor Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer; aTileSize
: Integer=GridDefaultTileSize
);
95 destructor Destroy (); override;
97 function insertBody (aObj
: TObject
; ax
, ay
, aWidth
, aHeight
: Integer; aTag
: Integer=0): TBodyProxy
;
98 procedure removeBody (aObj
: TBodyProxy
); // WARNING! this will NOT destroy proxy!
100 procedure moveBody (body
: TBodyProxy
; dx
, dy
: Integer);
101 procedure resizeBody (body
: TBodyProxy
; sx
, sy
: Integer);
102 procedure moveResizeBody (body
: TBodyProxy
; dx
, dy
, sx
, sy
: Integer);
104 function forEachInAABB (x
, y
, w
, h
: Integer; cb
: GridQueryCB
): Boolean;
106 function getProxyForBody (aObj
: TObject
; x
, y
, w
, h
: Integer): TBodyProxy
;
108 procedure dumpStats ();
113 TBinaryHeapLessFn
= function (a
, b
: TObject
): Boolean;
115 TBinaryHeapObj
= class(TObject
)
117 elem
: array of TObject
;
119 lessfn
: TBinaryHeapLessFn
;
122 procedure heapify (root
: Integer);
125 constructor Create (alessfn
: TBinaryHeapLessFn
);
126 destructor Destroy (); override;
130 procedure insert (val
: TObject
);
132 function front (): TObject
;
133 procedure popFront ();
135 property count
: Integer read elemUsed
;
145 // ////////////////////////////////////////////////////////////////////////// //
146 constructor TBodyProxy
.Create (aGrid
: TBodyGrid
; aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer);
149 setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
153 destructor TBodyProxy
.Destroy ();
159 procedure TBodyProxy
.setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer);
173 // ////////////////////////////////////////////////////////////////////////// //
174 constructor TBodyGrid
.Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer; aTileSize
: Integer=GridDefaultTileSize
);
178 if aTileSize
< 1 then aTileSize
:= 1;
179 if aTileSize
> 8192 then aTileSize
:= 8192; // arbitrary limit
180 if aPixWidth
< aTileSize
then aPixWidth
:= aTileSize
;
181 if aPixHeight
< aTileSize
then aPixHeight
:= aTileSize
;
182 mTileSize
:= aTileSize
;
185 mWidth
:= (aPixWidth
+aTileSize
-1) div aTileSize
;
186 mHeight
:= (aPixHeight
+aTileSize
-1) div aTileSize
;
187 SetLength(mGrid
, mWidth
*mHeight
);
188 SetLength(mCells
, mWidth
*mHeight
);
191 for idx
:= 0 to High(mCells
) do
193 mCells
[idx
].body
:= nil;
194 mCells
[idx
].next
:= idx
+1;
196 mCells
[High(mCells
)].next
:= -1; // last cell
198 for idx
:= 0 to High(mGrid
) do mGrid
[idx
] := -1;
204 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
]), MSG_NOTIFY
);
208 destructor TBodyGrid
.Destroy ();
212 // free all owned proxies
213 while mProxyFree
<> nil do
216 mProxyFree
:= px
.nextLink
;
220 while mProxyList
<> nil do
223 mProxyList
:= px
.nextLink
;
233 procedure TBodyGrid
.dumpStats ();
235 idx
, mcb
, cidx
, cnt
: Integer;
238 for idx
:= 0 to High(mGrid
) do
245 cidx
:= mCells
[cidx
].next
;
247 if (mcb
< cnt
) then mcb
:= cnt
;
249 e_WriteLog(Format('grid size: %dx%d (tile size: %d); pix: %dx%d; used cells: %d; max bodies in cell: %d; proxies allocated: %d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
, mUsedCells
, mcb
, mProxyCount
]), MSG_NOTIFY
);
253 function TBodyGrid
.allocCell
: Integer;
257 if (mFreeCell
< 0) then
259 // no free cells, want more
260 mFreeCell
:= Length(mCells
);
261 SetLength(mCells
, mFreeCell
+32768); // arbitrary number
262 for idx
:= mFreeCell
to High(mCells
) do
264 mCells
[idx
].body
:= nil;
265 mCells
[idx
].next
:= idx
+1;
267 mCells
[High(mCells
)].next
:= -1; // last cell
270 mFreeCell
:= mCells
[result
].next
;
271 mCells
[result
].next
:= -1;
273 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
277 procedure TBodyGrid
.freeCell (idx
: Integer);
279 if (idx
>= 0) and (idx
< High(mCells
)) then
281 if mCells
[idx
].body
= nil then exit
; // the thing that should not be
282 mCells
[idx
].body
:= nil;
283 mCells
[idx
].next
:= mFreeCell
;
290 function TBodyGrid
.allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer): TBodyProxy
;
292 if (mProxyFree
= nil) then
294 // no free proxies, create new
295 result
:= TBodyProxy
.Create(self
, aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
301 result
:= mProxyFree
;
302 mProxyFree
:= result
.nextLink
;
303 result
.setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
306 result
.nextLink
:= mProxyList
;
307 if (mProxyList
<> nil) then mProxyList
.prevLink
:= result
;
308 mProxyList
:= result
;
311 procedure TBodyGrid
.freeProxy (body
: TBodyProxy
);
313 if body
= nil then exit
; // just in case
314 // remove from used list
315 if (body
.prevLink
= nil) then
318 if (body
<> mProxyList
) then raise Exception
.Create('wutafuuuuu in grid?');
319 mProxyList
:= body
.nextLink
;
323 body
.prevLink
.nextLink
:= body
.nextLink
;
325 if (body
.nextLink
<> nil) then body
.nextLink
.prevLink
:= body
.prevLink
;
327 //body.mObj := nil; //WARNING! DON'T DO THIS! `removeBody()` depends on valid mObj
328 body
.prevLink
:= nil;
329 body
.nextLink
:= mProxyFree
;
334 function TBodyGrid
.forGridRect (x
, y
, w
, h
: Integer; cb
: GridInternalCB
): Boolean;
339 if (w
< 1) or (h
< 1) or not assigned(cb
) then exit
;
344 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
345 if (x
>= mWidth
*mTileSize
) or (y
>= mHeight
*mTileSize
) then exit
;
346 for gy
:= y
div mTileSize
to (y
+h
-1) div mTileSize
do
348 if (gy
< 0) then continue
;
349 if (gy
>= mHeight
) then break
;
350 for gx
:= x
div mTileSize
to (x
+w
-1) div mTileSize
do
352 if (gx
< 0) then continue
;
353 if (gx
>= mWidth
) then break
;
354 if (cb(gy
*mWidth
+gx
)) then begin result
:= true; exit
; end;
360 procedure TBodyGrid
.insert (body
: TBodyProxy
);
362 function inserter (grida
: Integer): Boolean;
366 result
:= false; // never stop
367 // add body to the given grid cell
369 //e_WriteLog(Format(' 01: allocated cell for grid coords (%d,%d), body coords:(%d,%d): #%d', [gx, gy, dx, dy, cidx]), MSG_NOTIFY);
370 mCells
[cidx
].body
:= body
;
371 mCells
[cidx
].next
:= mGrid
[grida
];
372 mGrid
[grida
] := cidx
;
376 if body
= nil then exit
;
377 forGridRect(body
.mX
, body
.mY
, body
.mWidth
, body
.mHeight
, inserter
);
381 // absolutely not tested
382 procedure TBodyGrid
.remove (body
: TBodyProxy
);
384 function remover (grida
: Integer): Boolean;
386 pidx
, idx
, tmp
: Integer;
388 result
:= false; // never stop
389 // find and remove cell
394 tmp
:= mCells
[idx
].next
;
395 if (mCells
[idx
].body
= body
) then
397 if (pidx
= -1) then mGrid
[grida
] := tmp
else mCells
[pidx
].next
:= tmp
;
399 break
; // assume that we cannot have one object added to bucket twice
410 if body
= nil then exit
;
411 forGridRect(body
.mX
, body
.mY
, body
.mWidth
, body
.mHeight
, remover
);
415 function TBodyGrid
.insertBody (aObj
: TObject
; aX
, aY
, aWidth
, aHeight
: Integer; aTag
: Integer=0): TBodyProxy
;
417 result
:= allocProxy(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
422 // WARNING! this will NOT destroy proxy!
423 procedure TBodyGrid
.removeBody (aObj
: TBodyProxy
);
425 if aObj
= nil then exit
;
426 if (aObj
.mGrid
<> self
) then raise Exception
.Create('trying to remove alien proxy from grid');
430 if (mProxyFree
<> aObj
) then raise Exception
.Create('grid deletion invariant fucked');
431 mProxyFree
:= aObj
.nextLink
;
432 aObj
.nextLink
:= nil;
436 procedure TBodyGrid
.moveResizeBody (body
: TBodyProxy
; dx
, dy
, sx
, sy
: Integer);
438 if (body
= nil) or ((dx
= 0) and (dy
= 0) and (sx
= 0) and (sy
= 0)) then exit
;
442 Inc(body
.mWidth
, sx
);
443 Inc(body
.mHeight
, sy
);
447 procedure TBodyGrid
.moveBody (body
: TBodyProxy
; dx
, dy
: Integer);
449 moveResizeBody(body
, dx
, dy
, 0, 0);
452 procedure TBodyGrid
.resizeBody (body
: TBodyProxy
; sx
, sy
: Integer);
454 moveResizeBody(body
, 0, 0, sx
, sy
);
458 function TBodyGrid
.forEachInAABB (x
, y
, w
, h
: Integer; cb
: GridQueryCB
): Boolean;
459 function iterator (grida
: Integer): Boolean;
467 if (mCells
[idx
].body
<> nil) and (mCells
[idx
].body
.mQueryMark
<> mLastQuery
) then
469 //e_WriteLog(Format(' query #%d body hit: (%d,%d)-(%dx%d) tag:%d', [mLastQuery, mCells[idx].body.mX, mCells[idx].body.mY, mCells[idx].body.mWidth, mCells[idx].body.mHeight, mCells[idx].body.mTag]), MSG_NOTIFY);
470 mCells
[idx
].body
.mQueryMark
:= mLastQuery
;
471 if (cb(mCells
[idx
].body
.mObj
, mCells
[idx
].body
.mTag
)) then begin result
:= true; exit
; end;
473 idx
:= mCells
[idx
].next
;
481 if not assigned(cb
) then exit
;
483 // increase query counter
485 if (mLastQuery
= 0) then
487 // just in case of overflow
489 for idx
:= 0 to High(mCells
) do if (mCells
[idx
].body
<> nil) then mCells
[idx
].body
.mQueryMark
:= 0;
491 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
493 result
:= forGridRect(x
, y
, w
, h
, iterator
);
497 function TBodyGrid
.getProxyForBody (aObj
: TObject
; x
, y
, w
, h
: Integer): TBodyProxy
;
499 res
: TBodyProxy
= nil;
501 function iterator (grida
: Integer): Boolean;
509 if (mCells
[idx
].body
<> nil) and (mCells
[idx
].body
= aObj
) then
512 res
:= mCells
[idx
].body
;
515 idx
:= mCells
[idx
].next
;
521 if (aObj
= nil) then exit
;
522 forGridRect(x
, y
, w
, h
, iterator
);
527 // ////////////////////////////////////////////////////////////////////////// //
528 constructor TBinaryHeapObj
.Create (alessfn
: TBinaryHeapLessFn
);
530 if not assigned(alessfn
) then raise Exception
.Create('wutafuck?!');
532 SetLength(elem
, 8192); // 'cause why not?
537 destructor TBinaryHeapObj
.Destroy ();
543 procedure TBinaryHeapObj
.clear ();
549 procedure TBinaryHeapObj
.heapify (root
: Integer);
551 smallest
, right
: Integer;
556 smallest
:= 2*root
+1; // left child
557 if (smallest
>= elemUsed
) then break
; // anyway
558 right
:= smallest
+1; // right child
559 if not lessfn(elem
[smallest
], elem
[root
]) then smallest
:= root
;
560 if (right
< elemUsed
) and (lessfn(elem
[right
], elem
[smallest
])) then smallest
:= right
;
561 if (smallest
= root
) then break
;
564 elem
[root
] := elem
[smallest
];
565 elem
[smallest
] := tmp
;
571 procedure TBinaryHeapObj
.insert (val
: TObject
);
575 if (val
= nil) then exit
;
577 if (i
= Length(elem
)) then SetLength(elem
, Length(elem
)+16384); // arbitrary number
581 par
:= (i
-1) div 2; // parent
582 if not lessfn(val
, elem
[par
]) then break
;
583 elem
[i
] := elem
[par
];
589 function TBinaryHeapObj
.front (): TObject
;
591 if elemUsed
> 0 then result
:= elem
[0] else result
:= nil;
595 procedure TBinaryHeapObj
.popFront ();
597 if (elemUsed
> 0) then
600 elem
[0] := elem
[elemUsed
];