5b2f8b135d27583c5a849aaec6ab3e46f5d4ef7e
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/>.
16 // universal spatial grid
17 {$INCLUDE ../shared/a_modes.inc}
18 {$DEFINE grid_use_buckets}
24 GridDefaultTileSize
= 32;
25 GridCellBucketSize
= 8; // WARNING! can't be less than 2!
28 TGridQueryCB
= function (obj
: TObject
; objx
, objy
, objw
, objh
: Integer; tag
: Integer): Boolean is nested
; // return `true` to stop
35 PBodyProxyRec
= ^TBodyProxyRec
;
36 TBodyProxyRec
= record
38 mX
, mY
, mWidth
, mHeight
: Integer; // aabb
39 mQueryMark
: DWord
; // was this object visited at this query?
43 nextLink
: TBodyProxy
; // next free or nothing
46 procedure setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer);
49 //constructor Create (aGrid: TBodyGrid; aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer);
50 //destructor Destroy (); override;
52 property x
: Integer read mX
;
53 property y
: Integer read mY
;
54 property width
: Integer read mWidth
;
55 property height
: Integer read mHeight
;
56 property obj
: TObject read mObj
;
57 property tag
: Integer read mTag
;
58 //property grid: TBodyGrid read mGrid;
61 PGridCell
= ^TGridCell
;
63 {$IFDEF grid_use_buckets}
64 bodies
: array [0..GridCellBucketSize
-1] of Integer; // -1: end of list
68 next
: Integer; // in this cell; index in mCells
71 GridInternalCB
= function (grida
: Integer): Boolean is nested
; // return `true` to stop
73 TBodyGrid
= class(TObject
)
76 mMinX
, mMinY
: Integer; // so grids can start at any origin
77 mWidth
, mHeight
: Integer; // in tiles
78 mGrid
: array of Integer; // mWidth*mHeight, index in mCells
79 mCells
: array of TGridCell
; // cell pool
80 mFreeCell
: Integer; // first free cell index or -1
83 mProxies
: array of TBodyProxyRec
;
84 mProxyFree
: TBodyProxy
; // free
85 mProxyCount
: Integer; // currently used
86 mProxyMaxCount
: Integer;
89 function allocCell
: Integer;
90 procedure freeCell (idx
: Integer); // `next` is simply overwritten
92 function allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer): TBodyProxy
;
93 procedure freeProxy (body
: TBodyProxy
);
95 procedure insert (body
: TBodyProxy
);
96 procedure remove (body
: TBodyProxy
);
98 function forGridRect (x
, y
, w
, h
: Integer; cb
: GridInternalCB
): Boolean;
101 constructor Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer; aTileSize
: Integer=GridDefaultTileSize
);
102 destructor Destroy (); override;
104 function insertBody (aObj
: TObject
; ax
, ay
, aWidth
, aHeight
: Integer; aTag
: Integer=0): TBodyProxy
;
105 procedure removeBody (aObj
: TBodyProxy
); // WARNING! this WILL destroy proxy!
107 procedure moveBody (body
: TBodyProxy
; dx
, dy
: Integer);
108 procedure resizeBody (body
: TBodyProxy
; sx
, sy
: Integer);
109 procedure moveResizeBody (body
: TBodyProxy
; dx
, dy
, sx
, sy
: Integer);
111 function forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): Boolean;
113 //function getProxyForBody (aObj: TObject; x, y, w, h: Integer): TBodyProxy;
115 procedure dumpStats ();
125 // ////////////////////////////////////////////////////////////////////////// //
126 procedure TBodyProxyRec
.setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer);
139 // ////////////////////////////////////////////////////////////////////////// //
140 constructor TBodyGrid
.Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer; aTileSize
: Integer=GridDefaultTileSize
);
144 if aTileSize
< 1 then aTileSize
:= 1;
145 if aTileSize
> 8192 then aTileSize
:= 8192; // arbitrary limit
146 if aPixWidth
< aTileSize
then aPixWidth
:= aTileSize
;
147 if aPixHeight
< aTileSize
then aPixHeight
:= aTileSize
;
148 mTileSize
:= aTileSize
;
151 mWidth
:= (aPixWidth
+aTileSize
-1) div aTileSize
;
152 mHeight
:= (aPixHeight
+aTileSize
-1) div aTileSize
;
153 SetLength(mGrid
, mWidth
*mHeight
);
154 SetLength(mCells
, mWidth
*mHeight
);
155 SetLength(mProxies
, 8192);
158 for idx
:= 0 to High(mCells
) do
160 {$IFDEF grid_use_buckets}
161 mCells
[idx
].bodies
[0] := -1;
163 mCells
[idx
].body
:= -1;
165 mCells
[idx
].next
:= idx
+1;
167 mCells
[High(mCells
)].next
:= -1; // last cell
169 for idx
:= 0 to High(mGrid
) do mGrid
[idx
] := -1;
171 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
172 mProxies
[High(mProxies
)].nextLink
:= -1;
178 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
]), MSG_NOTIFY
);
182 destructor TBodyGrid
.Destroy ();
191 procedure TBodyGrid
.dumpStats ();
193 idx
, mcb
, cidx
, cnt
: Integer;
196 for idx
:= 0 to High(mGrid
) do
203 cidx
:= mCells
[cidx
].next
;
205 if (mcb
< cnt
) then mcb
:= cnt
;
207 e_WriteLog(Format('grid size: %dx%d (tile size: %d); pix: %dx%d; used cells: %d; max bodies in cell: %d; max proxies allocated: %d; proxies used: %d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
, mUsedCells
, mcb
, mProxyMaxCount
, mProxyCount
]), MSG_NOTIFY
);
211 function TBodyGrid
.allocCell
: Integer;
215 if (mFreeCell
< 0) then
217 // no free cells, want more
218 mFreeCell
:= Length(mCells
);
219 SetLength(mCells
, mFreeCell
+32768); // arbitrary number
220 for idx
:= mFreeCell
to High(mCells
) do
222 {$IFDEF grid_use_buckets}
223 mCells
[idx
].bodies
[0] := -1;
225 mCells
[idx
].body
:= -1;
227 mCells
[idx
].next
:= idx
+1;
229 mCells
[High(mCells
)].next
:= -1; // last cell
232 mFreeCell
:= mCells
[result
].next
;
233 mCells
[result
].next
:= -1;
235 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
239 procedure TBodyGrid
.freeCell (idx
: Integer);
241 if (idx
>= 0) and (idx
< High(mCells
)) then
243 //if mCells[idx].body = -1 then exit; // the thing that should not be
244 //mCells[idx].body := -1;
245 mCells
[idx
].next
:= mFreeCell
;
252 function TBodyGrid
.allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: TObject
; aTag
: Integer): TBodyProxy
;
257 if (mProxyFree
= -1) then
259 // no free proxies, resize list
260 olen
:= Length(mProxies
);
261 SetLength(mProxies
, olen
+8192); // arbitrary number
262 for idx
:= olen
to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
263 mProxies
[High(mProxies
)].nextLink
:= -1;
267 result
:= mProxyFree
;
268 px
:= @mProxies
[result
];
269 mProxyFree
:= px
.nextLink
;
270 px
.setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
275 if (mProxyMaxCount
< mProxyCount
) then mProxyMaxCount
:= mProxyCount
;
278 procedure TBodyGrid
.freeProxy (body
: TBodyProxy
);
280 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
281 if (mProxyCount
= 0) then raise Exception
.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
283 mProxies
[body
].mObj
:= nil;
284 mProxies
[body
].nextLink
:= mProxyFree
;
290 function TBodyGrid
.forGridRect (x
, y
, w
, h
: Integer; cb
: GridInternalCB
): Boolean;
295 if (w
< 1) or (h
< 1) or not assigned(cb
) then exit
;
300 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
301 if (x
>= mWidth
*mTileSize
) or (y
>= mHeight
*mTileSize
) then exit
;
302 for gy
:= y
div mTileSize
to (y
+h
-1) div mTileSize
do
304 if (gy
< 0) then continue
;
305 if (gy
>= mHeight
) then break
;
306 for gx
:= x
div mTileSize
to (x
+w
-1) div mTileSize
do
308 if (gx
< 0) then continue
;
309 if (gx
>= mWidth
) then break
;
310 if (cb(gy
*mWidth
+gx
)) then begin result
:= true; exit
; end;
316 procedure TBodyGrid
.insert (body
: TBodyProxy
);
318 function inserter (grida
: Integer): Boolean;
322 {$IFDEF grid_use_buckets}
327 result
:= false; // never stop
328 // add body to the given grid cell
330 {$IFDEF grid_use_buckets}
335 for f
:= 0 to High(TGridCell
.bodies
) do
337 if (pi
.bodies
[f
] = -1) then
340 pi
.bodies
[f
] := body
;
341 if (f
+1 < Length(TGridCell
.bodies
)) then pi
.bodies
[f
+1] := -1;
346 // either no room, or no cell at all
348 mCells
[cidx
].bodies
[0] := body
;
349 mCells
[cidx
].bodies
[1] := -1;
350 mCells
[cidx
].next
:= pc
^;
354 //e_WriteLog(Format(' 01: allocated cell for grid coords (%d,%d), body coords:(%d,%d): #%d', [gx, gy, dx, dy, cidx]), MSG_NOTIFY);
355 mCells
[cidx
].body
:= body
;
356 mCells
[cidx
].next
:= pc
^;
364 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
365 px
:= @mProxies
[body
];
366 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, inserter
);
370 // absolutely not tested
371 procedure TBodyGrid
.remove (body
: TBodyProxy
);
374 function remover (grida: Integer): Boolean;
376 pidx, idx, tmp: Integer;
378 result := false; // never stop
379 // find and remove cell
384 tmp := mCells[idx].next;
385 if (mCells[idx].body = body) then
387 if (pidx = -1) then mGrid[grida] := tmp else mCells[pidx].next := tmp;
389 break; // assume that we cannot have one object added to bucket twice
404 if (body < 0) or (body > High(mProxies)) then exit; // just in case
405 px := @mProxies[body];
406 forGridRect(px.mX, px.mY, px.mWidth, px.mHeight, remover);
408 raise Exception
.Create('TBodyGrid.remove: not yet, sorry');
412 function TBodyGrid
.insertBody (aObj
: TObject
; aX
, aY
, aWidth
, aHeight
: Integer; aTag
: Integer=0): TBodyProxy
;
414 result
:= allocProxy(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
419 procedure TBodyGrid
.removeBody (aObj
: TBodyProxy
);
421 if (aObj
< 0) or (aObj
> High(mProxies
)) then exit
; // just in case
427 procedure TBodyGrid
.moveResizeBody (body
: TBodyProxy
; dx
, dy
, sx
, sy
: Integer);
431 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
432 if ((dx
= 0) and (dy
= 0) and (sx
= 0) and (sy
= 0)) then exit
;
434 px
:= @mProxies
[body
];
442 procedure TBodyGrid
.moveBody (body
: TBodyProxy
; dx
, dy
: Integer);
444 moveResizeBody(body
, dx
, dy
, 0, 0);
447 procedure TBodyGrid
.resizeBody (body
: TBodyProxy
; sx
, sy
: Integer);
449 moveResizeBody(body
, 0, 0, sx
, sy
);
453 function TBodyGrid
.forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): Boolean;
454 function iterator (grida
: Integer): Boolean;
458 {$IFDEF grid_use_buckets}
467 {$IFDEF grid_use_buckets}
469 for f
:= 0 to High(TGridCell
.bodies
) do
471 if (pi
.bodies
[f
] = -1) then break
;
472 px
:= @mProxies
[pi
.bodies
[f
]];
473 if (px
.mQueryMark
<> mLastQuery
) and ((px
.mTag
and tagmask
) <> 0) then
475 //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);
476 px
.mQueryMark
:= mLastQuery
;
477 if (cb(px
.mObj
, px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, px
.mTag
)) then begin result
:= true; exit
; end;
482 if (mCells
[idx
].body
<> -1) then
484 px
:= @mProxies
[mCells
[idx
].body
];
485 if (px
.mQueryMark
<> mLastQuery
) and ((px
.mTag
and tagmask
) <> 0) then
487 //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);
488 px
.mQueryMark
:= mLastQuery
;
489 if (cb(px
.mObj
, px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, px
.mTag
)) then begin result
:= true; exit
; end;
492 idx
:= mCells
[idx
].next
;
501 if not assigned(cb
) then exit
;
503 // increase query counter
505 if (mLastQuery
= 0) then
507 // just in case of overflow
509 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
511 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
513 result
:= forGridRect(x
, y
, w
, h
, iterator
);
518 function TBodyGrid.getProxyForBody (aObj: TObject; x, y, w, h: Integer): TBodyProxy;
520 res: TBodyProxy = -1;
522 function iterator (grida: Integer): Boolean;
530 if (mCells[idx].body <> -1) and (mProxies[mCells[idx].body].mObj = aObj) then
533 res := mCells[idx].body;
536 idx := mCells[idx].next;
542 if (aObj = nil) then exit;
543 forGridRect(x, y, w, h, iterator);