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
35 private
36 const
40 private
41 type
44 private
51 private
61 TGridInternalCB = function (grida: Integer; bodyId: TBodyProxyId): Boolean of object; // return `true` to stop
63 private
64 //mTileSize: Integer;
67 private
80 public
83 private
104 public
105 constructor Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
108 function insertBody (aObj: ITP; ax, ay, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
117 // `false` if `body` is surely invalid
120 //WARNING: don't modify grid while any query is in progress (no checks are made!)
121 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
122 // no callback: return `true` on the first hit
123 function forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
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
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 // cb with `(nil)` will be called before processing new tile
133 // no callback: return `true` on the nearest hit
134 function traceRay (x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP; overload;
135 function traceRay (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
137 //WARNING: don't modify grid while any query is in progress (no checks are made!)
138 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
139 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
140 function forEachAlongLine (x0, y0, x1, y1: Integer; cb: TGridAlongQueryCB; tagmask: Integer=-1): ITP;
144 //WARNING! no sanity checks!
154 // you are not supposed to understand this
155 // returns `true` if there is an intersection, and enter coords
156 // enter coords will be equal to (x0, y0) if starting point is inside the box
157 // if result is `false`, `inx` and `iny` are undefined
158 function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean;
165 implementation
167 uses
171 // ////////////////////////////////////////////////////////////////////////// //
172 procedure swapInt (var a: Integer; var b: Integer); inline; var t: Integer; begin t := a; a := b; b := t; end;
174 function distanceSq (x0, y0, x1, y1: Integer): Integer; inline; begin result := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0); end;
177 // ////////////////////////////////////////////////////////////////////////// //
178 // you are not supposed to understand this
179 // returns `true` if there is an intersection, and enter coords
180 // enter coords will be equal to (x0, y0) if starting point is inside the box
181 // if result is `false`, `inx` and `iny` are undefined
182 function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean;
183 var
195 begin
197 // why not
203 begin
204 // check this point
206 exit;
209 // check if staring point is inside the box
210 if (x0 >= bx) and (y0 >= by) and (x0 < bx+bw) and (y0 < by+bh) then begin result := true; exit; end;
212 // clip rectange
218 // horizontal setup
220 begin
221 // from left to right
224 end
225 else
226 begin
227 // from right to left
237 // vertical setup
239 begin
240 // from top to bottom
243 end
244 else
245 begin
246 // from bottom to top
260 begin
269 end
270 else
271 begin
285 begin
286 // clip at top
292 begin
301 begin
302 // clip at left
313 begin
314 // clip at bottom
324 //if (term = xd) then exit; // this is the only point, get out of here
336 // ////////////////////////////////////////////////////////////////////////// //
337 procedure TBodyGridBase.TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer);
338 begin
350 // ////////////////////////////////////////////////////////////////////////// //
351 constructor TBodyGridBase.Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
352 var
354 begin
356 {
357 if aTileSize < 1 then aTileSize := 1;
358 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
359 mTileSize := aTileSize;
360 }
371 // init free list
373 begin
378 // init grid
380 // init proxies
388 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth, mHeight, mTileSize, mWidth*mTileSize, mHeight*mTileSize]), MSG_NOTIFY);
393 begin
401 // ////////////////////////////////////////////////////////////////////////// //
403 var
405 begin
408 begin
412 begin
418 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);
422 // ////////////////////////////////////////////////////////////////////////// //
423 function TBodyGridBase.getGridWidthPx (): Integer; inline; begin result := mWidth*mTileSize; end;
424 function TBodyGridBase.getGridHeightPx (): Integer; inline; begin result := mHeight*mTileSize; end;
428 begin
429 // fix coords
437 begin
439 begin
442 end
443 else
444 begin
452 // ////////////////////////////////////////////////////////////////////////// //
454 begin
460 begin
462 begin
464 begin
466 end
467 else
468 begin
475 // ////////////////////////////////////////////////////////////////////////// //
477 var
479 begin
481 begin
482 // no free cells, want more
486 begin
497 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
502 begin
504 begin
505 //if mCells[idx].body = -1 then exit; // the thing that should not be
514 // ////////////////////////////////////////////////////////////////////////// //
515 function TBodyGridBase.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer): TBodyProxyId;
516 var
519 begin
521 begin
522 // no free proxies, resize list
529 // get one from list
534 // add to used list
536 // statistics
542 begin
544 if (mProxyCount = 0) then raise Exception.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
545 // add to free list
553 // ////////////////////////////////////////////////////////////////////////// //
554 function TBodyGridBase.forGridRect (x, y, w, h: Integer; cb: TGridInternalCB; bodyId: TBodyProxyId): Boolean;
555 const
557 var
560 begin
563 // fix coords
566 // go on
570 //tsize := mTileSize;
573 begin
577 begin
587 // ////////////////////////////////////////////////////////////////////////// //
589 var
594 begin
596 // add body to the given grid cell
599 begin
603 begin
605 begin
606 // can add here
609 exit;
613 // either no room, or no cell at all
622 var
624 begin
631 // absolutely not tested
633 var
637 begin
639 // find and remove cell
643 begin
648 begin
650 begin
651 // i found her!
653 begin
654 // this cell contains no elements, remove it
658 end
659 else
660 begin
661 // remove element from bucket
664 begin
680 // absolutely not tested
682 var
684 begin
691 // ////////////////////////////////////////////////////////////////////////// //
692 function TBodyGridBase.insertBody (aObj: ITP; aX, aY, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
693 begin
701 begin
708 // ////////////////////////////////////////////////////////////////////////// //
710 var
713 begin
721 // did any corner crossed tile boundary?
726 begin
733 end
734 else
735 begin
744 var
747 begin
750 // check if tile coords was changed
755 begin
756 // crossed tile boundary, do heavy work
758 end
759 else
760 begin
761 // nothing to do with the grid, just fix coordinates
768 var
772 begin
775 // check if tile coords was changed
783 begin
784 // crossed tile boundary, do heavy work
786 end
787 else
788 begin
789 // nothing to do with the grid, just fix size
796 // ////////////////////////////////////////////////////////////////////////// //
797 // no callback: return `true` on the first hit
798 function TBodyGridBase.forEachAtPoint (x, y: Integer; cb: TGridQueryCB; tagmask: Integer=-1): ITP;
799 var
806 begin
811 // make coords (0,0)-based
817 // restore coords
821 // increase query counter
824 begin
825 // just in case of overflow
832 begin
835 begin
840 begin
842 begin
845 begin
847 end
848 else
849 begin
851 exit;
861 // ////////////////////////////////////////////////////////////////////////// //
862 // no callback: return `true` on the first hit
863 function TBodyGridBase.forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
864 const
866 var
877 begin
886 // fix coords
891 //tsize := mTileSize;
896 // increase query counter
899 begin
900 // just in case of overflow
904 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
907 // go on
909 begin
913 begin
916 // process cells
919 begin
922 begin
928 //if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
929 //if ( ((ptag and TagDisabled) = 0) = ignoreDisabled) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
930 begin
935 begin
937 end
938 else
939 begin
941 exit;
952 // ////////////////////////////////////////////////////////////////////////// //
953 // no callback: return `true` on the nearest hit
954 function TBodyGridBase.traceRay (x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
955 var
957 begin
962 // no callback: return `true` on the nearest hit
963 (*
964 function TBodyGridBase.traceRay (out ex, ey: Integer; x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
965 const
966 tsize = mTileSize;
967 var
968 i: Integer;
969 dx, dy, d: Integer;
970 xerr, yerr: Integer;
971 incx, incy: Integer;
972 stepx, stepy: Integer;
973 x, y: Integer;
974 maxx, maxy: Integer;
975 gw, gh: Integer;
976 ccidx: Integer;
977 curci: Integer;
978 cc: PGridCell;
979 hasUntried: Boolean;
980 px: PBodyProxyRec;
981 lq: LongWord;
982 prevX, prevY: Integer;
983 minx, miny: Integer;
984 ptag: Integer;
985 lastDistSq, distSq: Integer;
986 wasHit: Boolean = false;
987 lastObj: ITP;
988 lastWasInGrid: Boolean;
989 tbcross: Boolean;
990 f: Integer;
991 begin
992 result := Default(ITP);
993 lastObj := Default(ITP);
994 tagmask := tagmask and TagFullMask;
995 if (tagmask = 0) then begin ex := x0; ey := y0; exit; end;
997 minx := mMinX;
998 miny := mMinY;
1000 dx := x1-x0;
1001 dy := y1-y0;
1003 if (dx > 0) then incx := 1 else if (dx < 0) then incx := -1 else incx := 0;
1004 if (dy > 0) then incy := 1 else if (dy < 0) then incy := -1 else incy := 0;
1006 dx := abs(dx);
1007 dy := abs(dy);
1009 if (dx > dy) then d := dx else d := dy;
1011 // `x` and `y` will be in grid coords
1012 x := x0-minx;
1013 y := y0-miny;
1015 // increase query counter
1016 Inc(mLastQuery);
1017 if (mLastQuery = 0) then
1018 begin
1019 // just in case of overflow
1020 mLastQuery := 1;
1021 for i := 0 to High(mProxies) do mProxies[i].mQueryMark := 0;
1022 end;
1023 lq := mLastQuery;
1025 // cache various things
1026 //tsize := mTileSize;
1027 gw := mWidth;
1028 gh := mHeight;
1029 maxx := gw*tsize-1;
1030 maxy := gh*tsize-1;
1032 // setup distance and flags
1033 lastDistSq := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)+1;
1034 lastWasInGrid := (x >= 0) and (y >= 0) and (x <= maxx) and (y <= maxy);
1036 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1037 if lastWasInGrid then ccidx := mGrid[(y div tsize)*gw+(x div tsize)] else ccidx := -1;
1039 // it is slightly faster this way
1040 xerr := -d;
1041 yerr := -d;
1043 // now trace
1044 for i := 1 to d do
1045 begin
1046 // prevs are always in map coords
1047 prevX := x+minx;
1048 prevY := y+miny;
1049 // do one step
1050 xerr += dx;
1051 yerr += dy;
1052 // invariant: one of those always changed
1053 if (xerr < 0) and (yerr < 0) then raise Exception.Create('internal bug in grid raycaster (0)');
1054 if (xerr >= 0) then begin xerr -= d; x += incx; stepx := incx; end else stepx := 0;
1055 if (yerr >= 0) then begin yerr -= d; y += incy; stepy := incy; end else stepy := 0;
1056 // invariant: we always doing a step
1057 if ((stepx or stepy) = 0) then raise Exception.Create('internal bug in grid raycaster (1)');
1058 begin
1059 // check for crossing tile/grid boundary
1060 if (x >= 0) and (y >= 0) and (x <= maxx) and (y <= maxy) then
1061 begin
1062 // we're still in grid
1063 lastWasInGrid := true;
1064 // check for tile edge crossing
1065 if (stepx < 0) and ((x mod tsize) = tsize-1) then tbcross := true
1066 else if (stepx > 0) and ((x mod tsize) = 0) then tbcross := true
1067 else if (stepy < 0) and ((y mod tsize) = tsize-1) then tbcross := true
1068 else if (stepy > 0) and ((y mod tsize) = 0) then tbcross := true
1069 else tbcross := false;
1070 // crossed tile edge?
1071 if tbcross then
1072 begin
1073 // had something in the cell we're leaving?
1074 if (ccidx <> -1) then
1075 begin
1076 // yes, signal cell completion
1077 if assigned(cb) then
1078 begin
1079 if cb(nil, 0, x+minx, y+miny, prevX, prevY) then begin result := lastObj; exit; end;
1080 end
1081 else if wasHit then
1082 begin
1083 result := lastObj;
1084 exit;
1085 end;
1086 end;
1087 // setup new cell index
1088 ccidx := mGrid[(y div tsize)*gw+(x div tsize)];
1089 end;
1090 end
1091 else
1092 begin
1093 // out of grid, had something in the last processed cell?
1094 if (ccidx <> -1) then
1095 begin
1096 // yes, signal cell completion
1097 ccidx := -1;
1098 if assigned(cb) then
1099 begin
1100 if cb(nil, 0, x+minx, y+miny, prevX, prevY) then begin result := lastObj; exit; end;
1101 end
1102 else if wasHit then
1103 begin
1104 result := lastObj;
1105 exit;
1106 end;
1107 end;
1108 if lastWasInGrid then exit; // oops, stepped out of the grid -- there is no way to return
1109 end;
1110 end;
1112 // has something to process in the current cell?
1113 if (ccidx <> -1) then
1114 begin
1115 // process cell
1116 curci := ccidx;
1117 hasUntried := false; // this will be set to `true` if we have some proxies we still want to process at the next step
1118 // convert coords to map (to avoid ajdusting coords inside the loop)
1119 Inc(x, minx);
1120 Inc(y, miny);
1121 // process cell list
1122 while (curci <> -1) do
1123 begin
1124 cc := @mCells[curci];
1125 for f := 0 to High(TGridCell.bodies) do
1126 begin
1127 if (cc.bodies[f] = -1) then break;
1128 px := @mProxies[cc.bodies[f]];
1129 ptag := px.mTag;
1130 if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
1131 begin
1132 // can we process this proxy?
1133 if (x >= px.mX) and (y >= px.mY) and (x < px.mX+px.mWidth) and (y < px.mY+px.mHeight) then
1134 begin
1135 px.mQueryMark := lq; // mark as processed
1136 if assigned(cb) then
1137 begin
1138 if cb(px.mObj, ptag, x, y, prevX, prevY) then
1139 begin
1140 result := lastObj;
1141 ex := prevX;
1142 ey := prevY;
1143 exit;
1144 end;
1145 end
1146 else
1147 begin
1148 // remember this hitpoint if it is nearer than an old one
1149 distSq := (prevX-x0)*(prevX-x0)+(prevY-y0)*(prevY-y0);
1150 if (distSq < lastDistSq) then
1151 begin
1152 wasHit := true;
1153 lastDistSq := distSq;
1154 ex := prevX;
1155 ey := prevY;
1156 lastObj := px.mObj;
1157 end;
1158 end;
1159 end
1160 else
1161 begin
1162 // this is possibly interesting proxy, set "has more to check" flag
1163 hasUntried := true;
1164 end;
1165 end;
1166 end;
1167 // next cell
1168 curci := cc.next;
1169 end;
1170 // still has something interesting in this cell?
1171 if not hasUntried then
1172 begin
1173 // nope, don't process this cell anymore; signal cell completion
1174 ccidx := -1;
1175 if assigned(cb) then
1176 begin
1177 if cb(nil, 0, x, y, prevX, prevY) then begin result := lastObj; exit; end;
1178 end
1179 else if wasHit then
1180 begin
1181 result := lastObj;
1182 exit;
1183 end;
1184 end;
1185 // convert coords to grid
1186 Dec(x, minx);
1187 Dec(y, miny);
1188 end;
1189 end;
1190 end;
1191 *)
1194 // no callback: return `true` on the nearest hit
1195 // you are not supposed to understand this
1196 function TBodyGridBase.traceRay (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
1197 const
1199 var
1225 begin
1233 if (ax0 = ax1) and (ay0 = ay1) then exit; // as the first point is ignored, just get outta here
1249 // offset query coords to (0,0)-based
1255 // clip rectange
1261 // horizontal setup
1263 begin
1264 // from left to right
1267 end
1268 else
1269 begin
1270 // from right to left
1280 // vertical setup
1282 begin
1283 // from top to bottom
1286 end
1287 else
1288 begin
1289 // from bottom to top
1303 begin
1312 end
1313 else
1314 begin
1328 begin
1329 // clip at top
1335 begin
1344 begin
1345 // clip at left
1356 begin
1357 // clip at bottom
1367 //if (term = xd) then exit; // this is the only point, get out of here
1373 // first move, to skip starting point
1377 // move coords
1380 // done?
1383 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ > mHeight*tsize) then raise Exception.Create('raycaster internal error (0)');
1385 if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
1387 // restore query coords
1390 //Inc(ax1, minx);
1391 //Inc(ay1, miny);
1393 // increase query counter
1396 begin
1397 // just in case of overflow
1404 // draw it; can omit checks
1406 begin
1407 // check cell(s)
1408 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ > mHeight*tsize) then raise Exception.Create('raycaster internal error (0)');
1409 // new tile?
1412 begin
1413 // yes
1415 begin
1416 // signal cell completion
1418 begin
1420 end
1422 begin
1424 exit;
1430 // has something to process in this tile?
1432 begin
1433 // process cell
1435 hasUntried := false; // this will be set to `true` if we have some proxies we still want to process at the next step
1436 // convert coords to map (to avoid ajdusting coords inside the loop)
1439 // process cell list
1441 begin
1444 begin
1449 begin
1450 // can we process this proxy?
1452 begin
1455 begin
1457 begin
1461 exit;
1463 end
1464 else
1465 begin
1466 // remember this hitpoint if it is nearer than an old one
1469 begin
1477 end
1478 else
1479 begin
1480 // this is possibly interesting proxy, set "has more to check" flag
1485 // next cell
1488 // still has something interesting in this cell?
1490 begin
1491 // nope, don't process this cell anymore; signal cell completion
1494 begin
1496 end
1498 begin
1500 exit;
1504 //putPixel(xptr^, yptr^);
1505 // move coords
1514 // ////////////////////////////////////////////////////////////////////////// //
1515 //FIXME! optimize this with real tile walking
1516 function TBodyGridBase.forEachAlongLine (x0, y0, x1, y1: Integer; cb: TGridAlongQueryCB; tagmask: Integer=-1): ITP;
1517 const
1519 var
1538 begin
1557 // `x` and `y` will be in grid coords
1561 // increase query counter
1564 begin
1565 // just in case of overflow
1571 // cache various things
1572 //tsize := mTileSize;
1578 // setup distance and flags
1581 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1584 // it is slightly faster this way
1588 // now trace
1590 begin
1591 // do one step
1594 // invariant: one of those always changed
1595 if (xerr < 0) and (yerr < 0) then raise Exception.Create('internal bug in grid raycaster (0)');
1598 // invariant: we always doing a step
1600 begin
1601 // check for crossing tile/grid boundary
1603 begin
1604 // we're still in grid
1606 // check for tile edge crossing
1612 // crossed tile edge?
1614 begin
1615 // setup new cell index
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)
1634 // process cell list
1636 begin
1639 begin
1644 begin
1649 // next cell
1653 // convert coords to grid