925fb80ca042470d6b78acb7cb623952ef2d77cb
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; log: Boolean=false): 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;
167 implementation
169 uses
173 // ////////////////////////////////////////////////////////////////////////// //
174 procedure swapInt (var a: Integer; var b: Integer); inline; var t: Integer; begin t := a; a := b; b := t; end;
175 function minInt (a, b: Integer): Integer; inline; begin if (a < b) then result := a else result := b; end;
176 function maxInt (a, b: Integer): Integer; inline; begin if (a > b) then result := a else result := b; end;
178 function distanceSq (x0, y0, x1, y1: Integer): Integer; inline; begin result := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0); end;
181 // ////////////////////////////////////////////////////////////////////////// //
182 // you are not supposed to understand this
183 // returns `true` if there is an intersection, and enter coords
184 // enter coords will be equal to (x0, y0) if starting point is inside the box
185 // if result is `false`, `inx` and `iny` are undefined
186 function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean;
187 var
195 //!term: Integer;
199 begin
201 // why not
207 begin
208 // check this point
210 exit;
213 // check if staring point is inside the box
214 if (x0 >= bx) and (y0 >= by) and (x0 < bx+bw) and (y0 < by+bh) then begin result := true; exit; end;
216 // clip rectange
222 // horizontal setup
224 begin
225 // from left to right
228 end
229 else
230 begin
231 // from right to left
241 // vertical setup
243 begin
244 // from top to bottom
247 end
248 else
249 begin
250 // from bottom to top
264 begin
273 end
274 else
275 begin
285 //!term := x1;
289 begin
290 // clip at top
296 begin
305 begin
306 // clip at left
316 (*
317 if (y1 > wy1) then
318 begin
319 // clip at bottom
320 temp := dx2*(wy1-y0)+dsx;
321 term := x0+temp div dy2;
322 rem := temp mod dy2;
323 if (rem = 0) then Dec(term);
324 end;
326 if (term > wx1) then term := wx1; // clip at right
328 Inc(term); // draw last point
329 //if (term = xd) then exit; // this is the only point, get out of here
330 *)
334 //!dx2 -= dy2;
342 // ////////////////////////////////////////////////////////////////////////// //
343 procedure TBodyGridBase.TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer);
344 begin
356 // ////////////////////////////////////////////////////////////////////////// //
357 constructor TBodyGridBase.Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
358 var
360 begin
362 {
363 if aTileSize < 1 then aTileSize := 1;
364 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
365 mTileSize := aTileSize;
366 }
377 // init free list
379 begin
384 // init grid
386 // init proxies
394 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth, mHeight, mTileSize, mWidth*mTileSize, mHeight*mTileSize]), MSG_NOTIFY);
399 begin
407 // ////////////////////////////////////////////////////////////////////////// //
409 var
411 begin
414 begin
418 begin
424 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);
428 // ////////////////////////////////////////////////////////////////////////// //
429 function TBodyGridBase.getGridWidthPx (): Integer; inline; begin result := mWidth*mTileSize; end;
430 function TBodyGridBase.getGridHeightPx (): Integer; inline; begin result := mHeight*mTileSize; end;
434 begin
435 // fix coords
443 begin
445 begin
448 end
449 else
450 begin
458 // ////////////////////////////////////////////////////////////////////////// //
460 begin
466 begin
468 begin
470 begin
472 end
473 else
474 begin
481 // ////////////////////////////////////////////////////////////////////////// //
483 var
485 begin
487 begin
488 // no free cells, want more
492 begin
503 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
508 begin
510 begin
511 //if mCells[idx].body = -1 then exit; // the thing that should not be
520 // ////////////////////////////////////////////////////////////////////////// //
521 function TBodyGridBase.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer): TBodyProxyId;
522 var
525 begin
527 begin
528 // no free proxies, resize list
535 // get one from list
540 // add to used list
542 // statistics
548 begin
550 if (mProxyCount = 0) then raise Exception.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
551 // add to free list
559 // ////////////////////////////////////////////////////////////////////////// //
560 function TBodyGridBase.forGridRect (x, y, w, h: Integer; cb: TGridInternalCB; bodyId: TBodyProxyId): Boolean;
561 const
563 var
566 begin
569 // fix coords
572 // go on
576 //tsize := mTileSize;
579 begin
583 begin
593 // ////////////////////////////////////////////////////////////////////////// //
595 var
600 begin
602 // add body to the given grid cell
605 begin
609 begin
611 begin
612 // can add here
615 exit;
619 // either no room, or no cell at all
628 var
630 begin
637 // absolutely not tested
639 var
643 begin
645 // find and remove cell
649 begin
654 begin
656 begin
657 // i found her!
659 begin
660 // this cell contains no elements, remove it
664 end
665 else
666 begin
667 // remove element from bucket
670 begin
686 // absolutely not tested
688 var
690 begin
697 // ////////////////////////////////////////////////////////////////////////// //
698 function TBodyGridBase.insertBody (aObj: ITP; aX, aY, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
699 begin
707 begin
714 // ////////////////////////////////////////////////////////////////////////// //
716 var
719 begin
727 // did any corner crossed tile boundary?
732 begin
739 end
740 else
741 begin
750 var
753 begin
755 // check if tile coords was changed
761 begin
762 // crossed tile boundary, do heavy work
767 end
768 else
769 begin
770 // nothing to do with the grid, just fix coordinates
777 var
780 begin
782 // check if tile coords was changed
790 begin
791 // crossed tile boundary, do heavy work
796 end
797 else
798 begin
799 // nothing to do with the grid, just fix size
806 // ////////////////////////////////////////////////////////////////////////// //
807 // no callback: return `true` on the first hit
808 function TBodyGridBase.forEachAtPoint (x, y: Integer; cb: TGridQueryCB; tagmask: Integer=-1): ITP;
809 var
816 begin
821 // make coords (0,0)-based
827 // restore coords
831 // increase query counter
834 begin
835 // just in case of overflow
842 begin
845 begin
850 begin
852 begin
855 begin
857 end
858 else
859 begin
861 exit;
871 // ////////////////////////////////////////////////////////////////////////// //
872 // no callback: return `true` on the first hit
873 function TBodyGridBase.forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
874 const
876 var
887 begin
896 // fix coords
901 //tsize := mTileSize;
906 // increase query counter
909 begin
910 // just in case of overflow
914 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
917 // go on
919 begin
923 begin
926 // process cells
929 begin
932 begin
938 //if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
939 //if ( ((ptag and TagDisabled) = 0) = ignoreDisabled) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
940 begin
945 begin
947 end
948 else
949 begin
951 exit;
962 // ////////////////////////////////////////////////////////////////////////// //
963 // no callback: return `true` on the nearest hit
964 function TBodyGridBase.traceRay (x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
965 var
967 begin
972 // no callback: return `true` on the nearest hit
973 // you are not supposed to understand this
974 function TBodyGridBase.traceRay (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
975 const
977 var
1003 begin
1011 if (ax0 = ax1) and (ay0 = ay1) then exit; // as the first point is ignored, just get outta here
1027 // offset query coords to (0,0)-based
1033 // clip rectange
1039 // horizontal setup
1041 begin
1042 // from left to right
1045 end
1046 else
1047 begin
1048 // from right to left
1058 // vertical setup
1060 begin
1061 // from top to bottom
1064 end
1065 else
1066 begin
1067 // from bottom to top
1081 begin
1090 end
1091 else
1092 begin
1106 begin
1107 // clip at top
1113 begin
1122 begin
1123 // clip at left
1134 begin
1135 // clip at bottom
1145 //if (term = xd) then exit; // this is the only point, get out of here
1151 // first move, to skip starting point
1155 // move coords
1158 // done?
1161 {$IF DEFINED(D2F_DEBUG)}
1162 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ > mHeight*tsize) then raise Exception.Create('raycaster internal error (0)');
1163 {$ENDIF}
1165 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
1167 // restore query coords
1170 //Inc(ax1, minx);
1171 //Inc(ay1, miny);
1173 // increase query counter
1176 begin
1177 // just in case of overflow
1184 // draw it; can omit checks
1186 begin
1187 // check cell(s)
1188 {$IF DEFINED(D2F_DEBUG)}
1189 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ > mHeight*tsize) then raise Exception.Create('raycaster internal error (0)');
1190 {$ENDIF}
1191 // new tile?
1194 begin
1195 // yes
1197 begin
1198 // signal cell completion
1200 begin
1202 end
1204 begin
1206 exit;
1212 // has something to process in this tile?
1214 begin
1215 // process cell
1217 hasUntried := false; // this will be set to `true` if we have some proxies we still want to process at the next step
1218 // convert coords to map (to avoid ajdusting coords inside the loop)
1221 // process cell list
1223 begin
1226 begin
1231 begin
1232 // can we process this proxy?
1234 begin
1237 begin
1239 begin
1243 exit;
1245 end
1246 else
1247 begin
1248 // remember this hitpoint if it is nearer than an old one
1251 begin
1259 end
1260 else
1261 begin
1262 // this is possibly interesting proxy, set "has more to check" flag
1267 // next cell
1270 // still has something interesting in this cell?
1272 begin
1273 // nope, don't process this cell anymore; signal cell completion
1276 begin
1278 end
1280 begin
1282 exit;
1286 //putPixel(xptr^, yptr^);
1287 // move coords
1296 // ////////////////////////////////////////////////////////////////////////// //
1297 //FIXME! optimize this with real tile walking
1298 function TBodyGridBase.forEachAlongLine (x0, y0, x1, y1: Integer; cb: TGridAlongQueryCB; tagmask: Integer=-1; log: Boolean=false): ITP;
1299 const
1301 var
1320 begin
1339 // `x` and `y` will be in grid coords
1343 // increase query counter
1346 begin
1347 // just in case of overflow
1353 // cache various things
1354 //tsize := mTileSize;
1360 // setup distance and flags
1363 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1366 // it is slightly faster this way
1370 if (log) then e_WriteLog(Format('tracing: (%d,%d)-(%d,%d)', [x, y, x1-minx, y1-miny]), MSG_NOTIFY);
1372 // now trace
1375 begin
1377 // do one step
1380 // invariant: one of those always changed
1381 {$IF DEFINED(D2F_DEBUG)}
1382 if (xerr < 0) and (yerr < 0) then raise Exception.Create('internal bug in grid raycaster (0)');
1383 {$ENDIF}
1386 // invariant: we always doing a step
1387 {$IF DEFINED(D2F_DEBUG)}
1389 {$ENDIF}
1390 begin
1391 // check for crossing tile/grid boundary
1393 begin
1394 // we're still in grid
1396 // check for tile edge crossing
1402 // crossed tile edge?
1404 begin
1405 // setup new cell index
1407 if (log) then e_WriteLog(Format(' stepped to new tile (%d,%d) -- (%d,%d)', [(x div tsize), (y div tsize), x, y]), MSG_NOTIFY);
1408 end
1409 else
1411 begin
1412 // we have nothing interesting here anymore, jump directly to tile edge
1413 // get minimal distance to tile edges
1414 if (incx < 0) then tedist := x-(x and (not tsize)) else if (incx > 0) then tedist := (x or (tsize+1))-x else tedist := 0;
1415 {$IF DEFINED(D2F_DEBUG)}
1417 {$ENDIF}
1418 if (incy < 0) then f := y-(y and (not tsize)) else if (incy > 0) then f := (y or (tsize+1))-y else f := 0;
1419 {$IF DEFINED(D2F_DEBUG)}
1421 {$ENDIF}
1423 // do jump
1425 begin
1426 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);
1432 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);
1435 end
1436 else
1437 begin
1438 // out of grid
1443 // has something to process in the current cell?
1445 begin
1446 // process cell
1448 // convert coords to map (to avoid ajdusting coords inside the loop)
1449 //Inc(x, minx);
1450 //Inc(y, miny);
1451 // process cell list
1453 begin
1456 begin
1461 begin
1466 // next cell
1470 // convert coords to grid
1471 //Dec(x, minx);
1472 //Dec(y, miny);