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}
24 TBodyProxyId
= Integer;
26 generic TBodyGridBase
<ITP
> = class(TObject
)
28 type TGridQueryCB
= function (obj
: ITP
; tag
: Integer): Boolean is nested
; // return `true` to stop
29 type TGridRayQueryCB
= function (obj
: ITP
; tag
: Integer; x
, y
, prevx
, prevy
: Integer): Boolean is nested
; // return `true` to stop
30 type TGridAlongQueryCB
= function (obj
: ITP
; tag
: Integer): Boolean is nested
; // return `true` to stop
32 const TagDisabled
= $40000000;
33 const TagFullMask
= $3fffffff;
37 GridDefaultTileSize
= 32; // must be power of two!
38 GridCellBucketSize
= 8; // WARNING! can't be less than 2!
42 PBodyProxyRec
= ^TBodyProxyRec
;
43 TBodyProxyRec
= record
45 mX
, mY
, mWidth
, mHeight
: Integer; // aabb
46 mQueryMark
: LongWord; // was this object visited at this query?
48 mTag
: Integer; // `TagDisabled` set: disabled ;-)
49 nextLink
: TBodyProxyId
; // next free or nothing
52 procedure setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
55 PGridCell
= ^TGridCell
;
57 bodies
: array [0..GridCellBucketSize
-1] of Integer; // -1: end of list
58 next
: Integer; // in this cell; index in mCells
61 TGridInternalCB
= function (grida
: Integer; bodyId
: TBodyProxyId
): Boolean of object; // return `true` to stop
65 const mTileSize
= GridDefaultTileSize
;
68 const tileSize
= mTileSize
;
71 mMinX
, mMinY
: Integer; // so grids can start at any origin
72 mWidth
, mHeight
: Integer; // in tiles
73 mGrid
: array of Integer; // mWidth*mHeight, index in mCells
74 mCells
: array of TGridCell
; // cell pool
75 mFreeCell
: Integer; // first free cell index or -1
78 mProxies
: array of TBodyProxyRec
;
79 mProxyFree
: TBodyProxyId
; // free
80 mProxyCount
: Integer; // currently used
81 mProxyMaxCount
: Integer;
84 dbgShowTraceLog
: Boolean;
87 function allocCell (): Integer;
88 procedure freeCell (idx
: Integer); // `next` is simply overwritten
90 function allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
91 procedure freeProxy (body
: TBodyProxyId
);
93 procedure insertInternal (body
: TBodyProxyId
);
94 procedure removeInternal (body
: TBodyProxyId
);
96 function forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
98 function inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
99 function remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
101 function getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
102 procedure setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
104 function getGridWidthPx (): Integer; inline;
105 function getGridHeightPx (): Integer; inline;
108 constructor Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
109 destructor Destroy (); override;
111 function insertBody (aObj
: ITP
; ax
, ay
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
112 procedure removeBody (body
: TBodyProxyId
); // WARNING! this WILL destroy proxy!
114 procedure moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
115 procedure resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
116 procedure moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
118 function insideGrid (x
, y
: Integer): Boolean; inline;
120 // `false` if `body` is surely invalid
121 function getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
123 //WARNING: don't modify grid while any query is in progress (no checks are made!)
124 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
125 // no callback: return `true` on the first hit
126 function forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
128 //WARNING: don't modify grid while any query is in progress (no checks are made!)
129 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
130 // no callback: return `true` on the first hit
131 function forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
133 //WARNING: don't modify grid while any query is in progress (no checks are made!)
134 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
135 // cb with `(nil)` will be called before processing new tile
136 // no callback: return `true` on the nearest hit
137 function traceRay (x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
; overload
;
138 function traceRay (out ex
, ey
: Integer; ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
140 //WARNING: don't modify grid while any query is in progress (no checks are made!)
141 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
142 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
143 function forEachAlongLine (x0
, y0
, x1
, y1
: Integer; cb
: TGridAlongQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
145 procedure dumpStats ();
147 //WARNING! no sanity checks!
148 property proxyEnabled
[pid
: TBodyProxyId
]: Boolean read getProxyEnabled write setProxyEnabled
;
150 property gridX0
: Integer read mMinX
;
151 property gridY0
: Integer read mMinY
;
152 property gridWidth
: Integer read getGridWidthPx
; // in pixels
153 property gridHeight
: Integer read getGridHeightPx
; // in pixels
157 // you are not supposed to understand this
158 // returns `true` if there is an intersection, and enter coords
159 // enter coords will be equal to (x0, y0) if starting point is inside the box
160 // if result is `false`, `inx` and `iny` are undefined
161 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
163 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline;
165 procedure swapInt (var a
: Integer; var b
: Integer); inline;
166 function minInt (a
, b
: Integer): Integer; inline;
167 function maxInt (a
, b
: Integer): Integer; inline;
176 // ////////////////////////////////////////////////////////////////////////// //
177 procedure swapInt (var a
: Integer; var b
: Integer); inline; var t
: Integer; begin t
:= a
; a
:= b
; b
:= t
; end;
178 function minInt (a
, b
: Integer): Integer; inline; begin if (a
< b
) then result
:= a
else result
:= b
; end;
179 function maxInt (a
, b
: Integer): Integer; inline; begin if (a
> b
) then result
:= a
else result
:= b
; end;
181 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline; begin result
:= (x1
-x0
)*(x1
-x0
)+(y1
-y0
)*(y1
-y0
); end;
184 // ////////////////////////////////////////////////////////////////////////// //
185 // you are not supposed to understand this
186 // returns `true` if there is an intersection, and enter coords
187 // enter coords will be equal to (x0, y0) if starting point is inside the box
188 // if result is `false`, `inx` and `iny` are undefined
189 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
191 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
192 stx
, sty
: Integer; // "steps" for x and y axes
193 dsx
, dsy
: Integer; // "lengthes" for x and y axes
194 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
195 xd
, yd
: Integer; // current coord
196 e
: Integer; // "error" (as in bresenham algo)
207 if (bw
< 1) or (bh
< 1) then exit
; // impossible box
209 if (x0
= x1
) and (y0
= y1
) then
212 result
:= (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
);
216 // check if staring point is inside the box
217 if (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
) then begin result
:= true; exit
; end;
228 // from left to right
229 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
230 stx
:= 1; // going right
234 // from right to left
235 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
236 stx
:= -1; // going left
247 // from top to bottom
248 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
249 sty
:= 1; // going down
253 // from bottom to top
254 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
255 sty
:= -1; // going up
294 temp
:= dx2
*(wy0
-y0
)-dsx
;
297 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
298 if (xd
+1 >= wx0
) then
302 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
307 if (not xfixed
) and (x0
< wx0
) then
310 temp
:= dy2
*(wx0
-x0
);
313 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
316 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
323 temp := dx2*(wy1-y0)+dsx;
324 term := x0+temp div dy2;
326 if (rem = 0) then Dec(term);
329 if (term > wx1) then term := wx1; // clip at right
331 Inc(term); // draw last point
332 //if (term = xd) then exit; // this is the only point, get out of here
335 if (sty
= -1) then yd
:= -yd
;
336 if (stx
= -1) then begin xd
:= -xd
; {!term := -term;} end;
345 // ////////////////////////////////////////////////////////////////////////// //
346 procedure TBodyGridBase
.TBodyProxyRec
.setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
359 // ////////////////////////////////////////////////////////////////////////// //
360 constructor TBodyGridBase
.Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
364 dbgShowTraceLog
:= false;
366 if aTileSize < 1 then aTileSize := 1;
367 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
368 mTileSize := aTileSize;
370 if (aPixWidth
< mTileSize
) then aPixWidth
:= mTileSize
;
371 if (aPixHeight
< mTileSize
) then aPixHeight
:= mTileSize
;
374 mWidth
:= (aPixWidth
+mTileSize
-1) div mTileSize
;
375 mHeight
:= (aPixHeight
+mTileSize
-1) div mTileSize
;
376 SetLength(mGrid
, mWidth
*mHeight
);
377 SetLength(mCells
, mWidth
*mHeight
);
378 SetLength(mProxies
, 8192);
381 for idx
:= 0 to High(mCells
) do
383 mCells
[idx
].bodies
[0] := -1;
384 mCells
[idx
].next
:= idx
+1;
386 mCells
[High(mCells
)].next
:= -1; // last cell
388 for idx
:= 0 to High(mGrid
) do mGrid
[idx
] := -1;
390 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
391 mProxies
[High(mProxies
)].nextLink
:= -1;
397 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
]), MSG_NOTIFY
);
401 destructor TBodyGridBase
.Destroy ();
410 // ////////////////////////////////////////////////////////////////////////// //
411 procedure TBodyGridBase
.dumpStats ();
413 idx
, mcb
, cidx
, cnt
: Integer;
416 for idx
:= 0 to High(mGrid
) do
423 cidx
:= mCells
[cidx
].next
;
425 if (mcb
< cnt
) then mcb
:= cnt
;
427 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
);
431 // ////////////////////////////////////////////////////////////////////////// //
432 function TBodyGridBase
.getGridWidthPx (): Integer; inline; begin result
:= mWidth
*mTileSize
; end;
433 function TBodyGridBase
.getGridHeightPx (): Integer; inline; begin result
:= mHeight
*mTileSize
; end;
436 function TBodyGridBase
.insideGrid (x
, y
: Integer): Boolean; inline;
441 result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
*mTileSize
) and (y
< mHeight
*mTileSize
);
445 function TBodyGridBase
.getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
447 if (body
>= 0) and (body
< Length(mProxies
)) then
449 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; end;
461 // ////////////////////////////////////////////////////////////////////////// //
462 function TBodyGridBase
.getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
464 if (pid
>= 0) then result
:= ((mProxies
[pid
].mTag
and TagDisabled
) = 0) else result
:= false;
468 procedure TBodyGridBase
.setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
474 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
and not TagDisabled
;
478 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
or TagDisabled
;
484 // ////////////////////////////////////////////////////////////////////////// //
485 function TBodyGridBase
.allocCell (): Integer;
489 if (mFreeCell
< 0) then
491 // no free cells, want more
492 mFreeCell
:= Length(mCells
);
493 SetLength(mCells
, mFreeCell
+32768); // arbitrary number
494 for idx
:= mFreeCell
to High(mCells
) do
496 mCells
[idx
].bodies
[0] := -1;
497 mCells
[idx
].next
:= idx
+1;
499 mCells
[High(mCells
)].next
:= -1; // last cell
502 mFreeCell
:= mCells
[result
].next
;
503 mCells
[result
].next
:= -1;
504 mCells
[result
].bodies
[0] := -1;
506 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
510 procedure TBodyGridBase
.freeCell (idx
: Integer);
512 if (idx
>= 0) and (idx
< Length(mCells
)) then
514 //if mCells[idx].body = -1 then exit; // the thing that should not be
515 mCells
[idx
].bodies
[0] := -1;
516 mCells
[idx
].next
:= mFreeCell
;
523 // ////////////////////////////////////////////////////////////////////////// //
524 function TBodyGridBase
.allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
529 if (mProxyFree
= -1) then
531 // no free proxies, resize list
532 olen
:= Length(mProxies
);
533 SetLength(mProxies
, olen
+8192); // arbitrary number
534 for idx
:= olen
to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
535 mProxies
[High(mProxies
)].nextLink
:= -1;
539 result
:= mProxyFree
;
540 px
:= @mProxies
[result
];
541 mProxyFree
:= px
.nextLink
;
542 px
.setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
547 if (mProxyMaxCount
< mProxyCount
) then mProxyMaxCount
:= mProxyCount
;
550 procedure TBodyGridBase
.freeProxy (body
: TBodyProxyId
);
552 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
553 if (mProxyCount
= 0) then raise Exception
.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
555 mProxies
[body
].mObj
:= nil;
556 mProxies
[body
].nextLink
:= mProxyFree
;
562 // ////////////////////////////////////////////////////////////////////////// //
563 function TBodyGridBase
.forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
571 if (w
< 1) or (h
< 1) or not assigned(cb
) then exit
;
576 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
579 //tsize := mTileSize;
580 if (x
>= gw
*tsize
) or (y
>= gh
*tsize
) then exit
;
581 for gy
:= y
div tsize
to (y
+h
-1) div tsize
do
583 if (gy
< 0) then continue
;
584 if (gy
>= gh
) then break
;
585 for gx
:= x
div tsize
to (x
+w
-1) div tsize
do
587 if (gx
< 0) then continue
;
588 if (gx
>= gw
) then break
;
589 result
:= cb(gy
*gw
+gx
, bodyId
);
596 // ////////////////////////////////////////////////////////////////////////// //
597 function TBodyGridBase
.inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
604 result
:= false; // never stop
605 // add body to the given grid cell
611 for f
:= 0 to High(TGridCell
.bodies
) do
613 if (pi
.bodies
[f
] = -1) then
616 pi
.bodies
[f
] := bodyId
;
617 if (f
+1 < Length(TGridCell
.bodies
)) then pi
.bodies
[f
+1] := -1;
622 // either no room, or no cell at all
624 mCells
[cidx
].bodies
[0] := bodyId
;
625 mCells
[cidx
].bodies
[1] := -1;
626 mCells
[cidx
].next
:= pc
;
627 mGrid
[grida
] := cidx
;
630 procedure TBodyGridBase
.insertInternal (body
: TBodyProxyId
);
634 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
635 px
:= @mProxies
[body
];
636 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, inserter
, body
);
640 // absolutely not tested
641 function TBodyGridBase
.remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
644 pidx
, idx
, tmp
: Integer;
647 result
:= false; // never stop
648 // find and remove cell
653 tmp
:= mCells
[idx
].next
;
656 while (f
< High(TGridCell
.bodies
)) do
658 if (pc
.bodies
[f
] = bodyId
) then
661 if (f
= 0) and (pc
.bodies
[1] = -1) then
663 // this cell contains no elements, remove it
664 tmp
:= mCells
[idx
].next
;
665 if (pidx
= -1) then mGrid
[grida
] := tmp
else mCells
[pidx
].next
:= tmp
;
670 // remove element from bucket
672 while (f
< High(TGridCell
.bodies
)) do
674 pc
.bodies
[f
-1] := pc
.bodies
[f
];
675 if (pc
.bodies
[f
] = -1) then break
;
678 pc
.bodies
[High(TGridCell
.bodies
)] := -1; // just in case
680 exit
; // assume that we cannot have one object added to bucket twice
689 // absolutely not tested
690 procedure TBodyGridBase
.removeInternal (body
: TBodyProxyId
);
694 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
695 px
:= @mProxies
[body
];
696 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
700 // ////////////////////////////////////////////////////////////////////////// //
701 function TBodyGridBase
.insertBody (aObj
: ITP
; aX
, aY
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
703 aTag
:= aTag
and TagFullMask
;
704 result
:= allocProxy(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
705 insertInternal(result
);
709 procedure TBodyGridBase
.removeBody (body
: TBodyProxyId
);
711 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
712 removeInternal(body
);
717 // ////////////////////////////////////////////////////////////////////////// //
718 procedure TBodyGridBase
.moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
721 x0
, y0
, w
, h
: Integer;
723 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
724 px
:= @mProxies
[body
];
729 if (nx
= x0
) and (ny
= y0
) and (nw
= w
) and (nh
= h
) then exit
;
730 // did any corner crossed tile boundary?
731 if (x0
div mTileSize
<> nx
div mTileSize
) or
732 (y0
div mTileSize
<> ny
div mTileSize
) or
733 ((x0
+w
) div mTileSize
<> (nx
+nw
) div mTileSize
) or
734 ((y0
+h
) div mTileSize
<> (ny
+nh
) div mTileSize
) then
736 removeInternal(body
);
741 insertInternal(body
);
752 procedure TBodyGridBase
.moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
757 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
758 // check if tile coords was changed
759 px
:= @mProxies
[body
];
762 if (nx
= x0
) and (ny
= y0
) then exit
;
763 if (nx
div mTileSize
<> x0
div mTileSize
) or (ny
div mTileSize
<> y0
div mTileSize
) then
765 // crossed tile boundary, do heavy work
766 removeInternal(body
);
769 insertInternal(body
);
773 // nothing to do with the grid, just fix coordinates
779 procedure TBodyGridBase
.resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
782 x0
, y0
, w
, h
: Integer;
784 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
785 // check if tile coords was changed
786 px
:= @mProxies
[body
];
791 if ((x0
+w
) div mTileSize
<> (x0
+nw
) div mTileSize
) or
792 ((y0
+h
) div mTileSize
<> (y0
+nh
) div mTileSize
) then
794 // crossed tile boundary, do heavy work
795 removeInternal(body
);
798 insertInternal(body
);
802 // nothing to do with the grid, just fix size
809 // ////////////////////////////////////////////////////////////////////////// //
810 // no callback: return `true` on the first hit
811 function TBodyGridBase
.forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
820 result
:= Default(ITP
);
821 tagmask
:= tagmask
and TagFullMask
;
822 if (tagmask
= 0) then exit
;
824 // make coords (0,0)-based
827 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
>= mHeight
*mTileSize
) then exit
;
829 curci
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
834 // increase query counter
836 if (mLastQuery
= 0) then
838 // just in case of overflow
840 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
844 while (curci
<> -1) do
846 cc
:= @mCells
[curci
];
847 for f
:= 0 to High(TGridCell
.bodies
) do
849 if (cc
.bodies
[f
] = -1) then break
;
850 px
:= @mProxies
[cc
.bodies
[f
]];
852 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
854 if (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
859 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
874 // ////////////////////////////////////////////////////////////////////////// //
875 // no callback: return `true` on the first hit
876 function TBodyGridBase
.forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
891 result
:= Default(ITP
);
892 if (w
< 1) or (h
< 1) then exit
;
893 tagmask
:= tagmask
and TagFullMask
;
894 if (tagmask
= 0) then exit
;
904 //tsize := mTileSize;
906 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
907 if (x
>= gw
*tsize
) or (y
>= mHeight
*tsize
) then exit
;
909 // increase query counter
911 if (mLastQuery
= 0) then
913 // just in case of overflow
915 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
917 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
921 for gy
:= y
div tsize
to (y
+h
-1) div tsize
do
923 if (gy
< 0) then continue
;
924 if (gy
>= mHeight
) then break
;
925 for gx
:= x
div tsize
to (x
+w
-1) div tsize
do
927 if (gx
< 0) then continue
;
928 if (gx
>= gw
) then break
;
930 curci
:= mGrid
[gy
*gw
+gx
];
931 while (curci
<> -1) do
933 cc
:= @mCells
[curci
];
934 for f
:= 0 to High(TGridCell
.bodies
) do
936 if (cc
.bodies
[f
] = -1) then break
;
937 px
:= @mProxies
[cc
.bodies
[f
]];
939 if (not allowDisabled
) and ((ptag
and TagDisabled
) <> 0) then continue
;
940 if ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
941 //if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
942 //if ( ((ptag and TagDisabled) = 0) = ignoreDisabled) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
944 if (x0
>= px
.mX
+px
.mWidth
) or (y0
>= px
.mY
+px
.mHeight
) then continue
;
945 if (x0
+w
<= px
.mX
) or (y0
+h
<= px
.mY
) then continue
;
949 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
965 // ////////////////////////////////////////////////////////////////////////// //
966 // no callback: return `true` on the nearest hit
967 function TBodyGridBase
.traceRay (x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
971 result
:= traceRay(ex
, ey
, x0
, y0
, x1
, y1
, cb
, tagmask
);
975 // no callback: return `true` on the nearest hit
976 // you are not supposed to understand this
977 function TBodyGridBase
.traceRay (out ex
, ey
: Integer; ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
981 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
982 stx
, sty
: Integer; // "steps" for x and y axes
983 dsx
, dsy
: Integer; // "lengthes" for x and y axes
984 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
985 xd
, yd
: Integer; // current coord
986 e
: Integer; // "error" (as in bresenham algo)
989 xptr
, yptr
: PInteger;
992 prevx
, prevy
: Integer;
994 ccidx
, curci
: Integer;
996 lastGA
: Integer = -1;
999 wasHit
: Boolean = false;
1000 gw
, gh
, minx
, miny
, maxx
, maxy
: Integer;
1004 f
, ptag
, distSq
: Integer;
1005 x0
, y0
, x1
, y1
: Integer;
1007 result
:= Default(ITP
);
1008 lastObj
:= Default(ITP
);
1009 tagmask
:= tagmask
and TagFullMask
;
1010 ex
:= ax1
; // why not?
1011 ey
:= ay1
; // why not?
1012 if (tagmask
= 0) then exit
;
1014 if (ax0
= ax1
) and (ay0
= ay1
) then exit
; // as the first point is ignored, just get outta here
1016 lastDistSq
:= distanceSq(ax0
, ay0
, ax1
, ay1
)+1;
1030 // offset query coords to (0,0)-based
1045 // from left to right
1046 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
1047 stx
:= 1; // going right
1051 // from right to left
1052 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
1053 stx
:= -1; // going left
1064 // from top to bottom
1065 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
1066 sty
:= 1; // going down
1070 // from bottom to top
1071 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
1072 sty
:= -1; // going up
1111 temp
:= dx2
*(wy0
-y0
)-dsx
;
1113 rem
:= temp
mod dy2
;
1114 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
1115 if (xd
+1 >= wx0
) then
1119 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
1124 if (not xfixed
) and (x0
< wx0
) then
1127 temp
:= dy2
*(wx0
-x0
);
1129 rem
:= temp
mod dx2
;
1130 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
1133 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
1139 temp
:= dx2
*(wy1
-y0
)+dsx
;
1140 term
:= x0
+temp
div dy2
;
1141 rem
:= temp
mod dy2
;
1142 if (rem
= 0) then Dec(term
);
1145 if (term
> wx1
) then term
:= wx1
; // clip at right
1147 Inc(term
); // draw last point
1148 //if (term = xd) then exit; // this is the only point, get out of here
1150 if (sty
= -1) then yd
:= -yd
;
1151 if (stx
= -1) then begin xd
:= -xd
; term
:= -term
; end;
1154 // first move, to skip starting point
1155 if (xd
= term
) then exit
;
1156 prevx
:= xptr
^+minx
;
1157 prevy
:= yptr
^+miny
;
1159 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
1162 if (xd
= term
) then exit
;
1164 {$IF DEFINED(D2F_DEBUG)}
1165 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ > mHeight
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
1168 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
1170 // restore query coords
1176 // increase query counter
1178 if (mLastQuery
= 0) then
1180 // just in case of overflow
1182 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1187 // draw it; can omit checks
1188 while (xd
<> term
) do
1191 {$IF DEFINED(D2F_DEBUG)}
1192 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ > mHeight
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
1195 ga
:= (yptr
^ div tsize
)*gw
+(xptr
^ div tsize
);
1196 if (ga
<> lastGA
) then
1199 if (ccidx
<> -1) then
1201 // signal cell completion
1202 if assigned(cb
) then
1204 if cb(nil, 0, xptr
^+minx
, yptr
^+miny
, prevx
, prevy
) then begin result
:= lastObj
; exit
; end;
1213 ccidx
:= mGrid
[lastGA
];
1215 // has something to process in this tile?
1216 if (ccidx
<> -1) then
1220 hasUntried
:= false; // this will be set to `true` if we have some proxies we still want to process at the next step
1221 // convert coords to map (to avoid ajdusting coords inside the loop)
1224 // process cell list
1225 while (curci
<> -1) do
1227 cc
:= @mCells
[curci
];
1228 for f
:= 0 to High(TGridCell
.bodies
) do
1230 if (cc
.bodies
[f
] = -1) then break
;
1231 px
:= @mProxies
[cc
.bodies
[f
]];
1233 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1235 // can we process this proxy?
1236 if (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
1238 px
.mQueryMark
:= lq
; // mark as processed
1239 if assigned(cb
) then
1241 if cb(px
.mObj
, ptag
, x
, y
, prevx
, prevy
) then
1251 // remember this hitpoint if it is nearer than an old one
1252 distSq
:= distanceSq(ax0
, ay0
, prevx
, prevy
);
1253 if (distSq
< lastDistSq
) then
1256 lastDistSq
:= distSq
;
1265 // this is possibly interesting proxy, set "has more to check" flag
1273 // still has something interesting in this cell?
1274 if not hasUntried
then
1276 // nope, don't process this cell anymore; signal cell completion
1278 if assigned(cb
) then
1280 if cb(nil, 0, x
, y
, prevx
, prevy
) then begin result
:= lastObj
; exit
; end;
1289 //putPixel(xptr^, yptr^);
1291 prevx
:= xptr
^+minx
;
1292 prevy
:= yptr
^+miny
;
1293 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
1299 // ////////////////////////////////////////////////////////////////////////// //
1300 //FIXME! optimize this with real tile walking
1301 function TBodyGridBase
.forEachAlongLine (x0
, y0
, x1
, y1
: Integer; cb
: TGridAlongQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
1307 xerr
, yerr
: Integer;
1308 incx
, incy
: Integer;
1309 stepx
, stepy
: Integer;
1311 maxx
, maxy
: Integer;
1318 minx
, miny
: Integer;
1320 lastWasInGrid
: Boolean;
1326 result
:= Default(ITP
);
1327 tagmask
:= tagmask
and TagFullMask
;
1328 if (tagmask
= 0) or not assigned(cb
) then exit
;
1336 if (dx
> 0) then incx
:= 1 else if (dx
< 0) then incx
:= -1 else incx
:= 0;
1337 if (dy
> 0) then incy
:= 1 else if (dy
< 0) then incy
:= -1 else incy
:= 0;
1339 if (incx
= 0) and (incy
= 0) then exit
; // just incase
1344 if (dx
> dy
) then d
:= dx
else d
:= dy
;
1346 // `x` and `y` will be in grid coords
1350 // increase query counter
1352 if (mLastQuery
= 0) then
1354 // just in case of overflow
1356 for i
:= 0 to High(mProxies
) do mProxies
[i
].mQueryMark
:= 0;
1360 // cache various things
1361 //tsize := mTileSize;
1367 // setup distance and flags
1368 lastWasInGrid
:= (x
>= 0) and (y
>= 0) and (x
<= maxx
) and (y
<= maxy
);
1370 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1371 if lastWasInGrid
then ccidx
:= mGrid
[(y
div tsize
)*gw
+(x
div tsize
)] else ccidx
:= -1;
1373 // it is slightly faster this way
1377 if (log
) then e_WriteLog(Format('tracing: (%d,%d)-(%d,%d)', [x
, y
, x1
-minx
, y1
-miny
]), MSG_NOTIFY
);
1387 // invariant: one of those always changed
1388 {$IF DEFINED(D2F_DEBUG)}
1389 if (xerr
< 0) and (yerr
< 0) then raise Exception
.Create('internal bug in grid raycaster (0)');
1391 if (xerr
>= 0) then begin xerr
-= d
; x
+= incx
; stepx
:= incx
; end else stepx
:= 0;
1392 if (yerr
>= 0) then begin yerr
-= d
; y
+= incy
; stepy
:= incy
; end else stepy
:= 0;
1393 // invariant: we always doing a step
1394 {$IF DEFINED(D2F_DEBUG)}
1395 if ((stepx
or stepy
) = 0) then raise Exception
.Create('internal bug in grid raycaster (1)');
1398 // check for crossing tile/grid boundary
1399 if (x
>= 0) and (y
>= 0) and (x
<= maxx
) and (y
<= maxy
) then
1401 // we're still in grid
1402 lastWasInGrid
:= true;
1403 // check for tile edge crossing
1404 if (stepx
< 0) and ((x
mod tsize
) = tsize
-1) then tbcross
:= true
1405 else if (stepx
> 0) and ((x
mod tsize
) = 0) then tbcross
:= true
1406 else if (stepy
< 0) and ((y
mod tsize
) = tsize
-1) then tbcross
:= true
1407 else if (stepy
> 0) and ((y
mod tsize
) = 0) then tbcross
:= true
1408 else tbcross
:= false;
1409 // crossed tile edge?
1412 // setup new cell index
1413 ccidx
:= mGrid
[(y
div tsize
)*gw
+(x
div tsize
)];
1414 if (log
) then e_WriteLog(Format(' stepped to new tile (%d,%d) -- (%d,%d)', [(x
div tsize
), (y
div tsize
), x
, y
]), MSG_NOTIFY
);
1417 if (ccidx
= -1) then
1419 // we have nothing interesting here anymore, jump directly to tile edge
1424 if (incy < 0) then tedist := y-(y and (not tsize)) else tedist := (y or (tsize-1))-y;
1425 if (tedist > 1) then
1427 if (log) then e_WriteLog(Format(' doing vertical jump from tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1430 if (log) then e_WriteLog(Format(' jumped to tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1433 else if (incy = 0) then
1436 if (incx < 0) then tedist := x-(x and (not tsize)) else tedist := (x or (tsize-1))-x;
1437 if (tedist > 1) then
1439 if (log) then e_WriteLog(Format(' doing horizontal jump from tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1442 if (log) then e_WriteLog(Format(' jumped to tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1448 // get minimal distance to tile edges
1449 if (incx < 0) then tedist := x-(x and (not tsize)) else if (incx > 0) then tedist := (x or (tsize+1))-x else tedist := 0;
1450 {$IF DEFINED(D2F_DEBUG)}
1451 if (tedist < 0) then raise Exception.Create('internal bug in grid raycaster (2.x)');
1453 if (incy < 0) then f := y-(y and (not tsize)) else if (incy > 0) then f := (y or (tsize+1))-y else f := 0;
1454 {$IF DEFINED(D2F_DEBUG)}
1455 if (f < 0) then raise Exception.Create('internal bug in grid raycaster (2.y)');
1457 if (tedist = 0) then tedist := f else if (f <> 0) then tedist := minInt(tedist, f);
1459 if (tedist > 1) then
1461 if (log) then e_WriteLog(Format(' doing jump from tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1464 if (xerr >= 0) then begin x += incx*((xerr div d)+1); xerr := (xerr mod d)-d; end;
1465 if (yerr >= 0) then begin y += incy*((yerr div d)+1); yerr := (yerr mod d)-d; end;
1467 if (log) then e_WriteLog(Format(' jumped to tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1475 if lastWasInGrid
then exit
; // oops, stepped out of the grid -- there is no way to return
1479 // has something to process in the current cell?
1480 if (ccidx
<> -1) then
1484 // convert coords to map (to avoid ajdusting coords inside the loop)
1487 // process cell list
1488 while (curci
<> -1) do
1490 cc
:= @mCells
[curci
];
1491 for f
:= 0 to High(TGridCell
.bodies
) do
1493 if (cc
.bodies
[f
] = -1) then break
;
1494 px
:= @mProxies
[cc
.bodies
[f
]];
1496 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1498 px
.mQueryMark
:= lq
; // mark as processed
1499 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
1505 ccidx
:= -1; // don't process this anymore
1506 // convert coords to grid