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 {$IF DEFINED(D2F_DEBUG)}
19 {.$DEFINE D2F_DEBUG_RAYTRACE}
20 {.$DEFINE D2F_DEBUG_XXQ}
21 {.$DEFINE D2F_DEBUG_MOVER}
29 TBodyProxyId
= Integer;
31 generic TBodyGridBase
<ITP
> = class(TObject
)
33 type TGridQueryCB
= function (obj
: ITP
; tag
: Integer): Boolean is nested
; // return `true` to stop
34 type TGridRayQueryCB
= function (obj
: ITP
; tag
: Integer; x
, y
, prevx
, prevy
: Integer): Boolean is nested
; // return `true` to stop
35 type TGridAlongQueryCB
= function (obj
: ITP
; tag
: Integer): Boolean is nested
; // return `true` to stop
37 type TCellQueryCB
= procedure (x
, y
: Integer) is nested
; // top-left cell corner coords
39 const TagDisabled
= $40000000;
40 const TagFullMask
= $3fffffff;
44 GridDefaultTileSize
= 32; // must be power of two!
45 GridCellBucketSize
= 8; // WARNING! can't be less than 2!
49 PBodyProxyRec
= ^TBodyProxyRec
;
50 TBodyProxyRec
= record
52 mX
, mY
, mWidth
, mHeight
: Integer; // aabb
53 mQueryMark
: LongWord; // was this object visited at this query?
55 mTag
: Integer; // `TagDisabled` set: disabled ;-)
56 nextLink
: TBodyProxyId
; // next free or nothing
59 procedure setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
62 PGridCell
= ^TGridCell
;
64 bodies
: array [0..GridCellBucketSize
-1] of Integer; // -1: end of list
65 next
: Integer; // in this cell; index in mCells
68 TGridInternalCB
= function (grida
: Integer; bodyId
: TBodyProxyId
): Boolean of object; // return `true` to stop
72 const mTileSize
= GridDefaultTileSize
;
75 const tileSize
= mTileSize
;
78 mMinX
, mMinY
: Integer; // so grids can start at any origin
79 mWidth
, mHeight
: Integer; // in tiles
80 mGrid
: array of Integer; // mWidth*mHeight, index in mCells
81 mCells
: array of TGridCell
; // cell pool
82 mFreeCell
: Integer; // first free cell index or -1
85 mProxies
: array of TBodyProxyRec
;
86 mProxyFree
: TBodyProxyId
; // free
87 mProxyCount
: Integer; // currently used
88 mProxyMaxCount
: Integer;
91 dbgShowTraceLog
: Boolean;
92 {$IF DEFINED(D2F_DEBUG)}
93 dbgRayTraceTileHitCB
: TCellQueryCB
;
97 function allocCell (): Integer;
98 procedure freeCell (idx
: Integer); // `next` is simply overwritten
100 function allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
101 procedure freeProxy (body
: TBodyProxyId
);
103 procedure insertInternal (body
: TBodyProxyId
);
104 procedure removeInternal (body
: TBodyProxyId
);
106 function forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
108 function inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
109 function remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
111 function getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
112 procedure setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
114 function getGridWidthPx (): Integer; inline;
115 function getGridHeightPx (): Integer; inline;
118 constructor Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
119 destructor Destroy (); override;
121 function insertBody (aObj
: ITP
; ax
, ay
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
122 procedure removeBody (body
: TBodyProxyId
); // WARNING! this WILL destroy proxy!
124 procedure moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
125 procedure resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
126 procedure moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
128 function insideGrid (x
, y
: Integer): Boolean; inline;
130 // `false` if `body` is surely invalid
131 function getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
132 function getBodyWH (body
: TBodyProxyId
; out rw
, rh
: Integer): Boolean; inline;
133 function getBodyDims (body
: TBodyProxyId
; out rx
, ry
, rw
, rh
: Integer): Boolean; inline;
135 //WARNING: don't modify grid while any query is in progress (no checks are made!)
136 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
137 // no callback: return `true` on the first hit
138 function forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): 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 // no callback: return `true` on the first hit
143 function forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
145 //WARNING: don't modify grid while any query is in progress (no checks are made!)
146 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
147 // cb with `(nil)` will be called before processing new tile
148 // no callback: return `true` on the nearest hit
149 //WARNING: don't change tags in callbacks here!
150 function traceRay (const x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
; overload
;
151 function traceRay (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
153 //WARNING: don't modify grid while any query is in progress (no checks are made!)
154 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
155 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
156 //WARNING: don't change tags in callbacks here!
157 function forEachAlongLine (const x0
, y0
, x1
, y1
: Integer; cb
: TGridAlongQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
160 procedure forEachBodyCell (body
: TBodyProxyId
; cb
: TCellQueryCB
);
161 function forEachInCell (x
, y
: Integer; cb
: TGridQueryCB
): ITP
;
162 procedure dumpStats ();
164 //WARNING! no sanity checks!
165 property proxyEnabled
[pid
: TBodyProxyId
]: Boolean read getProxyEnabled write setProxyEnabled
;
167 property gridX0
: Integer read mMinX
;
168 property gridY0
: Integer read mMinY
;
169 property gridWidth
: Integer read getGridWidthPx
; // in pixels
170 property gridHeight
: Integer read getGridHeightPx
; // in pixels
174 // you are not supposed to understand this
175 // returns `true` if there is an intersection, and enter coords
176 // enter coords will be equal to (x0, y0) if starting point is inside the box
177 // if result is `false`, `inx` and `iny` are undefined
178 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
180 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline;
182 procedure swapInt (var a
: Integer; var b
: Integer); inline;
183 function minInt (a
, b
: Integer): Integer; inline;
184 function maxInt (a
, b
: Integer): Integer; inline;
193 // ////////////////////////////////////////////////////////////////////////// //
194 procedure swapInt (var a
: Integer; var b
: Integer); inline; var t
: Integer; begin t
:= a
; a
:= b
; b
:= t
; end;
195 function minInt (a
, b
: Integer): Integer; inline; begin if (a
< b
) then result
:= a
else result
:= b
; end;
196 function maxInt (a
, b
: Integer): Integer; inline; begin if (a
> b
) then result
:= a
else result
:= b
; end;
198 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline; begin result
:= (x1
-x0
)*(x1
-x0
)+(y1
-y0
)*(y1
-y0
); end;
201 // ////////////////////////////////////////////////////////////////////////// //
202 // you are not supposed to understand this
203 // returns `true` if there is an intersection, and enter coords
204 // enter coords will be equal to (x0, y0) if starting point is inside the box
205 // if result is `false`, `inx` and `iny` are undefined
206 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
208 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
209 stx
, sty
: Integer; // "steps" for x and y axes
210 dsx
, dsy
: Integer; // "lengthes" for x and y axes
211 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
212 xd
, yd
: Integer; // current coord
213 e
: Integer; // "error" (as in bresenham algo)
224 if (bw
< 1) or (bh
< 1) then exit
; // impossible box
226 if (x0
= x1
) and (y0
= y1
) then
229 result
:= (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
);
233 // check if staring point is inside the box
234 if (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
) then begin result
:= true; exit
; end;
245 // from left to right
246 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
247 stx
:= 1; // going right
251 // from right to left
252 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
253 stx
:= -1; // going left
264 // from top to bottom
265 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
266 sty
:= 1; // going down
270 // from bottom to top
271 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
272 sty
:= -1; // going up
311 temp
:= dx2
*(wy0
-y0
)-dsx
;
314 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
315 if (xd
+1 >= wx0
) then
319 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
324 if (not xfixed
) and (x0
< wx0
) then
327 temp
:= dy2
*(wx0
-x0
);
330 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
333 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
340 temp := dx2*(wy1-y0)+dsx;
341 term := x0+temp div dy2;
343 if (rem = 0) then Dec(term);
346 if (term > wx1) then term := wx1; // clip at right
348 Inc(term); // draw last point
349 //if (term = xd) then exit; // this is the only point, get out of here
352 if (sty
= -1) then yd
:= -yd
;
353 if (stx
= -1) then begin xd
:= -xd
; {!term := -term;} end;
362 // ////////////////////////////////////////////////////////////////////////// //
363 procedure TBodyGridBase
.TBodyProxyRec
.setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
376 // ////////////////////////////////////////////////////////////////////////// //
377 constructor TBodyGridBase
.Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
381 dbgShowTraceLog
:= false;
382 {$IF DEFINED(D2F_DEBUG)}
383 dbgRayTraceTileHitCB
:= nil;
386 if aTileSize < 1 then aTileSize := 1;
387 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
388 mTileSize := aTileSize;
390 if (aPixWidth
< mTileSize
) then aPixWidth
:= mTileSize
;
391 if (aPixHeight
< mTileSize
) then aPixHeight
:= mTileSize
;
394 mWidth
:= (aPixWidth
+mTileSize
-1) div mTileSize
;
395 mHeight
:= (aPixHeight
+mTileSize
-1) div mTileSize
;
396 SetLength(mGrid
, mWidth
*mHeight
);
397 SetLength(mCells
, mWidth
*mHeight
);
398 SetLength(mProxies
, 8192);
401 for idx
:= 0 to High(mCells
) do
403 mCells
[idx
].bodies
[0] := -1;
404 mCells
[idx
].bodies
[GridCellBucketSize
-1] := -1; // "has free room" flag
405 mCells
[idx
].next
:= idx
+1;
407 mCells
[High(mCells
)].next
:= -1; // last cell
409 for idx
:= 0 to High(mGrid
) do mGrid
[idx
] := -1;
411 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
412 mProxies
[High(mProxies
)].nextLink
:= -1;
418 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
]), MSG_NOTIFY
);
422 destructor TBodyGridBase
.Destroy ();
431 // ////////////////////////////////////////////////////////////////////////// //
432 procedure TBodyGridBase
.dumpStats ();
434 idx
, mcb
, cidx
, cnt
: Integer;
437 for idx
:= 0 to High(mGrid
) do
444 cidx
:= mCells
[cidx
].next
;
446 if (mcb
< cnt
) then mcb
:= cnt
;
448 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
);
452 procedure TBodyGridBase
.forEachBodyCell (body
: TBodyProxyId
; cb
: TCellQueryCB
);
457 if (body
< 0) or (body
> High(mProxies
)) or not assigned(cb
) then exit
;
458 for g
:= 0 to High(mGrid
) do
461 while (cidx
<> -1) do
464 for f
:= 0 to GridCellBucketSize
-1 do
466 if (cc
.bodies
[f
] = -1) then break
;
467 if (cc
.bodies
[f
] = body
) then cb((g
mod mWidth
)*mTileSize
+mMinX
, (g
div mWidth
)*mTileSize
+mMinY
);
476 function TBodyGridBase
.forEachInCell (x
, y
: Integer; cb
: TGridQueryCB
): ITP
;
481 result
:= Default(ITP
);
482 if not assigned(cb
) then exit
;
485 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
> mHeight
*mTileSize
) then exit
;
486 cidx
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
487 while (cidx
<> -1) do
490 for f
:= 0 to GridCellBucketSize
-1 do
492 if (cc
.bodies
[f
] = -1) then break
;
493 if cb(mProxies
[cc
.bodies
[f
]].mObj
, mProxies
[cc
.bodies
[f
]].mTag
) then begin result
:= mProxies
[cc
.bodies
[f
]].mObj
; exit
; end;
501 // ////////////////////////////////////////////////////////////////////////// //
502 function TBodyGridBase
.getGridWidthPx (): Integer; inline; begin result
:= mWidth
*mTileSize
; end;
503 function TBodyGridBase
.getGridHeightPx (): Integer; inline; begin result
:= mHeight
*mTileSize
; end;
506 function TBodyGridBase
.insideGrid (x
, y
: Integer): Boolean; inline;
511 result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
*mTileSize
) and (y
< mHeight
*mTileSize
);
515 function TBodyGridBase
.getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
517 if (body
>= 0) and (body
< Length(mProxies
)) then
519 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; end;
531 function TBodyGridBase
.getBodyWH (body
: TBodyProxyId
; out rw
, rh
: Integer): Boolean; inline;
533 if (body
>= 0) and (body
< Length(mProxies
)) then
535 with mProxies
[body
] do begin rw
:= mWidth
; rh
:= mHeight
; end;
547 function TBodyGridBase
.getBodyDims (body
: TBodyProxyId
; out rx
, ry
, rw
, rh
: Integer): Boolean; inline;
549 if (body
>= 0) and (body
< Length(mProxies
)) then
551 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; rw
:= mWidth
; rh
:= mHeight
; end;
566 // ////////////////////////////////////////////////////////////////////////// //
567 function TBodyGridBase
.getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
569 if (pid
>= 0) then result
:= ((mProxies
[pid
].mTag
and TagDisabled
) = 0) else result
:= false;
573 procedure TBodyGridBase
.setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
579 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
and not TagDisabled
;
583 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
or TagDisabled
;
589 // ////////////////////////////////////////////////////////////////////////// //
590 function TBodyGridBase
.allocCell (): Integer;
595 if (mFreeCell
< 0) then
597 // no free cells, want more
598 mFreeCell
:= Length(mCells
);
599 SetLength(mCells
, mFreeCell
+32768); // arbitrary number
600 for idx
:= mFreeCell
to High(mCells
) do
602 mCells
[idx
].bodies
[0] := -1;
603 mCells
[idx
].bodies
[GridCellBucketSize
-1] := -1; // 'has free room' flag
604 mCells
[idx
].next
:= idx
+1;
606 mCells
[High(mCells
)].next
:= -1; // last cell
609 pc
:= @mCells
[result
];
610 mFreeCell
:= pc
.next
;
613 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
617 procedure TBodyGridBase
.freeCell (idx
: Integer);
619 if (idx
>= 0) and (idx
< Length(mCells
)) then
624 bodies
[GridCellBucketSize
-1] := -1; // 'has free room' flag
633 // ////////////////////////////////////////////////////////////////////////// //
634 function TBodyGridBase
.allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
639 if (mProxyFree
= -1) then
641 // no free proxies, resize list
642 olen
:= Length(mProxies
);
643 SetLength(mProxies
, olen
+8192); // arbitrary number
644 for idx
:= olen
to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
645 mProxies
[High(mProxies
)].nextLink
:= -1;
649 result
:= mProxyFree
;
650 px
:= @mProxies
[result
];
651 mProxyFree
:= px
.nextLink
;
652 px
.setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
657 if (mProxyMaxCount
< mProxyCount
) then mProxyMaxCount
:= mProxyCount
;
660 procedure TBodyGridBase
.freeProxy (body
: TBodyProxyId
);
662 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
663 if (mProxyCount
= 0) then raise Exception
.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
665 mProxies
[body
].mObj
:= nil;
666 mProxies
[body
].nextLink
:= mProxyFree
;
672 // ////////////////////////////////////////////////////////////////////////// //
673 function TBodyGridBase
.forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
681 if (w
< 1) or (h
< 1) or not assigned(cb
) then exit
;
686 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
689 //tsize := mTileSize;
690 if (x
>= gw
*tsize
) or (y
>= gh
*tsize
) then exit
;
691 for gy
:= y
div tsize
to (y
+h
-1) div tsize
do
693 if (gy
< 0) then continue
;
694 if (gy
>= gh
) then break
;
695 for gx
:= x
div tsize
to (x
+w
-1) div tsize
do
697 if (gx
< 0) then continue
;
698 if (gx
>= gw
) then break
;
699 result
:= cb(gy
*gw
+gx
, bodyId
);
706 // ////////////////////////////////////////////////////////////////////////// //
707 function TBodyGridBase
.inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
714 result
:= false; // never stop
715 // add body to the given grid cell
719 {$IF DEFINED(D2F_DEBUG)}
721 while (cidx
<> -1) do
724 for f
:= 0 to GridCellBucketSize
-1 do
726 if (pi
.bodies
[f
] = -1) then break
;
727 if (pi
.bodies
[f
] = bodyId
) then raise Exception
.Create('trying to insert already inserted proxy');
733 while (cidx
<> -1) do
736 // check "has room" flag
737 if (pi
.bodies
[GridCellBucketSize
-1] = -1) then
740 for f
:= 0 to GridCellBucketSize
-1 do
742 if (pi
.bodies
[f
] = -1) then
744 pi
.bodies
[f
] := bodyId
;
745 if (f
+1 < GridCellBucketSize
) then pi
.bodies
[f
+1] := -1;
749 raise Exception
.Create('internal error in grid inserter');
751 // no room, go to next cell in list (if there is any)
754 // no room in cells, add new cell to list
756 // either no room, or no cell at all
759 pi
.bodies
[0] := bodyId
;
762 mGrid
[grida
] := cidx
;
765 procedure TBodyGridBase
.insertInternal (body
: TBodyProxyId
);
769 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
770 px
:= @mProxies
[body
];
771 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, inserter
, body
);
775 // assume that we cannot have one object added to bucket twice
776 function TBodyGridBase
.remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
782 result
:= false; // never stop
783 // find and remove cell
784 pidx
:= -1; // previous cell index
785 cidx
:= mGrid
[grida
]; // current cell index
786 while (cidx
<> -1) do
789 for f
:= 0 to GridCellBucketSize
-1 do
791 if (pc
.bodies
[f
] = bodyId
) then
794 if (f
= 0) and (pc
.bodies
[1] = -1) then
796 // this cell contains no elements, remove it
797 if (pidx
= -1) then mGrid
[grida
] := pc
.next
else mCells
[pidx
].next
:= pc
.next
;
801 // remove element from bucket
802 for c
:= f
to GridCellBucketSize
-2 do
804 pc
.bodies
[c
] := pc
.bodies
[c
+1];
805 if (pc
.bodies
[c
] = -1) then break
;
807 pc
.bodies
[GridCellBucketSize
-1] := -1; // "has free room" flag
816 procedure TBodyGridBase
.removeInternal (body
: TBodyProxyId
);
820 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
821 px
:= @mProxies
[body
];
822 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
826 // ////////////////////////////////////////////////////////////////////////// //
827 function TBodyGridBase
.insertBody (aObj
: ITP
; aX
, aY
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
829 aTag
:= aTag
and TagFullMask
;
830 result
:= allocProxy(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
831 insertInternal(result
);
835 procedure TBodyGridBase
.removeBody (body
: TBodyProxyId
);
837 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
838 removeInternal(body
);
843 // ////////////////////////////////////////////////////////////////////////// //
844 procedure TBodyGridBase
.moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
847 x0
, y0
, w
, h
: Integer;
849 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
850 px
:= @mProxies
[body
];
855 {$IF DEFINED(D2F_DEBUG_MOVER)}
856 e_WriteLog(Format('proxy #%d: MOVERESIZE: xg=%d;yg=%d;w=%d;h=%d;nx=%d;ny=%d;nw=%d;nh=%d', [body
, x0
-mMinX
, y0
-mMinY
, w
, h
, nx
-mMinX
, ny
-mMinY
, nw
, nh
]), MSG_NOTIFY
);
858 if (nx
= x0
) and (ny
= y0
) and (nw
= w
) and (nh
= h
) then exit
;
864 // did any corner crossed tile boundary?
865 if (x0
div mTileSize
<> nx
div mTileSize
) or
866 (y0
div mTileSize
<> ny
div mTileSize
) or
867 ((x0
+w
) div mTileSize
<> (nx
+nw
) div mTileSize
) or
868 ((y0
+h
) div mTileSize
<> (ny
+nh
) div mTileSize
) then
870 removeInternal(body
);
875 insertInternal(body
);
886 //TODO: optimize for horizontal/vertical moves
887 procedure TBodyGridBase
.moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
891 ogx0
, ogx1
, ogy0
, ogy1
: Integer; // old grid rect
892 ngx0
, ngx1
, ngy0
, ngy1
: Integer; // new grid rect
897 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
898 // check if tile coords was changed
899 px
:= @mProxies
[body
];
902 if (nx
= x0
) and (ny
= y0
) then exit
;
908 // check for heavy work
911 ogx0
:= x0
div mTileSize
;
912 ogy0
:= y0
div mTileSize
;
913 ngx0
:= nx
div mTileSize
;
914 ngy0
:= ny
div mTileSize
;
915 ogx1
:= (x0
+pw
-1) div mTileSize
;
916 ogy1
:= (y0
+ph
-1) div mTileSize
;
917 ngx1
:= (nx
+pw
-1) div mTileSize
;
918 ngy1
:= (ny
+ph
-1) div mTileSize
;
919 {$IF DEFINED(D2F_DEBUG_MOVER)}
920 e_WriteLog(Format('proxy #%d: checkmove: xg=%d;yg=%d;w=%d;h=%d;nx=%d;ny=%d og:(%d,%d)-(%d,%d); ng:(%d,%d)-(%d,%d)', [body
, x0
, y0
, pw
, ph
, nx
, ny
, ogx0
, ogy0
, ogx1
, ogy1
, ngx0
, ngy0
, ngx1
, ngy1
]), MSG_NOTIFY
);
922 if (ogx0
<> ngx0
) or (ogy0
<> ngy0
) or (ogx1
<> ngx1
) or (ogy1
<> ngy1
) then
924 // crossed tile boundary, do heavy work
927 // cycle with old rect, remove body where it is necessary
928 // optimized for horizontal moves
929 {$IF DEFINED(D2F_DEBUG_MOVER)}
930 e_WriteLog(Format('proxy #%d: xg=%d;yg=%d;w=%d;h=%d;nx=%d;ny=%d og:(%d,%d)-(%d,%d); ng:(%d,%d)-(%d,%d)', [body
, x0
, y0
, pw
, ph
, nx
, ny
, ogx0
, ogy0
, ogx1
, ogy1
, ngx0
, ngy0
, ngx1
, ngy1
]), MSG_NOTIFY
);
932 // remove stale marks
933 if not ((ogy0
>= gh
) or (ogy1
< 0)) and
934 not ((ogx0
>= gw
) or (ogx1
< 0)) then
936 if (ogx0
< 0) then ogx0
:= 0;
937 if (ogy0
< 0) then ogy0
:= 0;
938 if (ogx1
> gw
-1) then ogx1
:= gw
-1;
939 if (ogy1
> gh
-1) then ogy1
:= gh
-1;
940 {$IF DEFINED(D2F_DEBUG_MOVER)}
941 e_WriteLog(Format(' norm og:(%d,%d)-(%d,%d)', [ogx0
, ogy0
, ogx1
, ogy1
]), MSG_NOTIFY
);
943 for gx
:= ogx0
to ogx1
do
945 if (gx
< ngx0
) or (gx
> ngx1
) then
947 // this column is completely outside of new rect
948 for gy
:= ogy0
to ogy1
do
950 {$IF DEFINED(D2F_DEBUG_MOVER)}
951 e_WriteLog(Format(' remove0:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
953 remover(gy
*gw
+gx
, body
);
959 for gy
:= ogy0
to ogy1
do
961 if (gy
< ngy0
) or (gy
> ngy1
) then
963 {$IF DEFINED(D2F_DEBUG_MOVER)}
964 e_WriteLog(Format(' remove1:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
966 remover(gy
*gw
+gx
, body
);
972 // cycle with new rect, add body where it is necessary
973 if not ((ngy0
>= gh
) or (ngy1
< 0)) and
974 not ((ngx0
>= gw
) or (ngx1
< 0)) then
976 if (ngx0
< 0) then ngx0
:= 0;
977 if (ngy0
< 0) then ngy0
:= 0;
978 if (ngx1
> gw
-1) then ngx1
:= gw
-1;
979 if (ngy1
> gh
-1) then ngy1
:= gh
-1;
980 {$IF DEFINED(D2F_DEBUG_MOVER)}
981 e_WriteLog(Format(' norm ng:(%d,%d)-(%d,%d)', [ngx0
, ngy0
, ngx1
, ngy1
]), MSG_NOTIFY
);
983 for gx
:= ngx0
to ngx1
do
985 if (gx
< ogx0
) or (gx
> ogx1
) then
987 // this column is completely outside of old rect
988 for gy
:= ngy0
to ngy1
do
990 {$IF DEFINED(D2F_DEBUG_MOVER)}
991 e_WriteLog(Format(' insert0:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
993 inserter(gy
*gw
+gx
, body
);
999 for gy
:= ngy0
to ngy1
do
1001 if (gy
< ogy0
) or (gy
> ogy1
) then
1003 {$IF DEFINED(D2F_DEBUG_MOVER)}
1004 e_WriteLog(Format(' insert1:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1006 inserter(gy
*gw
+gx
, body
);
1016 {$IF DEFINED(D2F_DEBUG_MOVER)}
1017 e_WriteLog(Format('proxy #%d: GRID OK: xg=%d;yg=%d;w=%d;h=%d;nx=%d;ny=%d og:(%d,%d)-(%d,%d); ng:(%d,%d)-(%d,%d)', [body
, x0
, y0
, pw
, ph
, nx
, ny
, ogx0
, ogy0
, ogx1
, ogy1
, ngx0
, ngy0
, ngx1
, ngy1
]), MSG_NOTIFY
);
1020 // update coordinates
1025 procedure TBodyGridBase
.resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
1028 x0
, y0
, w
, h
: Integer;
1030 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1031 // check if tile coords was changed
1032 px
:= @mProxies
[body
];
1037 {$IF DEFINED(D2F_DEBUG_MOVER)}
1038 e_WriteLog(Format('proxy #%d: RESIZE: xg=%d;yg=%d;w=%d;h=%d;nw=%d;nh=%d', [body
, x0
, y0
, w
, h
, nw
, nh
]), MSG_NOTIFY
);
1040 if ((x0
+w
) div mTileSize
<> (x0
+nw
) div mTileSize
) or
1041 ((y0
+h
) div mTileSize
<> (y0
+nh
) div mTileSize
) then
1043 // crossed tile boundary, do heavy work
1044 removeInternal(body
);
1047 insertInternal(body
);
1051 // nothing to do with the grid, just fix size
1058 // ////////////////////////////////////////////////////////////////////////// //
1059 // no callback: return `true` on the first hit
1060 function TBodyGridBase
.forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
1063 idx
, curci
: Integer;
1064 cc
: PGridCell
= nil;
1069 result
:= Default(ITP
);
1070 tagmask
:= tagmask
and TagFullMask
;
1071 if (tagmask
= 0) then exit
;
1073 {$IF DEFINED(D2F_DEBUG_XXQ)}
1074 if (assigned(cb
)) then e_WriteLog(Format('0: grid pointquery: (%d,%d)', [x
, y
]), MSG_NOTIFY
);
1077 // make coords (0,0)-based
1080 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
>= mHeight
*mTileSize
) then exit
;
1082 curci
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
1084 {$IF DEFINED(D2F_DEBUG_XXQ)}
1085 if (assigned(cb
)) then e_WriteLog(Format('1: grid pointquery: (%d,%d) (%d,%d) %d', [x
, y
, (x
div mTileSize
), (y
div mTileSize
), curci
]), MSG_NOTIFY
);
1092 // increase query counter
1094 if (mLastQuery
= 0) then
1096 // just in case of overflow
1098 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
1102 {$IF DEFINED(D2F_DEBUG_XXQ)}
1103 if (assigned(cb
)) then e_WriteLog(Format('2: grid pointquery: (%d,%d); lq=%u', [x
, y
, lq
]), MSG_NOTIFY
);
1106 while (curci
<> -1) do
1108 {$IF DEFINED(D2F_DEBUG_XXQ)}
1109 if (assigned(cb
)) then e_WriteLog(Format(' cell #%d', [curci
]), MSG_NOTIFY
);
1111 cc
:= @mCells
[curci
];
1112 for f
:= 0 to GridCellBucketSize
-1 do
1114 if (cc
.bodies
[f
] = -1) then break
;
1115 px
:= @mProxies
[cc
.bodies
[f
]];
1116 {$IF DEFINED(D2F_DEBUG_XXQ)}
1117 if (assigned(cb
)) then e_WriteLog(Format(' proxy #%d; qm:%u; tag:%08x; tagflag:%d %u', [cc
.bodies
[f
], px
.mQueryMark
, px
.mTag
, (px
.mTag
and tagmask
), LongWord(px
.mObj
)]), MSG_NOTIFY
);
1119 // shit. has to do it this way, so i can change tag in callback
1120 if (px
.mQueryMark
<> lq
) then
1122 px
.mQueryMark
:= lq
;
1124 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and
1125 (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
1127 if assigned(cb
) then
1129 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
1144 // ////////////////////////////////////////////////////////////////////////// //
1145 // no callback: return `true` on the first hit
1146 function TBodyGridBase
.forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
1154 cc
: PGridCell
= nil;
1161 result
:= Default(ITP
);
1162 if (w
< 1) or (h
< 1) then exit
;
1163 tagmask
:= tagmask
and TagFullMask
;
1164 if (tagmask
= 0) then exit
;
1174 //tsize := mTileSize;
1176 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
1177 if (x
>= gw
*tsize
) or (y
>= mHeight
*tsize
) then exit
;
1179 // increase query counter
1181 if (mLastQuery
= 0) then
1183 // just in case of overflow
1185 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
1187 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
1191 for gy
:= y
div tsize
to (y
+h
-1) div tsize
do
1193 if (gy
< 0) then continue
;
1194 if (gy
>= mHeight
) then break
;
1195 for gx
:= x
div tsize
to (x
+w
-1) div tsize
do
1197 if (gx
< 0) then continue
;
1198 if (gx
>= gw
) then break
;
1200 curci
:= mGrid
[gy
*gw
+gx
];
1201 while (curci
<> -1) do
1203 cc
:= @mCells
[curci
];
1204 for f
:= 0 to GridCellBucketSize
-1 do
1206 if (cc
.bodies
[f
] = -1) then break
;
1207 px
:= @mProxies
[cc
.bodies
[f
]];
1208 // shit. has to do it this way, so i can change tag in callback
1209 if (px
.mQueryMark
= lq
) then continue
;
1210 px
.mQueryMark
:= lq
;
1212 if (not allowDisabled
) and ((ptag
and TagDisabled
) <> 0) then continue
;
1213 if ((ptag
and tagmask
) = 0) then continue
;
1214 if (x0
>= px
.mX
+px
.mWidth
) or (y0
>= px
.mY
+px
.mHeight
) then continue
;
1215 if (x0
+w
<= px
.mX
) or (y0
+h
<= px
.mY
) then continue
;
1216 if assigned(cb
) then
1218 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
1233 // ////////////////////////////////////////////////////////////////////////// //
1234 // no callback: return `true` on the nearest hit
1235 function TBodyGridBase
.traceRay (const x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
1239 result
:= traceRay(ex
, ey
, x0
, y0
, x1
, y1
, cb
, tagmask
);
1243 // no callback: return `true` on the nearest hit
1244 // you are not supposed to understand this
1245 function TBodyGridBase
.traceRay (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
1249 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
1250 stx
, sty
: Integer; // "steps" for x and y axes
1251 dsx
, dsy
: Integer; // "lengthes" for x and y axes
1252 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
1253 xd
, yd
: Integer; // current coord
1254 e
: Integer; // "error" (as in bresenham algo)
1257 xptr
, yptr
: PInteger;
1260 prevx
, prevy
: Integer;
1261 lastDistSq
: Integer;
1262 ccidx
, curci
: Integer;
1263 hasUntried
: Boolean;
1264 lastGA
: Integer = -1;
1267 wasHit
: Boolean = false;
1268 gw
, gh
, minx
, miny
, maxx
, maxy
: Integer;
1272 f
, ptag
, distSq
: Integer;
1273 x0
, y0
, x1
, y1
: Integer;
1275 result
:= Default(ITP
);
1276 lastObj
:= Default(ITP
);
1277 tagmask
:= tagmask
and TagFullMask
;
1278 ex
:= ax1
; // why not?
1279 ey
:= ay1
; // why not?
1280 if (tagmask
= 0) then exit
;
1282 if (ax0
= ax1
) and (ay0
= ay1
) then exit
; // as the first point is ignored, just get outta here
1284 lastDistSq
:= distanceSq(ax0
, ay0
, ax1
, ay1
)+1;
1293 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1294 if assigned(dbgRayTraceTileHitCB
) then e_WriteLog(Format('TRACING: (%d,%d)-(%d,%d) [(%d,%d)-(%d,%d)]; maxdistsq=%d', [ax0
, ay0
, ax1
, ay1
, minx
, miny
, maxx
, maxy
, lastDistSq
]), MSG_NOTIFY
);
1302 // offset query coords to (0,0)-based
1317 // from left to right
1318 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
1319 stx
:= 1; // going right
1323 // from right to left
1324 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
1325 stx
:= -1; // going left
1336 // from top to bottom
1337 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
1338 sty
:= 1; // going down
1342 // from bottom to top
1343 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
1344 sty
:= -1; // going up
1383 temp
:= dx2
*(wy0
-y0
)-dsx
;
1385 rem
:= temp
mod dy2
;
1386 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
1387 if (xd
+1 >= wx0
) then
1391 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
1396 if (not xfixed
) and (x0
< wx0
) then
1399 temp
:= dy2
*(wx0
-x0
);
1401 rem
:= temp
mod dx2
;
1402 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
1405 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
1411 temp
:= dx2
*(wy1
-y0
)+dsx
;
1412 term
:= x0
+temp
div dy2
;
1413 rem
:= temp
mod dy2
;
1414 if (rem
= 0) then Dec(term
);
1417 if (term
> wx1
) then term
:= wx1
; // clip at right
1419 Inc(term
); // draw last point
1420 //if (term = xd) then exit; // this is the only point, get out of here
1422 if (sty
= -1) then yd
:= -yd
;
1423 if (stx
= -1) then begin xd
:= -xd
; term
:= -term
; end;
1426 // first move, to skip starting point
1427 // DON'T DO THIS! loop will take care of that
1428 if (xd
= term
) then exit
;
1429 prevx
:= xptr
^+minx
;
1430 prevy
:= yptr
^+miny
;
1433 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
1436 if (xd = term) then exit;
1439 {$IF DEFINED(D2F_DEBUG)}
1440 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ >= gh
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
1442 // DON'T DO THIS! loop will take care of that
1443 //lastGA := (yptr^ div tsize)*gw+(xptr^ div tsize);
1444 //ccidx := mGrid[lastGA];
1446 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1447 //if assigned(dbgRayTraceTileHitCB) then e_WriteLog('1:TRACING!', MSG_NOTIFY);
1450 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1451 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB((xptr
^ div tsize
*tsize
)+minx
, (yptr
^ div tsize
*tsize
)+miny
);
1454 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
1456 // increase query counter
1458 if (mLastQuery
= 0) then
1460 // just in case of overflow
1462 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1467 // draw it; can omit checks
1468 while (xd
<> term
) do
1471 {$IF DEFINED(D2F_DEBUG)}
1472 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ >= gh
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
1475 ga
:= (yptr
^ div tsize
)*gw
+(xptr
^ div tsize
);
1476 if (ga
<> lastGA
) then
1479 {$IF DEFINED(D2F_DEBUG)}
1480 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB((xptr
^ div tsize
*tsize
)+minx
, (yptr
^ div tsize
*tsize
)+miny
);
1482 if (ccidx
<> -1) then
1484 // signal cell completion
1485 if assigned(cb
) then
1487 if cb(nil, 0, xptr
^+minx
, yptr
^+miny
, prevx
, prevy
) then begin result
:= lastObj
; exit
; end;
1496 ccidx
:= mGrid
[lastGA
];
1498 // has something to process in this tile?
1499 if (ccidx
<> -1) then
1503 hasUntried
:= false; // this will be set to `true` if we have some proxies we still want to process at the next step
1504 // convert coords to map (to avoid ajdusting coords inside the loop)
1507 // process cell list
1508 while (curci
<> -1) do
1510 cc
:= @mCells
[curci
];
1511 for f
:= 0 to GridCellBucketSize
-1 do
1513 if (cc
.bodies
[f
] = -1) then break
;
1514 px
:= @mProxies
[cc
.bodies
[f
]];
1516 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1518 // can we process this proxy?
1519 if (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
1521 px
.mQueryMark
:= lq
; // mark as processed
1522 if assigned(cb
) then
1524 if cb(px
.mObj
, ptag
, x
, y
, prevx
, prevy
) then
1532 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1533 distSq := distanceSq(ax0, ay0, prevx, prevy);
1534 if assigned(dbgRayTraceTileHitCB) then e_WriteLog(Format(' hit(%d): a=(%d,%d), h=(%d,%d), p=(%d,%d); distsq=%d; lastsq=%d', [cc.bodies[f], ax0, ay0, x, y, prevx, prevy, distSq, lastDistSq]), MSG_NOTIFY);
1535 if (distSq < lastDistSq) then
1538 lastDistSq := distSq;
1548 // remember this hitpoint if it is nearer than an old one
1549 distSq
:= distanceSq(ax0
, ay0
, prevx
, prevy
);
1550 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1551 if assigned(dbgRayTraceTileHitCB
) then e_WriteLog(Format(' hit(%d): a=(%d,%d), h=(%d,%d), p=(%d,%d); distsq=%d; lastsq=%d', [cc
.bodies
[f
], ax0
, ay0
, x
, y
, prevx
, prevy
, distSq
, lastDistSq
]), MSG_NOTIFY
);
1553 if (distSq
< lastDistSq
) then
1556 lastDistSq
:= distSq
;
1565 // this is possibly interesting proxy, set "has more to check" flag
1573 // still has something interesting in this cell?
1574 if not hasUntried
then
1576 // nope, don't process this cell anymore; signal cell completion
1578 if assigned(cb
) then
1580 if cb(nil, 0, x
, y
, prevx
, prevy
) then begin result
:= lastObj
; exit
; end;
1589 //putPixel(xptr^, yptr^);
1591 prevx
:= xptr
^+minx
;
1592 prevy
:= yptr
^+miny
;
1593 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
1599 // ////////////////////////////////////////////////////////////////////////// //
1600 //FIXME! optimize this with real tile walking
1601 function TBodyGridBase
.forEachAlongLine (const x0
, y0
, x1
, y1
: Integer; cb
: TGridAlongQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
1607 xerr
, yerr
: Integer;
1608 incx
, incy
: Integer;
1609 stepx
, stepy
: Integer;
1611 maxx
, maxy
: Integer;
1618 minx
, miny
: Integer;
1620 lastWasInGrid
: Boolean;
1626 result
:= Default(ITP
);
1627 tagmask
:= tagmask
and TagFullMask
;
1628 if (tagmask
= 0) or not assigned(cb
) then exit
;
1636 if (dx
> 0) then incx
:= 1 else if (dx
< 0) then incx
:= -1 else incx
:= 0;
1637 if (dy
> 0) then incy
:= 1 else if (dy
< 0) then incy
:= -1 else incy
:= 0;
1639 if (incx
= 0) and (incy
= 0) then exit
; // just incase
1644 if (dx
> dy
) then d
:= dx
else d
:= dy
;
1646 // `x` and `y` will be in grid coords
1650 // increase query counter
1652 if (mLastQuery
= 0) then
1654 // just in case of overflow
1656 for i
:= 0 to High(mProxies
) do mProxies
[i
].mQueryMark
:= 0;
1660 // cache various things
1661 //tsize := mTileSize;
1667 // setup distance and flags
1668 lastWasInGrid
:= (x
>= 0) and (y
>= 0) and (x
<= maxx
) and (y
<= maxy
);
1670 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1671 if lastWasInGrid
then ccidx
:= mGrid
[(y
div tsize
)*gw
+(x
div tsize
)] else ccidx
:= -1;
1673 // it is slightly faster this way
1677 if (log
) then e_WriteLog(Format('tracing: (%d,%d)-(%d,%d)', [x
, y
, x1
-minx
, y1
-miny
]), MSG_NOTIFY
);
1687 // invariant: one of those always changed
1688 {$IF DEFINED(D2F_DEBUG)}
1689 if (xerr
< 0) and (yerr
< 0) then raise Exception
.Create('internal bug in grid raycaster (0)');
1691 if (xerr
>= 0) then begin xerr
-= d
; x
+= incx
; stepx
:= incx
; end else stepx
:= 0;
1692 if (yerr
>= 0) then begin yerr
-= d
; y
+= incy
; stepy
:= incy
; end else stepy
:= 0;
1693 // invariant: we always doing a step
1694 {$IF DEFINED(D2F_DEBUG)}
1695 if ((stepx
or stepy
) = 0) then raise Exception
.Create('internal bug in grid raycaster (1)');
1698 // check for crossing tile/grid boundary
1699 if (x
>= 0) and (y
>= 0) and (x
<= maxx
) and (y
<= maxy
) then
1701 // we're still in grid
1702 lastWasInGrid
:= true;
1703 // check for tile edge crossing
1704 if (stepx
< 0) and ((x
mod tsize
) = tsize
-1) then tbcross
:= true
1705 else if (stepx
> 0) and ((x
mod tsize
) = 0) then tbcross
:= true
1706 else if (stepy
< 0) and ((y
mod tsize
) = tsize
-1) then tbcross
:= true
1707 else if (stepy
> 0) and ((y
mod tsize
) = 0) then tbcross
:= true
1708 else tbcross
:= false;
1709 // crossed tile edge?
1712 // setup new cell index
1713 ccidx
:= mGrid
[(y
div tsize
)*gw
+(x
div tsize
)];
1714 if (log
) then e_WriteLog(Format(' stepped to new tile (%d,%d) -- (%d,%d)', [(x
div tsize
), (y
div tsize
), x
, y
]), MSG_NOTIFY
);
1717 if (ccidx
= -1) then
1719 // we have nothing interesting here anymore, jump directly to tile edge
1724 if (incy < 0) then tedist := y-(y and (not tsize)) else tedist := (y or (tsize-1))-y;
1725 if (tedist > 1) then
1727 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);
1730 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);
1733 else if (incy = 0) then
1736 if (incx < 0) then tedist := x-(x and (not tsize)) else tedist := (x or (tsize-1))-x;
1737 if (tedist > 1) then
1739 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);
1742 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);
1748 // get minimal distance to tile edges
1749 if (incx < 0) then tedist := x-(x and (not tsize)) else if (incx > 0) then tedist := (x or (tsize+1))-x else tedist := 0;
1750 {$IF DEFINED(D2F_DEBUG)}
1751 if (tedist < 0) then raise Exception.Create('internal bug in grid raycaster (2.x)');
1753 if (incy < 0) then f := y-(y and (not tsize)) else if (incy > 0) then f := (y or (tsize+1))-y else f := 0;
1754 {$IF DEFINED(D2F_DEBUG)}
1755 if (f < 0) then raise Exception.Create('internal bug in grid raycaster (2.y)');
1757 if (tedist = 0) then tedist := f else if (f <> 0) then tedist := minInt(tedist, f);
1759 if (tedist > 1) then
1761 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);
1764 if (xerr >= 0) then begin x += incx*((xerr div d)+1); xerr := (xerr mod d)-d; end;
1765 if (yerr >= 0) then begin y += incy*((yerr div d)+1); yerr := (yerr mod d)-d; end;
1767 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);
1775 if lastWasInGrid
then exit
; // oops, stepped out of the grid -- there is no way to return
1779 // has something to process in the current cell?
1780 if (ccidx
<> -1) then
1784 // convert coords to map (to avoid ajdusting coords inside the loop)
1787 // process cell list
1788 while (curci
<> -1) do
1790 cc
:= @mCells
[curci
];
1791 for f
:= 0 to GridCellBucketSize
-1 do
1793 if (cc
.bodies
[f
] = -1) then break
;
1794 px
:= @mProxies
[cc
.bodies
[f
]];
1796 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1798 px
.mQueryMark
:= lq
; // mark as processed
1799 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
1805 ccidx
:= -1; // don't process this anymore
1806 // convert coords to grid