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}
20 interface
23 type
27 public
28 type TGridQueryCB = function (obj: ITP; tag: Integer): Boolean is nested; // return `true` to stop
29 type TGridRayQueryCB = function (obj: ITP; tag: Integer; x, y, prevx, prevy: Integer): Boolean is nested; // return `true` to stop
30 type TGridAlongQueryCB = function (obj: ITP; tag: Integer): Boolean is nested; // return `true` to stop
37 private
38 const
42 private
43 type
46 private
53 private
63 TGridInternalCB = function (grida: Integer; bodyId: TBodyProxyId): Boolean of object; // return `true` to stop
65 private
66 //mTileSize: Integer;
69 public
72 private
85 public
88 private
109 public
110 constructor Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
113 function insertBody (aObj: ITP; ax, ay, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
122 // `false` if `body` is surely invalid
125 //WARNING: don't modify grid while any query is in progress (no checks are made!)
126 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
127 // no callback: return `true` on the first hit
128 function forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
130 //WARNING: don't modify grid while any query is in progress (no checks are made!)
131 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
132 // no callback: return `true` on the first hit
135 //WARNING: don't modify grid while any query is in progress (no checks are made!)
136 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
137 // cb with `(nil)` will be called before processing new tile
138 // no callback: return `true` on the nearest hit
139 function traceRay (x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP; overload;
140 function traceRay (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
142 //WARNING: don't modify grid while any query is in progress (no checks are made!)
143 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
144 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
145 function forEachAlongLine (x0, y0, x1, y1: Integer; cb: TGridAlongQueryCB; tagmask: Integer=-1; log: Boolean=false): ITP;
147 // debug
152 //WARNING! no sanity checks!
162 // you are not supposed to understand this
163 // returns `true` if there is an intersection, and enter coords
164 // enter coords will be equal to (x0, y0) if starting point is inside the box
165 // if result is `false`, `inx` and `iny` are undefined
166 function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean;
175 implementation
177 uses
181 // ////////////////////////////////////////////////////////////////////////// //
182 procedure swapInt (var a: Integer; var b: Integer); inline; var t: Integer; begin t := a; a := b; b := t; end;
183 function minInt (a, b: Integer): Integer; inline; begin if (a < b) then result := a else result := b; end;
184 function maxInt (a, b: Integer): Integer; inline; begin if (a > b) then result := a else result := b; end;
186 function distanceSq (x0, y0, x1, y1: Integer): Integer; inline; begin result := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0); end;
189 // ////////////////////////////////////////////////////////////////////////// //
190 // you are not supposed to understand this
191 // returns `true` if there is an intersection, and enter coords
192 // enter coords will be equal to (x0, y0) if starting point is inside the box
193 // if result is `false`, `inx` and `iny` are undefined
194 function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean;
195 var
203 //!term: Integer;
207 begin
209 // why not
215 begin
216 // check this point
218 exit;
221 // check if staring point is inside the box
222 if (x0 >= bx) and (y0 >= by) and (x0 < bx+bw) and (y0 < by+bh) then begin result := true; exit; end;
224 // clip rectange
230 // horizontal setup
232 begin
233 // from left to right
236 end
237 else
238 begin
239 // from right to left
249 // vertical setup
251 begin
252 // from top to bottom
255 end
256 else
257 begin
258 // from bottom to top
272 begin
281 end
282 else
283 begin
293 //!term := x1;
297 begin
298 // clip at top
304 begin
313 begin
314 // clip at left
324 (*
325 if (y1 > wy1) then
326 begin
327 // clip at bottom
328 temp := dx2*(wy1-y0)+dsx;
329 term := x0+temp div dy2;
330 rem := temp mod dy2;
331 if (rem = 0) then Dec(term);
332 end;
334 if (term > wx1) then term := wx1; // clip at right
336 Inc(term); // draw last point
337 //if (term = xd) then exit; // this is the only point, get out of here
338 *)
342 //!dx2 -= dy2;
350 // ////////////////////////////////////////////////////////////////////////// //
351 procedure TBodyGridBase.TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer);
352 begin
364 // ////////////////////////////////////////////////////////////////////////// //
365 constructor TBodyGridBase.Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
366 var
368 begin
370 {
371 if aTileSize < 1 then aTileSize := 1;
372 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
373 mTileSize := aTileSize;
374 }
385 // init free list
387 begin
392 // init grid
394 // init proxies
402 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth, mHeight, mTileSize, mWidth*mTileSize, mHeight*mTileSize]), MSG_NOTIFY);
407 begin
415 // ////////////////////////////////////////////////////////////////////////// //
417 var
419 begin
422 begin
426 begin
432 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);
437 var
440 //px: PBodyProxyRec;
441 begin
444 begin
447 begin
450 begin
452 if (cc.bodies[f] = body) then cb((g mod mWidth)*mTileSize+mMinX, (g div mWidth)*mTileSize+mMinY);
453 //px := @mProxies[cc.bodies[f]];
455 // next cell
463 var
466 begin
474 begin
477 begin
479 if cb(mProxies[cc.bodies[f]].mObj, mProxies[cc.bodies[f]].mTag) then begin result := mProxies[cc.bodies[f]].mObj; exit; end;
481 // next cell
487 // ////////////////////////////////////////////////////////////////////////// //
488 function TBodyGridBase.getGridWidthPx (): Integer; inline; begin result := mWidth*mTileSize; end;
489 function TBodyGridBase.getGridHeightPx (): Integer; inline; begin result := mHeight*mTileSize; end;
493 begin
494 // fix coords
502 begin
504 begin
507 end
508 else
509 begin
517 // ////////////////////////////////////////////////////////////////////////// //
519 begin
525 begin
527 begin
529 begin
531 end
532 else
533 begin
540 // ////////////////////////////////////////////////////////////////////////// //
542 var
545 begin
547 begin
548 // no free cells, want more
552 begin
563 //pc.bodies[0] := -1;
565 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
570 begin
572 begin
574 begin
585 // ////////////////////////////////////////////////////////////////////////// //
586 function TBodyGridBase.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer): TBodyProxyId;
587 var
590 begin
592 begin
593 // no free proxies, resize list
600 // get one from list
605 // add to used list
607 // statistics
613 begin
615 if (mProxyCount = 0) then raise Exception.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
616 // add to free list
624 // ////////////////////////////////////////////////////////////////////////// //
625 function TBodyGridBase.forGridRect (x, y, w, h: Integer; cb: TGridInternalCB; bodyId: TBodyProxyId): Boolean;
626 const
628 var
631 begin
634 // fix coords
637 // go on
641 //tsize := mTileSize;
644 begin
648 begin
658 // ////////////////////////////////////////////////////////////////////////// //
660 var
665 begin
667 // add body to the given grid cell
670 begin
673 begin
674 // can add here
676 begin
678 begin
681 exit;
687 // either no room, or no cell at all
697 var
699 begin
706 // assume that we cannot have one object added to bucket twice
708 var
712 begin
714 // find and remove cell
718 begin
721 begin
723 begin
724 // i found her!
726 begin
727 // this cell contains no elements, remove it
730 exit;
732 // remove element from bucket
734 begin
739 exit;
747 // absolutely not tested
749 var
751 begin
758 // ////////////////////////////////////////////////////////////////////////// //
759 function TBodyGridBase.insertBody (aObj: ITP; aX, aY, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
760 begin
768 begin
775 // ////////////////////////////////////////////////////////////////////////// //
777 var
780 begin
788 // did any corner crossed tile boundary?
793 begin
800 end
801 else
802 begin
810 //TODO: optimize for horizontal/vertical moves
812 var
820 begin
822 // check if tile coords was changed
827 // map -> grid
832 // check for heavy work
844 begin
845 // crossed tile boundary, do heavy work
848 // cycle with old rect, remove body where it is necessary
849 // optimized for horizontal moves
850 //e_WriteLog(Format('og:(%d,%d)-(%d,%d); ng:(%d,%d)-(%d,%d)', [ogx0, ogy0, ogx1, ogy1, ngx0, ngy0, ngx1, ngy1]), MSG_NOTIFY);
851 // remove stale marks
854 begin
859 //e_WriteLog(Format(' norm og:(%d,%d)-(%d,%d)', [ogx0, ogy0, ogx1, ogy1]), MSG_NOTIFY);
861 begin
863 begin
864 // this column is completely outside of new rect
866 begin
867 //e_WriteLog(Format(' remove:(%d,%d)', [gx, gy]), MSG_NOTIFY);
870 end
871 else
872 begin
873 // heavy checks
875 begin
877 begin
878 //e_WriteLog(Format(' remove:(%d,%d)', [gx, gy]), MSG_NOTIFY);
885 // cycle with new rect, add body where it is necessary
888 begin
893 //e_WriteLog(Format(' norm ng:(%d,%d)-(%d,%d)', [ngx0, ngy0, ngx1, ngy1]), MSG_NOTIFY);
895 begin
897 begin
898 // this column is completely outside of old rect
900 begin
901 //e_WriteLog(Format(' insert:(%d,%d)', [gx, gy]), MSG_NOTIFY);
904 end
905 else
906 begin
907 // heavy checks
909 begin
911 begin
912 //e_WriteLog(Format(' insert:(%d,%d)', [gx, gy]), MSG_NOTIFY);
919 // done
921 // update coordinates
927 var
930 begin
932 // check if tile coords was changed
940 begin
941 // crossed tile boundary, do heavy work
946 end
947 else
948 begin
949 // nothing to do with the grid, just fix size
956 // ////////////////////////////////////////////////////////////////////////// //
957 // no callback: return `true` on the first hit
958 function TBodyGridBase.forEachAtPoint (x, y: Integer; cb: TGridQueryCB; tagmask: Integer=-1): ITP;
959 var
966 begin
971 // make coords (0,0)-based
977 // restore coords
981 // increase query counter
984 begin
985 // just in case of overflow
992 begin
995 begin
1000 begin
1002 begin
1005 begin
1007 end
1008 else
1009 begin
1011 exit;
1021 // ////////////////////////////////////////////////////////////////////////// //
1022 // no callback: return `true` on the first hit
1023 function TBodyGridBase.forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
1024 const
1026 var
1037 begin
1046 // fix coords
1051 //tsize := mTileSize;
1056 // increase query counter
1059 begin
1060 // just in case of overflow
1064 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
1067 // go on
1069 begin
1073 begin
1076 // process cells
1079 begin
1082 begin
1088 //if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
1089 //if ( ((ptag and TagDisabled) = 0) = ignoreDisabled) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
1090 begin
1095 begin
1097 end
1098 else
1099 begin
1101 exit;
1112 // ////////////////////////////////////////////////////////////////////////// //
1113 // no callback: return `true` on the nearest hit
1114 function TBodyGridBase.traceRay (x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
1115 var
1117 begin
1122 // no callback: return `true` on the nearest hit
1123 // you are not supposed to understand this
1124 function TBodyGridBase.traceRay (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
1125 const
1127 var
1153 begin
1161 if (ax0 = ax1) and (ay0 = ay1) then exit; // as the first point is ignored, just get outta here
1177 // offset query coords to (0,0)-based
1183 // clip rectange
1189 // horizontal setup
1191 begin
1192 // from left to right
1195 end
1196 else
1197 begin
1198 // from right to left
1208 // vertical setup
1210 begin
1211 // from top to bottom
1214 end
1215 else
1216 begin
1217 // from bottom to top
1231 begin
1240 end
1241 else
1242 begin
1256 begin
1257 // clip at top
1263 begin
1272 begin
1273 // clip at left
1284 begin
1285 // clip at bottom
1295 //if (term = xd) then exit; // this is the only point, get out of here
1301 // first move, to skip starting point
1305 // move coords
1308 // done?
1311 {$IF DEFINED(D2F_DEBUG)}
1312 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ > mHeight*tsize) then raise Exception.Create('raycaster internal error (0)');
1313 {$ENDIF}
1315 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
1317 // restore query coords
1320 //Inc(ax1, minx);
1321 //Inc(ay1, miny);
1323 // increase query counter
1326 begin
1327 // just in case of overflow
1334 // draw it; can omit checks
1336 begin
1337 // check cell(s)
1338 {$IF DEFINED(D2F_DEBUG)}
1339 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ > mHeight*tsize) then raise Exception.Create('raycaster internal error (0)');
1340 {$ENDIF}
1341 // new tile?
1344 begin
1345 // yes
1347 begin
1348 // signal cell completion
1350 begin
1352 end
1354 begin
1356 exit;
1362 // has something to process in this tile?
1364 begin
1365 // process cell
1367 hasUntried := false; // this will be set to `true` if we have some proxies we still want to process at the next step
1368 // convert coords to map (to avoid ajdusting coords inside the loop)
1371 // process cell list
1373 begin
1376 begin
1381 begin
1382 // can we process this proxy?
1384 begin
1387 begin
1389 begin
1393 exit;
1395 end
1396 else
1397 begin
1398 // remember this hitpoint if it is nearer than an old one
1401 begin
1409 end
1410 else
1411 begin
1412 // this is possibly interesting proxy, set "has more to check" flag
1417 // next cell
1420 // still has something interesting in this cell?
1422 begin
1423 // nope, don't process this cell anymore; signal cell completion
1426 begin
1428 end
1430 begin
1432 exit;
1436 //putPixel(xptr^, yptr^);
1437 // move coords
1446 // ////////////////////////////////////////////////////////////////////////// //
1447 //FIXME! optimize this with real tile walking
1448 function TBodyGridBase.forEachAlongLine (x0, y0, x1, y1: Integer; cb: TGridAlongQueryCB; tagmask: Integer=-1; log: Boolean=false): ITP;
1449 const
1451 var
1470 //tedist: Integer;
1471 begin
1493 // `x` and `y` will be in grid coords
1497 // increase query counter
1500 begin
1501 // just in case of overflow
1507 // cache various things
1508 //tsize := mTileSize;
1514 // setup distance and flags
1517 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1520 // it is slightly faster this way
1524 if (log) then e_WriteLog(Format('tracing: (%d,%d)-(%d,%d)', [x, y, x1-minx, y1-miny]), MSG_NOTIFY);
1526 // now trace
1529 begin
1531 // do one step
1534 // invariant: one of those always changed
1535 {$IF DEFINED(D2F_DEBUG)}
1536 if (xerr < 0) and (yerr < 0) then raise Exception.Create('internal bug in grid raycaster (0)');
1537 {$ENDIF}
1540 // invariant: we always doing a step
1541 {$IF DEFINED(D2F_DEBUG)}
1543 {$ENDIF}
1544 begin
1545 // check for crossing tile/grid boundary
1547 begin
1548 // we're still in grid
1550 // check for tile edge crossing
1556 // crossed tile edge?
1558 begin
1559 // setup new cell index
1561 if (log) then e_WriteLog(Format(' stepped to new tile (%d,%d) -- (%d,%d)', [(x div tsize), (y div tsize), x, y]), MSG_NOTIFY);
1562 end
1563 else
1565 begin
1566 // we have nothing interesting here anymore, jump directly to tile edge
1567 (*
1568 if (incx = 0) then
1569 begin
1570 // vertical line
1571 if (incy < 0) then tedist := y-(y and (not tsize)) else tedist := (y or (tsize-1))-y;
1572 if (tedist > 1) then
1573 begin
1574 if (log) then e_WriteLog(Format(' doing vertical jump from tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1575 y += incy*tedist;
1576 Inc(i, tedist);
1577 if (log) then e_WriteLog(Format(' jumped to tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1578 end;
1579 end
1580 else if (incy = 0) then
1581 begin
1582 // horizontal line
1583 if (incx < 0) then tedist := x-(x and (not tsize)) else tedist := (x or (tsize-1))-x;
1584 if (tedist > 1) then
1585 begin
1586 if (log) then e_WriteLog(Format(' doing horizontal jump from tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1587 x += incx*tedist;
1588 Inc(i, tedist);
1589 if (log) then e_WriteLog(Format(' jumped to tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1590 end;
1591 end;
1592 *)
1593 (*
1594 else if (
1595 // get minimal distance to tile edges
1596 if (incx < 0) then tedist := x-(x and (not tsize)) else if (incx > 0) then tedist := (x or (tsize+1))-x else tedist := 0;
1597 {$IF DEFINED(D2F_DEBUG)}
1598 if (tedist < 0) then raise Exception.Create('internal bug in grid raycaster (2.x)');
1599 {$ENDIF}
1600 if (incy < 0) then f := y-(y and (not tsize)) else if (incy > 0) then f := (y or (tsize+1))-y else f := 0;
1601 {$IF DEFINED(D2F_DEBUG)}
1602 if (f < 0) then raise Exception.Create('internal bug in grid raycaster (2.y)');
1603 {$ENDIF}
1604 if (tedist = 0) then tedist := f else if (f <> 0) then tedist := minInt(tedist, f);
1605 // do jump
1606 if (tedist > 1) then
1607 begin
1608 if (log) then e_WriteLog(Format(' doing jump from tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1609 xerr += dx*tedist;
1610 yerr += dy*tedist;
1611 if (xerr >= 0) then begin x += incx*((xerr div d)+1); xerr := (xerr mod d)-d; end;
1612 if (yerr >= 0) then begin y += incy*((yerr div d)+1); yerr := (yerr mod d)-d; end;
1613 Inc(i, tedist);
1614 if (log) then e_WriteLog(Format(' jumped to tile (%d,%d) - (%d,%d) by %d steps', [(x div tsize), (y div tsize), x, y, tedist]), MSG_NOTIFY);
1615 end;
1616 *)
1618 end
1619 else
1620 begin
1621 // out of grid
1626 // has something to process in the current cell?
1628 begin
1629 // process cell
1631 // convert coords to map (to avoid ajdusting coords inside the loop)
1632 //Inc(x, minx);
1633 //Inc(y, miny);
1634 // process cell list
1636 begin
1639 begin
1644 begin
1649 // next cell
1653 // convert coords to grid
1654 //Dec(x, minx);
1655 //Dec(y, miny);