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;
164 implementation
166 uses
170 // ////////////////////////////////////////////////////////////////////////// //
171 procedure swapInt (var a: Integer; var b: Integer); inline; var t: Integer; begin t := a; a := b; b := t; end;
173 function distanceSq (x0, y0, x1, y1: Integer): Integer; inline; begin result := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0); end;
176 // ////////////////////////////////////////////////////////////////////////// //
177 // you are not supposed to understand this
178 // returns `true` if there is an intersection, and enter coords
179 // enter coords will be equal to (x0, y0) if starting point is inside the box
180 // if result is `false`, `inx` and `iny` are undefined
181 function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean;
182 var
194 begin
196 // why not
202 begin
203 // check this point
205 exit;
208 // check if staring point is inside the box
209 if (x0 >= bx) and (y0 >= by) and (x0 < bx+bw) and (y0 < by+bh) then begin result := true; exit; end;
211 // clip rectange
217 // horizontal setup
219 begin
220 // from left to right
223 end
224 else
225 begin
226 // from right to left
236 // vertical setup
238 begin
239 // from top to bottom
242 end
243 else
244 begin
245 // from bottom to top
259 begin
268 end
269 else
270 begin
284 begin
285 // clip at top
291 begin
300 begin
301 // clip at left
312 begin
313 // clip at bottom
323 //if (term = xd) then exit; // this is the only point, get out of here
335 // ////////////////////////////////////////////////////////////////////////// //
336 procedure TBodyGridBase.TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer);
337 begin
349 // ////////////////////////////////////////////////////////////////////////// //
350 constructor TBodyGridBase.Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
351 var
353 begin
355 {
356 if aTileSize < 1 then aTileSize := 1;
357 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
358 mTileSize := aTileSize;
359 }
370 // init free list
372 begin
377 // init grid
379 // init proxies
387 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth, mHeight, mTileSize, mWidth*mTileSize, mHeight*mTileSize]), MSG_NOTIFY);
392 begin
400 // ////////////////////////////////////////////////////////////////////////// //
402 var
404 begin
407 begin
411 begin
417 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);
421 // ////////////////////////////////////////////////////////////////////////// //
422 function TBodyGridBase.getGridWidthPx (): Integer; inline; begin result := mWidth*mTileSize; end;
423 function TBodyGridBase.getGridHeightPx (): Integer; inline; begin result := mHeight*mTileSize; end;
427 begin
428 // fix coords
436 begin
438 begin
441 end
442 else
443 begin
451 // ////////////////////////////////////////////////////////////////////////// //
453 begin
459 begin
461 begin
463 begin
465 end
466 else
467 begin
474 // ////////////////////////////////////////////////////////////////////////// //
476 var
478 begin
480 begin
481 // no free cells, want more
485 begin
496 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
501 begin
503 begin
504 //if mCells[idx].body = -1 then exit; // the thing that should not be
513 // ////////////////////////////////////////////////////////////////////////// //
514 function TBodyGridBase.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer): TBodyProxyId;
515 var
518 begin
520 begin
521 // no free proxies, resize list
528 // get one from list
533 // add to used list
535 // statistics
541 begin
543 if (mProxyCount = 0) then raise Exception.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
544 // add to free list
552 // ////////////////////////////////////////////////////////////////////////// //
553 function TBodyGridBase.forGridRect (x, y, w, h: Integer; cb: TGridInternalCB; bodyId: TBodyProxyId): Boolean;
554 const
556 var
559 begin
562 // fix coords
565 // go on
569 //tsize := mTileSize;
572 begin
576 begin
586 // ////////////////////////////////////////////////////////////////////////// //
588 var
593 begin
595 // add body to the given grid cell
598 begin
602 begin
604 begin
605 // can add here
608 exit;
612 // either no room, or no cell at all
621 var
623 begin
630 // absolutely not tested
632 var
636 begin
638 // find and remove cell
642 begin
647 begin
649 begin
650 // i found her!
652 begin
653 // this cell contains no elements, remove it
657 end
658 else
659 begin
660 // remove element from bucket
663 begin
679 // absolutely not tested
681 var
683 begin
690 // ////////////////////////////////////////////////////////////////////////// //
691 function TBodyGridBase.insertBody (aObj: ITP; aX, aY, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
692 begin
700 begin
707 // ////////////////////////////////////////////////////////////////////////// //
709 var
712 begin
720 // did any corner crossed tile boundary?
725 begin
732 end
733 else
734 begin
743 var
746 begin
749 // check if tile coords was changed
754 begin
755 // crossed tile boundary, do heavy work
757 end
758 else
759 begin
760 // nothing to do with the grid, just fix coordinates
767 var
771 begin
774 // check if tile coords was changed
782 begin
783 // crossed tile boundary, do heavy work
785 end
786 else
787 begin
788 // nothing to do with the grid, just fix size
795 // ////////////////////////////////////////////////////////////////////////// //
796 // no callback: return `true` on the first hit
797 function TBodyGridBase.forEachAtPoint (x, y: Integer; cb: TGridQueryCB; tagmask: Integer=-1): ITP;
798 var
805 begin
810 // make coords (0,0)-based
816 // restore coords
820 // increase query counter
823 begin
824 // just in case of overflow
831 begin
834 begin
839 begin
841 begin
844 begin
846 end
847 else
848 begin
850 exit;
860 // ////////////////////////////////////////////////////////////////////////// //
861 // no callback: return `true` on the first hit
862 function TBodyGridBase.forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
863 const
865 var
876 begin
885 // fix coords
890 //tsize := mTileSize;
895 // increase query counter
898 begin
899 // just in case of overflow
903 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
906 // go on
908 begin
912 begin
915 // process cells
918 begin
921 begin
927 //if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
928 //if ( ((ptag and TagDisabled) = 0) = ignoreDisabled) and ((ptag and tagmask) <> 0) and (px.mQueryMark <> lq) then
929 begin
934 begin
936 end
937 else
938 begin
940 exit;
951 // ////////////////////////////////////////////////////////////////////////// //
952 // no callback: return `true` on the nearest hit
953 function TBodyGridBase.traceRay (x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
954 var
956 begin
961 // no callback: return `true` on the nearest hit
962 // you are not supposed to understand this
963 function TBodyGridBase.traceRay (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
964 const
966 var
992 begin
1000 if (ax0 = ax1) and (ay0 = ay1) then exit; // as the first point is ignored, just get outta here
1016 // offset query coords to (0,0)-based
1022 // clip rectange
1028 // horizontal setup
1030 begin
1031 // from left to right
1034 end
1035 else
1036 begin
1037 // from right to left
1047 // vertical setup
1049 begin
1050 // from top to bottom
1053 end
1054 else
1055 begin
1056 // from bottom to top
1070 begin
1079 end
1080 else
1081 begin
1095 begin
1096 // clip at top
1102 begin
1111 begin
1112 // clip at left
1123 begin
1124 // clip at bottom
1134 //if (term = xd) then exit; // this is the only point, get out of here
1140 // first move, to skip starting point
1144 // move coords
1147 // done?
1150 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ > mHeight*tsize) then raise Exception.Create('raycaster internal error (0)');
1152 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
1154 // restore query coords
1157 //Inc(ax1, minx);
1158 //Inc(ay1, miny);
1160 // increase query counter
1163 begin
1164 // just in case of overflow
1171 // draw it; can omit checks
1173 begin
1174 // check cell(s)
1175 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ > mHeight*tsize) then raise Exception.Create('raycaster internal error (0)');
1176 // new tile?
1179 begin
1180 // yes
1182 begin
1183 // signal cell completion
1185 begin
1187 end
1189 begin
1191 exit;
1197 // has something to process in this tile?
1199 begin
1200 // process cell
1202 hasUntried := false; // this will be set to `true` if we have some proxies we still want to process at the next step
1203 // convert coords to map (to avoid ajdusting coords inside the loop)
1206 // process cell list
1208 begin
1211 begin
1216 begin
1217 // can we process this proxy?
1219 begin
1222 begin
1224 begin
1228 exit;
1230 end
1231 else
1232 begin
1233 // remember this hitpoint if it is nearer than an old one
1236 begin
1244 end
1245 else
1246 begin
1247 // this is possibly interesting proxy, set "has more to check" flag
1252 // next cell
1255 // still has something interesting in this cell?
1257 begin
1258 // nope, don't process this cell anymore; signal cell completion
1261 begin
1263 end
1265 begin
1267 exit;
1271 //putPixel(xptr^, yptr^);
1272 // move coords
1281 // ////////////////////////////////////////////////////////////////////////// //
1282 //FIXME! optimize this with real tile walking
1283 function TBodyGridBase.forEachAlongLine (x0, y0, x1, y1: Integer; cb: TGridAlongQueryCB; tagmask: Integer=-1): ITP;
1284 const
1286 var
1305 begin
1324 // `x` and `y` will be in grid coords
1328 // increase query counter
1331 begin
1332 // just in case of overflow
1338 // cache various things
1339 //tsize := mTileSize;
1345 // setup distance and flags
1348 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1351 // it is slightly faster this way
1355 // now trace
1357 begin
1358 // do one step
1361 // invariant: one of those always changed
1362 if (xerr < 0) and (yerr < 0) then raise Exception.Create('internal bug in grid raycaster (0)');
1365 // invariant: we always doing a step
1367 begin
1368 // check for crossing tile/grid boundary
1370 begin
1371 // we're still in grid
1373 // check for tile edge crossing
1379 // crossed tile edge?
1381 begin
1382 // setup new cell index
1385 end
1386 else
1387 begin
1388 // out of grid
1393 // has something to process in the current cell?
1395 begin
1396 // process cell
1398 // convert coords to map (to avoid ajdusting coords inside the loop)
1401 // process cell list
1403 begin
1406 begin
1411 begin
1416 // next cell
1420 // convert coords to grid