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;
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 mMinX
, mMinY
: Integer; // so grids can start at any origin
69 mWidth
, mHeight
: Integer; // in tiles
70 mGrid
: array of Integer; // mWidth*mHeight, index in mCells
71 mCells
: array of TGridCell
; // cell pool
72 mFreeCell
: Integer; // first free cell index or -1
75 mProxies
: array of TBodyProxyRec
;
76 mProxyFree
: TBodyProxyId
; // free
77 mProxyCount
: Integer; // currently used
78 mProxyMaxCount
: Integer;
81 dbgShowTraceLog
: Boolean;
84 function allocCell (): Integer;
85 procedure freeCell (idx
: Integer); // `next` is simply overwritten
87 function allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
88 procedure freeProxy (body
: TBodyProxyId
);
90 procedure insertInternal (body
: TBodyProxyId
);
91 procedure removeInternal (body
: TBodyProxyId
);
93 function forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
95 function inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
96 function remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
98 function getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
99 procedure setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
101 function getGridWidthPx (): Integer; inline;
102 function getGridHeightPx (): Integer; inline;
105 constructor Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
106 destructor Destroy (); override;
108 function insertBody (aObj
: ITP
; ax
, ay
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
109 procedure removeBody (body
: TBodyProxyId
); // WARNING! this WILL destroy proxy!
111 procedure moveBody (body
: TBodyProxyId
; dx
, dy
: Integer);
112 procedure resizeBody (body
: TBodyProxyId
; sx
, sy
: Integer);
113 procedure moveResizeBody (body
: TBodyProxyId
; dx
, dy
, sx
, sy
: Integer);
115 function insideGrid (x
, y
: Integer): Boolean; inline;
117 // `false` if `body` is surely invalid
118 function getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
120 //WARNING: don't modify grid while any query is in progress (no checks are made!)
121 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
122 // no callback: return `true` on the first hit
123 function forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
125 //WARNING: don't modify grid while any query is in progress (no checks are made!)
126 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
127 // no callback: return `true` on the first hit
128 function forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
130 //WARNING: don't modify grid while any query is in progress (no checks are made!)
131 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
132 // cb with `(nil)` will be called before processing new tile
133 // no callback: return `true` on the nearest hit
134 function traceRay (x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
; overload
;
135 function traceRay (out ex
, ey
: Integer; ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
137 //WARNING: don't modify grid while any query is in progress (no checks are made!)
138 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
139 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
140 function forEachAlongLine (x0
, y0
, x1
, y1
: Integer; cb
: TGridAlongQueryCB
; tagmask
: Integer=-1): ITP
;
142 procedure dumpStats ();
144 //WARNING! no sanity checks!
145 property proxyEnabled
[pid
: TBodyProxyId
]: Boolean read getProxyEnabled write setProxyEnabled
;
147 property gridX0
: Integer read mMinX
;
148 property gridY0
: Integer read mMinY
;
149 property gridWidth
: Integer read getGridWidthPx
; // in pixels
150 property gridHeight
: Integer read getGridHeightPx
; // in pixels
154 // you are not supposed to understand this
155 // returns `true` if there is an intersection, and enter coords
156 // enter coords will be equal to (x0, y0) if starting point is inside the box
157 // if result is `false`, `inx` and `iny` are undefined
158 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
161 procedure swapInt (var a
: Integer; var b
: Integer); inline;
162 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline;
171 // ////////////////////////////////////////////////////////////////////////// //
172 procedure swapInt (var a
: Integer; var b
: Integer); inline; var t
: Integer; begin t
:= a
; a
:= b
; b
:= t
; end;
174 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline; begin result
:= (x1
-x0
)*(x1
-x0
)+(y1
-y0
)*(y1
-y0
); end;
177 // ////////////////////////////////////////////////////////////////////////// //
178 // you are not supposed to understand this
179 // returns `true` if there is an intersection, and enter coords
180 // enter coords will be equal to (x0, y0) if starting point is inside the box
181 // if result is `false`, `inx` and `iny` are undefined
182 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
184 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
185 stx
, sty
: Integer; // "steps" for x and y axes
186 dsx
, dsy
: Integer; // "lengthes" for x and y axes
187 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
188 xd
, yd
: Integer; // current coord
189 e
: Integer; // "error" (as in bresenham algo)
200 if (bw
< 1) or (bh
< 1) then exit
; // impossible box
202 if (x0
= x1
) and (y0
= y1
) then
205 result
:= (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
);
209 // check if staring point is inside the box
210 if (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
) then begin result
:= true; exit
; end;
221 // from left to right
222 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
223 stx
:= 1; // going right
227 // from right to left
228 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
229 stx
:= -1; // going left
240 // from top to bottom
241 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
242 sty
:= 1; // going down
246 // from bottom to top
247 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
248 sty
:= -1; // going up
287 temp
:= dx2
*(wy0
-y0
)-dsx
;
290 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
291 if (xd
+1 >= wx0
) then
295 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
300 if (not xfixed
) and (x0
< wx0
) then
303 temp
:= dy2
*(wx0
-x0
);
306 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
309 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
315 temp
:= dx2
*(wy1
-y0
)+dsx
;
316 term
:= x0
+temp
div dy2
;
318 if (rem
= 0) then Dec(term
);
321 if (term
> wx1
) then term
:= wx1
; // clip at right
323 Inc(term
); // draw last point
324 //if (term = xd) then exit; // this is the only point, get out of here
326 if (sty
= -1) then yd
:= -yd
;
327 if (stx
= -1) then begin xd
:= -xd
; term
:= -term
; end;
336 // ////////////////////////////////////////////////////////////////////////// //
337 procedure TBodyGridBase
.TBodyProxyRec
.setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
350 // ////////////////////////////////////////////////////////////////////////// //
351 constructor TBodyGridBase
.Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
355 dbgShowTraceLog
:= false;
357 if aTileSize < 1 then aTileSize := 1;
358 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
359 mTileSize := aTileSize;
361 if (aPixWidth
< mTileSize
) then aPixWidth
:= mTileSize
;
362 if (aPixHeight
< mTileSize
) then aPixHeight
:= mTileSize
;
365 mWidth
:= (aPixWidth
+mTileSize
-1) div mTileSize
;
366 mHeight
:= (aPixHeight
+mTileSize
-1) div mTileSize
;
367 SetLength(mGrid
, mWidth
*mHeight
);
368 SetLength(mCells
, mWidth
*mHeight
);
369 SetLength(mProxies
, 8192);
372 for idx
:= 0 to High(mCells
) do
374 mCells
[idx
].bodies
[0] := -1;
375 mCells
[idx
].next
:= idx
+1;
377 mCells
[High(mCells
)].next
:= -1; // last cell
379 for idx
:= 0 to High(mGrid
) do mGrid
[idx
] := -1;
381 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
382 mProxies
[High(mProxies
)].nextLink
:= -1;
388 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
]), MSG_NOTIFY
);
392 destructor TBodyGridBase
.Destroy ();
401 // ////////////////////////////////////////////////////////////////////////// //
402 procedure TBodyGridBase
.dumpStats ();
404 idx
, mcb
, cidx
, cnt
: Integer;
407 for idx
:= 0 to High(mGrid
) do
414 cidx
:= mCells
[cidx
].next
;
416 if (mcb
< cnt
) then mcb
:= cnt
;
418 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
);
422 // ////////////////////////////////////////////////////////////////////////// //
423 function TBodyGridBase
.getGridWidthPx (): Integer; inline; begin result
:= mWidth
*mTileSize
; end;
424 function TBodyGridBase
.getGridHeightPx (): Integer; inline; begin result
:= mHeight
*mTileSize
; end;
427 function TBodyGridBase
.insideGrid (x
, y
: Integer): Boolean; inline;
432 result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
*mTileSize
) and (y
< mHeight
*mTileSize
);
436 function TBodyGridBase
.getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
438 if (body
>= 0) and (body
< Length(mProxies
)) then
440 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; end;
452 // ////////////////////////////////////////////////////////////////////////// //
453 function TBodyGridBase
.getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
455 if (pid
>= 0) then result
:= ((mProxies
[pid
].mTag
and TagDisabled
) = 0) else result
:= false;
459 procedure TBodyGridBase
.setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
465 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
and not TagDisabled
;
469 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
or TagDisabled
;
475 // ////////////////////////////////////////////////////////////////////////// //
476 function TBodyGridBase
.allocCell (): Integer;
480 if (mFreeCell
< 0) then
482 // no free cells, want more
483 mFreeCell
:= Length(mCells
);
484 SetLength(mCells
, mFreeCell
+32768); // arbitrary number
485 for idx
:= mFreeCell
to High(mCells
) do
487 mCells
[idx
].bodies
[0] := -1;
488 mCells
[idx
].next
:= idx
+1;
490 mCells
[High(mCells
)].next
:= -1; // last cell
493 mFreeCell
:= mCells
[result
].next
;
494 mCells
[result
].next
:= -1;
495 mCells
[result
].bodies
[0] := -1;
497 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
501 procedure TBodyGridBase
.freeCell (idx
: Integer);
503 if (idx
>= 0) and (idx
< Length(mCells
)) then
505 //if mCells[idx].body = -1 then exit; // the thing that should not be
506 mCells
[idx
].bodies
[0] := -1;
507 mCells
[idx
].next
:= mFreeCell
;
514 // ////////////////////////////////////////////////////////////////////////// //
515 function TBodyGridBase
.allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
520 if (mProxyFree
= -1) then
522 // no free proxies, resize list
523 olen
:= Length(mProxies
);
524 SetLength(mProxies
, olen
+8192); // arbitrary number
525 for idx
:= olen
to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
526 mProxies
[High(mProxies
)].nextLink
:= -1;
530 result
:= mProxyFree
;
531 px
:= @mProxies
[result
];
532 mProxyFree
:= px
.nextLink
;
533 px
.setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
538 if (mProxyMaxCount
< mProxyCount
) then mProxyMaxCount
:= mProxyCount
;
541 procedure TBodyGridBase
.freeProxy (body
: TBodyProxyId
);
543 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
544 if (mProxyCount
= 0) then raise Exception
.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
546 mProxies
[body
].mObj
:= nil;
547 mProxies
[body
].nextLink
:= mProxyFree
;
553 // ////////////////////////////////////////////////////////////////////////// //
554 function TBodyGridBase
.forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
562 if (w
< 1) or (h
< 1) or not assigned(cb
) then exit
;
567 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
570 //tsize := mTileSize;
571 if (x
>= gw
*tsize
) or (y
>= gh
*tsize
) then exit
;
572 for gy
:= y
div tsize
to (y
+h
-1) div tsize
do
574 if (gy
< 0) then continue
;
575 if (gy
>= gh
) then break
;
576 for gx
:= x
div tsize
to (x
+w
-1) div tsize
do
578 if (gx
< 0) then continue
;
579 if (gx
>= gw
) then break
;
580 result
:= cb(gy
*gw
+gx
, bodyId
);
587 // ////////////////////////////////////////////////////////////////////////// //
588 function TBodyGridBase
.inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
595 result
:= false; // never stop
596 // add body to the given grid cell
602 for f
:= 0 to High(TGridCell
.bodies
) do
604 if (pi
.bodies
[f
] = -1) then
607 pi
.bodies
[f
] := bodyId
;
608 if (f
+1 < Length(TGridCell
.bodies
)) then pi
.bodies
[f
+1] := -1;
613 // either no room, or no cell at all
615 mCells
[cidx
].bodies
[0] := bodyId
;
616 mCells
[cidx
].bodies
[1] := -1;
617 mCells
[cidx
].next
:= pc
;
618 mGrid
[grida
] := cidx
;
621 procedure TBodyGridBase
.insertInternal (body
: TBodyProxyId
);
625 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
626 px
:= @mProxies
[body
];
627 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, inserter
, body
);
631 // absolutely not tested
632 function TBodyGridBase
.remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
635 pidx
, idx
, tmp
: Integer;
638 result
:= false; // never stop
639 // find and remove cell
644 tmp
:= mCells
[idx
].next
;
647 while (f
< High(TGridCell
.bodies
)) do
649 if (pc
.bodies
[f
] = bodyId
) then
652 if (f
= 0) and (pc
.bodies
[1] = -1) then
654 // this cell contains no elements, remove it
655 tmp
:= mCells
[idx
].next
;
656 if (pidx
= -1) then mGrid
[grida
] := tmp
else mCells
[pidx
].next
:= tmp
;
661 // remove element from bucket
663 while (f
< High(TGridCell
.bodies
)) do
665 pc
.bodies
[f
-1] := pc
.bodies
[f
];
666 if (pc
.bodies
[f
] = -1) then break
;
669 pc
.bodies
[High(TGridCell
.bodies
)] := -1; // just in case
671 exit
; // assume that we cannot have one object added to bucket twice
680 // absolutely not tested
681 procedure TBodyGridBase
.removeInternal (body
: TBodyProxyId
);
685 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
686 px
:= @mProxies
[body
];
687 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
691 // ////////////////////////////////////////////////////////////////////////// //
692 function TBodyGridBase
.insertBody (aObj
: ITP
; aX
, aY
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
694 aTag
:= aTag
and TagFullMask
;
695 result
:= allocProxy(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
696 insertInternal(result
);
700 procedure TBodyGridBase
.removeBody (body
: TBodyProxyId
);
702 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
703 removeInternal(body
);
708 // ////////////////////////////////////////////////////////////////////////// //
709 procedure TBodyGridBase
.moveResizeBody (body
: TBodyProxyId
; dx
, dy
, sx
, sy
: Integer);
712 x0
, y0
, w
, h
: Integer;
714 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
715 if (dx
= 0) and (dy
= 0) and (sx
= 0) and (sy
= 0) then exit
;
716 px
:= @mProxies
[body
];
721 // did any corner crossed tile boundary?
722 if (x0
div mTileSize
<> (x0
+dx
) div mTileSize
) or
723 (y0
div mTileSize
<> (y0
+dx
) div mTileSize
) or
724 ((x0
+w
) div mTileSize
<> (x0
+w
+sx
) div mTileSize
) or
725 ((y0
+h
) div mTileSize
<> (y0
+h
+sy
) div mTileSize
) then
727 removeInternal(body
);
732 insertInternal(body
);
743 procedure TBodyGridBase
.moveBody (body
: TBodyProxyId
; dx
, dy
: Integer);
748 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
749 if (dx
= 0) and (dy
= 0) then exit
;
750 // check if tile coords was changed
751 px
:= @mProxies
[body
];
754 if (nx
div mTileSize
<> px
.mX
div mTileSize
) or (ny
div mTileSize
<> px
.mY
div mTileSize
) then
756 // crossed tile boundary, do heavy work
757 moveResizeBody(body
, dx
, dy
, 0, 0);
761 // nothing to do with the grid, just fix coordinates
767 procedure TBodyGridBase
.resizeBody (body
: TBodyProxyId
; sx
, sy
: Integer);
773 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
774 if (sx
= 0) and (sy
= 0) then exit
;
775 // check if tile coords was changed
776 px
:= @mProxies
[body
];
781 if ((x0
+px
.mWidth
) div mTileSize
<> (x0
+nw
) div mTileSize
) or
782 ((y0
+px
.mHeight
) div mTileSize
<> (y0
+nh
) div mTileSize
) then
784 // crossed tile boundary, do heavy work
785 moveResizeBody(body
, 0, 0, sx
, sy
);
789 // nothing to do with the grid, just fix size
796 // ////////////////////////////////////////////////////////////////////////// //
797 // no callback: return `true` on the first hit
798 function TBodyGridBase
.forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
807 result
:= Default(ITP
);
808 tagmask
:= tagmask
and TagFullMask
;
809 if (tagmask
= 0) then exit
;
811 // make coords (0,0)-based
814 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
>= mHeight
*mTileSize
) then exit
;
816 curci
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
821 // increase query counter
823 if (mLastQuery
= 0) then
825 // just in case of overflow
827 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
831 while (curci
<> -1) do
833 cc
:= @mCells
[curci
];
834 for f
:= 0 to High(TGridCell
.bodies
) do
836 if (cc
.bodies
[f
] = -1) then break
;
837 px
:= @mProxies
[cc
.bodies
[f
]];
839 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
841 if (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
846 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
861 // ////////////////////////////////////////////////////////////////////////// //
862 // no callback: return `true` on the first hit
863 function TBodyGridBase
.forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
878 result
:= Default(ITP
);
879 if (w
< 1) or (h
< 1) then exit
;
880 tagmask
:= tagmask
and TagFullMask
;
881 if (tagmask
= 0) then exit
;
891 //tsize := mTileSize;
893 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
894 if (x
>= gw
*tsize
) or (y
>= mHeight
*tsize
) then exit
;
896 // increase query counter
898 if (mLastQuery
= 0) then
900 // just in case of overflow
902 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
904 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
908 for gy
:= y
div tsize
to (y
+h
-1) div tsize
do
910 if (gy
< 0) then continue
;
911 if (gy
>= mHeight
) then break
;
912 for gx
:= x
div tsize
to (x
+w
-1) div tsize
do
914 if (gx
< 0) then continue
;
915 if (gx
>= gw
) then break
;
917 curci
:= mGrid
[gy
*gw
+gx
];
918 while (curci
<> -1) do
920 cc
:= @mCells
[curci
];
921 for f
:= 0 to High(TGridCell
.bodies
) do
923 if (cc
.bodies
[f
] = -1) then break
;
924 px
:= @mProxies
[cc
.bodies
[f
]];
926 if (not allowDisabled
) and ((ptag
and TagDisabled
) <> 0) then continue
;
927 if ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
928 //if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
929 //if ( ((ptag and TagDisabled) = 0) = ignoreDisabled) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
931 if (x0
>= px
.mX
+px
.mWidth
) or (y0
>= px
.mY
+px
.mHeight
) then continue
;
932 if (x0
+w
<= px
.mX
) or (y0
+h
<= px
.mY
) then continue
;
936 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
952 // ////////////////////////////////////////////////////////////////////////// //
953 // no callback: return `true` on the nearest hit
954 function TBodyGridBase
.traceRay (x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
958 result
:= traceRay(ex
, ey
, x0
, y0
, x1
, y1
, cb
, tagmask
);
962 // no callback: return `true` on the nearest hit
964 function TBodyGridBase.traceRay (out ex, ey: Integer; x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
972 stepx, stepy: Integer;
982 prevX, prevY: Integer;
985 lastDistSq, distSq: Integer;
986 wasHit: Boolean = false;
988 lastWasInGrid: Boolean;
992 result := Default(ITP);
993 lastObj := Default(ITP);
994 tagmask := tagmask and TagFullMask;
995 if (tagmask = 0) then begin ex := x0; ey := y0; exit; end;
1003 if (dx > 0) then incx := 1 else if (dx < 0) then incx := -1 else incx := 0;
1004 if (dy > 0) then incy := 1 else if (dy < 0) then incy := -1 else incy := 0;
1009 if (dx > dy) then d := dx else d := dy;
1011 // `x` and `y` will be in grid coords
1015 // increase query counter
1017 if (mLastQuery = 0) then
1019 // just in case of overflow
1021 for i := 0 to High(mProxies) do mProxies[i].mQueryMark := 0;
1025 // cache various things
1026 //tsize := mTileSize;
1032 // setup distance and flags
1033 lastDistSq := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)+1;
1034 lastWasInGrid := (x >= 0) and (y >= 0) and (x <= maxx) and (y <= maxy);
1036 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1037 if lastWasInGrid then ccidx := mGrid[(y div tsize)*gw+(x div tsize)] else ccidx := -1;
1039 // it is slightly faster this way
1046 // prevs are always in map coords
1052 // invariant: one of those always changed
1053 if (xerr < 0) and (yerr < 0) then raise Exception.Create('internal bug in grid raycaster (0)');
1054 if (xerr >= 0) then begin xerr -= d; x += incx; stepx := incx; end else stepx := 0;
1055 if (yerr >= 0) then begin yerr -= d; y += incy; stepy := incy; end else stepy := 0;
1056 // invariant: we always doing a step
1057 if ((stepx or stepy) = 0) then raise Exception.Create('internal bug in grid raycaster (1)');
1059 // check for crossing tile/grid boundary
1060 if (x >= 0) and (y >= 0) and (x <= maxx) and (y <= maxy) then
1062 // we're still in grid
1063 lastWasInGrid := true;
1064 // check for tile edge crossing
1065 if (stepx < 0) and ((x mod tsize) = tsize-1) then tbcross := true
1066 else if (stepx > 0) and ((x mod tsize) = 0) then tbcross := true
1067 else if (stepy < 0) and ((y mod tsize) = tsize-1) then tbcross := true
1068 else if (stepy > 0) and ((y mod tsize) = 0) then tbcross := true
1069 else tbcross := false;
1070 // crossed tile edge?
1073 // had something in the cell we're leaving?
1074 if (ccidx <> -1) then
1076 // yes, signal cell completion
1077 if assigned(cb) then
1079 if cb(nil, 0, x+minx, y+miny, prevX, prevY) then begin result := lastObj; exit; end;
1087 // setup new cell index
1088 ccidx := mGrid[(y div tsize)*gw+(x div tsize)];
1093 // out of grid, had something in the last processed cell?
1094 if (ccidx <> -1) then
1096 // yes, signal cell completion
1098 if assigned(cb) then
1100 if cb(nil, 0, x+minx, y+miny, prevX, prevY) then begin result := lastObj; exit; end;
1108 if lastWasInGrid then exit; // oops, stepped out of the grid -- there is no way to return
1112 // has something to process in the current cell?
1113 if (ccidx <> -1) then
1117 hasUntried := false; // this will be set to `true` if we have some proxies we still want to process at the next step
1118 // convert coords to map (to avoid ajdusting coords inside the loop)
1121 // process cell list
1122 while (curci <> -1) do
1124 cc := @mCells[curci];
1125 for f := 0 to High(TGridCell.bodies) do
1127 if (cc.bodies[f] = -1) then break;
1128 px := @mProxies[cc.bodies[f]];
1130 if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
1132 // can we process this proxy?
1133 if (x >= px.mX) and (y >= px.mY) and (x < px.mX+px.mWidth) and (y < px.mY+px.mHeight) then
1135 px.mQueryMark := lq; // mark as processed
1136 if assigned(cb) then
1138 if cb(px.mObj, ptag, x, y, prevX, prevY) then
1148 // remember this hitpoint if it is nearer than an old one
1149 distSq := (prevX-x0)*(prevX-x0)+(prevY-y0)*(prevY-y0);
1150 if (distSq < lastDistSq) then
1153 lastDistSq := distSq;
1162 // this is possibly interesting proxy, set "has more to check" flag
1170 // still has something interesting in this cell?
1171 if not hasUntried then
1173 // nope, don't process this cell anymore; signal cell completion
1175 if assigned(cb) then
1177 if cb(nil, 0, x, y, prevX, prevY) then begin result := lastObj; exit; end;
1185 // convert coords to grid
1194 // no callback: return `true` on the nearest hit
1195 // you are not supposed to understand this
1196 function TBodyGridBase
.traceRay (out ex
, ey
: Integer; ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
1200 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
1201 stx
, sty
: Integer; // "steps" for x and y axes
1202 dsx
, dsy
: Integer; // "lengthes" for x and y axes
1203 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
1204 xd
, yd
: Integer; // current coord
1205 e
: Integer; // "error" (as in bresenham algo)
1208 xptr
, yptr
: PInteger;
1211 prevx
, prevy
: Integer;
1212 lastDistSq
: Integer;
1213 ccidx
, curci
: Integer;
1214 hasUntried
: Boolean;
1215 lastGA
: Integer = -1;
1218 wasHit
: Boolean = false;
1219 gw
, gh
, minx
, miny
, maxx
, maxy
: Integer;
1223 f
, ptag
, distSq
: Integer;
1224 x0
, y0
, x1
, y1
: Integer;
1226 result
:= Default(ITP
);
1227 lastObj
:= Default(ITP
);
1228 tagmask
:= tagmask
and TagFullMask
;
1229 ex
:= ax1
; // why not?
1230 ey
:= ay1
; // why not?
1231 if (tagmask
= 0) then exit
;
1233 if (ax0
= ax1
) and (ay0
= ay1
) then exit
; // as the first point is ignored, just get outta here
1235 lastDistSq
:= distanceSq(ax0
, ay0
, ax1
, ay1
)+1;
1249 // offset query coords to (0,0)-based
1264 // from left to right
1265 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
1266 stx
:= 1; // going right
1270 // from right to left
1271 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
1272 stx
:= -1; // going left
1283 // from top to bottom
1284 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
1285 sty
:= 1; // going down
1289 // from bottom to top
1290 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
1291 sty
:= -1; // going up
1330 temp
:= dx2
*(wy0
-y0
)-dsx
;
1332 rem
:= temp
mod dy2
;
1333 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
1334 if (xd
+1 >= wx0
) then
1338 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
1343 if (not xfixed
) and (x0
< wx0
) then
1346 temp
:= dy2
*(wx0
-x0
);
1348 rem
:= temp
mod dx2
;
1349 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
1352 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
1358 temp
:= dx2
*(wy1
-y0
)+dsx
;
1359 term
:= x0
+temp
div dy2
;
1360 rem
:= temp
mod dy2
;
1361 if (rem
= 0) then Dec(term
);
1364 if (term
> wx1
) then term
:= wx1
; // clip at right
1366 Inc(term
); // draw last point
1367 //if (term = xd) then exit; // this is the only point, get out of here
1369 if (sty
= -1) then yd
:= -yd
;
1370 if (stx
= -1) then begin xd
:= -xd
; term
:= -term
; end;
1373 // first move, to skip starting point
1374 if (xd
= term
) then exit
;
1375 prevx
:= xptr
^+minx
;
1376 prevy
:= yptr
^+miny
;
1378 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
1381 if (xd
= term
) then exit
;
1383 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ > mHeight
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
1385 if (dbgShowTraceLog
) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0
, ay0
, ax1
, ay1
, xptr
^, yptr
^]), MSG_NOTIFY
);
1387 // restore query coords
1393 // increase query counter
1395 if (mLastQuery
= 0) then
1397 // just in case of overflow
1399 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1404 // draw it; can omit checks
1405 while (xd
<> term
) do
1408 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ > mHeight
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
1410 ga
:= (yptr
^ div tsize
)*gw
+(xptr
^ div tsize
);
1411 if (ga
<> lastGA
) then
1414 if (ccidx
<> -1) then
1416 // signal cell completion
1417 if assigned(cb
) then
1419 if cb(nil, 0, xptr
^+minx
, yptr
^+miny
, prevx
, prevy
) then begin result
:= lastObj
; exit
; end;
1428 ccidx
:= mGrid
[lastGA
];
1430 // has something to process in this tile?
1431 if (ccidx
<> -1) then
1435 hasUntried
:= false; // this will be set to `true` if we have some proxies we still want to process at the next step
1436 // convert coords to map (to avoid ajdusting coords inside the loop)
1439 // process cell list
1440 while (curci
<> -1) do
1442 cc
:= @mCells
[curci
];
1443 for f
:= 0 to High(TGridCell
.bodies
) do
1445 if (cc
.bodies
[f
] = -1) then break
;
1446 px
:= @mProxies
[cc
.bodies
[f
]];
1448 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1450 // can we process this proxy?
1451 if (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
1453 px
.mQueryMark
:= lq
; // mark as processed
1454 if assigned(cb
) then
1456 if cb(px
.mObj
, ptag
, x
, y
, prevx
, prevy
) then
1466 // remember this hitpoint if it is nearer than an old one
1467 distSq
:= distanceSq(ax0
, ay0
, prevx
, prevy
);
1468 if (distSq
< lastDistSq
) then
1471 lastDistSq
:= distSq
;
1480 // this is possibly interesting proxy, set "has more to check" flag
1488 // still has something interesting in this cell?
1489 if not hasUntried
then
1491 // nope, don't process this cell anymore; signal cell completion
1493 if assigned(cb
) then
1495 if cb(nil, 0, x
, y
, prevx
, prevy
) then begin result
:= lastObj
; exit
; end;
1504 //putPixel(xptr^, yptr^);
1506 prevx
:= xptr
^+minx
;
1507 prevy
:= yptr
^+miny
;
1508 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
1514 // ////////////////////////////////////////////////////////////////////////// //
1515 //FIXME! optimize this with real tile walking
1516 function TBodyGridBase
.forEachAlongLine (x0
, y0
, x1
, y1
: Integer; cb
: TGridAlongQueryCB
; tagmask
: Integer=-1): ITP
;
1522 xerr
, yerr
: Integer;
1523 incx
, incy
: Integer;
1524 stepx
, stepy
: Integer;
1526 maxx
, maxy
: Integer;
1533 minx
, miny
: Integer;
1535 lastWasInGrid
: Boolean;
1539 result
:= Default(ITP
);
1540 tagmask
:= tagmask
and TagFullMask
;
1541 if (tagmask
= 0) then exit
;
1549 if (dx
> 0) then incx
:= 1 else if (dx
< 0) then incx
:= -1 else incx
:= 0;
1550 if (dy
> 0) then incy
:= 1 else if (dy
< 0) then incy
:= -1 else incy
:= 0;
1555 if (dx
> dy
) then d
:= dx
else d
:= dy
;
1557 // `x` and `y` will be in grid coords
1561 // increase query counter
1563 if (mLastQuery
= 0) then
1565 // just in case of overflow
1567 for i
:= 0 to High(mProxies
) do mProxies
[i
].mQueryMark
:= 0;
1571 // cache various things
1572 //tsize := mTileSize;
1578 // setup distance and flags
1579 lastWasInGrid
:= (x
>= 0) and (y
>= 0) and (x
<= maxx
) and (y
<= maxy
);
1581 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1582 if lastWasInGrid
then ccidx
:= mGrid
[(y
div tsize
)*gw
+(x
div tsize
)] else ccidx
:= -1;
1584 // it is slightly faster this way
1594 // invariant: one of those always changed
1595 if (xerr
< 0) and (yerr
< 0) then raise Exception
.Create('internal bug in grid raycaster (0)');
1596 if (xerr
>= 0) then begin xerr
-= d
; x
+= incx
; stepx
:= incx
; end else stepx
:= 0;
1597 if (yerr
>= 0) then begin yerr
-= d
; y
+= incy
; stepy
:= incy
; end else stepy
:= 0;
1598 // invariant: we always doing a step
1599 if ((stepx
or stepy
) = 0) then raise Exception
.Create('internal bug in grid raycaster (1)');
1601 // check for crossing tile/grid boundary
1602 if (x
>= 0) and (y
>= 0) and (x
<= maxx
) and (y
<= maxy
) then
1604 // we're still in grid
1605 lastWasInGrid
:= true;
1606 // check for tile edge crossing
1607 if (stepx
< 0) and ((x
mod tsize
) = tsize
-1) then tbcross
:= true
1608 else if (stepx
> 0) and ((x
mod tsize
) = 0) then tbcross
:= true
1609 else if (stepy
< 0) and ((y
mod tsize
) = tsize
-1) then tbcross
:= true
1610 else if (stepy
> 0) and ((y
mod tsize
) = 0) then tbcross
:= true
1611 else tbcross
:= false;
1612 // crossed tile edge?
1615 // setup new cell index
1616 ccidx
:= mGrid
[(y
div tsize
)*gw
+(x
div tsize
)];
1622 if lastWasInGrid
then exit
; // oops, stepped out of the grid -- there is no way to return
1626 // has something to process in the current cell?
1627 if (ccidx
<> -1) then
1631 // convert coords to map (to avoid ajdusting coords inside the loop)
1634 // process cell list
1635 while (curci
<> -1) do
1637 cc
:= @mCells
[curci
];
1638 for f
:= 0 to High(TGridCell
.bodies
) do
1640 if (cc
.bodies
[f
] = -1) then break
;
1641 px
:= @mProxies
[cc
.bodies
[f
]];
1643 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1645 px
.mQueryMark
:= lq
; // mark as processed
1646 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
1652 ccidx
:= -1; // don't process this anymore
1653 // convert coords to grid