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}
25 interface
28 type
32 public
33 type TGridQueryCB = function (obj: ITP; tag: Integer): Boolean is nested; // return `true` to stop
34 type TGridRayQueryCB = function (obj: ITP; tag: Integer; x, y, prevx, prevy: Integer): Boolean is nested; // return `true` to stop
35 type TGridAlongQueryCB = function (obj: ITP; tag: Integer): Boolean is nested; // return `true` to stop
42 private
43 const
47 private
48 type
51 private
58 private
68 TGridInternalCB = function (grida: Integer; bodyId: TBodyProxyId): Boolean of object; // return `true` to stop
70 private
71 //mTileSize: Integer;
74 public
77 private
90 private
91 // optimized horizontal tracer
92 //function traceRayHOpt (out ex, ey: Integer; const ax0, ay0, ax1, ay1, x0, len, dx: Integer; cb: TGridRayQueryCB; tagmask: Integer): ITP;
94 public
96 {$IF DEFINED(D2F_DEBUG)}
98 {$ENDIF}
100 private
121 public
122 constructor Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
125 function insertBody (aObj: ITP; ax, ay, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
134 // `false` if `body` is surely invalid
139 //WARNING: don't modify grid while any query is in progress (no checks are made!)
140 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
141 // no callback: return `true` on the first hit
142 function forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
144 //WARNING: don't modify grid while any query is in progress (no checks are made!)
145 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
146 // no callback: return object on the first hit or nil
147 function forEachAtPoint (x, y: Integer; cb: TGridQueryCB; tagmask: Integer=-1; exittag: PInteger=nil): ITP;
149 //WARNING: don't modify grid while any query is in progress (no checks are made!)
150 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
151 // cb with `(nil)` will be called before processing new tile
152 // no callback: return object of the nearest hit or nil
153 // if `inverted` is true, trace will register bodies *exluding* tagmask
154 //WARNING: don't change tags in callbacks here!
155 function traceRay (const x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP; overload;
156 function traceRay (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
158 //function traceRayWhileIn (const x0, y0, x1, y1: Integer; tagmask: Integer=-1): ITP; overload;
159 //function traceRayWhileIn (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): ITP;
161 //WARNING: don't modify grid while any query is in progress (no checks are made!)
162 // you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
163 // trace line along the grid, calling `cb` for all objects in passed cells, in no particular order
164 //WARNING: don't change tags in callbacks here!
165 function forEachAlongLine (const x0, y0, x1, y1: Integer; cb: TGridAlongQueryCB; tagmask: Integer=-1; log: Boolean=false): ITP;
167 // debug
172 //WARNING! no sanity checks!
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;
195 implementation
197 uses
201 // ////////////////////////////////////////////////////////////////////////// //
202 procedure swapInt (var a: Integer; var b: Integer); inline; var t: Integer; begin t := a; a := b; b := t; end;
203 function minInt (a, b: Integer): Integer; inline; begin if (a < b) then result := a else result := b; end;
204 function maxInt (a, b: Integer): Integer; inline; begin if (a > b) then result := a else result := b; end;
206 function distanceSq (x0, y0, x1, y1: Integer): Integer; inline; begin result := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0); end;
209 // ////////////////////////////////////////////////////////////////////////// //
210 // you are not supposed to understand this
211 // returns `true` if there is an intersection, and enter coords
212 // enter coords will be equal to (x0, y0) if starting point is inside the box
213 // if result is `false`, `inx` and `iny` are undefined
214 function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean;
215 var
223 //!term: Integer;
227 begin
229 // why not
235 begin
236 // check this point
238 exit;
241 // check if staring point is inside the box
242 if (x0 >= bx) and (y0 >= by) and (x0 < bx+bw) and (y0 < by+bh) then begin result := true; exit; end;
244 // clip rectange
250 // horizontal setup
252 begin
253 // from left to right
256 end
257 else
258 begin
259 // from right to left
269 // vertical setup
271 begin
272 // from top to bottom
275 end
276 else
277 begin
278 // from bottom to top
292 begin
301 end
302 else
303 begin
313 //!term := x1;
317 begin
318 // clip at top
324 begin
333 begin
334 // clip at left
344 (*
345 if (y1 > wy1) then
346 begin
347 // clip at bottom
348 temp := dx2*(wy1-y0)+dsx;
349 term := x0+temp div dy2;
350 rem := temp mod dy2;
351 if (rem = 0) then Dec(term);
352 end;
354 if (term > wx1) then term := wx1; // clip at right
356 Inc(term); // draw last point
357 //if (term = xd) then exit; // this is the only point, get out of here
358 *)
362 //!dx2 -= dy2;
370 // ////////////////////////////////////////////////////////////////////////// //
371 procedure TBodyGridBase.TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer);
372 begin
384 // ////////////////////////////////////////////////////////////////////////// //
385 constructor TBodyGridBase.Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer{; aTileSize: Integer=GridDefaultTileSize});
386 var
388 begin
390 {$IF DEFINED(D2F_DEBUG)}
392 {$ENDIF}
393 {
394 if aTileSize < 1 then aTileSize := 1;
395 if aTileSize > 8192 then aTileSize := 8192; // arbitrary limit
396 mTileSize := aTileSize;
397 }
408 // init free list
410 begin
416 // init grid
418 // init proxies
426 e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth, mHeight, mTileSize, mWidth*mTileSize, mHeight*mTileSize]), MSG_NOTIFY);
431 begin
439 // ////////////////////////////////////////////////////////////////////////// //
441 var
443 begin
446 begin
450 begin
456 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);
461 var
464 begin
467 begin
470 begin
473 begin
475 if (cc.bodies[f] = body) then cb((g mod mWidth)*mTileSize+mMinX, (g div mWidth)*mTileSize+mMinY);
477 // next cell
485 var
488 begin
496 begin
499 begin
501 if cb(mProxies[cc.bodies[f]].mObj, mProxies[cc.bodies[f]].mTag) then begin result := mProxies[cc.bodies[f]].mObj; exit; end;
503 // next cell
509 // ////////////////////////////////////////////////////////////////////////// //
510 function TBodyGridBase.getGridWidthPx (): Integer; inline; begin result := mWidth*mTileSize; end;
511 function TBodyGridBase.getGridHeightPx (): Integer; inline; begin result := mHeight*mTileSize; end;
515 begin
516 // fix coords
524 begin
526 begin
529 end
530 else
531 begin
540 begin
542 begin
545 end
546 else
547 begin
555 function TBodyGridBase.getBodyDims (body: TBodyProxyId; out rx, ry, rw, rh: Integer): Boolean; inline;
556 begin
558 begin
561 end
562 else
563 begin
574 // ////////////////////////////////////////////////////////////////////////// //
576 begin
582 begin
584 begin
586 begin
588 end
589 else
590 begin
597 // ////////////////////////////////////////////////////////////////////////// //
599 var
602 begin
604 begin
605 // no free cells, want more
609 begin
621 //e_WriteLog(Format('grid: allocated new cell #%d (total: %d)', [result, mUsedCells]), MSG_NOTIFY);
626 begin
628 begin
630 begin
641 // ////////////////////////////////////////////////////////////////////////// //
642 function TBodyGridBase.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer): TBodyProxyId;
643 var
646 begin
648 begin
649 // no free proxies, resize list
656 // get one from list
661 // add to used list
663 // statistics
669 begin
671 if (mProxyCount = 0) then raise Exception.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
672 // add to free list
680 // ////////////////////////////////////////////////////////////////////////// //
681 function TBodyGridBase.forGridRect (x, y, w, h: Integer; cb: TGridInternalCB; bodyId: TBodyProxyId): Boolean;
682 const
684 var
687 begin
690 // fix coords
693 // go on
697 //tsize := mTileSize;
700 begin
704 begin
714 // ////////////////////////////////////////////////////////////////////////// //
716 var
721 begin
723 // add body to the given grid cell
726 begin
727 {$IF DEFINED(D2F_DEBUG)}
730 begin
733 begin
735 if (pi.bodies[f] = bodyId) then raise Exception.Create('trying to insert already inserted proxy');
739 {$ENDIF}
742 begin
744 // check "has room" flag
746 begin
747 // can add here
749 begin
751 begin
754 exit;
759 // no room, go to next cell in list (if there is any)
762 // no room in cells, add new cell to list
764 // either no room, or no cell at all
774 var
776 begin
783 // assume that we cannot have one object added to bucket twice
785 var
789 begin
791 // find and remove cell
795 begin
798 begin
800 begin
801 // i found her!
803 begin
804 // this cell contains no elements, remove it
807 exit;
809 // remove element from bucket
811 begin
816 exit;
825 var
827 begin
834 // ////////////////////////////////////////////////////////////////////////// //
835 function TBodyGridBase.insertBody (aObj: ITP; aX, aY, aWidth, aHeight: Integer; aTag: Integer=-1): TBodyProxyId;
836 begin
844 begin
851 // ////////////////////////////////////////////////////////////////////////// //
853 var
856 begin
863 {$IF DEFINED(D2F_DEBUG_MOVER)}
864 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);
865 {$ENDIF}
867 // map -> grid
872 // did any corner crossed tile boundary?
877 begin
884 end
885 else
886 begin
894 //TODO: optimize for horizontal/vertical moves
896 var
904 begin
906 // check if tile coords was changed
911 // map -> grid
916 // check for heavy work
927 {$IF DEFINED(D2F_DEBUG_MOVER)}
928 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);
929 {$ENDIF}
931 begin
932 // crossed tile boundary, do heavy work
935 // cycle with old rect, remove body where it is necessary
936 // optimized for horizontal moves
937 {$IF DEFINED(D2F_DEBUG_MOVER)}
938 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);
939 {$ENDIF}
940 // remove stale marks
943 begin
948 {$IF DEFINED(D2F_DEBUG_MOVER)}
950 {$ENDIF}
952 begin
954 begin
955 // this column is completely outside of new rect
957 begin
958 {$IF DEFINED(D2F_DEBUG_MOVER)}
960 {$ENDIF}
963 end
964 else
965 begin
966 // heavy checks
968 begin
970 begin
971 {$IF DEFINED(D2F_DEBUG_MOVER)}
973 {$ENDIF}
980 // cycle with new rect, add body where it is necessary
983 begin
988 {$IF DEFINED(D2F_DEBUG_MOVER)}
990 {$ENDIF}
992 begin
994 begin
995 // this column is completely outside of old rect
997 begin
998 {$IF DEFINED(D2F_DEBUG_MOVER)}
1000 {$ENDIF}
1003 end
1004 else
1005 begin
1006 // heavy checks
1008 begin
1010 begin
1011 {$IF DEFINED(D2F_DEBUG_MOVER)}
1013 {$ENDIF}
1020 // done
1021 end
1022 else
1023 begin
1024 {$IF DEFINED(D2F_DEBUG_MOVER)}
1025 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);
1026 {$ENDIF}
1028 // update coordinates
1034 var
1037 begin
1039 // check if tile coords was changed
1045 {$IF DEFINED(D2F_DEBUG_MOVER)}
1046 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);
1047 {$ENDIF}
1050 begin
1051 // crossed tile boundary, do heavy work
1056 end
1057 else
1058 begin
1059 // nothing to do with the grid, just fix size
1066 // ////////////////////////////////////////////////////////////////////////// //
1067 // no callback: return `true` on the first hit
1068 function TBodyGridBase.forEachAtPoint (x, y: Integer; cb: TGridQueryCB; tagmask: Integer=-1; exittag: PInteger=nil): ITP;
1069 var
1076 begin
1082 {$IF DEFINED(D2F_DEBUG_XXQ)}
1084 {$ENDIF}
1086 // make coords (0,0)-based
1093 {$IF DEFINED(D2F_DEBUG_XXQ)}
1094 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);
1095 {$ENDIF}
1097 // restore coords
1101 // increase query counter
1104 begin
1105 // just in case of overflow
1111 {$IF DEFINED(D2F_DEBUG_XXQ)}
1112 if (assigned(cb)) then e_WriteLog(Format('2: grid pointquery: (%d,%d); lq=%u', [x, y, lq]), MSG_NOTIFY);
1113 {$ENDIF}
1116 begin
1117 {$IF DEFINED(D2F_DEBUG_XXQ)}
1119 {$ENDIF}
1122 begin
1125 {$IF DEFINED(D2F_DEBUG_XXQ)}
1126 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);
1127 {$ENDIF}
1128 // shit. has to do it this way, so i can change tag in callback
1130 begin
1135 begin
1137 begin
1139 begin
1142 exit;
1144 end
1145 else
1146 begin
1149 exit;
1159 // ////////////////////////////////////////////////////////////////////////// //
1160 // no callback: return `true` on the first hit
1161 function TBodyGridBase.forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1; allowDisabled: Boolean=false): ITP;
1162 const
1164 var
1175 begin
1184 // fix coords
1189 //tsize := mTileSize;
1194 // increase query counter
1197 begin
1198 // just in case of overflow
1202 //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
1205 // go on
1207 begin
1211 begin
1214 // process cells
1217 begin
1220 begin
1223 // shit. has to do it this way, so i can change tag in callback
1232 begin
1234 end
1235 else
1236 begin
1238 exit;
1248 // ////////////////////////////////////////////////////////////////////////// //
1249 // no callback: return `true` on the nearest hit
1250 function TBodyGridBase.traceRay (const x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
1251 var
1253 begin
1258 // no callback: return `true` on the nearest hit
1259 // you are not supposed to understand this
1260 function TBodyGridBase.traceRay (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
1261 const
1263 var
1289 // horizontal walker
1292 begin
1301 begin
1304 begin
1307 exit;
1319 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1320 if assigned(dbgRayTraceTileHitCB) then e_WriteLog(Format('TRACING: (%d,%d)-(%d,%d) [(%d,%d)-(%d,%d)]; maxdistsq=%d', [ax0, ay0, ax1, ay1, minx, miny, maxx, maxy, lastDistSq]), MSG_NOTIFY);
1321 {$ENDIF}
1328 // offset query coords to (0,0)-based
1334 // clip rectange
1340 // horizontal setup
1342 begin
1343 // from left to right
1346 end
1347 else
1348 begin
1349 // from right to left
1359 // vertical setup
1361 begin
1362 // from top to bottom
1365 end
1366 else
1367 begin
1368 // from bottom to top
1382 begin
1391 end
1392 else
1393 begin
1407 begin
1408 // clip at top
1414 begin
1423 begin
1424 // clip at left
1435 begin
1436 // clip at bottom
1446 //if (term = xd) then exit; // this is the only point, get out of here
1452 // first move, to skip starting point
1453 // DON'T DO THIS! loop will take care of that
1455 begin
1458 begin
1460 begin
1462 begin
1465 end
1466 else
1467 begin
1470 end
1471 else
1472 begin
1477 exit;
1482 (*
1483 // move coords
1484 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
1485 xd += stx;
1486 // done?
1487 if (xd = term) then exit;
1488 *)
1490 {$IF DEFINED(D2F_DEBUG)}
1491 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ >= gh*tsize) then raise Exception.Create('raycaster internal error (0)');
1492 {$ENDIF}
1493 // DON'T DO THIS! loop will take care of that
1494 //lastGA := (yptr^ div tsize)*gw+(xptr^ div tsize);
1495 //ccidx := mGrid[lastGA];
1497 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1498 //if assigned(dbgRayTraceTileHitCB) then e_WriteLog('1:TRACING!', MSG_NOTIFY);
1499 {$ENDIF}
1501 //if (dbgShowTraceLog) then e_WriteLog(Format('raycast start: (%d,%d)-(%d,%d); xptr^=%d; yptr^=%d', [ax0, ay0, ax1, ay1, xptr^, yptr^]), MSG_NOTIFY);
1503 // increase query counter
1506 begin
1507 // just in case of overflow
1513 // if this is strict horizontal trace, use optimized codepath
1515 begin
1516 // horizontal trace: walk the whole tiles, calculating mindist once for each proxy in cell
1517 // stx < 0: going left, otherwise `stx` is > 0, and we're going right
1518 // vertical trace: walk the whole tiles, calculating mindist once for each proxy in cell
1519 // stx < 0: going up, otherwise `stx` is > 0, and we're going down
1522 {$IF DEFINED(D2F_DEBUG)}
1524 {$ENDIF}
1526 // one of those will never change
1529 {$IF DEFINED(D2F_DEBUG)}
1531 begin
1533 end
1534 else
1535 begin
1538 {$ENDIF}
1540 begin
1541 {$IF DEFINED(D2F_DEBUG)}
1542 if dbgShowTraceLog then e_LogWritefln(' htrace; ga=%d; x=%d, y=%d; y=%d; y=%d', [ga, xptr^+minx, yptr^+miny, y, ay0]);
1543 {$ENDIF}
1544 // new tile?
1546 begin
1549 // convert coords to map (to avoid ajdusting coords inside the loop)
1552 begin
1555 begin
1560 // constant coord should be inside
1563 begin
1565 // inside the proxy?
1568 begin
1570 begin
1572 begin
1576 exit;
1579 end
1580 else
1581 begin
1583 {$IF DEFINED(D2F_DEBUG)}
1584 if dbgShowTraceLog then e_LogWritefln(' EMBEDDED hhit(%d): a=(%d,%d), h=(%d,%d), distsq=%d; lastsq=%d', [cc.bodies[f], ax0, ay0, x, y, distSq, lastDistSq]);
1585 {$ENDIF}
1587 begin
1591 exit;
1594 continue;
1596 // remember this hitpoint if it is nearer than an old one
1598 begin
1601 begin
1602 // going left
1605 end
1606 else
1607 begin
1608 // going right
1612 end
1613 else
1614 begin
1617 begin
1618 // going up
1621 end
1622 else
1623 begin
1624 // going down
1630 begin
1632 begin
1634 end
1635 else
1636 begin
1640 begin
1644 exit;
1648 end
1649 else
1650 begin
1652 {$IF DEFINED(D2F_DEBUG)}
1653 if dbgShowTraceLog then e_LogWritefln(' hhit(%d): a=(%d,%d), h=(%d,%d), p=(%d,%d), distsq=%d; lastsq=%d', [cc.bodies[f], ax0, ay0, x, y, prevx, prevy, distSq, lastDistSq]);
1654 {$ENDIF}
1656 begin
1666 // next cell
1671 // skip to next tile
1673 begin
1675 begin
1676 // to the right
1678 {$IF DEFINED(D2F_DEBUG)}
1680 {$ENDIF}
1684 end
1685 else
1686 begin
1687 // to the left
1689 {$IF DEFINED(D2F_DEBUG)}
1691 {$ENDIF}
1696 end
1697 else
1698 begin
1700 begin
1701 // to the down
1703 {$IF DEFINED(D2F_DEBUG)}
1705 {$ENDIF}
1709 end
1710 else
1711 begin
1712 // to the up
1714 {$IF DEFINED(D2F_DEBUG)}
1716 {$ENDIF}
1724 // we can travel less than one cell
1726 exit;
1729 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1730 if assigned(dbgRayTraceTileHitCB) then dbgRayTraceTileHitCB((xptr^ div tsize*tsize)+minx, (yptr^ div tsize*tsize)+miny);
1731 {$ENDIF}
1734 // can omit checks
1736 begin
1737 // check cell(s)
1738 {$IF DEFINED(D2F_DEBUG)}
1739 if (xptr^ < 0) or (yptr^ < 0) or (xptr^ >= gw*tsize) and (yptr^ >= gh*tsize) then raise Exception.Create('raycaster internal error (0)');
1740 {$ENDIF}
1741 // new tile?
1743 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1744 if assigned(dbgRayTraceTileHitCB) then e_WriteLog(Format(' xd=%d; term=%d; gx=%d; gy=%d; ga=%d; lastga=%d', [xd, term, xptr^, yptr^, ga, lastGA]), MSG_NOTIFY);
1745 {$ENDIF}
1747 begin
1748 // yes
1749 {$IF DEFINED(D2F_DEBUG)}
1750 if assigned(dbgRayTraceTileHitCB) then dbgRayTraceTileHitCB((xptr^ div tsize*tsize)+minx, (yptr^ div tsize*tsize)+miny);
1751 {$ENDIF}
1753 begin
1754 // signal cell completion
1756 begin
1758 end
1760 begin
1762 exit;
1768 // has something to process in this tile?
1770 begin
1771 // process cell
1773 hasUntried := false; // this will be set to `true` if we have some proxies we still want to process at the next step
1774 // convert coords to map (to avoid ajdusting coords inside the loop)
1777 // process cell list
1779 begin
1782 begin
1787 begin
1788 // can we process this proxy?
1790 begin
1793 begin
1795 begin
1799 exit;
1801 (*
1802 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1803 distSq := distanceSq(ax0, ay0, prevx, prevy);
1804 if assigned(dbgRayTraceTileHitCB) then e_WriteLog(Format(' hit(%d): a=(%d,%d), h=(%d,%d), p=(%d,%d); distsq=%d; lastsq=%d', [cc.bodies[f], ax0, ay0, x, y, prevx, prevy, distSq, lastDistSq]), MSG_NOTIFY);
1805 if (distSq < lastDistSq) then
1806 begin
1807 wasHit := true;
1808 lastDistSq := distSq;
1809 ex := prevx;
1810 ey := prevy;
1811 lastObj := px.mObj;
1812 end;
1813 {$ENDIF}
1814 *)
1815 end
1816 else
1817 begin
1818 // remember this hitpoint if it is nearer than an old one
1820 {$IF DEFINED(D2F_DEBUG_RAYTRACE)}
1821 if assigned(dbgRayTraceTileHitCB) then e_WriteLog(Format(' hit(%d): a=(%d,%d), h=(%d,%d), p=(%d,%d); distsq=%d; lastsq=%d', [cc.bodies[f], ax0, ay0, x, y, prevx, prevy, distSq, lastDistSq]), MSG_NOTIFY);
1822 {$ENDIF}
1824 begin
1832 end
1833 else
1834 begin
1835 // this is possibly interesting proxy, set "has more to check" flag
1840 // next cell
1843 // still has something interesting in this cell?
1845 begin
1846 // nope, don't process this cell anymore; signal cell completion
1849 begin
1851 end
1853 begin
1855 exit;
1859 //putPixel(xptr^, yptr^);
1860 // move coords
1866 // we can travel less than one cell
1868 begin
1870 end
1871 else
1872 begin
1879 // ////////////////////////////////////////////////////////////////////////// //
1880 //FIXME! optimize this with real tile walking
1881 function TBodyGridBase.forEachAlongLine (const x0, y0, x1, y1: Integer; cb: TGridAlongQueryCB; tagmask: Integer=-1; log: Boolean=false): ITP;
1882 const
1884 var
1903 //tedist: Integer;
1904 begin
1926 // `x` and `y` will be in grid coords
1930 // increase query counter
1933 begin
1934 // just in case of overflow
1940 // cache various things
1941 //tsize := mTileSize;
1947 // setup distance and flags
1950 // setup starting tile ('cause we'll adjust tile vars only on tile edge crossing)
1953 // it is slightly faster this way
1957 if (log) then e_WriteLog(Format('tracing: (%d,%d)-(%d,%d)', [x, y, x1-minx, y1-miny]), MSG_NOTIFY);
1959 // now trace
1962 begin
1964 // do one step
1967 // invariant: one of those always changed
1968 {$IF DEFINED(D2F_DEBUG)}
1969 if (xerr < 0) and (yerr < 0) then raise Exception.Create('internal bug in grid raycaster (0)');
1970 {$ENDIF}
1973 // invariant: we always doing a step
1974 {$IF DEFINED(D2F_DEBUG)}
1976 {$ENDIF}
1977 begin
1978 // check for crossing tile/grid boundary
1980 begin
1981 // we're still in grid
1983 // check for tile edge crossing
1989 // crossed tile edge?
1991 begin
1992 // setup new cell index
1994 if (log) then e_WriteLog(Format(' stepped to new tile (%d,%d) -- (%d,%d)', [(x div tsize), (y div tsize), x, y]), MSG_NOTIFY);
1995 end
1996 else
1998 begin
1999 // we have nothing interesting here anymore, jump directly to tile edge
2000 (*
2001 if (incx = 0) then
2002 begin
2003 // vertical line
2004 if (incy < 0) then tedist := y-(y and (not tsize)) else tedist := (y or (tsize-1))-y;
2005 if (tedist > 1) then
2006 begin
2007 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);
2008 y += incy*tedist;
2009 Inc(i, tedist);
2010 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);
2011 end;
2012 end
2013 else if (incy = 0) then
2014 begin
2015 // horizontal line
2016 if (incx < 0) then tedist := x-(x and (not tsize)) else tedist := (x or (tsize-1))-x;
2017 if (tedist > 1) then
2018 begin
2019 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);
2020 x += incx*tedist;
2021 Inc(i, tedist);
2022 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);
2023 end;
2024 end;
2025 *)
2026 (*
2027 else if (
2028 // get minimal distance to tile edges
2029 if (incx < 0) then tedist := x-(x and (not tsize)) else if (incx > 0) then tedist := (x or (tsize+1))-x else tedist := 0;
2030 {$IF DEFINED(D2F_DEBUG)}
2031 if (tedist < 0) then raise Exception.Create('internal bug in grid raycaster (2.x)');
2032 {$ENDIF}
2033 if (incy < 0) then f := y-(y and (not tsize)) else if (incy > 0) then f := (y or (tsize+1))-y else f := 0;
2034 {$IF DEFINED(D2F_DEBUG)}
2035 if (f < 0) then raise Exception.Create('internal bug in grid raycaster (2.y)');
2036 {$ENDIF}
2037 if (tedist = 0) then tedist := f else if (f <> 0) then tedist := minInt(tedist, f);
2038 // do jump
2039 if (tedist > 1) then
2040 begin
2041 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);
2042 xerr += dx*tedist;
2043 yerr += dy*tedist;
2044 if (xerr >= 0) then begin x += incx*((xerr div d)+1); xerr := (xerr mod d)-d; end;
2045 if (yerr >= 0) then begin y += incy*((yerr div d)+1); yerr := (yerr mod d)-d; end;
2046 Inc(i, tedist);
2047 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);
2048 end;
2049 *)
2051 end
2052 else
2053 begin
2054 // out of grid
2059 // has something to process in the current cell?
2061 begin
2062 // process cell
2064 // convert coords to map (to avoid ajdusting coords inside the loop)
2065 //Inc(x, minx);
2066 //Inc(y, miny);
2067 // process cell list
2069 begin
2072 begin
2077 begin
2082 // next cell
2086 // convert coords to grid
2087 //Dec(x, minx);
2088 //Dec(y, miny);
2094 // ////////////////////////////////////////////////////////////////////////// //
2095 (*
2096 function TBodyGridBase.traceRayWhileIn (const x0, y0, x1, y1: Integer; tagmask: Integer=-1): ITP; overload;
2097 var
2098 ex, ey: Integer;
2099 begin
2100 result := traceRayWhileIn(ex, ey, x0, y0, x1, y1, tagmask);
2101 end;
2104 // FUCKIN' PASTA!
2105 function TBodyGridBase.traceRayWhileIn (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): ITP;
2106 const
2107 tsize = mTileSize;
2108 var
2109 wx0, wy0, wx1, wy1: Integer; // window coordinates
2110 stx, sty: Integer; // "steps" for x and y axes
2111 dsx, dsy: Integer; // "lengthes" for x and y axes
2112 dx2, dy2: Integer; // "double lengthes" for x and y axes
2113 xd, yd: Integer; // current coord
2114 e: Integer; // "error" (as in bresenham algo)
2115 rem: Integer;
2116 term: Integer;
2117 xptr, yptr: PInteger;
2118 xfixed: Boolean;
2119 temp: Integer;
2120 prevx, prevy: Integer;
2121 lastDistSq: Integer;
2122 ccidx, curci: Integer;
2123 hasUntried: Boolean;
2124 lastGA: Integer = -1;
2125 ga, x, y: Integer;
2126 lastObj: ITP;
2127 wasHit: Boolean = false;
2128 gw, gh, minx, miny, maxx, maxy: Integer;
2129 cc: PGridCell;
2130 px: PBodyProxyRec;
2131 lq: LongWord;
2132 f, ptag, distSq: Integer;
2133 x0, y0, x1, y1: Integer;
2134 inx, iny: Integer;
2135 begin
2136 result := Default(ITP);
2137 lastObj := Default(ITP);
2138 tagmask := tagmask and TagFullMask;
2139 ex := ax1; // why not?
2140 ey := ay1; // why not?
2141 if (tagmask = 0) then exit;
2143 if (ax0 = ax1) and (ay0 = ay1) then exit; // doesn't matter
2145 // we should start inside
2146 if (forEachAtPoint(ax0, ay0, nil, tagmask, @ptag) = nil) then
2147 begin
2148 ex := ax0; // why not?
2149 ey := ay0; // why not?
2150 exit;
2151 end;
2153 lastDistSq := distanceSq(ax0, ay0, ax1, ay1)+1;
2155 gw := mWidth;
2156 gh := mHeight;
2157 minx := mMinX;
2158 miny := mMinY;
2159 maxx := gw*tsize-1;
2160 maxy := gh*tsize-1;
2162 x0 := ax0;
2163 y0 := ay0;
2164 x1 := ax1;
2165 y1 := ay1;
2167 // offset query coords to (0,0)-based
2168 Dec(x0, minx);
2169 Dec(y0, miny);
2170 Dec(x1, minx);
2171 Dec(y1, miny);
2173 // clip rectange
2174 wx0 := 0;
2175 wy0 := 0;
2176 wx1 := maxx;
2177 wy1 := maxy;
2179 // horizontal setup
2180 if (x0 < x1) then
2181 begin
2182 // from left to right
2183 if (x0 > wx1) or (x1 < wx0) then exit; // out of screen
2184 stx := 1; // going right
2185 end
2186 else
2187 begin
2188 // from right to left
2189 if (x1 > wx1) or (x0 < wx0) then exit; // out of screen
2190 stx := -1; // going left
2191 x0 := -x0;
2192 x1 := -x1;
2193 wx0 := -wx0;
2194 wx1 := -wx1;
2195 swapInt(wx0, wx1);
2196 end;
2198 // vertical setup
2199 if (y0 < y1) then
2200 begin
2201 // from top to bottom
2202 if (y0 > wy1) or (y1 < wy0) then exit; // out of screen
2203 sty := 1; // going down
2204 end
2205 else
2206 begin
2207 // from bottom to top
2208 if (y1 > wy1) or (y0 < wy0) then exit; // out of screen
2209 sty := -1; // going up
2210 y0 := -y0;
2211 y1 := -y1;
2212 wy0 := -wy0;
2213 wy1 := -wy1;
2214 swapInt(wy0, wy1);
2215 end;
2217 dsx := x1-x0;
2218 dsy := y1-y0;
2220 if (dsx < dsy) then
2221 begin
2222 xptr := @yd;
2223 yptr := @xd;
2224 swapInt(x0, y0);
2225 swapInt(x1, y1);
2226 swapInt(dsx, dsy);
2227 swapInt(wx0, wy0);
2228 swapInt(wx1, wy1);
2229 swapInt(stx, sty);
2230 end
2231 else
2232 begin
2233 xptr := @xd;
2234 yptr := @yd;
2235 end;
2237 dx2 := 2*dsx;
2238 dy2 := 2*dsy;
2239 xd := x0;
2240 yd := y0;
2241 e := 2*dsy-dsx;
2242 term := x1;
2244 xfixed := false;
2245 if (y0 < wy0) then
2246 begin
2247 // clip at top
2248 temp := dx2*(wy0-y0)-dsx;
2249 xd += temp div dy2;
2250 rem := temp mod dy2;
2251 if (xd > wx1) then exit; // x is moved out of clipping rect, nothing to do
2252 if (xd+1 >= wx0) then
2253 begin
2254 yd := wy0;
2255 e -= rem+dsx;
2256 if (rem > 0) then begin Inc(xd); e += dy2; end;
2257 xfixed := true;
2258 end;
2259 end;
2261 if (not xfixed) and (x0 < wx0) then
2262 begin
2263 // clip at left
2264 temp := dy2*(wx0-x0);
2265 yd += temp div dx2;
2266 rem := temp mod dx2;
2267 if (yd > wy1) or (yd = wy1) and (rem >= dsx) then exit;
2268 xd := wx0;
2269 e += rem;
2270 if (rem >= dsx) then begin Inc(yd); e -= dx2; end;
2271 end;
2273 if (y1 > wy1) then
2274 begin
2275 // clip at bottom
2276 temp := dx2*(wy1-y0)+dsx;
2277 term := x0+temp div dy2;
2278 rem := temp mod dy2;
2279 if (rem = 0) then Dec(term);
2280 end;
2282 if (term > wx1) then term := wx1; // clip at right
2284 Inc(term); // draw last point
2285 //if (term = xd) then exit; // this is the only point, get out of here
2287 if (sty = -1) then yd := -yd;
2288 if (stx = -1) then begin xd := -xd; term := -term; end;
2289 dx2 -= dy2;
2291 // first move, to skip starting point
2292 // DON'T DO THIS! loop will take care of that
2293 if (xd = term) then
2294 begin
2295 result := forEachAtPoint(ax0, ay0, nil, tagmask, @ptag);
2296 if (result <> nil) and ((ptag and tagmask) <> 0) then result := nil;
2297 exit;
2298 end;
2300 prevx := xptr^+minx;
2301 prevy := yptr^+miny;
2303 // increase query counter
2304 Inc(mLastQuery);
2305 if (mLastQuery = 0) then
2306 begin
2307 // just in case of overflow
2308 mLastQuery := 1;
2309 for f := 0 to High(mProxies) do mProxies[f].mQueryMark := 0;
2310 end;
2311 lq := mLastQuery;
2313 ccidx := -1;
2314 // draw it; can omit checks
2315 while (xd <> term) do
2316 begin
2317 // check cell(s)
2318 // new tile?
2319 ga := (yptr^ div tsize)*gw+(xptr^ div tsize);
2320 if (ga <> lastGA) then
2321 begin
2322 // yes
2323 lastGA := ga;
2324 ccidx := mGrid[lastGA];
2325 // no objects in cell == exit
2326 if (ccidx = -1) then exit;
2327 end;
2328 // has something to process in this tile?
2329 if (ccidx <> -1) then
2330 begin
2331 // process cell
2332 curci := ccidx;
2333 // convert coords to map (to avoid ajdusting coords inside the loop)
2334 x := xptr^+minx;
2335 y := yptr^+miny;
2336 wasHit := false;
2337 // process cell list
2338 while (curci <> -1) do
2339 begin
2340 cc := @mCells[curci];
2341 for f := 0 to GridCellBucketSize-1 do
2342 begin
2343 if (cc.bodies[f] = -1) then break;
2344 px := @mProxies[cc.bodies[f]];
2345 ptag := px.mTag;
2346 if ((ptag and TagDisabled) = 0) and (px.mQueryMark <> lq) then
2347 begin
2348 function lineAABBIntersects (x0, y0, x1, y1: Integer; bx, by, bw, bh: Integer; out inx, iny: Integer): Boolean;
2349 // can we process this proxy?
2350 if (x >= px.mX) and (y >= px.mY) and (x < px.mX+px.mWidth) and (y < px.mY+px.mHeight) then
2351 begin
2352 px.mQueryMark := lq; // mark as processed
2353 if ((ptag and tagmask) = 0) then
2354 begin
2355 result := px.mObj;
2356 ex := x;
2357 ey := y;
2358 exit;
2359 end;
2360 // march out of the panel/cell
2361 while (xd <> term) do
2362 begin
2363 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
2364 xd += stx;
2365 // new cell?
2366 ga := (yptr^ div tsize)*gw+(xptr^ div tsize);
2367 if (ga <> lastGA) then break;
2368 // out of panel?
2369 if not ((x >= px.mX) and (y >= px.mY) and (x < px.mX+px.mWidth) and (y < px.mY+px.mHeight)) then break;
2370 end;
2371 end;
2372 end;
2373 end;
2374 // next cell
2375 curci := cc.next;
2376 end;
2377 // still has something interesting in this cell?
2378 if not hasUntried then
2379 begin
2380 // nope, don't process this cell anymore; signal cell completion
2381 ccidx := -1;
2382 if assigned(cb) then
2383 begin
2384 if cb(nil, 0, x, y, prevx, prevy) then begin result := lastObj; exit; end;
2385 end
2386 else if wasHit then
2387 begin
2388 result := lastObj;
2389 exit;
2390 end;
2391 end;
2392 end;
2393 //putPixel(xptr^, yptr^);
2394 // move coords
2395 prevx := xptr^+minx;
2396 prevy := yptr^+miny;
2397 if (e >= 0) then begin yd += sty; e -= dx2; end else e += dy2;
2398 xd += stx;
2399 end;
2400 // we can travel less than one cell
2401 if wasHit and not assigned(cb) then
2402 begin
2403 result := lastObj;
2404 end
2405 else
2406 begin
2407 ex := ax1; // why not?
2408 ey := ay1; // why not?
2409 end;
2410 end;
2411 *)