1 (* Copyright (C) Doom 2D: Forever Developers
2 *
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.
7 *
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.
12 *
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/>.
15 *)
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}
22 {$ENDIF}
23 {.$DEFINE GRID_USE_ORTHO_ACCEL}
24 {$DEFINE LINEAABB2}
27 interface
29 uses
30 mempool;
32 (*
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.
41 *)
43 const
46 type
52 type
56 public
63 private
64 const
67 public
68 type
71 private
78 private
90 public
105 private
106 type
115 TGridInternalCB = function (grida: Integer; bodyId: TBodyProxyId): Boolean of object; // return `true` to stop
117 private
118 //mTileSize: Integer;
122 public
125 type
127 private
131 public
138 private
151 public
153 {$IF DEFINED(D2F_DEBUG)}
155 {$ENDIF}
157 private
177 public
178 constructor Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
181 function insertBody (aObj: ITP; ax, ay, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
190 // `false` if `body` is surely invalid
195 // return number of ITP thingys put into frame pool
196 // if `firstHit` is `true`, return on first hit (obviously)
197 function forEachInAABB (x, y, w, h: Integer; tagmask: Integer=-1; allowDisabled: Boolean=false; firstHit: Boolean=false): Integer;
199 // return number of ITP thingys put into frame pool
200 // if `firstHit` is `true`, return on first hit (obviously)
201 function forEachAtPoint (x, y: Integer; tagmask: Integer=-1; allowDisabled: Boolean=false; firstHit: Boolean=false): Integer;
205 // return object of the nearest hit or nil
207 function traceRay (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): ITP;
209 // return `false` if we're still inside at the end
210 // line should be either strict horizontal, or strict vertical, otherwise an exception will be thrown
211 // `true`: endpoint will point at the last "inside" pixel
212 // `false`: endpoint will be (ax1, ay1)
213 function traceOrthoRayWhileIn (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): Boolean;
215 // trace line along the grid, put all objects from passed cells into frame pool, in no particular order
216 // return number of ITP thingys put into frame pool
217 function forEachAlongLine (ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1; log: Boolean=false): Integer;
219 // trace box with the given velocity; return object hit (if any)
220 // `cb` is used unconvetionally here: if it returns `false`, tracer will ignore the object
221 //WARNING: don't change tags in callbacks here!
222 function traceBox (out ex, ey: Integer; const ax0, ay0, aw, ah: Integer; const dx, dy: Integer; tagmask: Integer=-1): ITP;
224 // debug
225 function forEachBodyCell (body: TBodyProxyId): Integer; // this puts `TGridCellCoord` into frame pool for each cell
229 public
230 //WARNING! no sanity checks!
242 type
243 // common structure for all line tracers
245 public
248 private
256 public
257 // call `setyp` after this
262 // this will use `w[xy][01]` to clip coords
263 // return `false` if the whole line was clipped away
264 // on `true`, you should process first point, and go on
267 // call this *after* doing a step
268 // WARNING! if you will do a step when this returns `true`, you will fall into limbo
271 // as you will prolly call `done()` after doing a step anyway, this will do it for you
272 // move to next point, return `true` when the line is complete (i.e. you should stop)
275 // move to next tile; return `true` if the line is complete (and walker state is undefined then)
280 public
281 // current coords
288 //function minInt (a, b: Integer): Integer; inline;
289 //function maxInt (a, b: Integer): Integer; inline;
292 implementation
294 uses
298 // ////////////////////////////////////////////////////////////////////////// //
299 procedure swapInt (var a: Integer; var b: Integer); inline; var t: Integer; begin t := a; a := b; b := t; end;
300 //procedure swapInt (var a: Integer; var b: Integer); inline; begin a := a xor b; b := b xor a; a := a xor b; end;
301 //function minInt (a, b: Integer): Integer; inline; begin if (a < b) then result := a else result := b; end;
302 //function maxInt (a, b: Integer): Integer; inline; begin if (a > b) then result := a else result := b; end;
305 // ////////////////////////////////////////////////////////////////////////// //
307 begin
312 begin
313 // clip rectange
321 var
323 begin
324 if (wx1 < wx0) or (wy1 < wy0) then begin stleft := 0; xd := x0; yd := y0; result := false; exit; end;
328 begin
330 end
331 else
332 begin
341 // check for ortho lines
343 begin
344 // horizontal
351 end
353 begin
354 // vertical
361 end
362 else
363 begin
364 // diagonal
366 begin
367 // horizontal
371 end
372 else
373 begin
374 // vertical
390 // true: done
392 begin
394 begin
398 end
399 else
400 begin
409 // true: done
411 var
415 begin
420 // strictly horizontal?
422 begin
423 // only xd
425 begin
426 // xd: to left edge
429 end
430 else
431 begin
432 // xd: to right edge
438 exit;
441 // strictly vertical?
443 begin
444 // only xd
446 begin
447 // yd: to top edge
450 end
451 else
452 begin
453 // yd: to bottom edge
459 exit;
462 // diagonal
464 // calculate xwalk
466 begin
469 end
470 else
471 begin
476 // calculate ywalk
478 begin
481 end
482 else
483 begin
488 {
489 while (xd <> ex) and (yd <> ey) do
490 begin
491 if horiz then
492 begin
493 xd += stx;
494 err += errinc;
495 if (err >= 0) then begin err -= errmax; yd += sty; end;
496 end
497 else
498 begin
499 yd += sty;
500 err += errinc;
501 if (err >= 0) then begin err -= errmax; xd += stx; end;
502 end;
503 Dec(stleft);
504 if (stleft < 1) then begin result := true; exit; end;
505 end;
506 }
510 begin
511 // in which dir we want to walk?
515 begin
518 begin
522 end
523 else
524 begin
527 begin
532 // check for walk completion
541 // ////////////////////////////////////////////////////////////////////////// //
542 procedure TBodyGridBase.TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer);
543 begin
556 begin
561 begin
566 begin
571 begin
576 begin
581 begin
586 // ////////////////////////////////////////////////////////////////////////// //
587 constructor TBodyGridBase.TAtPointEnumerator.Create (acells: TCellArray; aidx: Integer; agetpx: TGetProxyFn);
588 begin
597 begin
599 begin
601 begin
605 exit;
615 begin
620 // ////////////////////////////////////////////////////////////////////////// //
621 constructor TBodyGridBase.Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
622 var
624 begin
626 {$IF DEFINED(D2F_DEBUG)}
628 {$ENDIF}
629 {
630 if aTileSize < 1 then aTileSize := 1;
631 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
632 mTileSize := aTileSize;
633 }
644 // init free list
646 begin
652 // init grid
654 // init proxies
662 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth, mHeight, mTileSize, mWidth*mTileSize, mHeight*mTileSize]), TMsgType.Notify);
667 begin
675 // ////////////////////////////////////////////////////////////////////////// //
677 var
679 begin
682 begin
686 begin
692 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);
697 var
701 begin
705 begin
708 begin
711 begin
714 begin
719 //cb((g mod mWidth)*mTileSize+mMinX, (g div mWidth)*mTileSize+mMinY);
722 // next cell
730 var
734 begin
741 begin
744 begin
746 //if cb(mProxies[cc.bodies[f]].mObj, mProxies[cc.bodies[f]].mTag) then begin result := mProxies[cc.bodies[f]].mObj; exit; end;
748 //presobj^ := mProxies[cc.bodies[f]].mObj;
752 // next cell
758 // ////////////////////////////////////////////////////////////////////////// //
759 function TBodyGridBase.getGridWidthPx (): Integer; inline; begin result := mWidth*mTileSize; end;
760 function TBodyGridBase.getGridHeightPx (): Integer; inline; begin result := mHeight*mTileSize; end;
764 begin
765 // fix coords
773 begin
775 begin
778 end
779 else
780 begin
789 begin
791 begin
794 end
795 else
796 begin
804 function TBodyGridBase.getBodyDims (body: TBodyProxyId; out rx, ry, rw, rh: Integer): Boolean; inline;
805 begin
807 begin
810 end
811 else
812 begin
823 // ////////////////////////////////////////////////////////////////////////// //
825 begin
826 if (pid >= 0) and (pid < Length(mProxies)) then result := ((mProxies[pid].mTag and TagDisabled) = 0) else result := false;
831 begin
833 begin
835 begin
837 end
838 else
839 begin
847 begin
852 // ////////////////////////////////////////////////////////////////////////// //
854 var
857 begin
859 begin
860 // no free cells, want more
864 begin
876 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
881 begin
883 begin
885 begin
896 // ////////////////////////////////////////////////////////////////////////// //
897 function TBodyGridBase.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer): TBodyProxyId;
898 var
901 begin
903 begin
904 // no free proxies, resize list
911 // get one from list
916 // add to used list
918 // statistics
924 begin
926 if (mProxyCount = 0) then raise Exception.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
927 // add to free list
935 // ////////////////////////////////////////////////////////////////////////// //
936 function TBodyGridBase.forGridRect (x, y, w, h: Integer; cb: TGridInternalCB; bodyId: TBodyProxyId): Boolean;
937 var
941 begin
944 // fix coords
947 // go on
956 // clip rect
962 // do the work
964 begin
966 begin
974 // ////////////////////////////////////////////////////////////////////////// //
976 var
981 begin
983 // add body to the given grid cell
986 begin
987 {$IF DEFINED(D2F_DEBUG)}
990 begin
993 begin
995 if (pi.bodies[f] = bodyId) then raise Exception.Create('trying to insert already inserted proxy');
999 {$ENDIF}
1002 begin
1004 // check "has room" flag
1006 begin
1007 // can add here
1009 begin
1011 begin
1014 exit;
1019 // no room, go to next cell in list (if there is any)
1022 // no room in cells, add new cell to list
1024 // either no room, or no cell at all
1034 // assume that we cannot have one object added to bucket twice
1036 var
1040 begin
1042 // find and remove cell
1046 begin
1049 begin
1051 begin
1052 // i found her!
1054 begin
1055 // this cell contains no elements, remove it
1058 exit;
1060 // remove element from bucket
1062 begin
1067 exit;
1076 // ////////////////////////////////////////////////////////////////////////// //
1077 function TBodyGridBase.insertBody (aObj: ITP; aX, aY, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
1078 begin
1081 //insertInternal(result);
1087 var
1089 begin
1092 //removeInternal(body);
1098 // ////////////////////////////////////////////////////////////////////////// //
1100 var
1103 begin
1110 {$IF DEFINED(D2F_DEBUG_MOVER)}
1111 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);
1112 {$ENDIF}
1114 // map -> grid
1119 // did any corner crossed tile boundary?
1124 begin
1125 //writeln('moveResizeBody: cell occupation changed! old=(', x0, ',', y0, ')-(', x0+w-1, ',', y0+h-1, '); new=(', nx, ',', ny, ')-(', nx+nw-1, ',', ny+nh-1, ')');
1126 //removeInternal(body);
1132 //insertInternal(body);
1134 end
1135 else
1136 begin
1145 //TODO: optimize for horizontal/vertical moves
1147 var
1155 begin
1157 // check if tile coords was changed
1162 // map -> grid
1167 // check for heavy work
1178 {$IF DEFINED(D2F_DEBUG_MOVER)}
1179 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);
1180 {$ENDIF}
1182 begin
1183 // crossed tile boundary, do heavy work
1186 // cycle with old rect, remove body where it is necessary
1187 // optimized for horizontal moves
1188 {$IF DEFINED(D2F_DEBUG_MOVER)}
1189 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);
1190 {$ENDIF}
1191 // remove stale marks
1194 begin
1199 {$IF DEFINED(D2F_DEBUG_MOVER)}
1201 {$ENDIF}
1203 begin
1205 begin
1206 // this column is completely outside of new rect
1208 begin
1209 {$IF DEFINED(D2F_DEBUG_MOVER)}
1211 {$ENDIF}
1214 end
1215 else
1216 begin
1217 // heavy checks
1219 begin
1221 begin
1222 {$IF DEFINED(D2F_DEBUG_MOVER)}
1224 {$ENDIF}
1231 // cycle with new rect, add body where it is necessary
1234 begin
1239 {$IF DEFINED(D2F_DEBUG_MOVER)}
1241 {$ENDIF}
1243 begin
1245 begin
1246 // this column is completely outside of old rect
1248 begin
1249 {$IF DEFINED(D2F_DEBUG_MOVER)}
1251 {$ENDIF}
1254 end
1255 else
1256 begin
1257 // heavy checks
1259 begin
1261 begin
1262 {$IF DEFINED(D2F_DEBUG_MOVER)}
1264 {$ENDIF}
1271 // done
1272 end
1273 else
1274 begin
1275 {$IF DEFINED(D2F_DEBUG_MOVER)}
1276 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);
1277 {$ENDIF}
1279 // update coordinates
1286 var
1289 begin
1291 // check if tile coords was changed
1297 {$IF DEFINED(D2F_DEBUG_MOVER)}
1298 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);
1299 {$ENDIF}
1302 begin
1303 // crossed tile boundary, do heavy work
1304 //removeInternal(body);
1308 //insertInternal(body);
1310 end
1311 else
1312 begin
1313 // nothing to do with the grid, just fix size
1320 // ////////////////////////////////////////////////////////////////////////// //
1322 var
1324 begin
1327 if (x >= 0) and (y >= 0) and (x < mWidth*mTileSize) and (y < mHeight*mTileSize) then ccidx := mGrid[(y div mTileSize)*mWidth+(x div mTileSize)];
1332 // ////////////////////////////////////////////////////////////////////////// //
1333 // no callback: return `true` on the first hit
1334 function TBodyGridBase.forEachAtPoint (x, y: Integer; tagmask: Integer=-1; allowDisabled: Boolean=false; firstHit: Boolean=false): Integer;
1335 var
1343 begin
1348 {$IF DEFINED(D2F_DEBUG_XXQ)}
1350 {$ENDIF}
1352 // make coords (0,0)-based
1359 {$IF DEFINED(D2F_DEBUG_XXQ)}
1360 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);
1361 {$ENDIF}
1363 // restore coords
1367 // increase query counter
1370 begin
1371 // just in case of overflow
1377 {$IF DEFINED(D2F_DEBUG_XXQ)}
1378 if (assigned(cb)) then e_WriteLog(Format('2: grid pointquery: (%d,%d); lq=%u', [x, y, lq]), MSG_NOTIFY);
1379 {$ENDIF}
1382 begin
1383 {$IF DEFINED(D2F_DEBUG_XXQ)}
1384 //if (assigned(cb)) then e_WriteLog(Format(' cell #%d', [curci]), MSG_NOTIFY);
1385 {$ENDIF}
1388 begin
1391 {$IF DEFINED(D2F_DEBUG_XXQ)}
1392 //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);
1393 {$ENDIF}
1400 begin
1412 // ////////////////////////////////////////////////////////////////////////// //
1413 // no callback: return `true` on the first hit
1414 // return number of ITP thingys put into frame pool
1415 function TBodyGridBase.forEachInAABB (x, y, w, h: Integer; tagmask: Integer=-1; allowDisabled: Boolean=false; firstHit: Boolean=false): Integer;
1416 var
1429 begin
1434 begin
1436 exit;
1445 // fix coords
1460 // clip rect
1467 // has something to do
1469 // increase query counter
1472 begin
1473 // just in case of overflow
1477 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
1480 // go on
1482 begin
1484 begin
1485 // process cells
1488 begin
1491 begin
1494 // shit! has to do it this way, so i can change tag in callback
1514 // ////////////////////////////////////////////////////////////////////////// //
1515 function TBodyGridBase.forEachAlongLine (ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1; log: Boolean=false): Integer;
1516 var
1527 //px0, py0, px1, py1: Integer;
1529 begin
1540 // make query coords (0,0)-based
1549 // increase query counter
1552 begin
1553 // just in case of overflow
1559 repeat
1561 // check tile
1563 // process cells
1565 begin
1568 begin
1573 begin
1580 // next cell
1583 // done processing cells, move to next tile
1588 // ////////////////////////////////////////////////////////////////////////// //
1589 // trace box with the given velocity; return object hit (if any)
1590 // `cb` is used unconvetionally here: if it returns `false`, tracer will ignore the object
1591 function TBodyGridBase.traceBox (out ex, ey: Integer; const ax0, ay0, aw, ah: Integer; const dx, dy: Integer; tagmask: Integer=-1): ITP;
1592 var
1603 begin
1617 if (cx1 < 0) or (cy1 < 0) or (cx0 >= mWidth*mTileSize) or (cy0 >= mHeight*mTileSize) then exit;
1623 // just in case
1626 // increase query counter
1629 begin
1630 // just in case of overflow
1637 begin
1639 begin
1642 begin
1645 begin
1650 begin
1652 if not sweepAABB(ax0, ay0, aw, ah, dx, dy, px.mX, px.mY, px.mWidth, px.mHeight, @u0) then continue;
1654 begin
1659 begin
1662 exit;
1667 // next cell
1674 begin
1677 // just in case, compensate for floating point inexactness
1678 if (ex >= hitpx.mX) and (ey >= hitpx.mY) and (ex < hitpx.mX+hitpx.mWidth) and (ey < hitpx.mY+hitpx.mHeight) then
1679 begin
1687 // ////////////////////////////////////////////////////////////////////////// //
1688 {.$DEFINE D2F_DEBUG_OTR}
1689 function TBodyGridBase.traceOrthoRayWhileIn (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): Boolean;
1690 var
1701 {$IF DEFINED(D2F_DEBUG_OTR)}
1703 {$ENDIF}
1705 begin
1721 // offset query coords to (0,0)-based
1728 begin
1730 // vertical
1732 begin
1733 // down
1735 //if (ay0 < 0) then ay0 := 0;
1739 end
1740 else
1741 begin
1742 // up
1744 //if (ay1 < 0) then ay1 := 0;
1749 // check tile
1751 begin
1757 begin
1760 begin
1766 begin
1767 // bound c0 and c1 to cell
1770 // fill the thing
1771 {$IF DEFINED(D2F_DEBUG_OTR)}
1772 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)]);
1773 {$ENDIF}
1774 //assert(c0 <= c1);
1778 // next cell
1781 {$IF DEFINED(D2F_DEBUG_OTR)}
1782 s := formatstrf(' x=%s; ay0=%s; ay1=%s; y0=%s; celly0=%s; celly1=%s; dy=%s; [', [ax0, ay0, ay1, y0, celly0, celly1, dy]);
1786 {$ENDIF}
1787 // now go till we hit cell boundary or empty space
1789 begin
1790 // up
1792 begin
1793 {$IF DEFINED(D2F_DEBUG_OTR)}
1794 e_LogWritefln(' filled: cdy=%s; y0=%s; celly0=%s; ay0=%s; ay1=%s', [y0-celly0, y0, celly0, ay0, ay1]);
1795 {$ENDIF}
1799 {$IF DEFINED(D2F_DEBUG_OTR)}
1800 e_LogWritefln(' span done: cdy=%s; y0=%s; celly0=%s; ay0=%s; ay1=%s', [y0-celly0, y0, celly0, ay0, ay1]);
1801 {$ENDIF}
1803 if (y0 >= celly0) then begin ey := ay0+1; {assert(forEachAtPoint(ex, ey, nil, tagmask) <> nil);} result := true; exit; end;
1804 end
1805 else
1806 begin
1807 // down
1813 end
1814 else
1815 begin
1816 // horizontal
1822 // ////////////////////////////////////////////////////////////////////////// //
1824 var
1826 begin
1831 // no callback: return `true` on the nearest hit
1832 // you are not supposed to understand this
1833 function TBodyGridBase.traceRay (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): ITP;
1834 var
1849 begin
1859 // make query coords (0,0)-based
1870 {$IF DEFINED(D2F_DEBUG)}
1871 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln('*** traceRay: (%s,%s)-(%s,%s)', [x0, y0, x1, y1]);
1872 {$ENDIF}
1874 // increase query counter
1877 begin
1878 // just in case of overflow
1884 repeat
1886 {$IF DEFINED(D2F_DEBUG)}
1888 {$ENDIF}
1889 // check tile
1891 // process cells
1894 begin
1897 begin
1902 begin
1904 // get adjusted proxy coords
1909 {$IF DEFINED(D2F_DEBUG)}
1910 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln(' cxy=(%s,%s); pan=(%s,%s)-(%s,%s)', [cx, cy, px0, py0, px1, py1]);
1911 {$ENDIF}
1912 // inside?
1914 begin
1915 // oops
1919 {$IF DEFINED(D2F_DEBUG)}
1921 {$ENDIF}
1922 exit;
1924 // do line-vs-aabb test
1926 begin
1927 // hit detected
1929 {$IF DEFINED(D2F_DEBUG)}
1930 //if assigned(dbgRayTraceTileHitCB) then e_LogWritefln(' hit=(%s,%s); distSq=%s; lastDistSq=%s', [hx, hy, distSq, lastDistSq]);
1931 {$ENDIF}
1933 begin
1943 // next cell
1946 // done processing cells; exit if we registered a hit
1947 // next cells can't have better candidates, obviously
1950 // move to next tile