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}
33 * In order to make this usable for kind-of-recursive calls,
34 * we'll use "frame memory pool" to return results. That is,
35 * we will allocate a memory pool that will be cleared on
36 * frame start, and then used as a simple "no-free" allocator.
37 * Grid will put results into this pool, and will never bother
38 * to free it. Caller should call "release" on result, and
39 * the pool will throw away everything.
40 * No more callbacks, of course.
44 GridTileSize
= 32; // must be power of two!
47 TBodyProxyId
= Integer;
49 generic TBodyGridBase
<ITP
> = class{$IFDEF USE_MEMPOOL}(TPoolObject
){$ENDIF}
53 type TGridQueryCB
= function (obj
: ITP
; tag
: Integer): Boolean is nested
; // return `true` to stop
54 //type TGridRayQueryCB = function (obj: ITP; tag: Integer; x, y, prevx, prevy: Integer): Boolean is nested; // return `true` to stop
55 type TCellQueryCB
= procedure (x
, y
: Integer) is nested
; // top-left cell corner coords
57 const TagDisabled
= $40000000;
58 const TagFullMask
= $3fffffff;
62 GridCellBucketSize
= 8; // WARNING! can't be less than 2!
66 PBodyProxyRec
= ^TBodyProxyRec
;
67 TBodyProxyRec
= record
69 mX
, mY
, mWidth
, mHeight
: Integer; // aabb
70 mQueryMark
: LongWord; // was this object visited at this query?
72 mTag
: Integer; // `TagDisabled` set: disabled ;-)
73 nextLink
: TBodyProxyId
; // next free or nothing
76 procedure setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
78 function getTag (): Integer; inline;
79 procedure setTag (v
: Integer); inline;
81 function getEnabled (): Boolean; inline;
82 procedure setEnabled (v
: Boolean); inline;
84 function getX1 (): Integer; inline;
85 function getY1 (): Integer; inline;
88 property x
: Integer read mX
;
89 property y
: Integer read mY
;
90 property width
: Integer read mWidth
;
91 property height
: Integer read mHeight
;
92 property tag
: Integer read getTag write setTag
;
93 property enabled
: Boolean read getEnabled write setEnabled
;
94 property obj
: ITP read mObj
;
96 property x0
: Integer read mX
;
97 property y0
: Integer read mY
;
98 property x1
: Integer read getX1
;
99 property y1
: Integer read getY1
;
104 PGridCell
= ^TGridCell
;
106 bodies
: array [0..GridCellBucketSize
-1] of Integer; // -1: end of list
107 next
: Integer; // in this cell; index in mCells
110 TCellArray
= array of TGridCell
;
112 TGridInternalCB
= function (grida
: Integer; bodyId
: TBodyProxyId
): Boolean of object; // return `true` to stop
115 //mTileSize: Integer;
116 const mTileSize
= GridTileSize
;
117 type TGetProxyFn
= function (pxidx
: Integer): PBodyProxyRec
of object;
120 const tileSize
= mTileSize
;
123 TAtPointEnumerator
= record
126 curidx
, curbki
: Integer;
129 constructor Create (acells
: TCellArray
; aidx
: Integer; agetpx
: TGetProxyFn
);
130 function MoveNext (): Boolean; inline;
131 function getCurrent (): PBodyProxyRec
; inline;
132 property Current
: PBodyProxyRec read getCurrent
;
136 mMinX
, mMinY
: Integer; // so grids can start at any origin
137 mWidth
, mHeight
: Integer; // in tiles
138 mGrid
: array of Integer; // mWidth*mHeight, index in mCells
139 mCells
: TCellArray
; // cell pool
140 mFreeCell
: Integer; // first free cell index or -1
141 mLastQuery
: LongWord;
143 mProxies
: array of TBodyProxyRec
;
144 mProxyFree
: TBodyProxyId
; // free
145 mProxyCount
: Integer; // currently used
146 mProxyMaxCount
: Integer;
150 dbgShowTraceLog
: Boolean;
151 {$IF DEFINED(D2F_DEBUG)}
152 dbgRayTraceTileHitCB
: TCellQueryCB
;
156 function allocCell (): Integer;
157 procedure freeCell (idx
: Integer); // `next` is simply overwritten
159 function allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
160 procedure freeProxy (body
: TBodyProxyId
);
162 function forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
164 function inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
165 function remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
167 function getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
168 procedure setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
170 function getGridWidthPx (): Integer; inline;
171 function getGridHeightPx (): Integer; inline;
173 function getProxyById (idx
: TBodyProxyId
): PBodyProxyRec
; inline;
176 constructor Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
177 destructor Destroy (); override;
179 function insertBody (aObj
: ITP
; ax
, ay
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
180 procedure removeBody (body
: TBodyProxyId
); // WARNING! this WILL destroy proxy!
182 procedure moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
183 procedure resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
184 procedure moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
186 function insideGrid (x
, y
: Integer): Boolean; inline;
188 // `false` if `body` is surely invalid
189 function getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
190 function getBodyWH (body
: TBodyProxyId
; out rw
, rh
: Integer): Boolean; inline;
191 function getBodyDims (body
: TBodyProxyId
; out rx
, ry
, rw
, rh
: Integer): Boolean; inline;
193 //WARNING: don't modify grid while any query is in progress (no checks are made!)
194 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
195 // no callback: return `true` on the first hit
196 //function forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
197 // return number of ITP thingys put into frame pool
198 function forEachInAABB (x
, y
, w
, h
: Integer; tagmask
: Integer=-1; allowDisabled
: Boolean=false; firstHit
: Boolean=false): Integer;
200 //WARNING: don't modify grid while any query is in progress (no checks are made!)
201 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
202 // no callback: return object on the first hit or nil
203 //function forEachAtPoint (x, y: Integer; cb: TGridQueryCB; tagmask: Integer=-1{; exittag: PInteger=nil}): ITP;
204 function forEachAtPoint (x
, y
: Integer; tagmask
: Integer=-1; allowDisabled
: Boolean=false; firstHit
: Boolean=false): Integer;
206 function atCellInPoint (x
, y
: Integer): TAtPointEnumerator
;
208 //WARNING: don't modify grid while any query is in progress (no checks are made!)
209 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
210 // cb with `(nil)` will be called before processing new tile
211 // no callback: return object of the nearest hit or nil
212 // if `inverted` is true, trace will register bodies *exluding* tagmask
213 //WARNING: don't change tags in callbacks here!
215 function traceRayOld (const x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP; overload;
216 function traceRayOld (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
219 //WARNING: don't modify grid while any query is in progress (no checks are made!)
220 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
221 // return object of the nearest hit or nil
222 // if `inverted` is true, trace will register bodies *exluding* tagmask
223 //WARNING: don't change tags in callbacks here!
224 function traceRay (const x0
, y0
, x1
, y1
: Integer; tagmask
: Integer=-1): ITP
; overload
;
225 function traceRay (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; tagmask
: Integer=-1): ITP
;
227 // return `false` if we're still inside at the end
228 // line should be either strict horizontal, or strict vertical, otherwise an exception will be thrown
229 // `true`: endpoint will point at the last "inside" pixel
230 // `false`: endpoint will be (ax1, ay1)
231 //WARNING: don't change tags in callbacks here!
232 function traceOrthoRayWhileIn (out ex
, ey
: Integer; ax0
, ay0
, ax1
, ay1
: Integer; tagmask
: Integer=-1): Boolean;
234 //WARNING: don't modify grid while any query is in progress (no checks are made!)
235 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
236 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
237 //WARNING: don't change tags in callbacks here!
238 function forEachAlongLine (ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
240 // trace box with the given velocity; return object hit (if any)
241 // `cb` is used unconvetionally here: if it returns `false`, tracer will ignore the object
242 //WARNING: don't change tags in callbacks here!
243 function traceBox (out ex
, ey
: Integer; const ax0
, ay0
, aw
, ah
: Integer; const dx
, dy
: Integer; tagmask
: Integer=-1): ITP
;
246 procedure forEachBodyCell (body
: TBodyProxyId
; cb
: TCellQueryCB
);
247 function forEachInCell (x
, y
: Integer; cb
: TGridQueryCB
): ITP
;
248 procedure dumpStats ();
251 //WARNING! no sanity checks!
252 property proxyEnabled
[pid
: TBodyProxyId
]: Boolean read getProxyEnabled write setProxyEnabled
;
254 property gridX0
: Integer read mMinX
;
255 property gridY0
: Integer read mMinY
;
256 property gridWidth
: Integer read getGridWidthPx
; // in pixels
257 property gridHeight
: Integer read getGridHeightPx
; // in pixels
259 property proxy
[idx
: TBodyProxyId
]: PBodyProxyRec read getProxyById
;
264 // common structure for all line tracers
267 const TileSize
= GridTileSize
;
270 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
271 stx
, sty
: Integer; // "steps" for x and y axes
272 stleft
: Integer; // "steps left"
273 err
, errinc
, errmax
: Integer;
274 xd
, yd
: Integer; // current coord
278 // call `setyp` after this
279 constructor Create (minx
, miny
, maxx
, maxy
: Integer);
281 procedure setClip (minx
, miny
, maxx
, maxy
: Integer); inline;
283 // this will use `w[xy][01]` to clip coords
284 // return `false` if the whole line was clipped away
285 // on `true`, you should process first point, and go on
286 function setup (x0
, y0
, x1
, y1
: Integer): Boolean;
288 // call this *after* doing a step
289 // WARNING! if you will do a step when this returns `true`, you will fall into limbo
290 function done (): Boolean; inline;
292 // as you will prolly call `done()` after doing a step anyway, this will do it for you
293 // move to next point, return `true` when the line is complete (i.e. you should stop)
294 function step (): Boolean; inline;
296 // move to next tile; return `true` if the line is complete (and walker state is undefined then)
297 function stepToNextTile (): Boolean; inline;
299 procedure getXY (out ox
, oy
: Integer); inline;
303 property x
: Integer read xd
;
304 property y
: Integer read yd
;
308 procedure swapInt (var a
: Integer; var b
: Integer); inline;
309 //function minInt (a, b: Integer): Integer; inline;
310 //function maxInt (a, b: Integer): Integer; inline;
316 SysUtils
, e_log
, g_console
, geom
, utils
;
319 // ////////////////////////////////////////////////////////////////////////// //
320 procedure swapInt (var a
: Integer; var b
: Integer); inline; var t
: Integer; begin t
:= a
; a
:= b
; b
:= t
; end;
321 //procedure swapInt (var a: Integer; var b: Integer); inline; begin a := a xor b; b := b xor a; a := a xor b; end;
322 //function minInt (a, b: Integer): Integer; inline; begin if (a < b) then result := a else result := b; end;
323 //function maxInt (a, b: Integer): Integer; inline; begin if (a > b) then result := a else result := b; end;
326 // ////////////////////////////////////////////////////////////////////////// //
327 constructor TLineWalker
.Create (minx
, miny
, maxx
, maxy
: Integer);
329 setClip(minx
, miny
, maxx
, maxy
);
332 procedure TLineWalker
.setClip (minx
, miny
, maxx
, maxy
: Integer); inline;
341 function TLineWalker
.setup (x0
, y0
, x1
, y1
: Integer): Boolean;
343 sx0
, sy0
, sx1
, sy1
: Single;
345 if (wx1
< wx0
) or (wy1
< wy0
) then begin stleft
:= 0; xd
:= x0
; yd
:= y0
; result
:= false; exit
; end;
347 if (x0
>= wx0
) and (y0
>= wy0
) and (x0
<= wx1
) and (y0
<= wy1
) and
348 (x1
>= wx0
) and (y1
>= wy0
) and (x1
<= wx1
) and (y1
<= wy1
) then
354 sx0
:= x0
; sy0
:= y0
;
355 sx1
:= x1
; sy1
:= y1
;
356 result
:= clipLine(sx0
, sy0
, sx1
, sy1
, wx0
, wy0
, wx1
, wy1
);
357 if not result
then begin stleft
:= 0; xd
:= x0
; yd
:= y0
; exit
; end;
358 x0
:= trunc(sx0
); y0
:= trunc(sy0
);
359 x1
:= trunc(sx1
); y1
:= trunc(sy1
);
362 // check for ortho lines
367 stleft
:= abs(x1
-x0
)+1;
368 if (x0
< x1
) then stx
:= 1 else stx
:= -1;
371 errmax
:= 10; // anything that is greater than zero
373 else if (x0
= x1
) then
377 stleft
:= abs(y1
-y0
)+1;
379 if (y0
< y1
) then sty
:= 1 else sty
:= -1;
381 errmax
:= 10; // anything that is greater than zero
386 if (abs(x1
-x0
) >= abs(y1
-y0
)) then
390 stleft
:= abs(x1
-x0
)+1;
391 errinc
:= abs(y1
-y0
)+1;
397 stleft
:= abs(y1
-y0
)+1;
398 errinc
:= abs(x1
-x0
)+1;
400 if (x0
< x1
) then stx
:= 1 else stx
:= -1;
401 if (y0
< y1
) then sty
:= 1 else sty
:= -1;
409 function TLineWalker
.done (): Boolean; inline; begin result
:= (stleft
<= 0); end;
412 function TLineWalker
.step (): Boolean; inline;
418 if (err
>= 0) then begin err
-= errmax
; yd
+= sty
; end;
424 if (err
>= 0) then begin err
-= errmax
; xd
+= stx
; end;
427 result
:= (stleft
<= 0);
431 function TLineWalker
.stepToNextTile (): Boolean; inline;
434 xwalk
, ywalk
, wklen
: Integer; // to the respective edges
439 if (stleft
< 2) then begin result
:= true; exit
; end; // max one pixel left, nothing to do
441 // strictly horizontal?
448 ex
:= (xd
and (not (TileSize
-1)))-1;
454 ex
:= (xd
or (TileSize
-1))+1;
457 result
:= (stleft
<= 0);
462 // strictly vertical?
469 ey
:= (yd
and (not (TileSize
-1)))-1;
474 // yd: to bottom edge
475 ey
:= (yd
or (TileSize
-1))+1;
478 result
:= (stleft
<= 0);
488 ex
:= (xd
and (not (TileSize
-1)))-1;
493 ex
:= (xd
or (TileSize
-1))+1;
500 ey
:= (yd
and (not (TileSize
-1)))-1;
505 ey
:= (yd
or (TileSize
-1))+1;
510 while (xd <> ex) and (yd <> ey) do
516 if (err >= 0) then begin err -= errmax; yd += sty; end;
522 if (err >= 0) then begin err -= errmax; xd += stx; end;
525 if (stleft < 1) then begin result := true; exit; end;
529 if (xwalk
<= ywalk
) then wklen
:= xwalk
else wklen
:= ywalk
;
532 // in which dir we want to walk?
534 if (stleft
<= 0) then begin result
:= true; exit
; end;
538 for f
:= 1 to wklen
do
541 if (err
>= 0) then begin err
-= errmax
; yd
+= sty
; end;
547 for f
:= 1 to wklen
do
550 if (err
>= 0) then begin err
-= errmax
; xd
+= stx
; end;
553 // check for walk completion
554 if (xd
= ex
) or (yd
= ey
) then exit
;
559 procedure TLineWalker
.getXY (out ox
, oy
: Integer); inline; begin ox
:= xd
; oy
:= yd
; end;
562 // ////////////////////////////////////////////////////////////////////////// //
563 procedure TBodyGridBase
.TBodyProxyRec
.setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
576 function TBodyGridBase
.TBodyProxyRec
.getTag (): Integer; inline;
578 result
:= mTag
and TagFullMask
;
581 procedure TBodyGridBase
.TBodyProxyRec
.setTag (v
: Integer); inline;
583 mTag
:= (mTag
and TagDisabled
) or (v
and TagFullMask
);
586 function TBodyGridBase
.TBodyProxyRec
.getEnabled (): Boolean; inline;
588 result
:= ((mTag
and TagDisabled
) = 0);
591 procedure TBodyGridBase
.TBodyProxyRec
.setEnabled (v
: Boolean); inline;
593 if v
then mTag
:= mTag
and (not TagDisabled
) else mTag
:= mTag
or TagDisabled
;
596 function TBodyGridBase
.TBodyProxyRec
.getX1 (): Integer; inline;
598 result
:= mX
+mWidth
-1;
601 function TBodyGridBase
.TBodyProxyRec
.getY1 (): Integer; inline;
603 result
:= mY
+mHeight
-1;
607 // ////////////////////////////////////////////////////////////////////////// //
608 constructor TBodyGridBase
.TAtPointEnumerator
.Create (acells
: TCellArray
; aidx
: Integer; agetpx
: TGetProxyFn
);
617 function TBodyGridBase
.TAtPointEnumerator
.MoveNext (): Boolean; inline;
619 while (curidx
<> -1) do
621 while (curbki
< GridCellBucketSize
) do
624 if (mCells
[curidx
].bodies
[curbki
] = -1) then break
;
628 curidx
:= mCells
[curidx
].next
;
635 function TBodyGridBase
.TAtPointEnumerator
.getCurrent (): PBodyProxyRec
; inline;
637 result
:= getpx(mCells
[curidx
].bodies
[curbki
]);
641 // ////////////////////////////////////////////////////////////////////////// //
642 constructor TBodyGridBase
.Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
646 dbgShowTraceLog
:= false;
647 {$IF DEFINED(D2F_DEBUG)}
648 dbgRayTraceTileHitCB
:= nil;
651 if aTileSize < 1 then aTileSize := 1;
652 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
653 mTileSize := aTileSize;
655 if (aPixWidth
< mTileSize
) then aPixWidth
:= mTileSize
;
656 if (aPixHeight
< mTileSize
) then aPixHeight
:= mTileSize
;
659 mWidth
:= (aPixWidth
+mTileSize
-1) div mTileSize
;
660 mHeight
:= (aPixHeight
+mTileSize
-1) div mTileSize
;
661 SetLength(mGrid
, mWidth
*mHeight
);
662 SetLength(mCells
, mWidth
*mHeight
);
663 SetLength(mProxies
, 8192);
666 for idx
:= 0 to High(mCells
) do
668 mCells
[idx
].bodies
[0] := -1;
669 mCells
[idx
].bodies
[GridCellBucketSize
-1] := -1; // "has free room" flag
670 mCells
[idx
].next
:= idx
+1;
672 mCells
[High(mCells
)].next
:= -1; // last cell
674 for idx
:= 0 to High(mGrid
) do mGrid
[idx
] := -1;
676 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
677 mProxies
[High(mProxies
)].nextLink
:= -1;
683 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
]), TMsgType
.Notify
);
687 destructor TBodyGridBase
.Destroy ();
696 // ////////////////////////////////////////////////////////////////////////// //
697 procedure TBodyGridBase
.dumpStats ();
699 idx
, mcb
, ccidx
, cnt
: Integer;
702 for idx
:= 0 to High(mGrid
) do
709 ccidx
:= mCells
[ccidx
].next
;
711 if (mcb
< cnt
) then mcb
:= cnt
;
713 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
]), TMsgType
.Notify
);
717 procedure TBodyGridBase
.forEachBodyCell (body
: TBodyProxyId
; cb
: TCellQueryCB
);
719 g
, f
, ccidx
: Integer;
722 if (body
< 0) or (body
> High(mProxies
)) or not assigned(cb
) then exit
;
723 for g
:= 0 to High(mGrid
) do
726 while (ccidx
<> -1) do
728 cc
:= @mCells
[ccidx
];
729 for f
:= 0 to GridCellBucketSize
-1 do
731 if (cc
.bodies
[f
] = -1) then break
;
732 if (cc
.bodies
[f
] = body
) then cb((g
mod mWidth
)*mTileSize
+mMinX
, (g
div mWidth
)*mTileSize
+mMinY
);
741 function TBodyGridBase
.forEachInCell (x
, y
: Integer; cb
: TGridQueryCB
): ITP
;
746 result
:= Default(ITP
);
747 if not assigned(cb
) then exit
;
750 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
> mHeight
*mTileSize
) then exit
;
751 ccidx
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
752 while (ccidx
<> -1) do
754 cc
:= @mCells
[ccidx
];
755 for f
:= 0 to GridCellBucketSize
-1 do
757 if (cc
.bodies
[f
] = -1) then break
;
758 if cb(mProxies
[cc
.bodies
[f
]].mObj
, mProxies
[cc
.bodies
[f
]].mTag
) then begin result
:= mProxies
[cc
.bodies
[f
]].mObj
; exit
; end;
766 // ////////////////////////////////////////////////////////////////////////// //
767 function TBodyGridBase
.getGridWidthPx (): Integer; inline; begin result
:= mWidth
*mTileSize
; end;
768 function TBodyGridBase
.getGridHeightPx (): Integer; inline; begin result
:= mHeight
*mTileSize
; end;
771 function TBodyGridBase
.insideGrid (x
, y
: Integer): Boolean; inline;
776 result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
*mTileSize
) and (y
< mHeight
*mTileSize
);
780 function TBodyGridBase
.getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
782 if (body
>= 0) and (body
< Length(mProxies
)) then
784 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; end;
796 function TBodyGridBase
.getBodyWH (body
: TBodyProxyId
; out rw
, rh
: Integer): Boolean; inline;
798 if (body
>= 0) and (body
< Length(mProxies
)) then
800 with mProxies
[body
] do begin rw
:= mWidth
; rh
:= mHeight
; end;
812 function TBodyGridBase
.getBodyDims (body
: TBodyProxyId
; out rx
, ry
, rw
, rh
: Integer): Boolean; inline;
814 if (body
>= 0) and (body
< Length(mProxies
)) then
816 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; rw
:= mWidth
; rh
:= mHeight
; end;
831 // ////////////////////////////////////////////////////////////////////////// //
832 function TBodyGridBase
.getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
834 if (pid
>= 0) and (pid
< Length(mProxies
)) then result
:= ((mProxies
[pid
].mTag
and TagDisabled
) = 0) else result
:= false;
838 procedure TBodyGridBase
.setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
840 if (pid
>= 0) and (pid
< Length(mProxies
)) then
844 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
and not TagDisabled
;
848 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
or TagDisabled
;
854 function TBodyGridBase
.getProxyById (idx
: TBodyProxyId
): PBodyProxyRec
; inline;
856 if (idx
>= 0) and (idx
< Length(mProxies
)) then result
:= @mProxies
[idx
] else result
:= nil;
860 // ////////////////////////////////////////////////////////////////////////// //
861 function TBodyGridBase
.allocCell (): Integer;
866 if (mFreeCell
< 0) then
868 // no free cells, want more
869 mFreeCell
:= Length(mCells
);
870 SetLength(mCells
, mFreeCell
+32768); // arbitrary number
871 for idx
:= mFreeCell
to High(mCells
) do
873 mCells
[idx
].bodies
[0] := -1;
874 mCells
[idx
].bodies
[GridCellBucketSize
-1] := -1; // 'has free room' flag
875 mCells
[idx
].next
:= idx
+1;
877 mCells
[High(mCells
)].next
:= -1; // last cell
880 pc
:= @mCells
[result
];
881 mFreeCell
:= pc
.next
;
884 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
888 procedure TBodyGridBase
.freeCell (idx
: Integer);
890 if (idx
>= 0) and (idx
< Length(mCells
)) then
895 bodies
[GridCellBucketSize
-1] := -1; // 'has free room' flag
904 // ////////////////////////////////////////////////////////////////////////// //
905 function TBodyGridBase
.allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
910 if (mProxyFree
= -1) then
912 // no free proxies, resize list
913 olen
:= Length(mProxies
);
914 SetLength(mProxies
, olen
+8192); // arbitrary number
915 for idx
:= olen
to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
916 mProxies
[High(mProxies
)].nextLink
:= -1;
920 result
:= mProxyFree
;
921 px
:= @mProxies
[result
];
922 mProxyFree
:= px
.nextLink
;
923 px
.setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
928 if (mProxyMaxCount
< mProxyCount
) then mProxyMaxCount
:= mProxyCount
;
931 procedure TBodyGridBase
.freeProxy (body
: TBodyProxyId
);
933 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
934 if (mProxyCount
= 0) then raise Exception
.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
936 mProxies
[body
].mObj
:= nil;
937 mProxies
[body
].nextLink
:= mProxyFree
;
943 // ////////////////////////////////////////////////////////////////////////// //
944 function TBodyGridBase
.forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
951 if (w
< 1) or (h
< 1) or not assigned(cb
) then exit
;
956 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
959 if (x
>= gw
*mTileSize
) or (y
>= gh
*mTileSize
) then exit
;
960 ex
:= (x
+w
-1) div mTileSize
;
961 ey
:= (y
+h
-1) div mTileSize
;
962 x
:= x
div mTileSize
;
963 y
:= y
div mTileSize
;
965 if (x
< 0) then x
:= 0 else if (x
>= gw
) then x
:= gw
-1;
966 if (y
< 0) then y
:= 0 else if (y
>= gh
) then y
:= gh
-1;
967 if (ex
< 0) then ex
:= 0 else if (ex
>= gw
) then ex
:= gw
-1;
968 if (ey
< 0) then ey
:= 0 else if (ey
>= gh
) then ey
:= gh
-1;
969 if (x
> ex
) or (y
> ey
) then exit
; // just in case
975 result
:= cb(gy
*gw
+gx
, bodyId
);
982 // ////////////////////////////////////////////////////////////////////////// //
983 function TBodyGridBase
.inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
990 result
:= false; // never stop
991 // add body to the given grid cell
995 {$IF DEFINED(D2F_DEBUG)}
997 while (ccidx
<> -1) do
999 pi
:= @mCells
[ccidx
];
1000 for f
:= 0 to GridCellBucketSize
-1 do
1002 if (pi
.bodies
[f
] = -1) then break
;
1003 if (pi
.bodies
[f
] = bodyId
) then raise Exception
.Create('trying to insert already inserted proxy');
1009 while (ccidx
<> -1) do
1011 pi
:= @mCells
[ccidx
];
1012 // check "has room" flag
1013 if (pi
.bodies
[GridCellBucketSize
-1] = -1) then
1016 for f
:= 0 to GridCellBucketSize
-1 do
1018 if (pi
.bodies
[f
] = -1) then
1020 pi
.bodies
[f
] := bodyId
;
1021 if (f
+1 < GridCellBucketSize
) then pi
.bodies
[f
+1] := -1;
1025 raise Exception
.Create('internal error in grid inserter');
1027 // no room, go to next cell in list (if there is any)
1030 // no room in cells, add new cell to list
1032 // either no room, or no cell at all
1033 ccidx
:= allocCell();
1034 pi
:= @mCells
[ccidx
];
1035 pi
.bodies
[0] := bodyId
;
1038 mGrid
[grida
] := ccidx
;
1042 // assume that we cannot have one object added to bucket twice
1043 function TBodyGridBase
.remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
1046 pidx
, ccidx
: Integer;
1049 result
:= false; // never stop
1050 // find and remove cell
1051 pidx
:= -1; // previous cell index
1052 ccidx
:= mGrid
[grida
]; // current cell index
1053 while (ccidx
<> -1) do
1055 pc
:= @mCells
[ccidx
];
1056 for f
:= 0 to GridCellBucketSize
-1 do
1058 if (pc
.bodies
[f
] = bodyId
) then
1061 if (f
= 0) and (pc
.bodies
[1] = -1) then
1063 // this cell contains no elements, remove it
1064 if (pidx
= -1) then mGrid
[grida
] := pc
.next
else mCells
[pidx
].next
:= pc
.next
;
1068 // remove element from bucket
1069 for c
:= f
to GridCellBucketSize
-2 do
1071 pc
.bodies
[c
] := pc
.bodies
[c
+1];
1072 if (pc
.bodies
[c
] = -1) then break
;
1074 pc
.bodies
[GridCellBucketSize
-1] := -1; // "has free room" flag
1084 // ////////////////////////////////////////////////////////////////////////// //
1085 function TBodyGridBase
.insertBody (aObj
: ITP
; aX
, aY
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
1087 aTag
:= aTag
and TagFullMask
;
1088 result
:= allocProxy(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
1089 //insertInternal(result);
1090 forGridRect(aX
, aY
, aWidth
, aHeight
, inserter
, result
);
1094 procedure TBodyGridBase
.removeBody (body
: TBodyProxyId
);
1098 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1099 px
:= @mProxies
[body
];
1100 //removeInternal(body);
1101 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
1106 // ////////////////////////////////////////////////////////////////////////// //
1107 procedure TBodyGridBase
.moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
1110 x0
, y0
, w
, h
: Integer;
1112 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1113 px
:= @mProxies
[body
];
1118 {$IF DEFINED(D2F_DEBUG_MOVER)}
1119 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
);
1121 if (nx
= x0
) and (ny
= y0
) and (nw
= w
) and (nh
= h
) then exit
;
1127 // did any corner crossed tile boundary?
1128 if (x0
div mTileSize
<> nx
div mTileSize
) or
1129 (y0
div mTileSize
<> ny
div mTileSize
) or
1130 ((x0
+w
-1) div mTileSize
<> (nx
+nw
-1) div mTileSize
) or
1131 ((y0
+h
-1) div mTileSize
<> (ny
+nh
-1) div mTileSize
) then
1133 //writeln('moveResizeBody: cell occupation changed! old=(', x0, ',', y0, ')-(', x0+w-1, ',', y0+h-1, '); new=(', nx, ',', ny, ')-(', nx+nw-1, ',', ny+nh-1, ')');
1134 //removeInternal(body);
1135 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
1140 //insertInternal(body);
1141 forGridRect(px
.mX
, px
.mY
, nw
, nh
, inserter
, body
);
1153 //TODO: optimize for horizontal/vertical moves
1154 procedure TBodyGridBase
.moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
1158 ogx0
, ogx1
, ogy0
, ogy1
: Integer; // old grid rect
1159 ngx0
, ngx1
, ngy0
, ngy1
: Integer; // new grid rect
1164 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1165 // check if tile coords was changed
1166 px
:= @mProxies
[body
];
1169 if (nx
= x0
) and (ny
= y0
) then exit
;
1175 // check for heavy work
1178 ogx0
:= x0
div mTileSize
;
1179 ogy0
:= y0
div mTileSize
;
1180 ngx0
:= nx
div mTileSize
;
1181 ngy0
:= ny
div mTileSize
;
1182 ogx1
:= (x0
+pw
-1) div mTileSize
;
1183 ogy1
:= (y0
+ph
-1) div mTileSize
;
1184 ngx1
:= (nx
+pw
-1) div mTileSize
;
1185 ngy1
:= (ny
+ph
-1) div mTileSize
;
1186 {$IF DEFINED(D2F_DEBUG_MOVER)}
1187 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
);
1189 if (ogx0
<> ngx0
) or (ogy0
<> ngy0
) or (ogx1
<> ngx1
) or (ogy1
<> ngy1
) then
1191 // crossed tile boundary, do heavy work
1194 // cycle with old rect, remove body where it is necessary
1195 // optimized for horizontal moves
1196 {$IF DEFINED(D2F_DEBUG_MOVER)}
1197 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
);
1199 // remove stale marks
1200 if not ((ogy0
>= gh
) or (ogy1
< 0)) and
1201 not ((ogx0
>= gw
) or (ogx1
< 0)) then
1203 if (ogx0
< 0) then ogx0
:= 0;
1204 if (ogy0
< 0) then ogy0
:= 0;
1205 if (ogx1
> gw
-1) then ogx1
:= gw
-1;
1206 if (ogy1
> gh
-1) then ogy1
:= gh
-1;
1207 {$IF DEFINED(D2F_DEBUG_MOVER)}
1208 e_WriteLog(Format(' norm og:(%d,%d)-(%d,%d)', [ogx0
, ogy0
, ogx1
, ogy1
]), MSG_NOTIFY
);
1210 for gx
:= ogx0
to ogx1
do
1212 if (gx
< ngx0
) or (gx
> ngx1
) then
1214 // this column is completely outside of new rect
1215 for gy
:= ogy0
to ogy1
do
1217 {$IF DEFINED(D2F_DEBUG_MOVER)}
1218 e_WriteLog(Format(' remove0:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1220 remover(gy
*gw
+gx
, body
);
1226 for gy
:= ogy0
to ogy1
do
1228 if (gy
< ngy0
) or (gy
> ngy1
) then
1230 {$IF DEFINED(D2F_DEBUG_MOVER)}
1231 e_WriteLog(Format(' remove1:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1233 remover(gy
*gw
+gx
, body
);
1239 // cycle with new rect, add body where it is necessary
1240 if not ((ngy0
>= gh
) or (ngy1
< 0)) and
1241 not ((ngx0
>= gw
) or (ngx1
< 0)) then
1243 if (ngx0
< 0) then ngx0
:= 0;
1244 if (ngy0
< 0) then ngy0
:= 0;
1245 if (ngx1
> gw
-1) then ngx1
:= gw
-1;
1246 if (ngy1
> gh
-1) then ngy1
:= gh
-1;
1247 {$IF DEFINED(D2F_DEBUG_MOVER)}
1248 e_WriteLog(Format(' norm ng:(%d,%d)-(%d,%d)', [ngx0
, ngy0
, ngx1
, ngy1
]), MSG_NOTIFY
);
1250 for gx
:= ngx0
to ngx1
do
1252 if (gx
< ogx0
) or (gx
> ogx1
) then
1254 // this column is completely outside of old rect
1255 for gy
:= ngy0
to ngy1
do
1257 {$IF DEFINED(D2F_DEBUG_MOVER)}
1258 e_WriteLog(Format(' insert0:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1260 inserter(gy
*gw
+gx
, body
);
1266 for gy
:= ngy0
to ngy1
do
1268 if (gy
< ogy0
) or (gy
> ogy1
) then
1270 {$IF DEFINED(D2F_DEBUG_MOVER)}
1271 e_WriteLog(Format(' insert1:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1273 inserter(gy
*gw
+gx
, body
);
1283 {$IF DEFINED(D2F_DEBUG_MOVER)}
1284 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
);
1287 // update coordinates
1293 procedure TBodyGridBase
.resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
1296 x0
, y0
, w
, h
: Integer;
1298 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1299 // check if tile coords was changed
1300 px
:= @mProxies
[body
];
1305 {$IF DEFINED(D2F_DEBUG_MOVER)}
1306 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
);
1308 if ((x0
+w
-1) div mTileSize
<> (x0
+nw
-1) div mTileSize
) or
1309 ((y0
+h
-1) div mTileSize
<> (y0
+nh
-1) div mTileSize
) then
1311 // crossed tile boundary, do heavy work
1312 //removeInternal(body);
1313 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
1316 //insertInternal(body);
1317 forGridRect(px
.mX
, px
.mY
, nw
, nh
, inserter
, body
);
1321 // nothing to do with the grid, just fix size
1328 // ////////////////////////////////////////////////////////////////////////// //
1329 function TBodyGridBase
.atCellInPoint (x
, y
: Integer): TAtPointEnumerator
;
1331 ccidx
: Integer = -1;
1335 if (x
>= 0) and (y
>= 0) and (x
< mWidth
*mTileSize
) and (y
< mHeight
*mTileSize
) then ccidx
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
1336 result
:= TAtPointEnumerator
.Create(mCells
, ccidx
, getProxyById
);
1340 // ////////////////////////////////////////////////////////////////////////// //
1341 // no callback: return `true` on the first hit
1342 function TBodyGridBase
.forEachAtPoint (x
, y
: Integer; tagmask
: Integer=-1; allowDisabled
: Boolean=false; firstHit
: Boolean=false): Integer;
1345 idx
, curci
: Integer;
1346 cc
: PGridCell
= nil;
1353 tagmask
:= tagmask
and TagFullMask
;
1354 if (tagmask
= 0) then exit
;
1356 {$IF DEFINED(D2F_DEBUG_XXQ)}
1357 if (assigned(cb
)) then e_WriteLog(Format('0: grid pointquery: (%d,%d)', [x
, y
]), MSG_NOTIFY
);
1360 // make coords (0,0)-based
1363 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
>= mHeight
*mTileSize
) then exit
;
1365 curci
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
1367 {$IF DEFINED(D2F_DEBUG_XXQ)}
1368 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
);
1375 // increase query counter
1377 if (mLastQuery
= 0) then
1379 // just in case of overflow
1381 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
1385 {$IF DEFINED(D2F_DEBUG_XXQ)}
1386 if (assigned(cb
)) then e_WriteLog(Format('2: grid pointquery: (%d,%d); lq=%u', [x
, y
, lq
]), MSG_NOTIFY
);
1389 while (curci
<> -1) do
1391 {$IF DEFINED(D2F_DEBUG_XXQ)}
1392 //if (assigned(cb)) then e_WriteLog(Format(' cell #%d', [curci]), MSG_NOTIFY);
1394 cc
:= @mCells
[curci
];
1395 for f
:= 0 to GridCellBucketSize
-1 do
1397 if (cc
.bodies
[f
] = -1) then break
;
1398 px
:= @mProxies
[cc
.bodies
[f
]];
1399 {$IF DEFINED(D2F_DEBUG_XXQ)}
1400 //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);
1402 if (px
.mQueryMark
= lq
) then continue
;
1403 px
.mQueryMark
:= lq
;
1405 if (not allowDisabled
) and ((ptag
and TagDisabled
) <> 0) then continue
;
1406 if ((ptag
and tagmask
) = 0) then continue
;
1407 if (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
1409 presobj
:= PITP(framePool
.alloc(sizeof(ITP
)));
1410 Move(px
.mObj
, presobj
^, sizeof(ITP
));
1412 if (firstHit
) then begin {mInQuery := false;} exit
; end;
1420 // ////////////////////////////////////////////////////////////////////////// //
1421 // no callback: return `true` on the first hit
1422 // return number of ITP thingys put into frame pool
1423 function TBodyGridBase
.forEachInAABB (x
, y
, w
, h
: Integer; tagmask
: Integer=-1; allowDisabled
: Boolean=false; firstHit
: Boolean=false): Integer;
1427 sx
, sy
, ex
, ey
: Integer;
1430 cc
: PGridCell
= nil;
1439 if (w
< 1) or (h
< 1) then exit
;
1441 if (w
= 1) and (h
= 1) then
1443 result
:= forEachAtPoint(x
, y
, tagmask
, allowDisabled
, firstHit
);
1447 tagmask
:= tagmask
and TagFullMask
;
1448 if (tagmask
= 0) then exit
;
1460 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
1461 if (x
>= gw
*mTileSize
) or (y
>= gh
*mTileSize
) then exit
;
1463 sx
:= x
div mTileSize
;
1464 sy
:= y
div mTileSize
;
1465 ex
:= (x
+w
-1) div mTileSize
;
1466 ey
:= (y
+h
-1) div mTileSize
;
1469 if (sx
< 0) then sx
:= 0 else if (sx
>= gw
) then sx
:= gw
-1;
1470 if (sy
< 0) then sy
:= 0 else if (sy
>= gh
) then sy
:= gh
-1;
1471 if (ex
< 0) then ex
:= 0 else if (ex
>= gw
) then ex
:= gw
-1;
1472 if (ey
< 0) then ey
:= 0 else if (ey
>= gh
) then ey
:= gh
-1;
1473 if (sx
> ex
) or (sy
> ey
) then exit
; // just in case
1475 // has something to do
1476 //if mInQuery then raise Exception.Create('recursive queries aren''t supported');
1479 // increase query counter
1481 if (mLastQuery
= 0) then
1483 // just in case of overflow
1485 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
1487 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
1491 for gy
:= sy
to ey
do
1493 for gx
:= sx
to ex
do
1496 curci
:= mGrid
[gy
*gw
+gx
];
1497 while (curci
<> -1) do
1499 cc
:= @mCells
[curci
];
1500 for f
:= 0 to GridCellBucketSize
-1 do
1502 if (cc
.bodies
[f
] = -1) then break
;
1503 px
:= @mProxies
[cc
.bodies
[f
]];
1504 // shit! has to do it this way, so i can change tag in callback
1505 if (px
.mQueryMark
= lq
) then continue
;
1506 px
.mQueryMark
:= lq
;
1508 if (not allowDisabled
) and ((ptag
and TagDisabled
) <> 0) then continue
;
1509 if ((ptag
and tagmask
) = 0) then continue
;
1510 if (x0
>= px
.mX
+px
.mWidth
) or (y0
>= px
.mY
+px
.mHeight
) then continue
;
1511 if (x0
+w
<= px
.mX
) or (y0
+h
<= px
.mY
) then continue
;
1512 presobj
:= PITP(framePool
.alloc(sizeof(ITP
)));
1513 Move(px
.mObj
, presobj
^, sizeof(ITP
));
1515 if (firstHit
) then begin {mInQuery := false;} exit
; end;
1517 if assigned(cb) then
1519 if cb(px.mObj, ptag) then begin result := px.mObj; mInQuery := false; exit; end;
1534 //mInQuery := false;
1538 // ////////////////////////////////////////////////////////////////////////// //
1539 function TBodyGridBase
.forEachAlongLine (ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
1547 gw
, gh
, minx
, miny
: Integer;
1551 //px0, py0, px1, py1: Integer;
1554 result
:= Default(ITP
);
1555 tagmask
:= tagmask
and TagFullMask
;
1556 if (tagmask
= 0) or not assigned(cb
) then exit
;
1563 // make query coords (0,0)-based
1569 lw
:= TLineWalker
.Create(0, 0, gw
*mTileSize
-1, gh
*mTileSize
-1);
1570 if not lw
.setup(x0
, y0
, x1
, y1
) then exit
; // out of screen
1572 //if mInQuery then raise Exception.Create('recursive queries aren''t supported');
1575 // increase query counter
1577 if (mLastQuery
= 0) then
1579 // just in case of overflow
1581 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1588 ccidx
:= mGrid
[(cy
div mTileSize
)*gw
+(cx
div mTileSize
)];
1590 while (ccidx
<> -1) do
1592 cc
:= @mCells
[ccidx
];
1593 for f
:= 0 to GridCellBucketSize
-1 do
1595 if (cc
.bodies
[f
] = -1) then break
;
1596 px
:= @mProxies
[cc
.bodies
[f
]];
1598 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1600 px
.mQueryMark
:= lq
; // mark as processed
1601 if cb(px
.mObj
, ptag
) then
1604 //mInQuery := false;
1612 // done processing cells, move to next tile
1613 until lw
.stepToNextTile();
1615 //mInQuery := false;
1619 // ////////////////////////////////////////////////////////////////////////// //
1620 // trace box with the given velocity; return object hit (if any)
1621 // `cb` is used unconvetionally here: if it returns `false`, tracer will ignore the object
1622 function TBodyGridBase
.traceBox (out ex
, ey
: Integer; const ax0
, ay0
, aw
, ah
: Integer; const dx
, dy
: Integer; tagmask
: Integer=-1): ITP
;
1630 minu0
: Single = 100000.0;
1632 cx0
, cy0
, cx1
, cy1
: Integer;
1633 hitpx
: PBodyProxyRec
= nil;
1635 result
:= Default(ITP
);
1638 if (aw
< 1) or (ah
< 1) then exit
;
1640 cx0
:= nmin(ax0
, ax0
+dx
);
1641 cy0
:= nmin(ay0
, ay0
+dy
);
1642 cx1
:= nmax(ax0
+aw
-1, ax0
+aw
-1+dx
);
1643 cy1
:= nmax(ay0
+ah
-1, ay0
+ah
-1+dy
);
1645 cx0
-= mMinX
; cy0
-= mMinY
;
1646 cx1
-= mMinX
; cy1
-= mMinY
;
1648 if (cx1
< 0) or (cy1
< 0) or (cx0
>= mWidth
*mTileSize
) or (cy0
>= mHeight
*mTileSize
) then exit
;
1650 if (cx0
< 0) then cx0
:= 0;
1651 if (cy0
< 0) then cy0
:= 0;
1652 if (cx1
>= mWidth
*mTileSize
) then cx1
:= mWidth
*mTileSize
-1;
1653 if (cy1
>= mHeight
*mTileSize
) then cy1
:= mHeight
*mTileSize
-1;
1655 if (cx0
> cx1
) or (cy0
> cy1
) then exit
;
1657 //if mInQuery then raise Exception.Create('recursive queries aren''t supported');
1660 // increase query counter
1662 if (mLastQuery
= 0) then
1664 // just in case of overflow
1666 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1670 for gy
:= cy0
div mTileSize
to cy1
div mTileSize
do
1672 for gx
:= cx0
div mTileSize
to cx1
div mTileSize
do
1674 ccidx
:= mGrid
[gy
*mWidth
+gx
];
1675 while (ccidx
<> -1) do
1677 cc
:= @mCells
[ccidx
];
1678 for f
:= 0 to GridCellBucketSize
-1 do
1680 if (cc
.bodies
[f
] = -1) then break
;
1681 px
:= @mProxies
[cc
.bodies
[f
]];
1683 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1685 px
.mQueryMark
:= lq
; // mark as processed
1686 if not sweepAABB(ax0
, ay0
, aw
, ah
, dx
, dy
, px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, @u0
) then continue
;
1687 if (minu0
> u0
) then
1696 //mInQuery := false;
1708 if (minu0
<= 1.0) then
1710 ex
:= ax0
+round(dx
*minu0
);
1711 ey
:= ay0
+round(dy
*minu0
);
1712 // just in case, compensate for floating point inexactness
1713 if (ex
>= hitpx
.mX
) and (ey
>= hitpx
.mY
) and (ex
< hitpx
.mX
+hitpx
.mWidth
) and (ey
< hitpx
.mY
+hitpx
.mHeight
) then
1715 ex
:= ax0
+trunc(dx
*minu0
);
1716 ey
:= ay0
+trunc(dy
*minu0
);
1720 //mInQuery := false;
1724 // ////////////////////////////////////////////////////////////////////////// //
1725 {.$DEFINE D2F_DEBUG_OTR}
1726 function TBodyGridBase
.traceOrthoRayWhileIn (out ex
, ey
: Integer; ax0
, ay0
, ax1
, ay1
: Integer; tagmask
: Integer=-1): Boolean;
1732 minx
, miny
: Integer;
1734 x0
, y0
, x1
, y1
: Integer;
1735 celly0
, celly1
: Integer;
1737 filled
: array[0..mTileSize
-1] of Byte;
1738 {$IF DEFINED(D2F_DEBUG_OTR)}
1746 if not ((ax0
= ax1
) or (ay0
= ay1
)) then raise Exception
.Create('orthoray is not orthogonal');
1748 tagmask
:= tagmask
and TagFullMask
;
1749 if (tagmask
= 0) then exit
;
1751 pmark
:= framePool
.mark();
1752 if (forEachAtPoint(ax0
, ay0
, tagmask
, false, true) = 0) then exit
;
1753 framePool
.release(pmark
);
1758 // offset query coords to (0,0)-based
1766 if (x0
< 0) or (x0
>= mWidth
*mTileSize
) then exit
; // oops
1771 if (y1
< 0) or (y0
>= mHeight
*mTileSize
) then exit
;
1772 //if (ay0 < 0) then ay0 := 0;
1773 if (y0
< 0) then exit
;
1774 if (y1
>= mHeight
*mTileSize
) then y1
:= mHeight
*mTileSize
-1;
1780 if (y0
< 0) or (y1
>= mHeight
*mTileSize
) then exit
;
1781 //if (ay1 < 0) then ay1 := 0;
1782 if (y1
< 0) then exit
;
1783 if (y0
>= mHeight
*mTileSize
) then y0
:= mHeight
*mTileSize
-1;
1789 ccidx
:= mGrid
[(y0
div mTileSize
)*mWidth
+(x0
div mTileSize
)];
1790 FillChar(filled
, sizeof(filled
), 0);
1791 celly0
:= y0
and (not (mTileSize
-1));
1792 celly1
:= celly0
+mTileSize
-1;
1793 while (ccidx
<> -1) do
1795 cc
:= @mCells
[ccidx
];
1796 for f
:= 0 to GridCellBucketSize
-1 do
1798 if (cc
.bodies
[f
] = -1) then break
;
1799 px
:= @mProxies
[cc
.bodies
[f
]];
1801 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and
1802 (ax0
>= px
.x0
) and (ax0
<= px
.x1
) then
1804 // bound c0 and c1 to cell
1805 c0
:= nclamp(px
.y0
-miny
, celly0
, celly1
);
1806 c1
:= nclamp(px
.y1
-miny
, celly0
, celly1
);
1808 {$IF DEFINED(D2F_DEBUG_OTR)}
1809 e_LogWritefln('**px.y0=%s; px.y1=%s; c0=%s; c1=%s; celly0=%s; celly1=%s; [%s..%s]', [px
.y0
-miny
, px
.y1
-miny
, c0
, c1
, celly0
, celly1
, c0
-celly0
, (c0
-celly0
)+(c1
-c0
)]);
1812 FillChar(filled
[c0
-celly0
], c1
-c0
+1, 1);
1818 {$IF DEFINED(D2F_DEBUG_OTR)}
1819 s
:= formatstrf(' x=%s; ay0=%s; ay1=%s; y0=%s; celly0=%s; celly1=%s; dy=%s; [', [ax0
, ay0
, ay1
, y0
, celly0
, celly1
, dy
]);
1820 for f
:= 0 to High(filled
) do if (filled
[f
] <> 0) then s
+= '1' else s
+= '0';
1824 // now go till we hit cell boundary or empty space
1828 while (y0
>= celly0
) and (filled
[y0
-celly0
] <> 0) do
1830 {$IF DEFINED(D2F_DEBUG_OTR)}
1831 e_LogWritefln(' filled: cdy=%s; y0=%s; celly0=%s; ay0=%s; ay1=%s', [y0
-celly0
, y0
, celly0
, ay0
, ay1
]);
1836 {$IF DEFINED(D2F_DEBUG_OTR)}
1837 e_LogWritefln(' span done: cdy=%s; y0=%s; celly0=%s; ay0=%s; ay1=%s', [y0
-celly0
, y0
, celly0
, ay0
, ay1
]);
1839 if (ay0
<= ay1
) then begin ey
:= ay1
; result
:= false; exit
; end;
1840 if (y0
>= celly0
) then begin ey
:= ay0
+1; {assert(forEachAtPoint(ex, ey, nil, tagmask) <> nil);} result
:= true; exit
; end;
1845 while (y0
<= celly1
) and (filled
[y0
-celly0
] <> 0) do begin Inc(y0
); Inc(ay0
); end;
1846 if (ay0
>= ay1
) then begin ey
:= ay1
; result
:= false; exit
; end;
1847 if (y0
<= celly1
) then begin ey
:= ay0
-1; result
:= true; exit
; end;
1859 // ////////////////////////////////////////////////////////////////////////// //
1860 function TBodyGridBase
.traceRay (const x0
, y0
, x1
, y1
: Integer; tagmask
: Integer=-1): ITP
;
1864 result
:= traceRay(ex
, ey
, x0
, y0
, x1
, y1
, tagmask
);
1868 // no callback: return `true` on the nearest hit
1869 // you are not supposed to understand this
1870 function TBodyGridBase
.traceRay (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; tagmask
: Integer=-1): ITP
;
1878 gw
, gh
, minx
, miny
: Integer;
1882 px0
, py0
, px1
, py1
: Integer;
1883 lastDistSq
, distSq
, hx
, hy
: Integer;
1884 firstCell
: Boolean = true;
1887 result
:= Default(ITP
);
1888 tagmask
:= tagmask
and TagFullMask
;
1889 if (tagmask
= 0) then exit
;
1896 // make query coords (0,0)-based
1902 lw
:= TLineWalker
.Create(0, 0, gw
*mTileSize
-1, gh
*mTileSize
-1);
1903 if not lw
.setup(x0
, y0
, x1
, y1
) then exit
; // out of screen
1905 lastDistSq
:= distanceSq(ax0
, ay0
, ax1
, ay1
)+1;
1907 {$IF DEFINED(D2F_DEBUG)}
1908 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln('*** traceRay: (%s,%s)-(%s,%s)', [x0, y0, x1, y1]);
1911 //if mInQuery then raise Exception.Create('recursive queries aren''t supported');
1914 // increase query counter
1916 if (mLastQuery
= 0) then
1918 // just in case of overflow
1920 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1926 {$IF DEFINED(D2F_DEBUG)}
1927 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB(cx
+mMinX
, cy
+mMinY
);
1930 ccidx
:= mGrid
[(cy
div mTileSize
)*gw
+(cx
div mTileSize
)];
1933 while (ccidx
<> -1) do
1935 cc
:= @mCells
[ccidx
];
1936 for f
:= 0 to GridCellBucketSize
-1 do
1938 if (cc
.bodies
[f
] = -1) then break
;
1939 px
:= @mProxies
[cc
.bodies
[f
]];
1941 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1943 px
.mQueryMark
:= lq
; // mark as processed
1944 // get adjusted proxy coords
1947 px1
:= px0
+px
.mWidth
-1;
1948 py1
:= py0
+px
.mHeight
-1;
1949 {$IF DEFINED(D2F_DEBUG)}
1950 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln(' cxy=(%s,%s); pan=(%s,%s)-(%s,%s)', [cx, cy, px0, py0, px1, py1]);
1953 if firstCell
and (x0
>= px0
) and (y0
>= py0
) and (x0
<= px1
) and (y0
<= py1
) then
1959 //mInQuery := false;
1960 {$IF DEFINED(D2F_DEBUG)}
1961 if assigned(dbgRayTraceTileHitCB
) then e_LogWriteln(' INSIDE!');
1965 // do line-vs-aabb test
1966 if lineAABBIntersects(x0
, y0
, x1
, y1
, px0
, py0
, px1
-px0
+1, py1
-py0
+1, hx
, hy
) then
1969 distSq
:= distanceSq(x0
, y0
, hx
, hy
);
1970 {$IF DEFINED(D2F_DEBUG)}
1971 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln(' hit=(%s,%s); distSq=%s; lastDistSq=%s', [hx, hy, distSq, lastDistSq]);
1973 if (distSq
< lastDistSq
) then
1975 lastDistSq
:= distSq
;
1987 // done processing cells; exit if we registered a hit
1988 // next cells can't have better candidates, obviously
1989 if wasHit
then begin {mInQuery := false;} exit
; end;
1991 // move to next tile
1992 until lw
.stepToNextTile();
1994 //mInQuery := false;
1998 // ////////////////////////////////////////////////////////////////////////// //
1999 // no callback: return `true` on the nearest hit
2001 function TBodyGridBase.traceRayOld (const x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
2005 result := traceRayOld(ex, ey, x0, y0, x1, y1, cb, tagmask);
2009 // no callback: return `true` on the nearest hit
2010 // you are not supposed to understand this
2011 function TBodyGridBase.traceRayOld (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
2013 wx0, wy0, wx1, wy1: Integer; // window coordinates
2014 stx, sty: Integer; // "steps" for x and y axes
2015 dsx, dsy: Integer; // "lengthes" for x and y axes
2016 dx2, dy2: Integer; // "double lengthes" for x and y axes
2017 xd, yd: Integer; // current coord
2018 e: Integer; // "error" (as in bresenham algo)
2021 xptr, yptr: PInteger;
2024 prevx, prevy: Integer;
2025 lastDistSq: Integer;
2026 ccidx, curci: Integer;
2027 hasUntried: Boolean;
2028 lastGA: Integer = -1;
2031 wasHit: Boolean = false;
2032 gw, gh, minx, miny, maxx, maxy: Integer;
2036 f, ptag, distSq: Integer;
2037 x0, y0, x1, y1: Integer;
2038 //swapped: Boolean = false; // true: xd is yd, and vice versa
2039 // horizontal walker
2040 {$IFDEF GRID_USE_ORTHO_ACCEL}
2041 wklen, wkstep: Integer;
2046 xdist, ydist: Integer;
2048 result := Default(ITP);
2049 lastObj := Default(ITP);
2050 tagmask := tagmask and TagFullMask;
2051 ex := ax1; // why not?
2052 ey := ay1; // why not?
2053 if (tagmask = 0) then exit;
2055 if (ax0 = ax1) and (ay0 = ay1) then
2057 result := forEachAtPoint(ax0, ay0, nil, tagmask, @ptag);
2058 if (result <> nil) then
2060 if assigned(cb) and not cb(result, ptag, ax0, ay0, ax0, ay0) then result := Default(ITP);
2065 lastDistSq := distanceSq(ax0, ay0, ax1, ay1)+1;
2071 maxx := gw*mTileSize-1;
2072 maxy := gh*mTileSize-1;
2074 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2075 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);
2083 // offset query coords to (0,0)-based
2098 // from left to right
2099 if (x0 > wx1) or (x1 < wx0) then exit; // out of screen
2100 stx := 1; // going right
2104 // from right to left
2105 if (x1 > wx1) or (x0 < wx0) then exit; // out of screen
2106 stx := -1; // going left
2117 // from top to bottom
2118 if (y0 > wy1) or (y1 < wy0) then exit; // out of screen
2119 sty := 1; // going down
2123 // from bottom to top
2124 if (y1 > wy1) or (y0 < wy0) then exit; // out of screen
2125 sty := -1; // going up
2165 temp := dx2*(wy0-y0)-dsx;
2167 rem := temp mod dy2;
2168 if (xd > wx1) then exit; // x is moved out of clipping rect, nothing to do
2169 if (xd+1 >= wx0) then
2173 //if (rem > 0) then begin Inc(xd); e += dy2; end; //BUGGY
2174 if (xd < wx0) then begin xd += 1; e += dy2; end; //???
2179 if (not xfixed) and (x0 < wx0) then
2182 temp := dy2*(wx0-x0);
2184 rem := temp mod dx2;
2185 if (yd > wy1) or (yd = wy1) and (rem >= dsx) then exit;
2188 if (rem >= dsx) then begin Inc(yd); e -= dx2; end;
2194 temp := dx2*(wy1-y0)+dsx;
2195 term := x0+temp div dy2;
2196 rem := temp mod dy2;
2197 if (rem = 0) then Dec(term);
2200 if (term > wx1) then term := wx1; // clip at right
2202 Inc(term); // draw last point
2203 //if (term = xd) then exit; // this is the only point, get out of here
2205 if (sty = -1) then yd := -yd;
2206 if (stx = -1) then begin xd := -xd; term := -term; end;
2209 // first move, to skip starting point
2210 // DON'T DO THIS! loop will take care of that
2214 result := forEachAtPoint(ax0, ay0, nil, tagmask, @ptag);
2215 if (result <> nil) then
2217 if assigned(cb) then
2219 if cb(result, ptag, ax0, ay0, ax0, ay0) then
2238 prevx := xptr^+minx;
2239 prevy := yptr^+miny;
2242 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
2245 if (xd = term) then exit;
2248 {$IF DEFINED(D2F_DEBUG)}
2249 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*mTileSize) and (yptr^ >= gh*mTileSize) then raise Exception.Create('raycaster internal error (0)');
2251 // DON'T DO THIS! loop will take care of that
2252 //lastGA := (yptr^ div tsize)*gw+(xptr^ div tsize);
2253 //ccidx := mGrid[lastGA];
2255 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2256 //if assigned(dbgRayTraceTileHitCB) then e_WriteLog('1:TRACING!', MSG_NOTIFY);
2259 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
2261 if mInQuery then raise Exception.Create('recursive queries aren''t supported');
2264 // increase query counter
2266 if (mLastQuery = 0) then
2268 // just in case of overflow
2270 for f := 0 to High(mProxies) do mProxies[f].mQueryMark := 0;
2274 {$IFDEF GRID_USE_ORTHO_ACCEL}
2275 // if this is strict horizontal/vertical trace, use optimized codepath
2276 if (ax0 = ax1) or (ay0 = ay1) then
2278 // horizontal trace: walk the whole tiles, calculating mindist once for each proxy in cell
2279 // stx < 0: going left, otherwise `stx` is > 0, and we're going right
2280 // vertical trace: walk the whole tiles, calculating mindist once for each proxy in cell
2281 // stx < 0: going up, otherwise `stx` is > 0, and we're going down
2282 hopt := (ay0 = ay1); // horizontal?
2283 if (stx < 0) then begin {wksign := -1;} wklen := -(term-xd); end else begin {wksign := 1;} wklen := term-xd; end;
2284 {$IF DEFINED(D2F_DEBUG)}
2285 if dbgShowTraceLog then e_LogWritefln('optimized htrace; wklen=%d', [wklen]);
2287 ga := (yptr^ div mTileSize)*gw+(xptr^ div mTileSize);
2288 // one of those will never change
2291 while (wklen > 0) do
2293 {$IF DEFINED(D2F_DEBUG)}
2294 if dbgShowTraceLog then e_LogWritefln(' htrace; ga=%d; x=%d, y=%d; y=%d; y=%d', [ga, xptr^+minx, yptr^+miny, y, ay0]);
2297 if (ga <> lastGA) then
2300 ccidx := mGrid[lastGA];
2301 // convert coords to map (to avoid ajdusting coords inside the loop)
2302 if hopt then x := xptr^+minx else y := yptr^+miny;
2303 while (ccidx <> -1) do
2305 cc := @mCells[ccidx];
2306 for f := 0 to GridCellBucketSize-1 do
2308 if (cc.bodies[f] = -1) then break;
2309 px := @mProxies[cc.bodies[f]];
2311 if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) and
2312 // constant coord should be inside
2313 ((hopt and (y >= px.y0) and (y <= px.y1)) or
2314 ((not hopt) and (x >= px.x0) and (x <= px.x1))) then
2316 px.mQueryMark := lq; // mark as processed
2317 // inside the proxy?
2318 if (hopt and (x > px.x0) and (x < px.x1)) or
2319 ((not hopt) and (y > px.y0) and (y < px.y1)) then
2322 if assigned(cb) then
2324 if cb(px.mObj, ptag, x, y, x, y) then
2335 distSq := distanceSq(ax0, ay0, x, y);
2336 {$IF DEFINED(D2F_DEBUG)}
2337 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]);
2339 if (distSq < lastDistSq) then
2350 // remember this hitpoint if it is nearer than an old one
2360 if (x < px.x1) then continue; // not on the right edge
2367 if (x > px.x0) then continue; // not on the left edge
2380 if (y < px.y1) then continue; // not on the bottom edge
2387 if (y > px.y0) then continue; // not on the top edge
2392 if assigned(cb) then
2394 if cb(px.mObj, ptag, x, y, prevx, prevy) then
2405 distSq := distanceSq(ax0, ay0, prevx, prevy);
2406 {$IF DEFINED(D2F_DEBUG)}
2407 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]);
2409 if (distSq < lastDistSq) then
2412 lastDistSq := distSq;
2423 if wasHit and not assigned(cb) then begin result := lastObj; mInQuery := false; exit; end;
2424 if assigned(cb) and cb(nil, 0, x, y, x, y) then begin result := lastObj; mInQuery := false; exit; end;
2426 // skip to next tile
2432 wkstep := ((xptr^ or (mTileSize-1))+1)-xptr^;
2433 {$IF DEFINED(D2F_DEBUG)}
2434 if dbgShowTraceLog then e_LogWritefln(' right step: wklen=%d; wkstep=%d', [wklen, wkstep]);
2436 if (wkstep >= wklen) then break;
2443 wkstep := xptr^-((xptr^ and (not (mTileSize-1)))-1);
2444 {$IF DEFINED(D2F_DEBUG)}
2445 if dbgShowTraceLog then e_LogWritefln(' left step: wklen=%d; wkstep=%d', [wklen, wkstep]);
2447 if (wkstep >= wklen) then break;
2457 wkstep := ((yptr^ or (mTileSize-1))+1)-yptr^;
2458 {$IF DEFINED(D2F_DEBUG)}
2459 if dbgShowTraceLog then e_LogWritefln(' down step: wklen=%d; wkstep=%d', [wklen, wkstep]);
2461 if (wkstep >= wklen) then break;
2468 wkstep := yptr^-((yptr^ and (not (mTileSize-1)))-1);
2469 {$IF DEFINED(D2F_DEBUG)}
2470 if dbgShowTraceLog then e_LogWritefln(' up step: wklen=%d; wkstep=%d', [wklen, wkstep]);
2472 if (wkstep >= wklen) then break;
2479 // we can travel less than one cell
2480 if wasHit and not assigned(cb) then result := lastObj else begin ex := ax1; ey := ay1; end;
2486 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2487 if assigned(dbgRayTraceTileHitCB) then dbgRayTraceTileHitCB((xptr^ div mTileSize*mTileSize)+minx, (yptr^ div mTileSize*mTileSize)+miny);
2490 //e_LogWritefln('*********************', []);
2493 while (xd <> term) do
2496 {$IF DEFINED(D2F_DEBUG)}
2497 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*mTileSize) and (yptr^ >= gh*mTileSize) then raise Exception.Create('raycaster internal error (0)');
2500 ga := (yptr^ div mTileSize)*gw+(xptr^ div mTileSize);
2501 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2502 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);
2504 if (ga <> lastGA) then
2507 {$IF DEFINED(D2F_DEBUG)}
2508 if assigned(dbgRayTraceTileHitCB) then dbgRayTraceTileHitCB((xptr^ div mTileSize*mTileSize)+minx, (yptr^ div mTileSize*mTileSize)+miny);
2510 if (ccidx <> -1) then
2512 // signal cell completion
2513 if assigned(cb) then
2515 if cb(nil, 0, xptr^+minx, yptr^+miny, prevx, prevy) then begin result := lastObj; mInQuery := false; exit; end;
2525 ccidx := mGrid[lastGA];
2527 // has something to process in this tile?
2528 if (ccidx <> -1) then
2532 hasUntried := false; // this will be set to `true` if we have some proxies we still want to process at the next step
2533 // convert coords to map (to avoid ajdusting coords inside the loop)
2536 // process cell list
2537 while (curci <> -1) do
2539 cc := @mCells[curci];
2540 for f := 0 to GridCellBucketSize-1 do
2542 if (cc.bodies[f] = -1) then break;
2543 px := @mProxies[cc.bodies[f]];
2545 if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
2547 // can we process this proxy?
2548 if (x >= px.mX) and (y >= px.mY) and (x < px.mX+px.mWidth) and (y < px.mY+px.mHeight) then
2550 px.mQueryMark := lq; // mark as processed
2551 if assigned(cb) then
2553 if cb(px.mObj, ptag, x, y, prevx, prevy) then
2564 // remember this hitpoint if it is nearer than an old one
2565 distSq := distanceSq(ax0, ay0, prevx, prevy);
2566 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2567 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);
2569 if (distSq < lastDistSq) then
2572 lastDistSq := distSq;
2581 // this is possibly interesting proxy, set "has more to check" flag
2589 // still has something interesting in this cell?
2590 if not hasUntried then
2592 // nope, don't process this cell anymore; signal cell completion
2594 if assigned(cb) then
2596 if cb(nil, 0, x, y, prevx, prevy) then begin result := lastObj; mInQuery := false; exit; end;
2606 if (ccidx = -1) then
2608 // move to cell edge, as we have nothing to trace here anymore
2609 if (stx < 0) then xdist := xd and (not (mTileSize-1)) else xdist := xd or (mTileSize-1);
2610 if (sty < 0) then ydist := yd and (not (mTileSize-1)) else ydist := yd or (mTileSize-1);
2611 //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]);
2612 while (xd <> xdist) and (yd <> ydist) do
2616 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
2617 //e_LogWritefln(' xd=%d; yd=%d', [xd, yd]);
2618 if (xd = term) then break;
2620 //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]);
2621 if (xd = term) then break;
2623 //putPixel(xptr^, yptr^);
2625 prevx := xptr^+minx;
2626 prevy := yptr^+miny;
2627 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
2630 // we can travel less than one cell
2631 if wasHit and not assigned(cb) then
2637 ex := ax1; // why not?
2638 ey := ay1; // why not?