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 GridTileSize
= 32; // must be power of two!
33 TBodyProxyId
= Integer;
35 generic TBodyGridBase
<ITP
> = class(TObject
)
37 type TGridQueryCB
= function (obj
: ITP
; tag
: Integer): Boolean is nested
; // return `true` to stop
38 type TGridRayQueryCB
= function (obj
: ITP
; tag
: Integer; x
, y
, prevx
, prevy
: Integer): Boolean is nested
; // return `true` to stop
39 type TCellQueryCB
= procedure (x
, y
: Integer) is nested
; // top-left cell corner coords
41 const TagDisabled
= $40000000;
42 const TagFullMask
= $3fffffff;
46 GridCellBucketSize
= 8; // WARNING! can't be less than 2!
50 PBodyProxyRec
= ^TBodyProxyRec
;
51 TBodyProxyRec
= record
53 mX
, mY
, mWidth
, mHeight
: Integer; // aabb
54 mQueryMark
: LongWord; // was this object visited at this query?
56 mTag
: Integer; // `TagDisabled` set: disabled ;-)
57 nextLink
: TBodyProxyId
; // next free or nothing
60 procedure setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
62 function getTag (): Integer; inline;
63 procedure setTag (v
: Integer); inline;
65 function getEnabled (): Boolean; inline;
66 procedure setEnabled (v
: Boolean); inline;
68 function getX1 (): Integer; inline;
69 function getY1 (): Integer; inline;
72 property x
: Integer read mX
;
73 property y
: Integer read mY
;
74 property width
: Integer read mWidth
;
75 property height
: Integer read mHeight
;
76 property tag
: Integer read getTag write setTag
;
77 property enabled
: Boolean read getEnabled write setEnabled
;
78 property obj
: ITP read mObj
;
80 property x0
: Integer read mX
;
81 property y0
: Integer read mY
;
82 property x1
: Integer read getX1
;
83 property y1
: Integer read getY1
;
88 PGridCell
= ^TGridCell
;
90 bodies
: array [0..GridCellBucketSize
-1] of Integer; // -1: end of list
91 next
: Integer; // in this cell; index in mCells
94 TCellArray
= array of TGridCell
;
96 TGridInternalCB
= function (grida
: Integer; bodyId
: TBodyProxyId
): Boolean of object; // return `true` to stop
100 const mTileSize
= GridTileSize
;
101 type TGetProxyFn
= function (pxidx
: Integer): PBodyProxyRec
of object;
104 const tileSize
= mTileSize
;
107 TAtPointEnumerator
= record
110 curidx
, curbki
: Integer;
113 constructor Create (acells
: TCellArray
; aidx
: Integer; agetpx
: TGetProxyFn
);
114 function MoveNext (): Boolean; inline;
115 function getCurrent (): PBodyProxyRec
; inline;
116 property Current
: PBodyProxyRec read getCurrent
;
120 mMinX
, mMinY
: Integer; // so grids can start at any origin
121 mWidth
, mHeight
: Integer; // in tiles
122 mGrid
: array of Integer; // mWidth*mHeight, index in mCells
123 mCells
: TCellArray
; // cell pool
124 mFreeCell
: Integer; // first free cell index or -1
125 mLastQuery
: LongWord;
127 mProxies
: array of TBodyProxyRec
;
128 mProxyFree
: TBodyProxyId
; // free
129 mProxyCount
: Integer; // currently used
130 mProxyMaxCount
: Integer;
134 dbgShowTraceLog
: Boolean;
135 {$IF DEFINED(D2F_DEBUG)}
136 dbgRayTraceTileHitCB
: TCellQueryCB
;
140 function allocCell (): Integer;
141 procedure freeCell (idx
: Integer); // `next` is simply overwritten
143 function allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
144 procedure freeProxy (body
: TBodyProxyId
);
146 function forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
148 function inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
149 function remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
151 function getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
152 procedure setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
154 function getGridWidthPx (): Integer; inline;
155 function getGridHeightPx (): Integer; inline;
157 function getProxyById (idx
: TBodyProxyId
): PBodyProxyRec
; inline;
160 constructor Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
161 destructor Destroy (); override;
163 function insertBody (aObj
: ITP
; ax
, ay
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
164 procedure removeBody (body
: TBodyProxyId
); // WARNING! this WILL destroy proxy!
166 procedure moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
167 procedure resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
168 procedure moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
170 function insideGrid (x
, y
: Integer): Boolean; inline;
172 // `false` if `body` is surely invalid
173 function getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
174 function getBodyWH (body
: TBodyProxyId
; out rw
, rh
: Integer): Boolean; inline;
175 function getBodyDims (body
: TBodyProxyId
; out rx
, ry
, rw
, rh
: Integer): Boolean; inline;
177 //WARNING: don't modify grid while any query is in progress (no checks are made!)
178 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
179 // no callback: return `true` on the first hit
180 function forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
182 //WARNING: don't modify grid while any query is in progress (no checks are made!)
183 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
184 // no callback: return object on the first hit or nil
185 function forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; exittag
: PInteger=nil): ITP
;
187 function atCellInPoint (x
, y
: Integer): TAtPointEnumerator
;
189 //WARNING: don't modify grid while any query is in progress (no checks are made!)
190 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
191 // cb with `(nil)` will be called before processing new tile
192 // no callback: return object of the nearest hit or nil
193 // if `inverted` is true, trace will register bodies *exluding* tagmask
194 //WARNING: don't change tags in callbacks here!
195 function traceRayOld (const x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
; overload
;
196 function traceRayOld (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
198 //WARNING: don't modify grid while any query is in progress (no checks are made!)
199 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
200 // cb with `(nil)` will be called before processing new tile
201 // no callback: return object of the nearest hit or nil
202 // if `inverted` is true, trace will register bodies *exluding* tagmask
203 // `cb` is used unconvetionally here: if it returns `false`, tracer will ignore the object
204 //WARNING: don't change tags in callbacks here!
205 function traceRay (const x0
, y0
, x1
, y1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
; overload
;
206 function traceRay (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
208 // return `false` if we're still inside at the end
209 // line should be either strict horizontal, or strict vertical, otherwise an exception will be thrown
210 // `true`: endpoint will point at the last "inside" pixel
211 // `false`: endpoint will be (ax1, ay1)
212 //WARNING: don't change tags in callbacks here!
213 function traceOrthoRayWhileIn (out ex
, ey
: Integer; ax0
, ay0
, ax1
, ay1
: Integer; tagmask
: Integer=-1): Boolean;
215 //WARNING: don't modify grid while any query is in progress (no checks are made!)
216 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
217 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
218 //WARNING: don't change tags in callbacks here!
219 function forEachAlongLine (ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
221 // trace box with the given velocity; return object hit (if any)
222 // `cb` is used unconvetionally here: if it returns `false`, tracer will ignore the object
223 //WARNING: don't change tags in callbacks here!
224 function traceBox (out ex
, ey
: Integer; const ax0
, ay0
, aw
, ah
: Integer; const dx
, dy
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
227 procedure forEachBodyCell (body
: TBodyProxyId
; cb
: TCellQueryCB
);
228 function forEachInCell (x
, y
: Integer; cb
: TGridQueryCB
): ITP
;
229 procedure dumpStats ();
232 //WARNING! no sanity checks!
233 property proxyEnabled
[pid
: TBodyProxyId
]: Boolean read getProxyEnabled write setProxyEnabled
;
235 property gridX0
: Integer read mMinX
;
236 property gridY0
: Integer read mMinY
;
237 property gridWidth
: Integer read getGridWidthPx
; // in pixels
238 property gridHeight
: Integer read getGridHeightPx
; // in pixels
240 property proxy
[idx
: TBodyProxyId
]: PBodyProxyRec read getProxyById
;
245 // common structure for all line tracers
248 const TileSize
= GridTileSize
;
251 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
252 stx
, sty
: Integer; // "steps" for x and y axes
253 stleft
: Integer; // "steps left"
254 err
, errinc
, errmax
: Integer;
255 xd
, yd
: Integer; // current coord
259 // call `setyp` after this
260 constructor Create (minx
, miny
, maxx
, maxy
: Integer);
262 procedure setClip (minx
, miny
, maxx
, maxy
: Integer); inline;
264 // this will use `w[xy][01]` to clip coords
265 // return `false` if the whole line was clipped away
266 // on `true`, you should process first point, and go on
267 function setup (x0
, y0
, x1
, y1
: Integer): Boolean;
269 // call this *after* doing a step
270 // WARNING! if you will do a step when this returns `true`, you will fall into limbo
271 function done (): Boolean; inline;
273 // as you will prolly call `done()` after doing a step anyway, this will do it for you
274 // move to next point, return `true` when the line is complete (i.e. you should stop)
275 function step (): Boolean; inline;
277 // move to next tile; return `true` if the line is complete (and walker state is undefined then)
278 function stepToNextTile (): Boolean; inline;
280 procedure getXY (out ox
, oy
: Integer); inline;
284 property x
: Integer read xd
;
285 property y
: Integer read yd
;
289 // you are not supposed to understand this
290 // returns `true` if there is an intersection, and enter coords
291 // enter coords will be equal to (x0, y0) if starting point is inside the box
292 // if result is `false`, `inx` and `iny` are undefined
293 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
295 // sweep two AABB's to see if and when they are overlapping
296 // returns `true` if collision was detected (but boxes doesn't overlap)
297 // u1 and u1 has no sense if no collision was detected
298 // u0 = normalized time of first collision (i.e. collision starts at myMove*u0)
299 // u1 = normalized time of second collision (i.e. collision stops after myMove*u1)
300 // hitedge for `it`: 0: top; 1: right; 2: bottom; 3: left
301 // enter/exit coords will form non-intersecting configuration (i.e. will be before/after the actual collision)
302 function sweepAABB (mex0
, mey0
, mew
, meh
: Integer; medx
, medy
: Integer; itx0
, ity0
, itw
, ith
: Integer;
303 u0
: PSingle=nil; hitedge
: PInteger=nil; u1
: PSingle=nil): Boolean;
305 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline;
307 procedure swapInt (var a
: Integer; var b
: Integer); inline;
308 //function minInt (a, b: Integer): Integer; inline;
309 //function maxInt (a, b: Integer): Integer; inline;
315 SysUtils
, e_log
, g_console
, utils
;
318 // ////////////////////////////////////////////////////////////////////////// //
319 procedure swapInt (var a
: Integer; var b
: Integer); inline; var t
: Integer; begin t
:= a
; a
:= b
; b
:= t
; end;
320 //procedure swapInt (var a: Integer; var b: Integer); inline; begin a := a xor b; b := b xor a; a := a xor b; end;
321 //function minInt (a, b: Integer): Integer; inline; begin if (a < b) then result := a else result := b; end;
322 //function maxInt (a, b: Integer): Integer; inline; begin if (a > b) then result := a else result := b; end;
324 function distanceSq (x0
, y0
, x1
, y1
: Integer): Integer; inline; begin result
:= (x1
-x0
)*(x1
-x0
)+(y1
-y0
)*(y1
-y0
); end;
327 // ////////////////////////////////////////////////////////////////////////// //
328 function clipLine (var x0
, y0
, x1
, y1
: Single; xmin
, ymin
, xmax
, ymax
: Single): Boolean;
336 function xcode (x
, y
: Single): Byte; inline;
339 if (x
< xmin
) then result
:= result
or Left
else if (x
> xmax
) then result
:= result
or Right
;
340 if (y
< ymin
) then result
:= result
or Bottom
else if (y
> ymax
) then result
:= result
or Top
;
344 outcode0
, outcode1
, outcodeOut
: Byte;
348 result
:= false; // accept
349 outcode0
:= xcode(x0
, y0
);
350 outcode1
:= xcode(x1
, y1
);
353 if ((outcode0
or outcode1
) = 0) then begin result
:= true; exit
; end; // accept
354 if ((outcode0
and outcode1
) <> 0) then exit
; // reject
355 outcodeOut
:= outcode0
;
356 if (outcodeOut
= 0) then outcodeOut
:= outcode1
;
357 if ((outcodeOut
and Top
) <> 0) then
359 x
:= x0
+(x1
-x0
)*(ymax
-y0
)/(y1
-y0
);
362 else if ((outcodeOut
and Bottom
) <> 0) then
364 x
:= x0
+(x1
-x0
)*(ymin
-y0
)/(y1
-y0
);
367 else if ((outcodeOut
and Right
) <> 0) then
369 y
:= y0
+(y1
-y0
)*(xmax
-x0
)/(x1
-x0
);
372 else if ((outcodeOut
and Left
) <> 0) then
374 y
:= y0
+(y1
-y0
)*(xmin
-x0
)/(x1
-x0
);
377 if (outcodeOut
= outcode0
) then
381 outcode0
:= xcode(x0
, y0
);
387 outcode1
:= xcode(x1
, y1
);
393 // returns `true` if there is an intersection, and enter coords
394 // enter coords will be equal to (x0, y0) if starting point is inside the box
395 // if result is `false`, `inx` and `iny` are undefined
396 function lineAABBIntersects (x0
, y0
, x1
, y1
: Integer; bx
, by
, bw
, bh
: Integer; out inx
, iny
: Integer): Boolean;
398 sx0
, sy0
, sx1
, sy1
: Single;
403 if (bw
< 1) or (bh
< 1) then exit
;
404 if (x0
>= bx
) and (y0
>= by
) and (x0
< bx
+bw
) and (y0
< by
+bh
) then begin result
:= true; exit
; end;
405 sx0
:= x0
; sy0
:= y0
;
406 sx1
:= x1
; sy1
:= y1
;
407 result
:= clipLine(sx0
, sy0
, sx1
, sy1
, bx
, by
, bx
+bw
-1, by
+bh
-1);
413 if (inx
= bx
) then Dec(inx
) else if (inx
= bx
+bw
-1) then Inc(inx
);
414 if (iny
= by
) then Dec(iny
) else if (iny
= by
+bh
-1) then Inc(iny
);
424 // ////////////////////////////////////////////////////////////////////////// //
425 constructor TLineWalker
.Create (minx
, miny
, maxx
, maxy
: Integer);
427 setClip(minx
, miny
, maxx
, maxy
);
430 procedure TLineWalker
.setClip (minx
, miny
, maxx
, maxy
: Integer); inline;
439 function TLineWalker
.setup (x0
, y0
, x1
, y1
: Integer): Boolean;
441 sx0
, sy0
, sx1
, sy1
: Single;
443 if (wx1
< wx0
) or (wy1
< wy0
) then begin stleft
:= 0; xd
:= x0
; yd
:= y0
; result
:= false; exit
; end;
445 if (x0
>= wx0
) and (y0
>= wy0
) and (x0
<= wx1
) and (y0
<= wy1
) and
446 (x1
>= wx0
) and (y1
>= wy0
) and (x1
<= wx1
) and (y1
<= wy1
) then
452 sx0
:= x0
; sy0
:= y0
;
453 sx1
:= x1
; sy1
:= y1
;
454 result
:= clipLine(sx0
, sy0
, sx1
, sy1
, wx0
, wy0
, wx1
, wy1
);
455 if not result
then begin stleft
:= 0; xd
:= x0
; yd
:= y0
; exit
; end;
456 x0
:= trunc(sx0
); y0
:= trunc(sy0
);
457 x1
:= trunc(sx1
); y1
:= trunc(sy1
);
460 // check for ortho lines
465 stleft
:= abs(x1
-x0
)+1;
466 if (x0
< x1
) then stx
:= 1 else stx
:= -1;
469 errmax
:= 10; // anything that is greater than zero
471 else if (x0
= x1
) then
475 stleft
:= abs(y1
-y0
)+1;
477 if (y0
< y1
) then sty
:= 1 else sty
:= -1;
479 errmax
:= 10; // anything that is greater than zero
484 if (abs(x1
-x0
) >= abs(y1
-y0
)) then
488 stleft
:= abs(x1
-x0
)+1;
489 errinc
:= abs(y1
-y0
)+1;
495 stleft
:= abs(y1
-y0
)+1;
496 errinc
:= abs(x1
-x0
)+1;
498 if (x0
< x1
) then stx
:= 1 else stx
:= -1;
499 if (y0
< y1
) then sty
:= 1 else sty
:= -1;
507 function TLineWalker
.done (): Boolean; inline; begin result
:= (stleft
<= 0); end;
510 function TLineWalker
.step (): Boolean; inline;
516 if (err
>= 0) then begin err
-= errmax
; yd
+= sty
; end;
522 if (err
>= 0) then begin err
-= errmax
; xd
+= stx
; end;
525 result
:= (stleft
<= 0);
529 function TLineWalker
.stepToNextTile (): Boolean; inline;
532 xwalk
, ywalk
, wklen
: Integer; // to the respective edges
537 if (stleft
< 2) then begin result
:= true; exit
; end; // max one pixel left, nothing to do
539 // strictly horizontal?
546 ex
:= (xd
and (not (TileSize
-1)))-1;
552 ex
:= (xd
or (TileSize
-1))+1;
555 result
:= (stleft
<= 0);
560 // strictly vertical?
567 ey
:= (yd
and (not (TileSize
-1)))-1;
572 // yd: to bottom edge
573 ey
:= (yd
or (TileSize
-1))+1;
576 result
:= (stleft
<= 0);
586 ex
:= (xd
and (not (TileSize
-1)))-1;
591 ex
:= (xd
or (TileSize
-1))+1;
598 ey
:= (yd
and (not (TileSize
-1)))-1;
603 ey
:= (yd
or (TileSize
-1))+1;
608 while (xd <> ex) and (yd <> ey) do
614 if (err >= 0) then begin err -= errmax; yd += sty; end;
620 if (err >= 0) then begin err -= errmax; xd += stx; end;
623 if (stleft < 1) then begin result := true; exit; end;
627 if (xwalk
<= ywalk
) then wklen
:= xwalk
else wklen
:= ywalk
;
630 // in which dir we want to walk?
632 if (stleft
<= 0) then begin result
:= true; exit
; end;
636 for f
:= 1 to wklen
do
639 if (err
>= 0) then begin err
-= errmax
; yd
+= sty
; end;
645 for f
:= 1 to wklen
do
648 if (err
>= 0) then begin err
-= errmax
; xd
+= stx
; end;
651 // check for walk completion
652 if (xd
= ex
) or (yd
= ey
) then exit
;
657 procedure TLineWalker
.getXY (out ox
, oy
: Integer); inline; begin ox
:= xd
; oy
:= yd
; end;
660 // ////////////////////////////////////////////////////////////////////////// //
661 function sweepAABB (mex0
, mey0
, mew
, meh
: Integer; medx
, medy
: Integer; itx0
, ity0
, itw
, ith
: Integer;
662 u0
: PSingle=nil; hitedge
: PInteger=nil; u1
: PSingle=nil): Boolean;
666 function axisOverlap (me0
, me1
, it0
, it1
, d
, he0
, he1
: Integer): Boolean; inline;
674 if (d
>= 0) then exit
; // oops, no hit
676 if (t
> tin
) then begin tin
:= t
; hitedge
^ := he1
; end;
678 else if (it1
< me0
) then
680 if (d
<= 0) then exit
; // oops, no hit
682 if (t
> tin
) then begin tin
:= t
; hitedge
^ := he0
; end;
685 if (d
< 0) and (it1
> me0
) then
688 if (t
< tout
) then tout
:= t
;
690 else if (d
> 0) and (me1
> it0
) then
693 if (t
< tout
) then tout
:= t
;
700 mex1
, mey1
, itx1
, ity1
, vx
, vy
: Integer;
704 if (u0
<> nil) then u0
^ := -1.0;
705 if (u1
<> nil) then u1
^ := -1.0;
706 if (hitedge
= nil) then hitedge
:= @htt
else hitedge
^ := -1;
708 if (mew
< 1) or (meh
< 1) or (itw
< 1) or (ith
< 1) then exit
;
715 // check if they are overlapping right now (SAT)
716 //if (mex1 >= itx0) and (mex0 <= itx1) and (mey1 >= ity0) and (mey0 <= ity1) then begin result := true; exit; end;
718 if (medx
= 0) and (medy
= 0) then exit
; // both boxes are sationary
720 // treat b as stationary, so invert v to get relative velocity
727 if not axisOverlap(mex0
, mex1
, itx0
, itx1
, vx
, 1, 3) then exit
;
728 if not axisOverlap(mey0
, mey1
, ity0
, ity1
, vy
, 2, 0) then exit
;
730 if (u0
<> nil) then u0
^ := tin
;
731 if (u1
<> nil) then u1
^ := tout
;
733 if (tin
<= tout
) and (tin
>= 0.0) and (tin
<= 1.0) then
740 // ////////////////////////////////////////////////////////////////////////// //
741 procedure TBodyGridBase
.TBodyProxyRec
.setup (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer);
754 function TBodyGridBase
.TBodyProxyRec
.getTag (): Integer; inline;
756 result
:= mTag
and TagFullMask
;
759 procedure TBodyGridBase
.TBodyProxyRec
.setTag (v
: Integer); inline;
761 mTag
:= (mTag
and TagDisabled
) or (v
and TagFullMask
);
764 function TBodyGridBase
.TBodyProxyRec
.getEnabled (): Boolean; inline;
766 result
:= ((mTag
and TagDisabled
) = 0);
769 procedure TBodyGridBase
.TBodyProxyRec
.setEnabled (v
: Boolean); inline;
771 if v
then mTag
:= mTag
and (not TagDisabled
) else mTag
:= mTag
or TagDisabled
;
774 function TBodyGridBase
.TBodyProxyRec
.getX1 (): Integer; inline;
776 result
:= mX
+mWidth
-1;
779 function TBodyGridBase
.TBodyProxyRec
.getY1 (): Integer; inline;
781 result
:= mY
+mHeight
-1;
785 // ////////////////////////////////////////////////////////////////////////// //
786 constructor TBodyGridBase
.TAtPointEnumerator
.Create (acells
: TCellArray
; aidx
: Integer; agetpx
: TGetProxyFn
);
795 function TBodyGridBase
.TAtPointEnumerator
.MoveNext (): Boolean; inline;
797 while (curidx
<> -1) do
799 while (curbki
< GridCellBucketSize
) do
802 if (mCells
[curidx
].bodies
[curbki
] = -1) then break
;
806 curidx
:= mCells
[curidx
].next
;
813 function TBodyGridBase
.TAtPointEnumerator
.getCurrent (): PBodyProxyRec
; inline;
815 result
:= getpx(mCells
[curidx
].bodies
[curbki
]);
819 // ////////////////////////////////////////////////////////////////////////// //
820 constructor TBodyGridBase
.Create (aMinPixX
, aMinPixY
, aPixWidth
, aPixHeight
: Integer{; aTileSize: Integer=GridDefaultTileSize});
824 dbgShowTraceLog
:= false;
825 {$IF DEFINED(D2F_DEBUG)}
826 dbgRayTraceTileHitCB
:= nil;
829 if aTileSize < 1 then aTileSize := 1;
830 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
831 mTileSize := aTileSize;
833 if (aPixWidth
< mTileSize
) then aPixWidth
:= mTileSize
;
834 if (aPixHeight
< mTileSize
) then aPixHeight
:= mTileSize
;
837 mWidth
:= (aPixWidth
+mTileSize
-1) div mTileSize
;
838 mHeight
:= (aPixHeight
+mTileSize
-1) div mTileSize
;
839 SetLength(mGrid
, mWidth
*mHeight
);
840 SetLength(mCells
, mWidth
*mHeight
);
841 SetLength(mProxies
, 8192);
844 for idx
:= 0 to High(mCells
) do
846 mCells
[idx
].bodies
[0] := -1;
847 mCells
[idx
].bodies
[GridCellBucketSize
-1] := -1; // "has free room" flag
848 mCells
[idx
].next
:= idx
+1;
850 mCells
[High(mCells
)].next
:= -1; // last cell
852 for idx
:= 0 to High(mGrid
) do mGrid
[idx
] := -1;
854 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
855 mProxies
[High(mProxies
)].nextLink
:= -1;
861 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth
, mHeight
, mTileSize
, mWidth
*mTileSize
, mHeight
*mTileSize
]), MSG_NOTIFY
);
865 destructor TBodyGridBase
.Destroy ();
874 // ////////////////////////////////////////////////////////////////////////// //
875 procedure TBodyGridBase
.dumpStats ();
877 idx
, mcb
, ccidx
, cnt
: Integer;
880 for idx
:= 0 to High(mGrid
) do
887 ccidx
:= mCells
[ccidx
].next
;
889 if (mcb
< cnt
) then mcb
:= cnt
;
891 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
);
895 procedure TBodyGridBase
.forEachBodyCell (body
: TBodyProxyId
; cb
: TCellQueryCB
);
897 g
, f
, ccidx
: Integer;
900 if (body
< 0) or (body
> High(mProxies
)) or not assigned(cb
) then exit
;
901 for g
:= 0 to High(mGrid
) do
904 while (ccidx
<> -1) do
906 cc
:= @mCells
[ccidx
];
907 for f
:= 0 to GridCellBucketSize
-1 do
909 if (cc
.bodies
[f
] = -1) then break
;
910 if (cc
.bodies
[f
] = body
) then cb((g
mod mWidth
)*mTileSize
+mMinX
, (g
div mWidth
)*mTileSize
+mMinY
);
919 function TBodyGridBase
.forEachInCell (x
, y
: Integer; cb
: TGridQueryCB
): ITP
;
924 result
:= Default(ITP
);
925 if not assigned(cb
) then exit
;
928 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
> mHeight
*mTileSize
) then exit
;
929 ccidx
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
930 while (ccidx
<> -1) do
932 cc
:= @mCells
[ccidx
];
933 for f
:= 0 to GridCellBucketSize
-1 do
935 if (cc
.bodies
[f
] = -1) then break
;
936 if cb(mProxies
[cc
.bodies
[f
]].mObj
, mProxies
[cc
.bodies
[f
]].mTag
) then begin result
:= mProxies
[cc
.bodies
[f
]].mObj
; exit
; end;
944 // ////////////////////////////////////////////////////////////////////////// //
945 function TBodyGridBase
.getGridWidthPx (): Integer; inline; begin result
:= mWidth
*mTileSize
; end;
946 function TBodyGridBase
.getGridHeightPx (): Integer; inline; begin result
:= mHeight
*mTileSize
; end;
949 function TBodyGridBase
.insideGrid (x
, y
: Integer): Boolean; inline;
954 result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
*mTileSize
) and (y
< mHeight
*mTileSize
);
958 function TBodyGridBase
.getBodyXY (body
: TBodyProxyId
; out rx
, ry
: Integer): Boolean; inline;
960 if (body
>= 0) and (body
< Length(mProxies
)) then
962 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; end;
974 function TBodyGridBase
.getBodyWH (body
: TBodyProxyId
; out rw
, rh
: Integer): Boolean; inline;
976 if (body
>= 0) and (body
< Length(mProxies
)) then
978 with mProxies
[body
] do begin rw
:= mWidth
; rh
:= mHeight
; end;
990 function TBodyGridBase
.getBodyDims (body
: TBodyProxyId
; out rx
, ry
, rw
, rh
: Integer): Boolean; inline;
992 if (body
>= 0) and (body
< Length(mProxies
)) then
994 with mProxies
[body
] do begin rx
:= mX
; ry
:= mY
; rw
:= mWidth
; rh
:= mHeight
; end;
1009 // ////////////////////////////////////////////////////////////////////////// //
1010 function TBodyGridBase
.getProxyEnabled (pid
: TBodyProxyId
): Boolean; inline;
1012 if (pid
>= 0) and (pid
< Length(mProxies
)) then result
:= ((mProxies
[pid
].mTag
and TagDisabled
) = 0) else result
:= false;
1016 procedure TBodyGridBase
.setProxyEnabled (pid
: TBodyProxyId
; val
: Boolean); inline;
1018 if (pid
>= 0) and (pid
< Length(mProxies
)) then
1022 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
and not TagDisabled
;
1026 mProxies
[pid
].mTag
:= mProxies
[pid
].mTag
or TagDisabled
;
1032 function TBodyGridBase
.getProxyById (idx
: TBodyProxyId
): PBodyProxyRec
; inline;
1034 if (idx
>= 0) and (idx
< Length(mProxies
)) then result
:= @mProxies
[idx
] else result
:= nil;
1038 // ////////////////////////////////////////////////////////////////////////// //
1039 function TBodyGridBase
.allocCell (): Integer;
1044 if (mFreeCell
< 0) then
1046 // no free cells, want more
1047 mFreeCell
:= Length(mCells
);
1048 SetLength(mCells
, mFreeCell
+32768); // arbitrary number
1049 for idx
:= mFreeCell
to High(mCells
) do
1051 mCells
[idx
].bodies
[0] := -1;
1052 mCells
[idx
].bodies
[GridCellBucketSize
-1] := -1; // 'has free room' flag
1053 mCells
[idx
].next
:= idx
+1;
1055 mCells
[High(mCells
)].next
:= -1; // last cell
1057 result
:= mFreeCell
;
1058 pc
:= @mCells
[result
];
1059 mFreeCell
:= pc
.next
;
1062 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
1066 procedure TBodyGridBase
.freeCell (idx
: Integer);
1068 if (idx
>= 0) and (idx
< Length(mCells
)) then
1073 bodies
[GridCellBucketSize
-1] := -1; // 'has free room' flag
1082 // ////////////////////////////////////////////////////////////////////////// //
1083 function TBodyGridBase
.allocProxy (aX
, aY
, aWidth
, aHeight
: Integer; aObj
: ITP
; aTag
: Integer): TBodyProxyId
;
1088 if (mProxyFree
= -1) then
1090 // no free proxies, resize list
1091 olen
:= Length(mProxies
);
1092 SetLength(mProxies
, olen
+8192); // arbitrary number
1093 for idx
:= olen
to High(mProxies
) do mProxies
[idx
].nextLink
:= idx
+1;
1094 mProxies
[High(mProxies
)].nextLink
:= -1;
1097 // get one from list
1098 result
:= mProxyFree
;
1099 px
:= @mProxies
[result
];
1100 mProxyFree
:= px
.nextLink
;
1101 px
.setup(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
1106 if (mProxyMaxCount
< mProxyCount
) then mProxyMaxCount
:= mProxyCount
;
1109 procedure TBodyGridBase
.freeProxy (body
: TBodyProxyId
);
1111 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1112 if (mProxyCount
= 0) then raise Exception
.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
1114 mProxies
[body
].mObj
:= nil;
1115 mProxies
[body
].nextLink
:= mProxyFree
;
1121 // ////////////////////////////////////////////////////////////////////////// //
1122 function TBodyGridBase
.forGridRect (x
, y
, w
, h
: Integer; cb
: TGridInternalCB
; bodyId
: TBodyProxyId
): Boolean;
1129 if (w
< 1) or (h
< 1) or not assigned(cb
) then exit
;
1134 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
1137 if (x
>= gw
*mTileSize
) or (y
>= gh
*mTileSize
) then exit
;
1138 ex
:= (x
+w
-1) div mTileSize
;
1139 ey
:= (y
+h
-1) div mTileSize
;
1140 x
:= x
div mTileSize
;
1141 y
:= y
div mTileSize
;
1143 if (x
< 0) then x
:= 0 else if (x
>= gw
) then x
:= gw
-1;
1144 if (y
< 0) then y
:= 0 else if (y
>= gh
) then y
:= gh
-1;
1145 if (ex
< 0) then ex
:= 0 else if (ex
>= gw
) then ex
:= gw
-1;
1146 if (ey
< 0) then ey
:= 0 else if (ey
>= gh
) then ey
:= gh
-1;
1147 if (x
> ex
) or (y
> ey
) then exit
; // just in case
1149 for gy
:= y
to ey
do
1151 for gx
:= x
to ex
do
1153 result
:= cb(gy
*gw
+gx
, bodyId
);
1154 if result
then exit
;
1160 // ////////////////////////////////////////////////////////////////////////// //
1161 function TBodyGridBase
.inserter (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
1168 result
:= false; // never stop
1169 // add body to the given grid cell
1173 {$IF DEFINED(D2F_DEBUG)}
1175 while (ccidx
<> -1) do
1177 pi
:= @mCells
[ccidx
];
1178 for f
:= 0 to GridCellBucketSize
-1 do
1180 if (pi
.bodies
[f
] = -1) then break
;
1181 if (pi
.bodies
[f
] = bodyId
) then raise Exception
.Create('trying to insert already inserted proxy');
1187 while (ccidx
<> -1) do
1189 pi
:= @mCells
[ccidx
];
1190 // check "has room" flag
1191 if (pi
.bodies
[GridCellBucketSize
-1] = -1) then
1194 for f
:= 0 to GridCellBucketSize
-1 do
1196 if (pi
.bodies
[f
] = -1) then
1198 pi
.bodies
[f
] := bodyId
;
1199 if (f
+1 < GridCellBucketSize
) then pi
.bodies
[f
+1] := -1;
1203 raise Exception
.Create('internal error in grid inserter');
1205 // no room, go to next cell in list (if there is any)
1208 // no room in cells, add new cell to list
1210 // either no room, or no cell at all
1211 ccidx
:= allocCell();
1212 pi
:= @mCells
[ccidx
];
1213 pi
.bodies
[0] := bodyId
;
1216 mGrid
[grida
] := ccidx
;
1220 // assume that we cannot have one object added to bucket twice
1221 function TBodyGridBase
.remover (grida
: Integer; bodyId
: TBodyProxyId
): Boolean;
1224 pidx
, ccidx
: Integer;
1227 result
:= false; // never stop
1228 // find and remove cell
1229 pidx
:= -1; // previous cell index
1230 ccidx
:= mGrid
[grida
]; // current cell index
1231 while (ccidx
<> -1) do
1233 pc
:= @mCells
[ccidx
];
1234 for f
:= 0 to GridCellBucketSize
-1 do
1236 if (pc
.bodies
[f
] = bodyId
) then
1239 if (f
= 0) and (pc
.bodies
[1] = -1) then
1241 // this cell contains no elements, remove it
1242 if (pidx
= -1) then mGrid
[grida
] := pc
.next
else mCells
[pidx
].next
:= pc
.next
;
1246 // remove element from bucket
1247 for c
:= f
to GridCellBucketSize
-2 do
1249 pc
.bodies
[c
] := pc
.bodies
[c
+1];
1250 if (pc
.bodies
[c
] = -1) then break
;
1252 pc
.bodies
[GridCellBucketSize
-1] := -1; // "has free room" flag
1262 // ////////////////////////////////////////////////////////////////////////// //
1263 function TBodyGridBase
.insertBody (aObj
: ITP
; aX
, aY
, aWidth
, aHeight
: Integer; aTag
: Integer=-1): TBodyProxyId
;
1265 aTag
:= aTag
and TagFullMask
;
1266 result
:= allocProxy(aX
, aY
, aWidth
, aHeight
, aObj
, aTag
);
1267 //insertInternal(result);
1268 forGridRect(aX
, aY
, aWidth
, aHeight
, inserter
, result
);
1272 procedure TBodyGridBase
.removeBody (body
: TBodyProxyId
);
1276 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1277 px
:= @mProxies
[body
];
1278 //removeInternal(body);
1279 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
1284 // ////////////////////////////////////////////////////////////////////////// //
1285 procedure TBodyGridBase
.moveResizeBody (body
: TBodyProxyId
; nx
, ny
, nw
, nh
: Integer);
1288 x0
, y0
, w
, h
: Integer;
1290 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1291 px
:= @mProxies
[body
];
1296 {$IF DEFINED(D2F_DEBUG_MOVER)}
1297 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
);
1299 if (nx
= x0
) and (ny
= y0
) and (nw
= w
) and (nh
= h
) then exit
;
1305 // did any corner crossed tile boundary?
1306 if (x0
div mTileSize
<> nx
div mTileSize
) or
1307 (y0
div mTileSize
<> ny
div mTileSize
) or
1308 ((x0
+w
-1) div mTileSize
<> (nx
+nw
-1) div mTileSize
) or
1309 ((y0
+h
-1) div mTileSize
<> (ny
+nh
-1) div mTileSize
) then
1311 //writeln('moveResizeBody: cell occupation changed! old=(', x0, ',', y0, ')-(', x0+w-1, ',', y0+h-1, '); new=(', nx, ',', ny, ')-(', nx+nw-1, ',', ny+nh-1, ')');
1312 //removeInternal(body);
1313 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
1318 //insertInternal(body);
1319 forGridRect(px
.mX
, px
.mY
, nw
, nh
, inserter
, body
);
1331 //TODO: optimize for horizontal/vertical moves
1332 procedure TBodyGridBase
.moveBody (body
: TBodyProxyId
; nx
, ny
: Integer);
1336 ogx0
, ogx1
, ogy0
, ogy1
: Integer; // old grid rect
1337 ngx0
, ngx1
, ngy0
, ngy1
: Integer; // new grid rect
1342 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1343 // check if tile coords was changed
1344 px
:= @mProxies
[body
];
1347 if (nx
= x0
) and (ny
= y0
) then exit
;
1353 // check for heavy work
1356 ogx0
:= x0
div mTileSize
;
1357 ogy0
:= y0
div mTileSize
;
1358 ngx0
:= nx
div mTileSize
;
1359 ngy0
:= ny
div mTileSize
;
1360 ogx1
:= (x0
+pw
-1) div mTileSize
;
1361 ogy1
:= (y0
+ph
-1) div mTileSize
;
1362 ngx1
:= (nx
+pw
-1) div mTileSize
;
1363 ngy1
:= (ny
+ph
-1) div mTileSize
;
1364 {$IF DEFINED(D2F_DEBUG_MOVER)}
1365 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
);
1367 if (ogx0
<> ngx0
) or (ogy0
<> ngy0
) or (ogx1
<> ngx1
) or (ogy1
<> ngy1
) then
1369 // crossed tile boundary, do heavy work
1372 // cycle with old rect, remove body where it is necessary
1373 // optimized for horizontal moves
1374 {$IF DEFINED(D2F_DEBUG_MOVER)}
1375 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
);
1377 // remove stale marks
1378 if not ((ogy0
>= gh
) or (ogy1
< 0)) and
1379 not ((ogx0
>= gw
) or (ogx1
< 0)) then
1381 if (ogx0
< 0) then ogx0
:= 0;
1382 if (ogy0
< 0) then ogy0
:= 0;
1383 if (ogx1
> gw
-1) then ogx1
:= gw
-1;
1384 if (ogy1
> gh
-1) then ogy1
:= gh
-1;
1385 {$IF DEFINED(D2F_DEBUG_MOVER)}
1386 e_WriteLog(Format(' norm og:(%d,%d)-(%d,%d)', [ogx0
, ogy0
, ogx1
, ogy1
]), MSG_NOTIFY
);
1388 for gx
:= ogx0
to ogx1
do
1390 if (gx
< ngx0
) or (gx
> ngx1
) then
1392 // this column is completely outside of new rect
1393 for gy
:= ogy0
to ogy1
do
1395 {$IF DEFINED(D2F_DEBUG_MOVER)}
1396 e_WriteLog(Format(' remove0:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1398 remover(gy
*gw
+gx
, body
);
1404 for gy
:= ogy0
to ogy1
do
1406 if (gy
< ngy0
) or (gy
> ngy1
) then
1408 {$IF DEFINED(D2F_DEBUG_MOVER)}
1409 e_WriteLog(Format(' remove1:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1411 remover(gy
*gw
+gx
, body
);
1417 // cycle with new rect, add body where it is necessary
1418 if not ((ngy0
>= gh
) or (ngy1
< 0)) and
1419 not ((ngx0
>= gw
) or (ngx1
< 0)) then
1421 if (ngx0
< 0) then ngx0
:= 0;
1422 if (ngy0
< 0) then ngy0
:= 0;
1423 if (ngx1
> gw
-1) then ngx1
:= gw
-1;
1424 if (ngy1
> gh
-1) then ngy1
:= gh
-1;
1425 {$IF DEFINED(D2F_DEBUG_MOVER)}
1426 e_WriteLog(Format(' norm ng:(%d,%d)-(%d,%d)', [ngx0
, ngy0
, ngx1
, ngy1
]), MSG_NOTIFY
);
1428 for gx
:= ngx0
to ngx1
do
1430 if (gx
< ogx0
) or (gx
> ogx1
) then
1432 // this column is completely outside of old rect
1433 for gy
:= ngy0
to ngy1
do
1435 {$IF DEFINED(D2F_DEBUG_MOVER)}
1436 e_WriteLog(Format(' insert0:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1438 inserter(gy
*gw
+gx
, body
);
1444 for gy
:= ngy0
to ngy1
do
1446 if (gy
< ogy0
) or (gy
> ogy1
) then
1448 {$IF DEFINED(D2F_DEBUG_MOVER)}
1449 e_WriteLog(Format(' insert1:(%d,%d)', [gx
, gy
]), MSG_NOTIFY
);
1451 inserter(gy
*gw
+gx
, body
);
1461 {$IF DEFINED(D2F_DEBUG_MOVER)}
1462 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
);
1465 // update coordinates
1471 procedure TBodyGridBase
.resizeBody (body
: TBodyProxyId
; nw
, nh
: Integer);
1474 x0
, y0
, w
, h
: Integer;
1476 if (body
< 0) or (body
> High(mProxies
)) then exit
; // just in case
1477 // check if tile coords was changed
1478 px
:= @mProxies
[body
];
1483 {$IF DEFINED(D2F_DEBUG_MOVER)}
1484 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
);
1486 if ((x0
+w
-1) div mTileSize
<> (x0
+nw
-1) div mTileSize
) or
1487 ((y0
+h
-1) div mTileSize
<> (y0
+nh
-1) div mTileSize
) then
1489 // crossed tile boundary, do heavy work
1490 //removeInternal(body);
1491 forGridRect(px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, remover
, body
);
1494 //insertInternal(body);
1495 forGridRect(px
.mX
, px
.mY
, nw
, nh
, inserter
, body
);
1499 // nothing to do with the grid, just fix size
1506 // ////////////////////////////////////////////////////////////////////////// //
1507 function TBodyGridBase
.atCellInPoint (x
, y
: Integer): TAtPointEnumerator
;
1509 ccidx
: Integer = -1;
1513 if (x
>= 0) and (y
>= 0) and (x
< mWidth
*mTileSize
) and (y
< mHeight
*mTileSize
) then ccidx
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
1514 result
:= TAtPointEnumerator
.Create(mCells
, ccidx
, getProxyById
);
1518 // ////////////////////////////////////////////////////////////////////////// //
1519 // no callback: return `true` on the first hit
1520 function TBodyGridBase
.forEachAtPoint (x
, y
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; exittag
: PInteger=nil): ITP
;
1523 idx
, curci
: Integer;
1524 cc
: PGridCell
= nil;
1529 result
:= Default(ITP
);
1530 if (exittag
<> nil) then exittag
^ := 0;
1531 tagmask
:= tagmask
and TagFullMask
;
1532 if (tagmask
= 0) then exit
;
1534 {$IF DEFINED(D2F_DEBUG_XXQ)}
1535 if (assigned(cb
)) then e_WriteLog(Format('0: grid pointquery: (%d,%d)', [x
, y
]), MSG_NOTIFY
);
1538 // make coords (0,0)-based
1541 if (x
< 0) or (y
< 0) or (x
>= mWidth
*mTileSize
) or (y
>= mHeight
*mTileSize
) then exit
;
1543 curci
:= mGrid
[(y
div mTileSize
)*mWidth
+(x
div mTileSize
)];
1545 {$IF DEFINED(D2F_DEBUG_XXQ)}
1546 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
);
1553 // increase query counter
1555 if (mLastQuery
= 0) then
1557 // just in case of overflow
1559 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
1563 {$IF DEFINED(D2F_DEBUG_XXQ)}
1564 if (assigned(cb
)) then e_WriteLog(Format('2: grid pointquery: (%d,%d); lq=%u', [x
, y
, lq
]), MSG_NOTIFY
);
1567 while (curci
<> -1) do
1569 {$IF DEFINED(D2F_DEBUG_XXQ)}
1570 if (assigned(cb
)) then e_WriteLog(Format(' cell #%d', [curci
]), MSG_NOTIFY
);
1572 cc
:= @mCells
[curci
];
1573 for f
:= 0 to GridCellBucketSize
-1 do
1575 if (cc
.bodies
[f
] = -1) then break
;
1576 px
:= @mProxies
[cc
.bodies
[f
]];
1577 {$IF DEFINED(D2F_DEBUG_XXQ)}
1578 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
);
1580 // shit. has to do it this way, so i can change tag in callback
1581 if (px
.mQueryMark
<> lq
) then
1583 px
.mQueryMark
:= lq
;
1585 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and
1586 (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
1588 if assigned(cb
) then
1590 if cb(px
.mObj
, ptag
) then
1593 if (exittag
<> nil) then exittag
^ := ptag
;
1600 if (exittag
<> nil) then exittag
^ := ptag
;
1611 // ////////////////////////////////////////////////////////////////////////// //
1612 // no callback: return `true` on the first hit
1613 function TBodyGridBase
.forEachInAABB (x
, y
, w
, h
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; allowDisabled
: Boolean=false): ITP
;
1617 sx
, sy
, ex
, ey
: Integer;
1620 cc
: PGridCell
= nil;
1627 result
:= Default(ITP
);
1628 if (w
< 1) or (h
< 1) then exit
;
1629 tagmask
:= tagmask
and TagFullMask
;
1630 if (tagmask
= 0) then exit
;
1642 if (x
+w
<= 0) or (y
+h
<= 0) then exit
;
1643 if (x
>= gw
*mTileSize
) or (y
>= gh
*mTileSize
) then exit
;
1645 sx
:= x
div mTileSize
;
1646 sy
:= y
div mTileSize
;
1647 ex
:= (x
+w
-1) div mTileSize
;
1648 ey
:= (y
+h
-1) div mTileSize
;
1651 if (sx
< 0) then sx
:= 0 else if (sx
>= gw
) then sx
:= gw
-1;
1652 if (sy
< 0) then sy
:= 0 else if (sy
>= gh
) then sy
:= gh
-1;
1653 if (ex
< 0) then ex
:= 0 else if (ex
>= gw
) then ex
:= gw
-1;
1654 if (ey
< 0) then ey
:= 0 else if (ey
>= gh
) then ey
:= gh
-1;
1655 if (sx
> ex
) or (sy
> ey
) then exit
; // just in case
1657 // has something to do
1658 if mInQuery
then raise Exception
.Create('recursive queries aren''t supported');
1661 // increase query counter
1663 if (mLastQuery
= 0) then
1665 // just in case of overflow
1667 for idx
:= 0 to High(mProxies
) do mProxies
[idx
].mQueryMark
:= 0;
1669 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
1673 for gy
:= sy
to ey
do
1675 for gx
:= sx
to ex
do
1678 curci
:= mGrid
[gy
*gw
+gx
];
1679 while (curci
<> -1) do
1681 cc
:= @mCells
[curci
];
1682 for f
:= 0 to GridCellBucketSize
-1 do
1684 if (cc
.bodies
[f
] = -1) then break
;
1685 px
:= @mProxies
[cc
.bodies
[f
]];
1686 // shit! has to do it this way, so i can change tag in callback
1687 if (px
.mQueryMark
= lq
) then continue
;
1688 px
.mQueryMark
:= lq
;
1690 if (not allowDisabled
) and ((ptag
and TagDisabled
) <> 0) then continue
;
1691 if ((ptag
and tagmask
) = 0) then continue
;
1692 if (x0
>= px
.mX
+px
.mWidth
) or (y0
>= px
.mY
+px
.mHeight
) then continue
;
1693 if (x0
+w
<= px
.mX
) or (y0
+h
<= px
.mY
) then continue
;
1694 if assigned(cb
) then
1696 if cb(px
.mObj
, ptag
) then begin result
:= px
.mObj
; mInQuery
:= false; exit
; end;
1714 // ////////////////////////////////////////////////////////////////////////// //
1715 function TBodyGridBase
.forEachAlongLine (ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1; log
: Boolean=false): ITP
;
1723 gw
, gh
, minx
, miny
: Integer;
1727 //px0, py0, px1, py1: Integer;
1730 result
:= Default(ITP
);
1731 tagmask
:= tagmask
and TagFullMask
;
1732 if (tagmask
= 0) or not assigned(cb
) then exit
;
1739 // make query coords (0,0)-based
1745 lw
:= TLineWalker
.Create(0, 0, gw
*mTileSize
-1, gh
*mTileSize
-1);
1746 if not lw
.setup(x0
, y0
, x1
, y1
) then exit
; // out of screen
1748 if mInQuery
then raise Exception
.Create('recursive queries aren''t supported');
1751 // increase query counter
1753 if (mLastQuery
= 0) then
1755 // just in case of overflow
1757 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1764 ccidx
:= mGrid
[(cy
div mTileSize
)*gw
+(cx
div mTileSize
)];
1766 while (ccidx
<> -1) do
1768 cc
:= @mCells
[ccidx
];
1769 for f
:= 0 to GridCellBucketSize
-1 do
1771 if (cc
.bodies
[f
] = -1) then break
;
1772 px
:= @mProxies
[cc
.bodies
[f
]];
1774 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1776 px
.mQueryMark
:= lq
; // mark as processed
1777 if cb(px
.mObj
, ptag
) then
1788 // done processing cells, move to next tile
1789 until lw
.stepToNextTile();
1795 // ////////////////////////////////////////////////////////////////////////// //
1796 // trace box with the given velocity; return object hit (if any)
1797 // `cb` is used unconvetionally here: if it returns `false`, tracer will ignore the object
1798 function TBodyGridBase
.traceBox (out ex
, ey
: Integer; const ax0
, ay0
, aw
, ah
: Integer; const dx
, dy
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
1806 minu0
: Single = 100000.0;
1808 cx0
, cy0
, cx1
, cy1
: Integer;
1809 hitpx
: PBodyProxyRec
= nil;
1811 result
:= Default(ITP
);
1814 if (aw
< 1) or (ah
< 1) then exit
;
1816 cx0
:= nmin(ax0
, ax0
+dx
);
1817 cy0
:= nmin(ay0
, ay0
+dy
);
1818 cx1
:= nmax(ax0
+aw
-1, ax0
+aw
-1+dx
);
1819 cy1
:= nmax(ay0
+ah
-1, ay0
+ah
-1+dy
);
1821 cx0
-= mMinX
; cy0
-= mMinY
;
1822 cx1
-= mMinX
; cy1
-= mMinY
;
1824 if (cx1
< 0) or (cy1
< 0) or (cx0
>= mWidth
*mTileSize
) or (cy0
>= mHeight
*mTileSize
) then exit
;
1826 if (cx0
< 0) then cx0
:= 0;
1827 if (cy0
< 0) then cy0
:= 0;
1828 if (cx1
>= mWidth
*mTileSize
) then cx1
:= mWidth
*mTileSize
-1;
1829 if (cy1
>= mHeight
*mTileSize
) then cy1
:= mHeight
*mTileSize
-1;
1831 if (cx0
> cx1
) or (cy0
> cy1
) then exit
;
1833 if mInQuery
then raise Exception
.Create('recursive queries aren''t supported');
1836 // increase query counter
1838 if (mLastQuery
= 0) then
1840 // just in case of overflow
1842 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
1846 for gy
:= cy0
div mTileSize
to cy1
div mTileSize
do
1848 for gx
:= cx0
div mTileSize
to cx1
div mTileSize
do
1850 ccidx
:= mGrid
[gy
*mWidth
+gx
];
1851 while (ccidx
<> -1) do
1853 cc
:= @mCells
[ccidx
];
1854 for f
:= 0 to GridCellBucketSize
-1 do
1856 if (cc
.bodies
[f
] = -1) then break
;
1857 px
:= @mProxies
[cc
.bodies
[f
]];
1859 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
1861 px
.mQueryMark
:= lq
; // mark as processed
1862 if assigned(cb
) then
1864 if not cb(px
.mObj
, ptag
) then continue
;
1866 if not sweepAABB(ax0
, ay0
, aw
, ah
, dx
, dy
, px
.mX
, px
.mY
, px
.mWidth
, px
.mHeight
, @u0
) then continue
;
1867 if (minu0
> u0
) then
1888 if (minu0
<= 1.0) then
1890 ex
:= ax0
+round(dx
*minu0
);
1891 ey
:= ay0
+round(dy
*minu0
);
1892 // just in case, compensate for floating point inexactness
1893 if (ex
>= hitpx
.mX
) and (ey
>= hitpx
.mY
) and (ex
< hitpx
.mX
+hitpx
.mWidth
) and (ey
< hitpx
.mY
+hitpx
.mHeight
) then
1895 ex
:= ax0
+trunc(dx
*minu0
);
1896 ey
:= ay0
+trunc(dy
*minu0
);
1904 // ////////////////////////////////////////////////////////////////////////// //
1905 {.$DEFINE D2F_DEBUG_OTR}
1906 function TBodyGridBase
.traceOrthoRayWhileIn (out ex
, ey
: Integer; ax0
, ay0
, ax1
, ay1
: Integer; tagmask
: Integer=-1): Boolean;
1912 minx
, miny
: Integer;
1914 x0
, y0
, x1
, y1
: Integer;
1915 celly0
, celly1
: Integer;
1917 filled
: array[0..mTileSize
-1] of Byte;
1918 {$IF DEFINED(D2F_DEBUG_OTR)}
1925 if not ((ax0
= ax1
) or (ay0
= ay1
)) then raise Exception
.Create('orthoray is not orthogonal');
1927 tagmask
:= tagmask
and TagFullMask
;
1928 if (tagmask
= 0) then exit
;
1930 if (forEachAtPoint(ax0
, ay0
, nil, tagmask
) = nil) then exit
;
1935 // offset query coords to (0,0)-based
1943 if (x0
< 0) or (x0
>= mWidth
*mTileSize
) then exit
; // oops
1948 if (y1
< 0) or (y0
>= mHeight
*mTileSize
) then exit
;
1949 //if (ay0 < 0) then ay0 := 0;
1950 if (y0
< 0) then exit
;
1951 if (y1
>= mHeight
*mTileSize
) then y1
:= mHeight
*mTileSize
-1;
1957 if (y0
< 0) or (y1
>= mHeight
*mTileSize
) then exit
;
1958 //if (ay1 < 0) then ay1 := 0;
1959 if (y1
< 0) then exit
;
1960 if (y0
>= mHeight
*mTileSize
) then y0
:= mHeight
*mTileSize
-1;
1966 ccidx
:= mGrid
[(y0
div mTileSize
)*mWidth
+(x0
div mTileSize
)];
1967 FillChar(filled
, sizeof(filled
), 0);
1968 celly0
:= y0
and (not (mTileSize
-1));
1969 celly1
:= celly0
+mTileSize
-1;
1970 while (ccidx
<> -1) do
1972 cc
:= @mCells
[ccidx
];
1973 for f
:= 0 to GridCellBucketSize
-1 do
1975 if (cc
.bodies
[f
] = -1) then break
;
1976 px
:= @mProxies
[cc
.bodies
[f
]];
1978 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and
1979 (ax0
>= px
.x0
) and (ax0
<= px
.x1
) then
1981 // bound c0 and c1 to cell
1982 c0
:= nclamp(px
.y0
-miny
, celly0
, celly1
);
1983 c1
:= nclamp(px
.y1
-miny
, celly0
, celly1
);
1985 {$IF DEFINED(D2F_DEBUG_OTR)}
1986 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
)]);
1989 FillChar(filled
[c0
-celly0
], c1
-c0
+1, 1);
1995 {$IF DEFINED(D2F_DEBUG_OTR)}
1996 s
:= formatstrf(' x=%s; ay0=%s; ay1=%s; y0=%s; celly0=%s; celly1=%s; dy=%s; [', [ax0
, ay0
, ay1
, y0
, celly0
, celly1
, dy
]);
1997 for f
:= 0 to High(filled
) do if (filled
[f
] <> 0) then s
+= '1' else s
+= '0';
2001 // now go till we hit cell boundary or empty space
2005 while (y0
>= celly0
) and (filled
[y0
-celly0
] <> 0) do
2007 {$IF DEFINED(D2F_DEBUG_OTR)}
2008 e_LogWritefln(' filled: cdy=%s; y0=%s; celly0=%s; ay0=%s; ay1=%s', [y0
-celly0
, y0
, celly0
, ay0
, ay1
]);
2013 {$IF DEFINED(D2F_DEBUG_OTR)}
2014 e_LogWritefln(' span done: cdy=%s; y0=%s; celly0=%s; ay0=%s; ay1=%s', [y0
-celly0
, y0
, celly0
, ay0
, ay1
]);
2016 if (ay0
<= ay1
) then begin ey
:= ay1
; result
:= false; exit
; end;
2017 if (y0
>= celly0
) then begin ey
:= ay0
+1; {assert(forEachAtPoint(ex, ey, nil, tagmask) <> nil);} result
:= true; exit
; end;
2022 while (y0
<= celly1
) and (filled
[y0
-celly0
] <> 0) do begin Inc(y0
); Inc(ay0
); end;
2023 if (ay0
>= ay1
) then begin ey
:= ay1
; result
:= false; exit
; end;
2024 if (y0
<= celly1
) then begin ey
:= ay0
-1; result
:= true; exit
; end;
2036 // ////////////////////////////////////////////////////////////////////////// //
2037 function TBodyGridBase
.traceRay (const x0
, y0
, x1
, y1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
2041 result
:= traceRay(ex
, ey
, x0
, y0
, x1
, y1
, cb
, tagmask
);
2045 // no callback: return `true` on the nearest hit
2046 // you are not supposed to understand this
2047 function TBodyGridBase
.traceRay (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridQueryCB
; tagmask
: Integer=-1): ITP
;
2055 gw
, gh
, minx
, miny
: Integer;
2059 px0
, py0
, px1
, py1
: Integer;
2060 lastDistSq
, distSq
, hx
, hy
: Integer;
2061 firstCell
: Boolean = true;
2064 result
:= Default(ITP
);
2065 tagmask
:= tagmask
and TagFullMask
;
2066 if (tagmask
= 0) then exit
;
2073 // make query coords (0,0)-based
2079 lw
:= TLineWalker
.Create(0, 0, gw
*mTileSize
-1, gh
*mTileSize
-1);
2080 if not lw
.setup(x0
, y0
, x1
, y1
) then exit
; // out of screen
2082 lastDistSq
:= distanceSq(ax0
, ay0
, ax1
, ay1
)+1;
2084 {$IF DEFINED(D2F_DEBUG)}
2085 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln('*** traceRay: (%s,%s)-(%s,%s)', [x0, y0, x1, y1]);
2088 if mInQuery
then raise Exception
.Create('recursive queries aren''t supported');
2091 // increase query counter
2093 if (mLastQuery
= 0) then
2095 // just in case of overflow
2097 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
2103 {$IF DEFINED(D2F_DEBUG)}
2104 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB(cx
+mMinX
, cy
+mMinY
);
2107 ccidx
:= mGrid
[(cy
div mTileSize
)*gw
+(cx
div mTileSize
)];
2110 while (ccidx
<> -1) do
2112 cc
:= @mCells
[ccidx
];
2113 for f
:= 0 to GridCellBucketSize
-1 do
2115 if (cc
.bodies
[f
] = -1) then break
;
2116 px
:= @mProxies
[cc
.bodies
[f
]];
2118 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
2120 px
.mQueryMark
:= lq
; // mark as processed
2121 if assigned(cb
) then
2123 if not cb(px
.mObj
, ptag
) then continue
;
2125 // get adjusted proxy coords
2128 px1
:= px0
+px
.mWidth
-1;
2129 py1
:= py0
+px
.mHeight
-1;
2130 {$IF DEFINED(D2F_DEBUG)}
2131 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln(' cxy=(%s,%s); pan=(%s,%s)-(%s,%s)', [cx, cy, px0, py0, px1, py1]);
2134 if firstCell
and (x0
>= px0
) and (y0
>= py0
) and (x0
<= px1
) and (y0
<= py1
) then
2141 {$IF DEFINED(D2F_DEBUG)}
2142 if assigned(dbgRayTraceTileHitCB
) then e_LogWriteln(' INSIDE!');
2146 // do line-vs-aabb test
2147 if lineAABBIntersects(x0
, y0
, x1
, y1
, px0
, py0
, px1
-px0
+1, py1
-py0
+1, hx
, hy
) then
2150 distSq
:= distanceSq(x0
, y0
, hx
, hy
);
2151 {$IF DEFINED(D2F_DEBUG)}
2152 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln(' hit=(%s,%s); distSq=%s; lastDistSq=%s', [hx, hy, distSq, lastDistSq]);
2154 if (distSq
< lastDistSq
) then
2156 lastDistSq
:= distSq
;
2168 // done processing cells; exit if we registered a hit
2169 // next cells can't have better candidates, obviously
2170 if wasHit
then begin mInQuery
:= false; exit
; end;
2172 // move to next tile
2173 until lw
.stepToNextTile();
2179 // ////////////////////////////////////////////////////////////////////////// //
2180 // no callback: return `true` on the nearest hit
2181 function TBodyGridBase
.traceRayOld (const x0
, y0
, x1
, y1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
2185 result
:= traceRayOld(ex
, ey
, x0
, y0
, x1
, y1
, cb
, tagmask
);
2189 // no callback: return `true` on the nearest hit
2190 // you are not supposed to understand this
2191 function TBodyGridBase
.traceRayOld (out ex
, ey
: Integer; const ax0
, ay0
, ax1
, ay1
: Integer; cb
: TGridRayQueryCB
; tagmask
: Integer=-1): ITP
;
2193 wx0
, wy0
, wx1
, wy1
: Integer; // window coordinates
2194 stx
, sty
: Integer; // "steps" for x and y axes
2195 dsx
, dsy
: Integer; // "lengthes" for x and y axes
2196 dx2
, dy2
: Integer; // "double lengthes" for x and y axes
2197 xd
, yd
: Integer; // current coord
2198 e
: Integer; // "error" (as in bresenham algo)
2201 xptr
, yptr
: PInteger;
2204 prevx
, prevy
: Integer;
2205 lastDistSq
: Integer;
2206 ccidx
, curci
: Integer;
2207 hasUntried
: Boolean;
2208 lastGA
: Integer = -1;
2211 wasHit
: Boolean = false;
2212 gw
, gh
, minx
, miny
, maxx
, maxy
: Integer;
2216 f
, ptag
, distSq
: Integer;
2217 x0
, y0
, x1
, y1
: Integer;
2218 //swapped: Boolean = false; // true: xd is yd, and vice versa
2219 // horizontal walker
2220 {$IFDEF GRID_USE_ORTHO_ACCEL}
2221 wklen
, wkstep
: Integer;
2226 xdist
, ydist
: Integer;
2228 result
:= Default(ITP
);
2229 lastObj
:= Default(ITP
);
2230 tagmask
:= tagmask
and TagFullMask
;
2231 ex
:= ax1
; // why not?
2232 ey
:= ay1
; // why not?
2233 if (tagmask
= 0) then exit
;
2235 if (ax0
= ax1
) and (ay0
= ay1
) then
2237 result
:= forEachAtPoint(ax0
, ay0
, nil, tagmask
, @ptag
);
2238 if (result
<> nil) then
2240 if assigned(cb
) and not cb(result
, ptag
, ax0
, ay0
, ax0
, ay0
) then result
:= Default(ITP
);
2245 lastDistSq
:= distanceSq(ax0
, ay0
, ax1
, ay1
)+1;
2251 maxx
:= gw
*mTileSize
-1;
2252 maxy
:= gh
*mTileSize
-1;
2254 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2255 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
);
2263 // offset query coords to (0,0)-based
2278 // from left to right
2279 if (x0
> wx1
) or (x1
< wx0
) then exit
; // out of screen
2280 stx
:= 1; // going right
2284 // from right to left
2285 if (x1
> wx1
) or (x0
< wx0
) then exit
; // out of screen
2286 stx
:= -1; // going left
2297 // from top to bottom
2298 if (y0
> wy1
) or (y1
< wy0
) then exit
; // out of screen
2299 sty
:= 1; // going down
2303 // from bottom to top
2304 if (y1
> wy1
) or (y0
< wy0
) then exit
; // out of screen
2305 sty
:= -1; // going up
2345 temp
:= dx2
*(wy0
-y0
)-dsx
;
2347 rem
:= temp
mod dy2
;
2348 if (xd
> wx1
) then exit
; // x is moved out of clipping rect, nothing to do
2349 if (xd
+1 >= wx0
) then
2353 //if (rem > 0) then begin Inc(xd); e += dy2; end; //BUGGY
2354 if (xd
< wx0
) then begin xd
+= 1; e
+= dy2
; end; //???
2359 if (not xfixed
) and (x0
< wx0
) then
2362 temp
:= dy2
*(wx0
-x0
);
2364 rem
:= temp
mod dx2
;
2365 if (yd
> wy1
) or (yd
= wy1
) and (rem
>= dsx
) then exit
;
2368 if (rem
>= dsx
) then begin Inc(yd
); e
-= dx2
; end;
2374 temp
:= dx2
*(wy1
-y0
)+dsx
;
2375 term
:= x0
+temp
div dy2
;
2376 rem
:= temp
mod dy2
;
2377 if (rem
= 0) then Dec(term
);
2380 if (term
> wx1
) then term
:= wx1
; // clip at right
2382 Inc(term
); // draw last point
2383 //if (term = xd) then exit; // this is the only point, get out of here
2385 if (sty
= -1) then yd
:= -yd
;
2386 if (stx
= -1) then begin xd
:= -xd
; term
:= -term
; end;
2389 // first move, to skip starting point
2390 // DON'T DO THIS! loop will take care of that
2394 result
:= forEachAtPoint(ax0
, ay0
, nil, tagmask
, @ptag
);
2395 if (result
<> nil) then
2397 if assigned(cb
) then
2399 if cb(result
, ptag
, ax0
, ay0
, ax0
, ay0
) then
2418 prevx
:= xptr
^+minx
;
2419 prevy
:= yptr
^+miny
;
2422 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
2425 if (xd = term) then exit;
2428 {$IF DEFINED(D2F_DEBUG)}
2429 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*mTileSize
) and (yptr
^ >= gh
*mTileSize
) then raise Exception
.Create('raycaster internal error (0)');
2431 // DON'T DO THIS! loop will take care of that
2432 //lastGA := (yptr^ div tsize)*gw+(xptr^ div tsize);
2433 //ccidx := mGrid[lastGA];
2435 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2436 //if assigned(dbgRayTraceTileHitCB) then e_WriteLog('1:TRACING!', MSG_NOTIFY);
2439 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
2441 if mInQuery
then raise Exception
.Create('recursive queries aren''t supported');
2444 // increase query counter
2446 if (mLastQuery
= 0) then
2448 // just in case of overflow
2450 for f
:= 0 to High(mProxies
) do mProxies
[f
].mQueryMark
:= 0;
2454 {$IFDEF GRID_USE_ORTHO_ACCEL}
2455 // if this is strict horizontal/vertical trace, use optimized codepath
2456 if (ax0
= ax1
) or (ay0
= ay1
) then
2458 // horizontal trace: walk the whole tiles, calculating mindist once for each proxy in cell
2459 // stx < 0: going left, otherwise `stx` is > 0, and we're going right
2460 // vertical trace: walk the whole tiles, calculating mindist once for each proxy in cell
2461 // stx < 0: going up, otherwise `stx` is > 0, and we're going down
2462 hopt
:= (ay0
= ay1
); // horizontal?
2463 if (stx
< 0) then begin {wksign := -1;} wklen
:= -(term
-xd
); end else begin {wksign := 1;} wklen
:= term
-xd
; end;
2464 {$IF DEFINED(D2F_DEBUG)}
2465 if dbgShowTraceLog
then e_LogWritefln('optimized htrace; wklen=%d', [wklen
]);
2467 ga
:= (yptr
^ div mTileSize
)*gw
+(xptr
^ div mTileSize
);
2468 // one of those will never change
2471 while (wklen
> 0) do
2473 {$IF DEFINED(D2F_DEBUG)}
2474 if dbgShowTraceLog
then e_LogWritefln(' htrace; ga=%d; x=%d, y=%d; y=%d; y=%d', [ga
, xptr
^+minx
, yptr
^+miny
, y
, ay0
]);
2477 if (ga
<> lastGA
) then
2480 ccidx
:= mGrid
[lastGA
];
2481 // convert coords to map (to avoid ajdusting coords inside the loop)
2482 if hopt
then x
:= xptr
^+minx
else y
:= yptr
^+miny
;
2483 while (ccidx
<> -1) do
2485 cc
:= @mCells
[ccidx
];
2486 for f
:= 0 to GridCellBucketSize
-1 do
2488 if (cc
.bodies
[f
] = -1) then break
;
2489 px
:= @mProxies
[cc
.bodies
[f
]];
2491 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) and
2492 // constant coord should be inside
2493 ((hopt
and (y
>= px
.y0
) and (y
<= px
.y1
)) or
2494 ((not hopt
) and (x
>= px
.x0
) and (x
<= px
.x1
))) then
2496 px
.mQueryMark
:= lq
; // mark as processed
2497 // inside the proxy?
2498 if (hopt
and (x
> px
.x0
) and (x
< px
.x1
)) or
2499 ((not hopt
) and (y
> px
.y0
) and (y
< px
.y1
)) then
2502 if assigned(cb
) then
2504 if cb(px
.mObj
, ptag
, x
, y
, x
, y
) then
2515 distSq
:= distanceSq(ax0
, ay0
, x
, y
);
2516 {$IF DEFINED(D2F_DEBUG)}
2517 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
]);
2519 if (distSq
< lastDistSq
) then
2530 // remember this hitpoint if it is nearer than an old one
2540 if (x
< px
.x1
) then continue
; // not on the right edge
2547 if (x
> px
.x0
) then continue
; // not on the left edge
2560 if (y
< px
.y1
) then continue
; // not on the bottom edge
2567 if (y
> px
.y0
) then continue
; // not on the top edge
2572 if assigned(cb
) then
2574 if cb(px
.mObj
, ptag
, x
, y
, prevx
, prevy
) then
2585 distSq
:= distanceSq(ax0
, ay0
, prevx
, prevy
);
2586 {$IF DEFINED(D2F_DEBUG)}
2587 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
]);
2589 if (distSq
< lastDistSq
) then
2592 lastDistSq
:= distSq
;
2603 if wasHit
and not assigned(cb
) then begin result
:= lastObj
; mInQuery
:= false; exit
; end;
2604 if assigned(cb
) and cb(nil, 0, x
, y
, x
, y
) then begin result
:= lastObj
; mInQuery
:= false; exit
; end;
2606 // skip to next tile
2612 wkstep
:= ((xptr
^ or (mTileSize
-1))+1)-xptr
^;
2613 {$IF DEFINED(D2F_DEBUG)}
2614 if dbgShowTraceLog
then e_LogWritefln(' right step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
2616 if (wkstep
>= wklen
) then break
;
2623 wkstep
:= xptr
^-((xptr
^ and (not (mTileSize
-1)))-1);
2624 {$IF DEFINED(D2F_DEBUG)}
2625 if dbgShowTraceLog
then e_LogWritefln(' left step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
2627 if (wkstep
>= wklen
) then break
;
2637 wkstep
:= ((yptr
^ or (mTileSize
-1))+1)-yptr
^;
2638 {$IF DEFINED(D2F_DEBUG)}
2639 if dbgShowTraceLog
then e_LogWritefln(' down step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
2641 if (wkstep
>= wklen
) then break
;
2648 wkstep
:= yptr
^-((yptr
^ and (not (mTileSize
-1)))-1);
2649 {$IF DEFINED(D2F_DEBUG)}
2650 if dbgShowTraceLog
then e_LogWritefln(' up step: wklen=%d; wkstep=%d', [wklen
, wkstep
]);
2652 if (wkstep
>= wklen
) then break
;
2659 // we can travel less than one cell
2660 if wasHit
and not assigned(cb
) then result
:= lastObj
else begin ex
:= ax1
; ey
:= ay1
; end;
2666 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2667 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB((xptr
^ div mTileSize
*mTileSize
)+minx
, (yptr
^ div mTileSize
*mTileSize
)+miny
);
2670 //e_LogWritefln('*********************', []);
2673 while (xd
<> term
) do
2676 {$IF DEFINED(D2F_DEBUG)}
2677 if (xptr
^ < 0) or (yptr
^ < 0) or (xptr
^ >= gw
*mTileSize
) and (yptr
^ >= gh
*mTileSize
) then raise Exception
.Create('raycaster internal error (0)');
2680 ga
:= (yptr
^ div mTileSize
)*gw
+(xptr
^ div mTileSize
);
2681 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2682 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
);
2684 if (ga
<> lastGA
) then
2687 {$IF DEFINED(D2F_DEBUG)}
2688 if assigned(dbgRayTraceTileHitCB
) then dbgRayTraceTileHitCB((xptr
^ div mTileSize
*mTileSize
)+minx
, (yptr
^ div mTileSize
*mTileSize
)+miny
);
2690 if (ccidx
<> -1) then
2692 // signal cell completion
2693 if assigned(cb
) then
2695 if cb(nil, 0, xptr
^+minx
, yptr
^+miny
, prevx
, prevy
) then begin result
:= lastObj
; mInQuery
:= false; exit
; end;
2705 ccidx
:= mGrid
[lastGA
];
2707 // has something to process in this tile?
2708 if (ccidx
<> -1) then
2712 hasUntried
:= false; // this will be set to `true` if we have some proxies we still want to process at the next step
2713 // convert coords to map (to avoid ajdusting coords inside the loop)
2716 // process cell list
2717 while (curci
<> -1) do
2719 cc
:= @mCells
[curci
];
2720 for f
:= 0 to GridCellBucketSize
-1 do
2722 if (cc
.bodies
[f
] = -1) then break
;
2723 px
:= @mProxies
[cc
.bodies
[f
]];
2725 if ((ptag
and TagDisabled
) = 0) and ((ptag
and tagmask
) <> 0) and (px
.mQueryMark
<> lq
) then
2727 // can we process this proxy?
2728 if (x
>= px
.mX
) and (y
>= px
.mY
) and (x
< px
.mX
+px
.mWidth
) and (y
< px
.mY
+px
.mHeight
) then
2730 px
.mQueryMark
:= lq
; // mark as processed
2731 if assigned(cb
) then
2733 if cb(px
.mObj
, ptag
, x
, y
, prevx
, prevy
) then
2744 // remember this hitpoint if it is nearer than an old one
2745 distSq
:= distanceSq(ax0
, ay0
, prevx
, prevy
);
2746 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
2747 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
);
2749 if (distSq
< lastDistSq
) then
2752 lastDistSq
:= distSq
;
2761 // this is possibly interesting proxy, set "has more to check" flag
2769 // still has something interesting in this cell?
2770 if not hasUntried
then
2772 // nope, don't process this cell anymore; signal cell completion
2774 if assigned(cb
) then
2776 if cb(nil, 0, x
, y
, prevx
, prevy
) then begin result
:= lastObj
; mInQuery
:= false; exit
; end;
2786 if (ccidx
= -1) then
2788 // move to cell edge, as we have nothing to trace here anymore
2789 if (stx
< 0) then xdist
:= xd
and (not (mTileSize
-1)) else xdist
:= xd
or (mTileSize
-1);
2790 if (sty
< 0) then ydist
:= yd
and (not (mTileSize
-1)) else ydist
:= yd
or (mTileSize
-1);
2791 //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]);
2792 while (xd
<> xdist
) and (yd
<> ydist
) do
2796 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
2797 //e_LogWritefln(' xd=%d; yd=%d', [xd, yd]);
2798 if (xd
= term
) then break
;
2800 //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]);
2801 if (xd
= term
) then break
;
2803 //putPixel(xptr^, yptr^);
2805 prevx
:= xptr
^+minx
;
2806 prevy
:= yptr
^+miny
;
2807 if (e
>= 0) then begin yd
+= sty
; e
-= dx2
; end else e
+= dy2
;
2810 // we can travel less than one cell
2811 if wasHit
and not assigned(cb
) then
2817 ex
:= ax1
; // why not?
2818 ey
:= ay1
; // why not?