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}
23 {$DEFINE GRID_USE_ORTHO_ACCEL}
30 TBodyProxyId
= Integer;
32 generic TBodyGridBase
<ITP
> = class(TObject
)
34 type TGridQueryCB
= function (obj
: ITP
; tag
: Integer): Boolean is nested
; // return `true` to stop
35 type TGridRayQueryCB
= function (obj
: ITP
; tag
: Integer; x
, y
, prevx
, prevy
: 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);
61 function getTag (): Integer; inline;
62 procedure setTag (v
: Integer); inline;
64 function getEnabled (): Boolean; inline;
65 procedure setEnabled (v
: Boolean); inline;
68 property x
: Integer read mX
;
69 property y
: Integer read mY
;
70 property width
: Integer read mWidth
;
71 property height
: Integer read mHeight
;
72 property tag
: Integer read getTag write setTag
;
73 property enabled
: Boolean read getEnabled write setEnabled
;
78 PGridCell
= ^TGridCell
;
80 bodies
: array [0..GridCellBucketSize
-1] of Integer; // -1: end of list
81 next
: Integer; // in this cell; index in mCells
84 TGridInternalCB
= function (grida
: Integer; bodyId
: TBodyProxyId
): Boolean of object; // return `true` to stop
88 const mTileSize
= GridDefaultTileSize
;
91 const tileSize
= mTileSize
;
94 mMinX
, mMinY
: Integer; // so grids can start at any origin
95 mWidth
, mHeight
: Integer; // in tiles
96 mGrid
: array of Integer; // mWidth*mHeight, index in mCells
97 mCells
: array of TGridCell
; // cell pool
98 mFreeCell
: Integer; // first free cell index or -1
101 mProxies
: array of TBodyProxyRec
;
102 mProxyFree
: TBodyProxyId
; // free
103 mProxyCount
: Integer; // currently used
104 mProxyMaxCount
: Integer;
107 dbgShowTraceLog
: Boolean;
108 {$IF DEFINED(D2F_DEBUG)}
109 dbgRayTraceTileHitCB
: TCellQueryCB
;
113 function allocCell (): Integer;
114 procedure freeCell (idx
: Integer); // `next` is simply overwritten
116 function allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
117 procedure freeProxy (body
: TBodyProxyId
);
119 procedure insertInternal (body
: TBodyProxyId
);
120 procedure removeInternal (body
: TBodyProxyId
);
122 function forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
124 function inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
125 function remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
127 function getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
128 procedure setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
130 function getGridWidthPx (): Integer; inline;
131 function getGridHeightPx (): Integer; inline;
133 function getProxyById (idx
: TBodyProxyId
): PBodyProxyRec
; inline;
136 constructor Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
137 destructor Destroy (); override;
139 function insertBody (aObj
: ITP
; ax
, ay
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
140 procedure removeBody (body
: TBodyProxyId
); // WARNING! this WILL destroy proxy!
142 procedure moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
143 procedure resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
144 procedure moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
146 function insideGrid (x
, y
: Integer): Boolean; inline;
148 // `false` if `body` is surely invalid
149 function getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
150 function getBodyWH (body
: TBodyProxyId
; out rw
, rh
: Integer): Boolean; inline;
151 function getBodyDims (body
: TBodyProxyId
; out rx
, ry
, rw
, rh
: Integer): Boolean; inline;
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 // no callback: return `true` on the first hit
156 function forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
158 //WARNING: don't modify grid while any query is in progress (no checks are made!)
159 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
160 // no callback: return object on the first hit or nil
161 function forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; exittag
: PInteger=nil): ITP
;
163 //WARNING: don't modify grid while any query is in progress (no checks are made!)
164 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
165 // cb with `(nil)` will be called before processing new tile
166 // no callback: return object of the nearest hit or nil
167 // if `inverted` is true, trace will register bodies *exluding* tagmask
168 //WARNING: don't change tags in callbacks here!
169 function traceRay (const x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
; overload
;
170 function traceRay (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
172 //function traceOrthoRayWhileIn (const x0, y0, x1, y1: Integer; tagmask: Integer=-1): ITP; overload;
173 //function traceOrthoRayWhileIn (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): ITP;
175 //WARNING: don't modify grid while any query is in progress (no checks are made!)
176 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
177 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
178 //WARNING: don't change tags in callbacks here!
179 function forEachAlongLine (ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
182 procedure forEachBodyCell (body
: TBodyProxyId
; cb
: TCellQueryCB
);
183 function forEachInCell (x
, y
: Integer; cb
: TGridQueryCB
): ITP
;
184 procedure dumpStats ();
187 //WARNING! no sanity checks!
188 property proxyEnabled
[pid
: TBodyProxyId
]: Boolean read getProxyEnabled write setProxyEnabled
;
190 property gridX0
: Integer read mMinX
;
191 property gridY0
: Integer read mMinY
;
192 property gridWidth
: Integer read getGridWidthPx
; // in pixels
193 property gridHeight
: Integer read getGridHeightPx
; // in pixels
195 property proxy
[idx
: TBodyProxyId
]: PBodyProxyRec read getProxyById
;
199 // you are not supposed to understand this
200 // returns `true` if there is an intersection, and enter coords
201 // enter coords will be equal to (x0, y0) if starting point is inside the box
202 // if result is `false`, `inx` and `iny` are undefined
203 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
205 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline;
207 procedure swapInt (var a
: Integer; var b
: Integer); inline;
208 function minInt (a
, b
: Integer): Integer; inline;
209 function maxInt (a
, b
: Integer): Integer; inline;
215 SysUtils
, e_log
, g_console
;
218 // ////////////////////////////////////////////////////////////////////////// //
219 procedure swapInt (var a
: Integer; var b
: Integer); inline; var t
: Integer; begin t
:= a
; a
:= b
; b
:= t
; end;
220 function minInt (a
, b
: Integer): Integer; inline; begin if (a
< b
) then result
:= a
else result
:= b
; end;
221 function maxInt (a
, b
: Integer): Integer; inline; begin if (a
> b
) then result
:= a
else result
:= b
; end;
223 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline; begin result
:= (x1
-x0
)*(x1
-x0
)+(y1
-y0
)*(y1
-y0
); end;
226 // ////////////////////////////////////////////////////////////////////////// //
227 // you are not supposed to understand this
228 // returns `true` if there is an intersection, and enter coords
229 // enter coords will be equal to (x0, y0) if starting point is inside the box
230 // if result is `false`, `inx` and `iny` are undefined
231 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
233 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
234 stx
, sty
: Integer; // "steps" for x and y axes
235 dsx
, dsy
: Integer; // "lengthes" for x and y axes
236 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
237 xd
, yd
: Integer; // current coord
238 e
: Integer; // "error" (as in bresenham algo)
249 if (bw
< 1) or (bh
< 1) then exit
; // impossible box
251 if (x0
= x1
) and (y0
= y1
) then
254 result
:= (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
);
258 // check if staring point is inside the box
259 if (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
) then begin result
:= true; exit
; end;
270 // from left to right
271 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
272 stx
:= 1; // going right
276 // from right to left
277 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
278 stx
:= -1; // going left
289 // from top to bottom
290 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
291 sty
:= 1; // going down
295 // from bottom to top
296 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
297 sty
:= -1; // going up
336 temp
:= dx2
*(wy0
-y0
)-dsx
;
339 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
340 if (xd
+1 >= wx0
) then
344 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
349 if (not xfixed
) and (x0
< wx0
) then
352 temp
:= dy2
*(wx0
-x0
);
355 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
358 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
365 temp := dx2*(wy1-y0)+dsx;
366 term := x0+temp div dy2;
368 if (rem = 0) then Dec(term);
371 if (term > wx1) then term := wx1; // clip at right
373 Inc(term); // draw last point
374 //if (term = xd) then exit; // this is the only point, get out of here
377 if (sty
= -1) then yd
:= -yd
;
378 if (stx
= -1) then begin xd
:= -xd
; {!term := -term;} end;
387 // ////////////////////////////////////////////////////////////////////////// //
388 procedure TBodyGridBase
.TBodyProxyRec
.setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
401 function TBodyGridBase
.TBodyProxyRec
.getTag (): Integer; inline;
403 result
:= mTag
and TagFullMask
;
406 procedure TBodyGridBase
.TBodyProxyRec
.setTag (v
: Integer); inline;
408 mTag
:= (mTag
and TagDisabled
) or (v
and TagFullMask
);
411 function TBodyGridBase
.TBodyProxyRec
.getEnabled (): Boolean; inline;
413 result
:= ((mTag
and TagDisabled
) = 0);
416 procedure TBodyGridBase
.TBodyProxyRec
.setEnabled (v
: Boolean); inline;
418 if v
then mTag
:= mTag
and (not TagDisabled
) else mTag
:= mTag
or TagDisabled
;
422 // ////////////////////////////////////////////////////////////////////////// //
423 constructor TBodyGridBase
.Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
427 dbgShowTraceLog
:= false;
428 {$IF DEFINED(D2F_DEBUG)}
429 dbgRayTraceTileHitCB
:= nil;
432 if aTileSize < 1 then aTileSize := 1;
433 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
434 mTileSize := aTileSize;
436 if (aPixWidth
< mTileSize
) then aPixWidth
:= mTileSize
;
437 if (aPixHeight
< mTileSize
) then aPixHeight
:= mTileSize
;
440 mWidth
:= (aPixWidth
+mTileSize
-1) div mTileSize
;
441 mHeight
:= (aPixHeight
+mTileSize
-1) div mTileSize
;
442 SetLength(mGrid
, mWidth
*mHeight
);
443 SetLength(mCells
, mWidth
*mHeight
);
444 SetLength(mProxies
, 8192);
447 for idx
:= 0 to High(mCells
) do
449 mCells
[idx
].bodies
[0] := -1;
450 mCells
[idx
].bodies
[GridCellBucketSize
-1] := -1; // "has free room" flag
451 mCells
[idx
].next
:= idx
+1;
453 mCells
[High(mCells
)].next
:= -1; // last cell
455 for idx
:= 0 to High(mGrid
) do mGrid
[idx
] := -1;
457 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
458 mProxies
[High(mProxies
)].nextLink
:= -1;
464 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
]), MSG_NOTIFY
);
468 destructor TBodyGridBase
.Destroy ();
477 // ////////////////////////////////////////////////////////////////////////// //
478 procedure TBodyGridBase
.dumpStats ();
480 idx
, mcb
, cidx
, cnt
: Integer;
483 for idx
:= 0 to High(mGrid
) do
490 cidx
:= mCells
[cidx
].next
;
492 if (mcb
< cnt
) then mcb
:= cnt
;
494 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
);
498 procedure TBodyGridBase
.forEachBodyCell (body
: TBodyProxyId
; cb
: TCellQueryCB
);
503 if (body
< 0) or (body
> High(mProxies
)) or not assigned(cb
) then exit
;
504 for g
:= 0 to High(mGrid
) do
507 while (cidx
<> -1) do
510 for f
:= 0 to GridCellBucketSize
-1 do
512 if (cc
.bodies
[f
] = -1) then break
;
513 if (cc
.bodies
[f
] = body
) then cb((g
mod mWidth
)*mTileSize
+mMinX
, (g
div mWidth
)*mTileSize
+mMinY
);
522 function TBodyGridBase
.forEachInCell (x
, y
: Integer; cb
: TGridQueryCB
): ITP
;
527 result
:= Default(ITP
);
528 if not assigned(cb
) then exit
;
531 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
> mHeight
*mTileSize
) then exit
;
532 cidx
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
533 while (cidx
<> -1) do
536 for f
:= 0 to GridCellBucketSize
-1 do
538 if (cc
.bodies
[f
] = -1) then break
;
539 if cb(mProxies
[cc
.bodies
[f
]].mObj
, mProxies
[cc
.bodies
[f
]].mTag
) then begin result
:= mProxies
[cc
.bodies
[f
]].mObj
; exit
; end;
547 // ////////////////////////////////////////////////////////////////////////// //
548 function TBodyGridBase
.getGridWidthPx (): Integer; inline; begin result
:= mWidth
*mTileSize
; end;
549 function TBodyGridBase
.getGridHeightPx (): Integer; inline; begin result
:= mHeight
*mTileSize
; end;
552 function TBodyGridBase
.insideGrid (x
, y
: Integer): Boolean; inline;
557 result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
*mTileSize
) and (y
< mHeight
*mTileSize
);
561 function TBodyGridBase
.getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
563 if (body
>= 0) and (body
< Length(mProxies
)) then
565 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; end;
577 function TBodyGridBase
.getBodyWH (body
: TBodyProxyId
; out rw
, rh
: Integer): Boolean; inline;
579 if (body
>= 0) and (body
< Length(mProxies
)) then
581 with mProxies
[body
] do begin rw
:= mWidth
; rh
:= mHeight
; end;
593 function TBodyGridBase
.getBodyDims (body
: TBodyProxyId
; out rx
, ry
, rw
, rh
: Integer): Boolean; inline;
595 if (body
>= 0) and (body
< Length(mProxies
)) then
597 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; rw
:= mWidth
; rh
:= mHeight
; end;
612 // ////////////////////////////////////////////////////////////////////////// //
613 function TBodyGridBase
.getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
615 if (pid
>= 0) then result
:= ((mProxies
[pid
].mTag
and TagDisabled
) = 0) else result
:= false;
619 procedure TBodyGridBase
.setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
625 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
and not TagDisabled
;
629 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
or TagDisabled
;
635 function TBodyGridBase
.getProxyById (idx
: TBodyProxyId
): PBodyProxyRec
; inline;
637 if (idx
>= 0) and (idx
< High(mProxies
)) then result
:= @mProxies
[idx
] else result
:= nil;
641 // ////////////////////////////////////////////////////////////////////////// //
642 function TBodyGridBase
.allocCell (): Integer;
647 if (mFreeCell
< 0) then
649 // no free cells, want more
650 mFreeCell
:= Length(mCells
);
651 SetLength(mCells
, mFreeCell
+32768); // arbitrary number
652 for idx
:= mFreeCell
to High(mCells
) do
654 mCells
[idx
].bodies
[0] := -1;
655 mCells
[idx
].bodies
[GridCellBucketSize
-1] := -1; // 'has free room' flag
656 mCells
[idx
].next
:= idx
+1;
658 mCells
[High(mCells
)].next
:= -1; // last cell
661 pc
:= @mCells
[result
];
662 mFreeCell
:= pc
.next
;
665 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
669 procedure TBodyGridBase
.freeCell (idx
: Integer);
671 if (idx
>= 0) and (idx
< Length(mCells
)) then
676 bodies
[GridCellBucketSize
-1] := -1; // 'has free room' flag
685 // ////////////////////////////////////////////////////////////////////////// //
686 function TBodyGridBase
.allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
691 if (mProxyFree
= -1) then
693 // no free proxies, resize list
694 olen
:= Length(mProxies
);
695 SetLength(mProxies
, olen
+8192); // arbitrary number
696 for idx
:= olen
to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
697 mProxies
[High(mProxies
)].nextLink
:= -1;
701 result
:= mProxyFree
;
702 px
:= @mProxies
[result
];
703 mProxyFree
:= px
.nextLink
;
704 px
.setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
709 if (mProxyMaxCount
< mProxyCount
) then mProxyMaxCount
:= mProxyCount
;
712 procedure TBodyGridBase
.freeProxy (body
: TBodyProxyId
);
714 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
715 if (mProxyCount
= 0) then raise Exception
.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
717 mProxies
[body
].mObj
:= nil;
718 mProxies
[body
].nextLink
:= mProxyFree
;
724 // ////////////////////////////////////////////////////////////////////////// //
725 function TBodyGridBase
.forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
733 if (w
< 1) or (h
< 1) or not assigned(cb
) then exit
;
738 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
741 //tsize := mTileSize;
742 if (x
>= gw
*tsize
) or (y
>= gh
*tsize
) then exit
;
743 for gy
:= y
div tsize
to (y
+h
-1) div tsize
do
745 if (gy
< 0) then continue
;
746 if (gy
>= gh
) then break
;
747 for gx
:= x
div tsize
to (x
+w
-1) div tsize
do
749 if (gx
< 0) then continue
;
750 if (gx
>= gw
) then break
;
751 result
:= cb(gy
*gw
+gx
, bodyId
);
758 // ////////////////////////////////////////////////////////////////////////// //
759 function TBodyGridBase
.inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
766 result
:= false; // never stop
767 // add body to the given grid cell
771 {$IF DEFINED(D2F_DEBUG)}
773 while (cidx
<> -1) do
776 for f
:= 0 to GridCellBucketSize
-1 do
778 if (pi
.bodies
[f
] = -1) then break
;
779 if (pi
.bodies
[f
] = bodyId
) then raise Exception
.Create('trying to insert already inserted proxy');
785 while (cidx
<> -1) do
788 // check "has room" flag
789 if (pi
.bodies
[GridCellBucketSize
-1] = -1) then
792 for f
:= 0 to GridCellBucketSize
-1 do
794 if (pi
.bodies
[f
] = -1) then
796 pi
.bodies
[f
] := bodyId
;
797 if (f
+1 < GridCellBucketSize
) then pi
.bodies
[f
+1] := -1;
801 raise Exception
.Create('internal error in grid inserter');
803 // no room, go to next cell in list (if there is any)
806 // no room in cells, add new cell to list
808 // either no room, or no cell at all
811 pi
.bodies
[0] := bodyId
;
814 mGrid
[grida
] := cidx
;
817 procedure TBodyGridBase
.insertInternal (body
: TBodyProxyId
);
821 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
822 px
:= @mProxies
[body
];
823 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, inserter
, body
);
827 // assume that we cannot have one object added to bucket twice
828 function TBodyGridBase
.remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
834 result
:= false; // never stop
835 // find and remove cell
836 pidx
:= -1; // previous cell index
837 cidx
:= mGrid
[grida
]; // current cell index
838 while (cidx
<> -1) do
841 for f
:= 0 to GridCellBucketSize
-1 do
843 if (pc
.bodies
[f
] = bodyId
) then
846 if (f
= 0) and (pc
.bodies
[1] = -1) then
848 // this cell contains no elements, remove it
849 if (pidx
= -1) then mGrid
[grida
] := pc
.next
else mCells
[pidx
].next
:= pc
.next
;
853 // remove element from bucket
854 for c
:= f
to GridCellBucketSize
-2 do
856 pc
.bodies
[c
] := pc
.bodies
[c
+1];
857 if (pc
.bodies
[c
] = -1) then break
;
859 pc
.bodies
[GridCellBucketSize
-1] := -1; // "has free room" flag
868 procedure TBodyGridBase
.removeInternal (body
: TBodyProxyId
);
872 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
873 px
:= @mProxies
[body
];
874 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
878 // ////////////////////////////////////////////////////////////////////////// //
879 function TBodyGridBase
.insertBody (aObj
: ITP
; aX
, aY
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
881 aTag
:= aTag
and TagFullMask
;
882 result
:= allocProxy(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
883 insertInternal(result
);
887 procedure TBodyGridBase
.removeBody (body
: TBodyProxyId
);
889 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
890 removeInternal(body
);
895 // ////////////////////////////////////////////////////////////////////////// //
896 procedure TBodyGridBase
.moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
899 x0
, y0
, w
, h
: Integer;
901 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
902 px
:= @mProxies
[body
];
907 {$IF DEFINED(D2F_DEBUG_MOVER)}
908 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
);
910 if (nx
= x0
) and (ny
= y0
) and (nw
= w
) and (nh
= h
) then exit
;
916 // did any corner crossed tile boundary?
917 if (x0
div mTileSize
<> nx
div mTileSize
) or
918 (y0
div mTileSize
<> ny
div mTileSize
) or
919 ((x0
+w
) div mTileSize
<> (nx
+nw
) div mTileSize
) or
920 ((y0
+h
) div mTileSize
<> (ny
+nh
) div mTileSize
) then
922 removeInternal(body
);
927 insertInternal(body
);
938 //TODO: optimize for horizontal/vertical moves
939 procedure TBodyGridBase
.moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
943 ogx0
, ogx1
, ogy0
, ogy1
: Integer; // old grid rect
944 ngx0
, ngx1
, ngy0
, ngy1
: Integer; // new grid rect
949 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
950 // check if tile coords was changed
951 px
:= @mProxies
[body
];
954 if (nx
= x0
) and (ny
= y0
) then exit
;
960 // check for heavy work
963 ogx0
:= x0
div mTileSize
;
964 ogy0
:= y0
div mTileSize
;
965 ngx0
:= nx
div mTileSize
;
966 ngy0
:= ny
div mTileSize
;
967 ogx1
:= (x0
+pw
-1) div mTileSize
;
968 ogy1
:= (y0
+ph
-1) div mTileSize
;
969 ngx1
:= (nx
+pw
-1) div mTileSize
;
970 ngy1
:= (ny
+ph
-1) div mTileSize
;
971 {$IF DEFINED(D2F_DEBUG_MOVER)}
972 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
);
974 if (ogx0
<> ngx0
) or (ogy0
<> ngy0
) or (ogx1
<> ngx1
) or (ogy1
<> ngy1
) then
976 // crossed tile boundary, do heavy work
979 // cycle with old rect, remove body where it is necessary
980 // optimized for horizontal moves
981 {$IF DEFINED(D2F_DEBUG_MOVER)}
982 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
);
984 // remove stale marks
985 if not ((ogy0
>= gh
) or (ogy1
< 0)) and
986 not ((ogx0
>= gw
) or (ogx1
< 0)) then
988 if (ogx0
< 0) then ogx0
:= 0;
989 if (ogy0
< 0) then ogy0
:= 0;
990 if (ogx1
> gw
-1) then ogx1
:= gw
-1;
991 if (ogy1
> gh
-1) then ogy1
:= gh
-1;
992 {$IF DEFINED(D2F_DEBUG_MOVER)}
993 e_WriteLog(Format(' norm og:(%d,%d)-(%d,%d)', [ogx0
, ogy0
, ogx1
, ogy1
]), MSG_NOTIFY
);
995 for gx
:= ogx0
to ogx1
do
997 if (gx
< ngx0
) or (gx
> ngx1
) then
999 // this column is completely outside of new rect
1000 for gy
:= ogy0
to ogy1
do
1002 {$IF DEFINED(D2F_DEBUG_MOVER)}
1003 e_WriteLog(Format(' remove0:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1005 remover(gy
*gw
+gx
, body
);
1011 for gy
:= ogy0
to ogy1
do
1013 if (gy
< ngy0
) or (gy
> ngy1
) then
1015 {$IF DEFINED(D2F_DEBUG_MOVER)}
1016 e_WriteLog(Format(' remove1:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1018 remover(gy
*gw
+gx
, body
);
1024 // cycle with new rect, add body where it is necessary
1025 if not ((ngy0
>= gh
) or (ngy1
< 0)) and
1026 not ((ngx0
>= gw
) or (ngx1
< 0)) then
1028 if (ngx0
< 0) then ngx0
:= 0;
1029 if (ngy0
< 0) then ngy0
:= 0;
1030 if (ngx1
> gw
-1) then ngx1
:= gw
-1;
1031 if (ngy1
> gh
-1) then ngy1
:= gh
-1;
1032 {$IF DEFINED(D2F_DEBUG_MOVER)}
1033 e_WriteLog(Format(' norm ng:(%d,%d)-(%d,%d)', [ngx0
, ngy0
, ngx1
, ngy1
]), MSG_NOTIFY
);
1035 for gx
:= ngx0
to ngx1
do
1037 if (gx
< ogx0
) or (gx
> ogx1
) then
1039 // this column is completely outside of old rect
1040 for gy
:= ngy0
to ngy1
do
1042 {$IF DEFINED(D2F_DEBUG_MOVER)}
1043 e_WriteLog(Format(' insert0:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1045 inserter(gy
*gw
+gx
, body
);
1051 for gy
:= ngy0
to ngy1
do
1053 if (gy
< ogy0
) or (gy
> ogy1
) then
1055 {$IF DEFINED(D2F_DEBUG_MOVER)}
1056 e_WriteLog(Format(' insert1:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1058 inserter(gy
*gw
+gx
, body
);
1068 {$IF DEFINED(D2F_DEBUG_MOVER)}
1069 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
);
1072 // update coordinates
1077 procedure TBodyGridBase
.resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
1080 x0
, y0
, w
, h
: Integer;
1082 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1083 // check if tile coords was changed
1084 px
:= @mProxies
[body
];
1089 {$IF DEFINED(D2F_DEBUG_MOVER)}
1090 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
);
1092 if ((x0
+w
) div mTileSize
<> (x0
+nw
) div mTileSize
) or
1093 ((y0
+h
) div mTileSize
<> (y0
+nh
) div mTileSize
) then
1095 // crossed tile boundary, do heavy work
1096 removeInternal(body
);
1099 insertInternal(body
);
1103 // nothing to do with the grid, just fix size
1110 // ////////////////////////////////////////////////////////////////////////// //
1111 // no callback: return `true` on the first hit
1112 function TBodyGridBase
.forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; exittag
: PInteger=nil): ITP
;
1115 idx
, curci
: Integer;
1116 cc
: PGridCell
= nil;
1121 result
:= Default(ITP
);
1122 if (exittag
<> nil) then exittag
^ := 0;
1123 tagmask
:= tagmask
and TagFullMask
;
1124 if (tagmask
= 0) then exit
;
1126 {$IF DEFINED(D2F_DEBUG_XXQ)}
1127 if (assigned(cb
)) then e_WriteLog(Format('0: grid pointquery: (%d,%d)', [x
, y
]), MSG_NOTIFY
);
1130 // make coords (0,0)-based
1133 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
>= mHeight
*mTileSize
) then exit
;
1135 curci
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
1137 {$IF DEFINED(D2F_DEBUG_XXQ)}
1138 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
);
1145 // increase query counter
1147 if (mLastQuery
= 0) then
1149 // just in case of overflow
1151 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
1155 {$IF DEFINED(D2F_DEBUG_XXQ)}
1156 if (assigned(cb
)) then e_WriteLog(Format('2: grid pointquery: (%d,%d); lq=%u', [x
, y
, lq
]), MSG_NOTIFY
);
1159 while (curci
<> -1) do
1161 {$IF DEFINED(D2F_DEBUG_XXQ)}
1162 if (assigned(cb
)) then e_WriteLog(Format(' cell #%d', [curci
]), MSG_NOTIFY
);
1164 cc
:= @mCells
[curci
];
1165 for f
:= 0 to GridCellBucketSize
-1 do
1167 if (cc
.bodies
[f
] = -1) then break
;
1168 px
:= @mProxies
[cc
.bodies
[f
]];
1169 {$IF DEFINED(D2F_DEBUG_XXQ)}
1170 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
);
1172 // shit. has to do it this way, so i can change tag in callback
1173 if (px
.mQueryMark
<> lq
) then
1175 px
.mQueryMark
:= lq
;
1177 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and
1178 (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
1180 if assigned(cb
) then
1182 if cb(px
.mObj
, ptag
) then
1185 if (exittag
<> nil) then exittag
^ := ptag
;
1192 if (exittag
<> nil) then exittag
^ := ptag
;
1203 // ////////////////////////////////////////////////////////////////////////// //
1204 // no callback: return `true` on the first hit
1205 function TBodyGridBase
.forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
1213 cc
: PGridCell
= nil;
1220 result
:= Default(ITP
);
1221 if (w
< 1) or (h
< 1) then exit
;
1222 tagmask
:= tagmask
and TagFullMask
;
1223 if (tagmask
= 0) then exit
;
1233 //tsize := mTileSize;
1235 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
1236 if (x
>= gw
*tsize
) or (y
>= mHeight
*tsize
) then exit
;
1238 // increase query counter
1240 if (mLastQuery
= 0) then
1242 // just in case of overflow
1244 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
1246 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
1250 for gy
:= y
div tsize
to (y
+h
-1) div tsize
do
1252 if (gy
< 0) then continue
;
1253 if (gy
>= mHeight
) then break
;
1254 for gx
:= x
div tsize
to (x
+w
-1) div tsize
do
1256 if (gx
< 0) then continue
;
1257 if (gx
>= gw
) then break
;
1259 curci
:= mGrid
[gy
*gw
+gx
];
1260 while (curci
<> -1) do
1262 cc
:= @mCells
[curci
];
1263 for f
:= 0 to GridCellBucketSize
-1 do
1265 if (cc
.bodies
[f
] = -1) then break
;
1266 px
:= @mProxies
[cc
.bodies
[f
]];
1267 // shit. has to do it this way, so i can change tag in callback
1268 if (px
.mQueryMark
= lq
) then continue
;
1269 px
.mQueryMark
:= lq
;
1271 if (not allowDisabled
) and ((ptag
and TagDisabled
) <> 0) then continue
;
1272 if ((ptag
and tagmask
) = 0) then continue
;
1273 if (x0
>= px
.mX
+px
.mWidth
) or (y0
>= px
.mY
+px
.mHeight
) then continue
;
1274 if (x0
+w
<= px
.mX
) or (y0
+h
<= px
.mY
) then continue
;
1275 if assigned(cb
) then
1277 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
1292 // ////////////////////////////////////////////////////////////////////////// //
1293 // no callback: return `true` on the nearest hit
1294 function TBodyGridBase
.traceRay (const x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
1298 result
:= traceRay(ex
, ey
, x0
, y0
, x1
, y1
, cb
, tagmask
);
1302 // no callback: return `true` on the nearest hit
1303 // you are not supposed to understand this
1304 function TBodyGridBase
.traceRay (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
1308 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
1309 stx
, sty
: Integer; // "steps" for x and y axes
1310 dsx
, dsy
: Integer; // "lengthes" for x and y axes
1311 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
1312 xd
, yd
: Integer; // current coord
1313 e
: Integer; // "error" (as in bresenham algo)
1316 xptr
, yptr
: PInteger;
1319 prevx
, prevy
: Integer;
1320 lastDistSq
: Integer;
1321 ccidx
, curci
: Integer;
1322 hasUntried
: Boolean;
1323 lastGA
: Integer = -1;
1326 wasHit
: Boolean = false;
1327 gw
, gh
, minx
, miny
, maxx
, maxy
: Integer;
1331 f
, ptag
, distSq
: Integer;
1332 x0
, y0
, x1
, y1
: Integer;
1333 //swapped: Boolean = false; // true: xd is yd, and vice versa
1334 // horizontal walker
1335 {$IFDEF GRID_USE_ORTHO_ACCEL}
1336 wklen
, wkstep
: Integer;
1341 xdist
, ydist
: Integer;
1343 result
:= Default(ITP
);
1344 lastObj
:= Default(ITP
);
1345 tagmask
:= tagmask
and TagFullMask
;
1346 ex
:= ax1
; // why not?
1347 ey
:= ay1
; // why not?
1348 if (tagmask
= 0) then exit
;
1350 if (ax0
= ax1
) and (ay0
= ay1
) then
1352 result
:= forEachAtPoint(ax0
, ay0
, nil, tagmask
, @ptag
);
1353 if (result
<> nil) then
1355 if assigned(cb
) and not cb(result
, ptag
, ax0
, ay0
, ax0
, ay0
) then result
:= Default(ITP
);
1360 lastDistSq
:= distanceSq(ax0
, ay0
, ax1
, ay1
)+1;
1369 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1370 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
);
1378 // offset query coords to (0,0)-based
1393 // from left to right
1394 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
1395 stx
:= 1; // going right
1399 // from right to left
1400 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
1401 stx
:= -1; // going left
1412 // from top to bottom
1413 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
1414 sty
:= 1; // going down
1418 // from bottom to top
1419 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
1420 sty
:= -1; // going up
1460 temp
:= dx2
*(wy0
-y0
)-dsx
;
1462 rem
:= temp
mod dy2
;
1463 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
1464 if (xd
+1 >= wx0
) then
1468 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
1473 if (not xfixed
) and (x0
< wx0
) then
1476 temp
:= dy2
*(wx0
-x0
);
1478 rem
:= temp
mod dx2
;
1479 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
1482 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
1488 temp
:= dx2
*(wy1
-y0
)+dsx
;
1489 term
:= x0
+temp
div dy2
;
1490 rem
:= temp
mod dy2
;
1491 if (rem
= 0) then Dec(term
);
1494 if (term
> wx1
) then term
:= wx1
; // clip at right
1496 Inc(term
); // draw last point
1497 //if (term = xd) then exit; // this is the only point, get out of here
1499 if (sty
= -1) then yd
:= -yd
;
1500 if (stx
= -1) then begin xd
:= -xd
; term
:= -term
; end;
1503 // first move, to skip starting point
1504 // DON'T DO THIS! loop will take care of that
1508 result
:= forEachAtPoint(ax0
, ay0
, nil, tagmask
, @ptag
);
1509 if (result
<> nil) then
1511 if assigned(cb
) then
1513 if cb(result
, ptag
, ax0
, ay0
, ax0
, ay0
) then
1532 prevx
:= xptr
^+minx
;
1533 prevy
:= yptr
^+miny
;
1536 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
1539 if (xd = term) then exit;
1542 {$IF DEFINED(D2F_DEBUG)}
1543 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ >= gh
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
1545 // DON'T DO THIS! loop will take care of that
1546 //lastGA := (yptr^ div tsize)*gw+(xptr^ div tsize);
1547 //ccidx := mGrid[lastGA];
1549 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1550 //if assigned(dbgRayTraceTileHitCB) then e_WriteLog('1:TRACING!', MSG_NOTIFY);
1553 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
1555 // increase query counter
1557 if (mLastQuery
= 0) then
1559 // just in case of overflow
1561 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1565 {$IFDEF GRID_USE_ORTHO_ACCEL}
1566 // if this is strict horizontal/vertical trace, use optimized codepath
1567 if (ax0
= ax1
) or (ay0
= ay1
) then
1569 // horizontal trace: walk the whole tiles, calculating mindist once for each proxy in cell
1570 // stx < 0: going left, otherwise `stx` is > 0, and we're going right
1571 // vertical trace: walk the whole tiles, calculating mindist once for each proxy in cell
1572 // stx < 0: going up, otherwise `stx` is > 0, and we're going down
1573 hopt
:= (ay0
= ay1
); // horizontal?
1574 if (stx
< 0) then begin {wksign := -1;} wklen
:= -(term
-xd
); end else begin {wksign := 1;} wklen
:= term
-xd
; end;
1575 {$IF DEFINED(D2F_DEBUG)}
1576 if dbgShowTraceLog
then e_LogWritefln('optimized htrace; wklen=%d', [wklen
]);
1578 ga
:= (yptr
^ div tsize
)*gw
+(xptr
^ div tsize
);
1579 // one of those will never change
1584 {$IF DEFINED(D2F_DEBUG)}
1587 if (y
<> ay0
) then raise Exception
.Create('htrace fatal internal error');
1591 if (x
<> ax0
) then raise Exception
.Create('vtrace fatal internal error');
1594 while (wklen
> 0) do
1596 {$IF DEFINED(D2F_DEBUG)}
1597 if dbgShowTraceLog
then e_LogWritefln(' htrace; ga=%d; x=%d, y=%d; y=%d; y=%d', [ga
, xptr
^+minx
, yptr
^+miny
, y
, ay0
]);
1600 if (ga
<> lastGA
) then
1603 ccidx
:= mGrid
[lastGA
];
1604 // convert coords to map (to avoid ajdusting coords inside the loop)
1605 if hopt
then x
:= xptr
^+minx
else y
:= yptr
^+miny
;
1606 while (ccidx
<> -1) do
1608 cc
:= @mCells
[ccidx
];
1609 for f
:= 0 to GridCellBucketSize
-1 do
1611 if (cc
.bodies
[f
] = -1) then break
;
1612 px
:= @mProxies
[cc
.bodies
[f
]];
1614 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) and
1615 // constant coord should be inside
1616 ((hopt
and (y
>= px
.mY
) and (y
< px
.mY
+px
.mHeight
)) or
1617 ((not hopt
) and (x
>= px
.mX
) and (x
< px
.mX
+px
.mWidth
))) then
1619 px
.mQueryMark
:= lq
; // mark as processed
1620 // inside the proxy?
1621 if (hopt
and (x
> px
.mX
) and (x
< px
.mX
+px
.mWidth
-1)) or
1622 ((not hopt
) and (y
> px
.mY
) and (y
< px
.mY
+px
.mHeight
-1)) then
1625 if assigned(cb
) then
1627 if cb(px
.mObj
, ptag
, x
, y
, x
, y
) then
1637 distSq
:= distanceSq(ax0
, ay0
, x
, y
);
1638 {$IF DEFINED(D2F_DEBUG)}
1639 if dbgShowTraceLog
then e_LogWritefln(' EMBEDDED hhit(%d): a=(%d,%d), h=(%d,%d), distsq=%d; lastsq=%d', [cc
.bodies
[f
], ax0
, ay0
, x
, y
, distSq
, lastDistSq
]);
1641 if (distSq
< lastDistSq
) then
1651 // remember this hitpoint if it is nearer than an old one
1661 if (x
< px
.mX
+px
.mWidth
-1) then continue
; // not on the right edge
1662 prevx
:= px
.mX
+px
.mWidth
;
1668 if (x
> px
.mX
) then continue
; // not on the left edge
1681 if (y
< px
.mY
+px
.mHeight
-1) then continue
; // not on the bottom edge
1682 prevy
:= px
.mY
+px
.mHeight
;
1688 if (y
> px
.mY
) then continue
; // not on the top edge
1693 if assigned(cb
) then
1695 if cb(px
.mObj
, ptag
, x
, y
, prevx
, prevy
) then
1705 distSq
:= distanceSq(ax0
, ay0
, prevx
, prevy
);
1706 {$IF DEFINED(D2F_DEBUG)}
1707 if dbgShowTraceLog
then e_LogWritefln(' hhit(%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
]);
1709 if (distSq
< lastDistSq
) then
1712 lastDistSq
:= distSq
;
1723 if wasHit
and not assigned(cb
) then begin result
:= lastObj
; exit
; end;
1724 if assigned(cb
) and cb(nil, 0, x
, y
, x
, y
) then begin result
:= lastObj
; exit
; end;
1726 // skip to next tile
1732 wkstep
:= ((xptr
^ or (mTileSize
-1))+1)-xptr
^;
1733 {$IF DEFINED(D2F_DEBUG)}
1734 if dbgShowTraceLog
then e_LogWritefln(' right step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
1736 if (wkstep
>= wklen
) then break
;
1743 wkstep
:= xptr
^-((xptr
^ and (not (mTileSize
-1)))-1);
1744 {$IF DEFINED(D2F_DEBUG)}
1745 if dbgShowTraceLog
then e_LogWritefln(' left step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
1747 if (wkstep
>= wklen
) then break
;
1757 wkstep
:= ((yptr
^ or (mTileSize
-1))+1)-yptr
^;
1758 {$IF DEFINED(D2F_DEBUG)}
1759 if dbgShowTraceLog
then e_LogWritefln(' down step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
1761 if (wkstep
>= wklen
) then break
;
1768 wkstep
:= yptr
^-((yptr
^ and (not (mTileSize
-1)))-1);
1769 {$IF DEFINED(D2F_DEBUG)}
1770 if dbgShowTraceLog
then e_LogWritefln(' up step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
1772 if (wkstep
>= wklen
) then break
;
1779 // we can travel less than one cell
1780 if wasHit
and not assigned(cb
) then result
:= lastObj
else begin ex
:= ax1
; ey
:= ay1
; end;
1785 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1786 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB((xptr
^ div tsize
*tsize
)+minx
, (yptr
^ div tsize
*tsize
)+miny
);
1789 //e_LogWritefln('*********************', []);
1792 while (xd
<> term
) do
1795 {$IF DEFINED(D2F_DEBUG)}
1796 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ >= gh
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
1799 ga
:= (yptr
^ div tsize
)*gw
+(xptr
^ div tsize
);
1800 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1801 if assigned(dbgRayTraceTileHitCB
) then e_WriteLog(Format(' xd=%d; term=%d; gx=%d; gy=%d; ga=%d; lastga=%d', [xd
, term
, xptr
^, yptr
^, ga
, lastGA
]), MSG_NOTIFY
);
1803 if (ga
<> lastGA
) then
1806 {$IF DEFINED(D2F_DEBUG)}
1807 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB((xptr
^ div tsize
*tsize
)+minx
, (yptr
^ div tsize
*tsize
)+miny
);
1809 if (ccidx
<> -1) then
1811 // signal cell completion
1812 if assigned(cb
) then
1814 if cb(nil, 0, xptr
^+minx
, yptr
^+miny
, prevx
, prevy
) then begin result
:= lastObj
; exit
; end;
1823 ccidx
:= mGrid
[lastGA
];
1825 // has something to process in this tile?
1826 if (ccidx
<> -1) then
1830 hasUntried
:= false; // this will be set to `true` if we have some proxies we still want to process at the next step
1831 // convert coords to map (to avoid ajdusting coords inside the loop)
1834 // process cell list
1835 while (curci
<> -1) do
1837 cc
:= @mCells
[curci
];
1838 for f
:= 0 to GridCellBucketSize
-1 do
1840 if (cc
.bodies
[f
] = -1) then break
;
1841 px
:= @mProxies
[cc
.bodies
[f
]];
1843 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1845 // can we process this proxy?
1846 if (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
1848 px
.mQueryMark
:= lq
; // mark as processed
1849 if assigned(cb
) then
1851 if cb(px
.mObj
, ptag
, x
, y
, prevx
, prevy
) then
1861 // remember this hitpoint if it is nearer than an old one
1862 distSq
:= distanceSq(ax0
, ay0
, prevx
, prevy
);
1863 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1864 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
);
1866 if (distSq
< lastDistSq
) then
1869 lastDistSq
:= distSq
;
1878 // this is possibly interesting proxy, set "has more to check" flag
1886 // still has something interesting in this cell?
1887 if not hasUntried
then
1889 // nope, don't process this cell anymore; signal cell completion
1891 if assigned(cb
) then
1893 if cb(nil, 0, x
, y
, prevx
, prevy
) then begin result
:= lastObj
; exit
; end;
1902 if (ccidx
= -1) then
1904 // move to cell edge, as we have nothing to trace here anymore
1905 if (stx
< 0) then xdist
:= xd
and (not (mTileSize
-1)) else xdist
:= xd
or (mTileSize
-1);
1906 if (sty
< 0) then ydist
:= yd
and (not (mTileSize
-1)) else ydist
:= yd
or (mTileSize
-1);
1907 //e_LogWritefln('0: swapped=%d; xd=%d; yd=%d; stx=%d; sty=%d; e=%d; dx2=%d; dy2=%d; term=%d; xdist=%d; ydist=%d', [swapped, xd, yd, stx, sty, e, dx2, dy2, term, xdist, ydist]);
1908 while (xd
<> xdist
) and (yd
<> ydist
) do
1912 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
1913 //e_LogWritefln(' xd=%d; yd=%d', [xd, yd]);
1914 if (xd
= term
) then break
;
1916 //e_LogWritefln('1: swapped=%d; xd=%d; yd=%d; stx=%d; sty=%d; e=%d; dx2=%d; dy2=%d; term=%d; xdist=%d; ydist=%d', [swapped, xd, yd, stx, sty, e, dx2, dy2, term, xdist, ydist]);
1917 if (xd
= term
) then break
;
1919 //putPixel(xptr^, yptr^);
1921 prevx
:= xptr
^+minx
;
1922 prevy
:= yptr
^+miny
;
1923 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
1926 // we can travel less than one cell
1927 if wasHit
and not assigned(cb
) then
1933 ex
:= ax1
; // why not?
1934 ey
:= ay1
; // why not?
1939 // ////////////////////////////////////////////////////////////////////////// //
1940 //FIXME! optimize this with real tile walking
1941 function TBodyGridBase
.forEachAlongLine (ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
1945 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
1946 stx
, sty
: Integer; // "steps" for x and y axes
1947 dsx
, dsy
: Integer; // "lengthes" for x and y axes
1948 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
1949 xd
, yd
: Integer; // current coord
1950 e
: Integer; // "error" (as in bresenham algo)
1953 xptr
, yptr
: PInteger;
1956 ccidx
, curci
: Integer;
1957 lastGA
: Integer = -1;
1959 gw
, gh
, minx
, miny
, maxx
, maxy
: Integer;
1964 x0
, y0
, x1
, y1
: Integer;
1965 //swapped: Boolean = false; // true: xd is yd, and vice versa
1966 // horizontal walker
1967 {$IFDEF GRID_USE_ORTHO_ACCEL}
1968 wklen
, wkstep
: Integer;
1973 xdist
, ydist
: Integer;
1976 result
:= Default(ITP
);
1977 tagmask
:= tagmask
and TagFullMask
;
1978 if (tagmask
= 0) or not assigned(cb
) then exit
;
1980 if (ax0
= ax1
) and (ay0
= ay1
) then
1982 result
:= forEachAtPoint(ax0
, ay0
, cb
, tagmask
, @ptag
);
1998 // offset query coords to (0,0)-based
2013 // from left to right
2014 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
2015 stx
:= 1; // going right
2019 // from right to left
2020 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
2021 stx
:= -1; // going left
2032 // from top to bottom
2033 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
2034 sty
:= 1; // going down
2038 // from bottom to top
2039 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
2040 sty
:= -1; // going up
2080 temp
:= dx2
*(wy0
-y0
)-dsx
;
2082 rem
:= temp
mod dy2
;
2083 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
2084 if (xd
+1 >= wx0
) then
2088 if (rem
> 0) then begin Inc(xd
); e
+= dy2
; end;
2093 if (not xfixed
) and (x0
< wx0
) then
2096 temp
:= dy2
*(wx0
-x0
);
2098 rem
:= temp
mod dx2
;
2099 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
2102 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
2108 temp
:= dx2
*(wy1
-y0
)+dsx
;
2109 term
:= x0
+temp
div dy2
;
2110 rem
:= temp
mod dy2
;
2111 if (rem
= 0) then Dec(term
);
2114 if (term
> wx1
) then term
:= wx1
; // clip at right
2116 Inc(term
); // draw last point
2117 //if (term = xd) then exit; // this is the only point, get out of here
2119 if (sty
= -1) then yd
:= -yd
;
2120 if (stx
= -1) then begin xd
:= -xd
; term
:= -term
; end;
2123 // first move, to skip starting point
2124 // DON'T DO THIS! loop will take care of that
2127 result
:= forEachAtPoint(ax0
, ay0
, cb
, tagmask
, @ptag
);
2133 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
2136 if (xd = term) then exit;
2139 {$IF DEFINED(D2F_DEBUG)}
2140 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ >= gh
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
2142 // DON'T DO THIS! loop will take care of that
2143 //lastGA := (yptr^ div tsize)*gw+(xptr^ div tsize);
2144 //ccidx := mGrid[lastGA];
2146 // increase query counter
2148 if (mLastQuery
= 0) then
2150 // just in case of overflow
2152 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
2156 {$IFDEF GRID_USE_ORTHO_ACCEL}
2157 // if this is strict horizontal/vertical trace, use optimized codepath
2158 if (ax0
= ax1
) or (ay0
= ay1
) then
2160 // horizontal trace: walk the whole tiles, calculating mindist once for each proxy in cell
2161 // stx < 0: going left, otherwise `stx` is > 0, and we're going right
2162 // vertical trace: walk the whole tiles, calculating mindist once for each proxy in cell
2163 // stx < 0: going up, otherwise `stx` is > 0, and we're going down
2164 hopt
:= (ay0
= ay1
); // horizontal?
2165 if (stx
< 0) then begin {wksign := -1;} wklen
:= -(term
-xd
); end else begin {wksign := 1;} wklen
:= term
-xd
; end;
2166 {$IF DEFINED(D2F_DEBUG)}
2167 if dbgShowTraceLog
then e_LogWritefln('optimized htrace; wklen=%d', [wklen
]);
2169 ga
:= (yptr
^ div tsize
)*gw
+(xptr
^ div tsize
);
2170 // one of those will never change
2173 {$IF DEFINED(D2F_DEBUG)}
2176 if (y
<> ay0
) then raise Exception
.Create('htrace fatal internal error');
2180 if (x
<> ax0
) then raise Exception
.Create('vtrace fatal internal error');
2183 while (wklen
> 0) do
2185 {$IF DEFINED(D2F_DEBUG)}
2186 if dbgShowTraceLog
then e_LogWritefln(' htrace; ga=%d; x=%d, y=%d; y=%d; y=%d', [ga
, xptr
^+minx
, yptr
^+miny
, y
, ay0
]);
2189 if (ga
<> lastGA
) then
2192 ccidx
:= mGrid
[lastGA
];
2193 // convert coords to map (to avoid ajdusting coords inside the loop)
2194 if hopt
then x
:= xptr
^+minx
else y
:= yptr
^+miny
;
2195 while (ccidx
<> -1) do
2197 cc
:= @mCells
[ccidx
];
2198 for f
:= 0 to GridCellBucketSize
-1 do
2200 if (cc
.bodies
[f
] = -1) then break
;
2201 px
:= @mProxies
[cc
.bodies
[f
]];
2203 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
2205 px
.mQueryMark
:= lq
; // mark as processed
2206 if assigned(cb
) then
2208 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
2221 // skip to next tile
2227 wkstep
:= ((xptr
^ or (mTileSize
-1))+1)-xptr
^;
2228 {$IF DEFINED(D2F_DEBUG)}
2229 if dbgShowTraceLog
then e_LogWritefln(' right step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
2231 if (wkstep
>= wklen
) then break
;
2238 wkstep
:= xptr
^-((xptr
^ and (not (mTileSize
-1)))-1);
2239 {$IF DEFINED(D2F_DEBUG)}
2240 if dbgShowTraceLog
then e_LogWritefln(' left step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
2242 if (wkstep
>= wklen
) then break
;
2252 wkstep
:= ((yptr
^ or (mTileSize
-1))+1)-yptr
^;
2253 {$IF DEFINED(D2F_DEBUG)}
2254 if dbgShowTraceLog
then e_LogWritefln(' down step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
2256 if (wkstep
>= wklen
) then break
;
2263 wkstep
:= yptr
^-((yptr
^ and (not (mTileSize
-1)))-1);
2264 {$IF DEFINED(D2F_DEBUG)}
2265 if dbgShowTraceLog
then e_LogWritefln(' up step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
2267 if (wkstep
>= wklen
) then break
;
2278 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2279 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB((xptr
^ div tsize
*tsize
)+minx
, (yptr
^ div tsize
*tsize
)+miny
);
2284 while (xd
<> term
) do
2287 {$IF DEFINED(D2F_DEBUG)}
2288 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*tsize
) and (yptr
^ >= gh
*tsize
) then raise Exception
.Create('raycaster internal error (0)');
2291 ga
:= (yptr
^ div tsize
)*gw
+(xptr
^ div tsize
);
2292 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2293 if assigned(dbgRayTraceTileHitCB
) then e_WriteLog(Format(' xd=%d; term=%d; gx=%d; gy=%d; ga=%d; lastga=%d', [xd
, term
, xptr
^, yptr
^, ga
, lastGA
]), MSG_NOTIFY
);
2295 if (ga
<> lastGA
) then
2298 {$IF DEFINED(D2F_DEBUG)}
2299 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB((xptr
^ div tsize
*tsize
)+minx
, (yptr
^ div tsize
*tsize
)+miny
);
2302 ccidx
:= mGrid
[lastGA
];
2304 // has something to process in this tile?
2305 if (ccidx
<> -1) then
2309 // convert coords to map (to avoid ajdusting coords inside the loop)
2312 // process cell list
2313 while (curci
<> -1) do
2315 cc
:= @mCells
[curci
];
2316 for f
:= 0 to GridCellBucketSize
-1 do
2318 if (cc
.bodies
[f
] = -1) then break
;
2319 px
:= @mProxies
[cc
.bodies
[f
]];
2321 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
2323 px
.mQueryMark
:= lq
; // mark as processed
2324 if assigned(cb
) then
2326 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; exit
; end;
2338 // nothing more interesting in this cell
2341 // move to cell edge, as we have nothing to trace here anymore
2342 if (stx
< 0) then xdist
:= xd
and (not (mTileSize
-1)) else xdist
:= xd
or (mTileSize
-1);
2343 if (sty
< 0) then ydist
:= yd
and (not (mTileSize
-1)) else ydist
:= yd
or (mTileSize
-1);
2344 //e_LogWritefln('0: swapped=%d; xd=%d; yd=%d; stx=%d; sty=%d; e=%d; dx2=%d; dy2=%d; term=%d; xdist=%d; ydist=%d', [swapped, xd, yd, stx, sty, e, dx2, dy2, term, xdist, ydist]);
2345 while (xd
<> xdist
) and (yd
<> ydist
) do
2349 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
2350 //e_LogWritefln(' xd=%d; yd=%d', [xd, yd]);
2351 if (xd
= term
) then break
;
2353 //e_LogWritefln('1: swapped=%d; xd=%d; yd=%d; stx=%d; sty=%d; e=%d; dx2=%d; dy2=%d; term=%d; xdist=%d; ydist=%d', [swapped, xd, yd, stx, sty, e, dx2, dy2, term, xdist, ydist]);
2354 if (xd
= term
) then break
;
2355 //putPixel(xptr^, yptr^);
2357 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;