DEADSOFTWARE

converted grid and tree to generics (fuck you, FPC! your generics fuckin' sux fuckin...
authorKetmar Dark <ketmar@ketmar.no-ip.org>
Sat, 19 Aug 2017 19:07:36 +0000 (22:07 +0300)
committerKetmar Dark <ketmar@ketmar.no-ip.org>
Sat, 19 Aug 2017 19:17:13 +0000 (22:17 +0300)
src/game/g_grid.pas
src/game/g_items.pas
src/game/g_map.pas
src/game/z_aabbtree.pas

index 81974570c4e201bd52d550e2b556fed261e8ffd6..d1149adf46c64d47c5790f5731c2ea56e90ea278 100644 (file)
@@ -20,57 +20,46 @@ unit g_grid;
 
 interface
 
-const
-  GridDefaultTileSize = 32;
-  GridCellBucketSize = 8; // WARNING! can't be less than 2!
 
 type
-  TGridQueryCB = function (obj: TObject; tag: Integer): Boolean is nested; // return `true` to stop
+  TBodyProxyId = Integer;
 
-type
-  TBodyGrid = class;
-
-  TBodyProxy = Integer;
+  generic TBodyGridBase<ITP> = class(TObject)
+  public
+    type TGridQueryCB = function (obj: ITP; tag: Integer): Boolean is nested; // return `true` to stop
 
-  PBodyProxyRec = ^TBodyProxyRec;
-  TBodyProxyRec = record
   private
-    mX, mY, mWidth, mHeight: Integer; // aabb
-    mQueryMark: DWord; // was this object visited at this query?
-    mObj: TObject;
-    //mGrid: TBodyGrid;
-    mTag: Integer;
-    nextLink: TBodyProxy; // next free or nothing
+    const
+      GridDefaultTileSize = 32;
+      GridCellBucketSize = 8; // WARNING! can't be less than 2!
 
   private
-    procedure setup (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer);
-
-  public
-    //constructor Create (aGrid: TBodyGrid; aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer);
-    //destructor Destroy (); override;
-
-    property x: Integer read mX;
-    property y: Integer read mY;
-    property width: Integer read mWidth;
-    property height: Integer read mHeight;
-    property obj: TObject read mObj;
-    property tag: Integer read mTag;
-    //property grid: TBodyGrid read mGrid;
-  end;
+    type
+      PBodyProxyRec = ^TBodyProxyRec;
+      TBodyProxyRec = record
+      private
+        mX, mY, mWidth, mHeight: Integer; // aabb
+        mQueryMark: DWord; // was this object visited at this query?
+        mObj: ITP;
+        mTag: Integer;
+        nextLink: TBodyProxyId; // next free or nothing
+
+      private
+        procedure setup (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer);
+      end;
 
-  PGridCell = ^TGridCell;
-  TGridCell = record
-    {$IFDEF grid_use_buckets}
-    bodies: array [0..GridCellBucketSize-1] of Integer; // -1: end of list
-    {$ELSE}
-    body: Integer;
-    {$ENDIF}
-    next: Integer; // in this cell; index in mCells
-  end;
+      PGridCell = ^TGridCell;
+      TGridCell = record
+        {$IFDEF grid_use_buckets}
+        bodies: array [0..GridCellBucketSize-1] of Integer; // -1: end of list
+        {$ELSE}
+        body: Integer;
+        {$ENDIF}
+        next: Integer; // in this cell; index in mCells
+      end;
 
-  TGridInternalCB = function (grida: Integer): Boolean is nested; // return `true` to stop
+      TGridInternalCB = function (grida: Integer): Boolean of object; // return `true` to stop
 
-  TBodyGrid = class(TObject)
   private
     mTileSize: Integer;
     mMinX, mMinY: Integer; // so grids can start at any origin
@@ -81,37 +70,43 @@ type
     mLastQuery: DWord;
     mUsedCells: Integer;
     mProxies: array of TBodyProxyRec;
-    mProxyFree: TBodyProxy; // free
+    mProxyFree: TBodyProxyId; // free
     mProxyCount: Integer; // currently used
     mProxyMaxCount: Integer;
 
+    mUData: TBodyProxyId; // for inserter/remover
+    mTagMask: Integer; // for iterator
+    mItCB: TGridQueryCB; // for iterator
+
   private
     function allocCell: Integer;
     procedure freeCell (idx: Integer); // `next` is simply overwritten
 
-    function allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer): TBodyProxy;
-    procedure freeProxy (body: TBodyProxy);
+    function allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer): TBodyProxyId;
+    procedure freeProxy (body: TBodyProxyId);
 
-    procedure insert (body: TBodyProxy);
-    procedure remove (body: TBodyProxy);
+    procedure insert (body: TBodyProxyId);
+    procedure remove (body: TBodyProxyId);
 
     function forGridRect (x, y, w, h: Integer; cb: TGridInternalCB): Boolean;
 
+    function inserter (grida: Integer): Boolean;
+    function remover (grida: Integer): Boolean;
+    function iterator (grida: Integer): Boolean;
+
   public
     constructor Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer; aTileSize: Integer=GridDefaultTileSize);
     destructor Destroy (); override;
 
-    function insertBody (aObj: TObject; ax, ay, aWidth, aHeight: Integer; aTag: Integer=0): TBodyProxy;
-    procedure removeBody (aObj: TBodyProxy); // WARNING! this WILL destroy proxy!
+    function insertBody (aObj: ITP; ax, ay, aWidth, aHeight: Integer; aTag: Integer=0): TBodyProxyId;
+    procedure removeBody (aObj: TBodyProxyId); // WARNING! this WILL destroy proxy!
 
-    procedure moveBody (body: TBodyProxy; dx, dy: Integer);
-    procedure resizeBody (body: TBodyProxy; sx, sy: Integer);
-    procedure moveResizeBody (body: TBodyProxy; dx, dy, sx, sy: Integer);
+    procedure moveBody (body: TBodyProxyId; dx, dy: Integer);
+    procedure resizeBody (body: TBodyProxyId; sx, sy: Integer);
+    procedure moveResizeBody (body: TBodyProxyId; dx, dy, sx, sy: Integer);
 
     function forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1): Boolean;
 
-    //function getProxyForBody (aObj: TObject; x, y, w, h: Integer): TBodyProxy;
-
     procedure dumpStats ();
   end;
 
@@ -123,7 +118,7 @@ uses
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer);
+procedure TBodyGridBase.TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer);
 begin
   mX := aX;
   mY := aY;
@@ -137,7 +132,7 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-constructor TBodyGrid.Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer; aTileSize: Integer=GridDefaultTileSize);
+constructor TBodyGridBase.Create (aMinPixX, aMinPixY, aPixWidth, aPixHeight: Integer; aTileSize: Integer=GridDefaultTileSize);
 var
   idx: Integer;
 begin
@@ -175,11 +170,14 @@ begin
   mProxyFree := 0;
   mProxyCount := 0;
   mProxyMaxCount := 0;
+  mUData := 0;
+  mTagMask := 0;
+  mItCB := nil;
   e_WriteLog(Format('created grid with size: %dx%d (tile size: %d); pix: %dx%d', [mWidth, mHeight, mTileSize, mWidth*mTileSize, mHeight*mTileSize]), MSG_NOTIFY);
 end;
 
 
-destructor TBodyGrid.Destroy ();
+destructor TBodyGridBase.Destroy ();
 begin
   mCells := nil;
   mGrid := nil;
@@ -188,7 +186,7 @@ begin
 end;
 
 
-procedure TBodyGrid.dumpStats ();
+procedure TBodyGridBase.dumpStats ();
 var
   idx, mcb, cidx, cnt: Integer;
 begin
@@ -208,7 +206,7 @@ begin
 end;
 
 
-function TBodyGrid.allocCell: Integer;
+function TBodyGridBase.allocCell: Integer;
 var
   idx: Integer;
 begin
@@ -236,7 +234,7 @@ begin
 end;
 
 
-procedure TBodyGrid.freeCell (idx: Integer);
+procedure TBodyGridBase.freeCell (idx: Integer);
 begin
   if (idx >= 0) and (idx < High(mCells)) then
   begin
@@ -249,7 +247,7 @@ begin
 end;
 
 
-function TBodyGrid.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer): TBodyProxy;
+function TBodyGridBase.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: ITP; aTag: Integer): TBodyProxyId;
 var
   olen, idx: Integer;
   px: PBodyProxyRec;
@@ -275,7 +273,7 @@ begin
   if (mProxyMaxCount < mProxyCount) then mProxyMaxCount := mProxyCount;
 end;
 
-procedure TBodyGrid.freeProxy (body: TBodyProxy);
+procedure TBodyGridBase.freeProxy (body: TBodyProxyId);
 begin
   if (body < 0) or (body > High(mProxies)) then exit; // just in case
   if (mProxyCount = 0) then raise Exception.Create('wutafuuuuu in grid (no allocated proxies, what i should free now?)');
@@ -287,7 +285,7 @@ begin
 end;
 
 
-function TBodyGrid.forGridRect (x, y, w, h: Integer; cb: TGridInternalCB): Boolean;
+function TBodyGridBase.forGridRect (x, y, w, h: Integer; cb: TGridInternalCB): Boolean;
 var
   gx, gy: Integer;
 begin
@@ -313,118 +311,152 @@ begin
 end;
 
 
-procedure TBodyGrid.insert (body: TBodyProxy);
-
-  function inserter (grida: Integer): Boolean;
-  var
-    cidx: Integer;
-    pc: PInteger;
-    {$IFDEF grid_use_buckets}
-    pi: PGridCell;
-    f: Integer;
-    {$ENDIF}
+function TBodyGridBase.inserter (grida: Integer): Boolean;
+var
+  cidx: Integer;
+  pc: PInteger;
+  {$IFDEF grid_use_buckets}
+  pi: PGridCell;
+  f: Integer;
+  {$ENDIF}
+begin
+  result := false; // never stop
+  // add body to the given grid cell
+  pc := @mGrid[grida];
+  {$IFDEF grid_use_buckets}
+  if (pc^ <> -1) then
   begin
-    result := false; // never stop
-    // add body to the given grid cell
-    pc := @mGrid[grida];
-    {$IFDEF grid_use_buckets}
-    if (pc^ <> -1) then
+    pi := @mCells[pc^];
+    f := 0;
+    for f := 0 to High(TGridCell.bodies) do
     begin
-      pi := @mCells[pc^];
-      f := 0;
-      for f := 0 to High(TGridCell.bodies) do
+      if (pi.bodies[f] = -1) then
       begin
-        if (pi.bodies[f] = -1) then
-        begin
-          // can add here
-          pi.bodies[f] := body;
-          if (f+1 < Length(TGridCell.bodies)) then pi.bodies[f+1] := -1;
-          exit;
-        end;
+        // can add here
+        pi.bodies[f] := mUData;
+        if (f+1 < Length(TGridCell.bodies)) then pi.bodies[f+1] := -1;
+        exit;
       end;
     end;
-    // either no room, or no cell at all
-    cidx := allocCell();
-    mCells[cidx].bodies[0] := body;
-    mCells[cidx].bodies[1] := -1;
-    mCells[cidx].next := pc^;
-    pc^ := cidx;
-    {$ELSE}
-    cidx := allocCell();
-    //e_WriteLog(Format('  01: allocated cell for grid coords (%d,%d), body coords:(%d,%d): #%d', [gx, gy, dx, dy, cidx]), MSG_NOTIFY);
-    mCells[cidx].body := body;
-    mCells[cidx].next := pc^;
-    pc^ := cidx;
-    {$ENDIF}
   end;
+  // either no room, or no cell at all
+  cidx := allocCell();
+  mCells[cidx].bodies[0] := mUData;
+  mCells[cidx].bodies[1] := -1;
+  mCells[cidx].next := pc^;
+  pc^ := cidx;
+  {$ELSE}
+  cidx := allocCell();
+  //e_WriteLog(Format('  01: allocated cell for grid coords (%d,%d), body coords:(%d,%d): #%d', [gx, gy, dx, dy, cidx]), MSG_NOTIFY);
+  mCells[cidx].body := mUData;
+  mCells[cidx].next := pc^;
+  pc^ := cidx;
+  {$ENDIF}
+end;
+
 
+procedure TBodyGridBase.insert (body: TBodyProxyId);
 var
   px: PBodyProxyRec;
+  oudata: Integer;
 begin
   if (body < 0) or (body > High(mProxies)) then exit; // just in case
   px := @mProxies[body];
+  oudata := mUData;
+  mUData := body;
   forGridRect(px.mX, px.mY, px.mWidth, px.mHeight, inserter);
+  mUData := oudata;
 end;
 
 
-// absolutely not tested
-procedure TBodyGrid.remove (body: TBodyProxy);
-
-(*
-  function remover (grida: Integer): Boolean;
-  var
-    pidx, idx, tmp: Integer;
+function TBodyGridBase.remover (grida: Integer): Boolean;
+var
+  pidx, idx, tmp, f: Integer;
+  pc: PGridCell;
+begin
+  result := false; // never stop
+  // find and remove cell
+  pidx := -1;
+  idx := mGrid[grida];
+  while (idx >= 0) do
   begin
-    result := false; // never stop
-    // find and remove cell
-    pidx := -1;
-    idx := mGrid[grida];
-    while idx >= 0 do
+    tmp := mCells[idx].next;
+    {$IFDEF grid_use_buckets}
+    pc := @mCells[idx];
+    f := 0;
+    while (f < High(TGridCell.bodies)) do
     begin
-      tmp := mCells[idx].next;
-      if (mCells[idx].body = body) then
+      if (pc.bodies[f] = mUData) then
       begin
-        if (pidx = -1) then mGrid[grida] := tmp else mCells[pidx].next := tmp;
-        freeCell(idx);
-        break; // assume that we cannot have one object added to bucket twice
-      end
-      else
-      begin
-        pidx := idx;
+        // i found her!
+        if (f = 0) and (pc.bodies[1] = -1) then
+        begin
+          // this cell contains no elements, remove it
+          tmp := mCells[idx].next;
+          if (pidx = -1) then mGrid[grida] := tmp else mCells[pidx].next := tmp;
+          freeCell(idx);
+        end
+        else
+        begin
+          // remove element from bucket
+          Inc(f);
+          while (f < High(TGridCell.bodies)) do
+          begin
+            pc.bodies[f-1] := pc.bodies[f];
+            if (pc.bodies[f] = -1) then break;
+            Inc(f);
+          end;
+          pc.bodies[High(TGridCell.bodies)] := -1; // just in case
+        end;
+        exit; // assume that we cannot have one object added to bucket twice
       end;
-      idx := tmp;
+      Inc(f);
+    end;
+    {$ELSE}
+    if (mCells[idx].body = mUData) then
+    begin
+      if (pidx = -1) then mGrid[grida] := tmp else mCells[pidx].next := tmp;
+      freeCell(idx);
+      exit; // assume that we cannot have one object added to bucket twice
     end;
+    {$ENDIF}
+    pidx := idx;
+    idx := tmp;
   end;
+end;
 
+
+// absolutely not tested
+procedure TBodyGridBase.remove (body: TBodyProxyId);
 var
   px: PBodyProxyRec;
-*)
+  oudata: Integer;
 begin
-(*
   if (body < 0) or (body > High(mProxies)) then exit; // just in case
   px := @mProxies[body];
+  oudata := mUData;
+  mUData := body;
   forGridRect(px.mX, px.mY, px.mWidth, px.mHeight, remover);
-*)
-  raise Exception.Create('TBodyGrid.remove: not yet, sorry');
+  mUData := oudata;
 end;
 
 
-function TBodyGrid.insertBody (aObj: TObject; aX, aY, aWidth, aHeight: Integer; aTag: Integer=0): TBodyProxy;
+function TBodyGridBase.insertBody (aObj: ITP; aX, aY, aWidth, aHeight: Integer; aTag: Integer=0): TBodyProxyId;
 begin
   result := allocProxy(aX, aY, aWidth, aHeight, aObj, aTag);
   insert(result);
 end;
 
 
-procedure TBodyGrid.removeBody (aObj: TBodyProxy);
+procedure TBodyGridBase.removeBody (aObj: TBodyProxyId);
 begin
   if (aObj < 0) or (aObj > High(mProxies)) then exit; // just in case
-  removeBody(aObj);
+  remove(aObj);
   freeProxy(aObj);
 end;
 
 
-procedure TBodyGrid.moveResizeBody (body: TBodyProxy; dx, dy, sx, sy: Integer);
+procedure TBodyGridBase.moveResizeBody (body: TBodyProxyId; dx, dy, sx, sy: Integer);
 var
   px: PBodyProxyRec;
 begin
@@ -439,63 +471,65 @@ begin
   insert(body);
 end;
 
-procedure TBodyGrid.moveBody (body: TBodyProxy; dx, dy: Integer);
+procedure TBodyGridBase.moveBody (body: TBodyProxyId; dx, dy: Integer);
 begin
   moveResizeBody(body, dx, dy, 0, 0);
 end;
 
-procedure TBodyGrid.resizeBody (body: TBodyProxy; sx, sy: Integer);
+procedure TBodyGridBase.resizeBody (body: TBodyProxyId; sx, sy: Integer);
 begin
   moveResizeBody(body, 0, 0, sx, sy);
 end;
 
 
-function TBodyGrid.forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1): Boolean;
-  function iterator (grida: Integer): Boolean;
-  var
-    idx: Integer;
-    px: PBodyProxyRec;
-    {$IFDEF grid_use_buckets}
-    pi: PGridCell;
-    f: Integer;
-    {$ENDIF}
+function TBodyGridBase.iterator (grida: Integer): Boolean;
+var
+  idx: Integer;
+  px: PBodyProxyRec;
+  {$IFDEF grid_use_buckets}
+  pi: PGridCell;
+  f: Integer;
+  {$ENDIF}
+begin
+  result := false;
+  idx := mGrid[grida];
+  while (idx >= 0) do
   begin
-    result := false;
-    idx := mGrid[grida];
-    while (idx >= 0) do
+    {$IFDEF grid_use_buckets}
+    pi := @mCells[idx];
+    for f := 0 to High(TGridCell.bodies) do
     begin
-      {$IFDEF grid_use_buckets}
-      pi := @mCells[idx];
-      for f := 0 to High(TGridCell.bodies) do
+      if (pi.bodies[f] = -1) then break;
+      px := @mProxies[pi.bodies[f]];
+      if (px.mQueryMark <> mLastQuery) and ((px.mTag and mTagMask) <> 0) then
       begin
-        if (pi.bodies[f] = -1) then break;
-        px := @mProxies[pi.bodies[f]];
-        if (px.mQueryMark <> mLastQuery) and ((px.mTag and tagmask) <> 0) then
-        begin
-          //e_WriteLog(Format('  query #%d body hit: (%d,%d)-(%dx%d) tag:%d', [mLastQuery, mCells[idx].body.mX, mCells[idx].body.mY, mCells[idx].body.mWidth, mCells[idx].body.mHeight, mCells[idx].body.mTag]), MSG_NOTIFY);
-          px.mQueryMark := mLastQuery;
-          if (cb(px.mObj, px.mTag)) then begin result := true; exit; end;
-        end;
+        //e_WriteLog(Format('  query #%d body hit: (%d,%d)-(%dx%d) tag:%d', [mLastQuery, mCells[idx].body.mX, mCells[idx].body.mY, mCells[idx].body.mWidth, mCells[idx].body.mHeight, mCells[idx].body.mTag]), MSG_NOTIFY);
+        px.mQueryMark := mLastQuery;
+        if (mItCB(px.mObj, px.mTag)) then begin result := true; exit; end;
       end;
-      idx := pi.next;
-      {$ELSE}
-      if (mCells[idx].body <> -1) then
+    end;
+    idx := pi.next;
+    {$ELSE}
+    if (mCells[idx].body <> -1) then
+    begin
+      px := @mProxies[mCells[idx].body];
+      if (px.mQueryMark <> mLastQuery) and ((px.mTag and mTagMask) <> 0) then
       begin
-        px := @mProxies[mCells[idx].body];
-        if (px.mQueryMark <> mLastQuery) and ((px.mTag and tagmask) <> 0) then
-        begin
-          //e_WriteLog(Format('  query #%d body hit: (%d,%d)-(%dx%d) tag:%d', [mLastQuery, mCells[idx].body.mX, mCells[idx].body.mY, mCells[idx].body.mWidth, mCells[idx].body.mHeight, mCells[idx].body.mTag]), MSG_NOTIFY);
-          px.mQueryMark := mLastQuery;
-          if (cb(px.mObj, px.mTag)) then begin result := true; exit; end;
-        end;
+        //e_WriteLog(Format('  query #%d body hit: (%d,%d)-(%dx%d) tag:%d', [mLastQuery, mCells[idx].body.mX, mCells[idx].body.mY, mCells[idx].body.mWidth, mCells[idx].body.mHeight, mCells[idx].body.mTag]), MSG_NOTIFY);
+        px.mQueryMark := mLastQuery;
+        if (mItCB(px.mObj, px.mTag)) then begin result := true; exit; end;
       end;
-      idx := mCells[idx].next;
-      {$ENDIF}
     end;
+    idx := mCells[idx].next;
+    {$ENDIF}
   end;
+end;
 
+function TBodyGridBase.forEachInAABB (x, y, w, h: Integer; cb: TGridQueryCB; tagmask: Integer=-1): Boolean;
 var
   idx: Integer;
+  otagmask: Integer;
+  ocb: TGridQueryCB;
 begin
   result := false;
   if not assigned(cb) then exit;
@@ -510,40 +544,14 @@ begin
   end;
   //e_WriteLog(Format('grid: query #%d: (%d,%d)-(%dx%d)', [mLastQuery, minx, miny, maxx, maxy]), MSG_NOTIFY);
 
+  otagmask := mTagMask;
+  mTagMask := tagmask;
+  ocb := mItCB;
+  mItCB := cb;
   result := forGridRect(x, y, w, h, iterator);
+  mTagMask := otagmask;
+  mItCB := ocb;
 end;
 
 
-(*
-function TBodyGrid.getProxyForBody (aObj: TObject; x, y, w, h: Integer): TBodyProxy;
-var
-  res: TBodyProxy = -1;
-
-  function iterator (grida: Integer): Boolean;
-  var
-    idx: Integer;
-  begin
-    result := false;
-    idx := mGrid[grida];
-    while idx >= 0 do
-    begin
-      if (mCells[idx].body <> -1) and (mProxies[mCells[idx].body].mObj = aObj) then
-      begin
-        result := true;
-        res := mCells[idx].body;
-        exit;
-      end;
-      idx := mCells[idx].next;
-    end;
-  end;
-
-begin
-  result := -1;
-  if (aObj = nil) then exit;
-  forGridRect(x, y, w, h, iterator);
-  result := res;
-end;
-*)
-
-
 end.
index 6d7cf5a10a7918ea8bc0480160d33c9769c0099e..4950e4c4b9a1412599f1b3e098f65d8f1f13b0e9 100644 (file)
@@ -88,10 +88,35 @@ uses
   g_grid, z_aabbtree, binheap;
 
 
-// ////////////////////////////////////////////////////////////////////////// //
 var
-  itemTree: TDynAABBTree = nil;
   ggItems: Array of TItem = nil;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+type
+  TDynAABBTreeItemBase = specialize TDynAABBTreeBase<Integer>;
+
+  TDynAABBTreeItem = class(TDynAABBTreeItemBase)
+    function getFleshAABB (out aabb: AABB2D; flesh: Integer; tag: Integer): Boolean; override;
+  end;
+
+function TDynAABBTreeItem.getFleshAABB (out aabb: AABB2D; flesh: Integer; tag: Integer): Boolean;
+var
+  it: PItem;
+begin
+  result := false;
+  if (flesh < 0) or (flesh > High(ggItems)) then raise Exception.Create('DynTree: trying to get dimensions of inexistant item');
+  it := @ggItems[flesh];
+  if (it.Obj.Rect.Width < 1) or (it.Obj.Rect.Height < 1) then exit;
+  aabb := AABB2D.Create(it.Obj.X, it.Obj.Y, it.Obj.X+it.Obj.Rect.Width-1, it.Obj.Y+it.Obj.Rect.Height-1);
+  if not aabb.valid then raise Exception.Create('wutafuuuuuuu?!');
+  result := true;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+var
+  itemTree: TDynAABBTreeItem = nil;
   freeIds: TBinaryHeapInt = nil; // free item ids
 
 
@@ -121,28 +146,6 @@ begin
 end;
 
 
-// ////////////////////////////////////////////////////////////////////////// //
-type
-  TDynAABBTreeItem = class(TDynAABBTree)
-    function getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean; override;
-  end;
-
-function TDynAABBTreeItem.getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean;
-var
-  it: PItem;
-begin
-  result := false;
-  if (flesh = nil) then begin aabb := AABB2D.Create(0, 0, 0, 0); exit; end;
-  //if not g_ItemValidId(tag) then raise Exception.Create('DynTree: trying to get dimensions of inexistant item');
-  if (tag < 0) or (tag > High(ggItems)) then raise Exception.Create('DynTree: trying to get dimensions of inexistant item');
-  it := @ggItems[tag];
-  if (it.Obj.Rect.Width < 1) or (it.Obj.Rect.Height < 1) then exit;
-  aabb := AABB2D.Create(it.Obj.X, it.Obj.Y, it.Obj.X+it.Obj.Rect.Width, it.Obj.Y+it.Obj.Rect.Height);
-  if not aabb.valid then raise Exception.Create('wutafuuuuuuu?!');
-  result := true;
-end;
-
-
 // ////////////////////////////////////////////////////////////////////////// //
 procedure TItem.positionChanged ();
 var
@@ -150,7 +153,7 @@ var
 begin
   if (treeNode = -1) then
   begin
-    treeNode := itemTree.insertObject(itemTree{doesn't matter}, arrIdx, true); // static object
+    treeNode := itemTree.insertObject(arrIdx, 0, true); // static object
     itemTree.getNodeXY(treeNode, x, y);
     {$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('item #%d: inserted into the tree; nodeid=%d; x=%d; y=%d', [arrIdx, treeNode, x, y]), MSG_NOTIFY);{$ENDIF}
   end
@@ -164,7 +167,7 @@ begin
     itemTree.updateObject(treeNode);
     {$ELSE}
     itemTree.removeObject(treeNode);
-    treeNode := itemTree.insertObject(itemTree{doesn't matter}, arrIdx, true); // static object
+    treeNode := itemTree.insertObject(arrIdx, 0, true); // static object
     {$ENDIF}
 
     itemTree.getNodeXY(treeNode, x, y);
index cd2aff9951cc4908138407a965ef2acb17d5b77d..0543abe30aa620cb0506c08d8e2c480dc06a0356 100644 (file)
@@ -189,6 +189,26 @@ const
   FLAG_SIGNATURE = $47414C46; // 'FLAG'
 
 
+type
+  TPanelGrid = specialize TBodyGridBase<TPanel>;
+
+  TDynAABBTreePanelBase = specialize TDynAABBTreeBase<TPanel>;
+
+  TDynAABBTreeMap = class(TDynAABBTreePanelBase)
+    function getFleshAABB (out aabb: AABB2D; pan: TPanel; tag: Integer): Boolean; override;
+  end;
+
+function TDynAABBTreeMap.getFleshAABB (out aabb: AABB2D; pan: TPanel; tag: Integer): Boolean;
+begin
+  result := false;
+  if (pan = nil) then begin aabb := AABB2D.Create(0, 0, 0, 0); exit; end;
+  aabb := AABB2D.Create(pan.X, pan.Y, pan.X+pan.Width-1, pan.Y+pan.Height-1);
+  if (pan.Width < 1) or (pan.Height < 1) then exit;
+  if not aabb.valid then raise Exception.Create('wutafuuuuuuu?!');
+  result := true;
+end;
+
+
 function panelTypeToTag (panelType: Word): Integer;
 begin
   case panelType of
@@ -231,37 +251,14 @@ type
     PArrID: Integer;
   end;
 
-type
-  TDynAABBTreeMap = class(TDynAABBTree)
-    function getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean; override;
-  end;
-
-function TDynAABBTreeMap.getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean;
-var
-  pan: TPanel;
-begin
-  result := false;
-  if (flesh = nil) then begin aabb := AABB2D.Create(0, 0, 0, 0); exit; end;
-  //pan := (flesh as TPanel);
-  pan := TPanel(flesh);
-  aabb := AABB2D.Create(pan.X, pan.Y, pan.X+pan.Width, pan.Y+pan.Height);
-  if (pan.Width < 1) or (pan.Height < 1) then exit;
-  //if (pan.Width = 1) then aabb.maxX += 1;
-  //if (pan.Height = 1) then aabb.maxY += 1;
-  //if (pan.Width < 3) or (pan.Height < 3) then exit;
-  //aabb := AABB2D.Create(pan.X, pan.Y, pan.X+pan.Width-2, pan.Y+pan.Height-2);
-  if not aabb.valid then raise Exception.Create('wutafuuuuuuu?!');
-  result := aabb.valid;
-end;
-
 var
   PanelById:     array of TPanelID;
   Textures:      TLevelTextureArray;
   RespawnPoints: Array of TRespawnPoint;
   FlagPoints:    Array [FLAG_RED..FLAG_BLUE] of PFlagPoint;
   //DOMFlagPoints: Array of TFlagPoint;
-  gMapGrid: TBodyGrid = nil;
-  mapTree: TDynAABBTree = nil;
+  gMapGrid: TPanelGrid = nil;
+  mapTree: TDynAABBTreeMap = nil;
 
 
 procedure g_Map_ProfilersBegin ();
@@ -1080,7 +1077,7 @@ begin
 
   e_WriteLog(Format('map dimensions: (%d,%d)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1]), MSG_WARNING);
 
-  gMapGrid := TBodyGrid.Create(mapX0, mapY0, mapX1-mapX0+1, mapY1-mapY0+1);
+  gMapGrid := TPanelGrid.Create(mapX0, mapY0, mapX1-mapX0+1, mapY1-mapY0+1);
   mapTree := TDynAABBTreeMap.Create();
 
   addPanelsToGrid(gWalls, PANEL_WALL);
@@ -1923,16 +1920,10 @@ end;
 
 // new algo
 procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer);
-  function checker (obj: TObject; tag: Integer): Boolean;
-  var
-    pan: TPanel;
+  function checker (pan: TPanel; tag: Integer): Boolean;
   begin
     result := false; // don't stop, ever
-    //pan := (obj as TPanel);
-    pan := TPanel(obj);
-    //if (PanelType = PANEL_CLOSEDOOR) then begin if not pan.Door then exit; end else begin if pan.Door then exit; end;
     if ((tag and GridTagDoor) <> 0) <> pan.Door then exit;
-    //dplAddPanel(pan);
     gDrawPanelList.insert(pan);
   end;
 
@@ -1949,26 +1940,13 @@ begin
     gMapGrid.forEachInAABB(x0, y0, wdt, hgt, checker, (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore));
   end;
   // list will be rendered in `g_game.DrawPlayer()`
-  (*
-  while (gDrawPanelList.count > 0) do
-  begin
-    //(gDrawPanelList.front() as TPanel).Draw();
-    TPanel(gDrawPanelList.front()).Draw();
-    gDrawPanelList.popFront();
-  end;
-  *)
 end;
 
 
 procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
-  function checker (obj: TObject; tag: Integer): Boolean;
-  var
-    pan: TPanel;
+  function checker (pan: TPanel; tag: Integer): Boolean;
   begin
     result := false; // don't stop, ever
-    //if (tag <> GridTagWall) and (tag <> GridTagDoor) then exit; // only walls
-    //pan := (obj as TPanel);
-    pan := TPanel(obj);
     pan.DrawShadowVolume(lightX, lightY, radius);
   end;
 
@@ -2144,12 +2122,9 @@ end;
 
 
 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
-  function checker (obj: TObject; tag: Integer): Boolean;
-  var
-    pan: TPanel;
+  function checker (pan: TPanel; tag: Integer): Boolean;
   begin
     result := false; // don't stop, ever
-    pan := TPanel(obj);
 
     if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
     begin
@@ -2222,9 +2197,7 @@ var
   texid: DWORD;
 
   // slightly different from the old code, but meh...
-  function checker (obj: TObject; tag: Integer): Boolean;
-  var
-    pan: TPanel;
+  function checker (pan: TPanel; tag: Integer): Boolean;
   begin
     result := false; // don't stop, ever
     //if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2)) = 0) then exit;
@@ -2235,10 +2208,9 @@ var
       //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
     end;
     // collision?
-    pan := (obj as TPanel);
     if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
     // yeah
-    texid := TPanel(obj).GetTextureID();
+    texid := pan.GetTextureID();
     // water? water has the highest priority, so stop right here
     if ((tag and GridTagWater) <> 0) then begin cctype := 0; result := true; exit; end;
     // acid2?
index a64e6322b7cbd98518e4ee9671cdb2a70aaccd94..3371a1ea82e90e17db293748e4c43b581ba4ce72 100644 (file)
@@ -29,8 +29,6 @@ uses
 type
   {$IFDEF aabbtree_use_floats}TreeNumber = Single;{$ELSE}TreeNumber = Integer;{$ENDIF}
 
-  TTreeFlesh = TObject;
-
 
 // ////////////////////////////////////////////////////////////////////////// //
 type
@@ -133,7 +131,10 @@ type
 // ////////////////////////////////////////////////////////////////////////// //
 // Dynamic AABB Tree: can be used to speed up broad phase in various engines
 type
-  TDynAABBTree = class(TObject)
+  generic TDynAABBTreeBase<ITP> = class(TObject)
+  public
+    type TTreeFlesh = ITP;
+
   private
     type
       PTreeNode = ^TTreeNode;
@@ -167,9 +168,13 @@ type
         procedure dumpToLog ();
       end;
 
-      TVisitCheckerCB = function (node: PTreeNode): Boolean is nested;
+      TVisitCheckerCB = function (node: PTreeNode): Boolean of object;
       //TVisitVisitorCB = function (abody: TTreeFlesh; atag: Integer): Boolean is nested;
 
+    const ModeNoChecks = 0;
+    const ModeAABB = 1;
+    const ModePoint = 2;
+
   public
     // return `true` to stop
     type TForEachLeafCB = function (abody: TTreeFlesh; var aabb: AABB2D): Boolean is nested; // WARNING! don't modify AABB here!
@@ -184,23 +189,13 @@ type
     const LinearMotionGapMultiplier = 17; // *10
     {$ENDIF}
 
-  private
-    mNodes: array of TTreeNode; // nodes of the tree
-    mRootNodeId: Integer; // id of the root node of the tree
-    mFreeNodeId: Integer; // id of the first node of the list of free (allocated) nodes in the tree that we can use
-    mAllocCount: Integer; // number of allocated nodes in the tree
-    mNodeCount: Integer; // number of nodes in the tree
-
-    // extra AABB Gap used to allow the collision shape to move a little bit
-    // without triggering a large modification of the tree which can be costly
-    mExtraGap: TreeNumber;
-
   public
     // called when a overlapping node has been found during the call to forEachAABBOverlap()
     // return `true` to stop
     type TQueryOverlapCB = function (abody: TTreeFlesh; atag: Integer): Boolean is nested;
     type TSegQueryCallback = function (abody: TTreeFlesh; ax, ay, bx, by: Single): Single is nested; // return dist from (ax,ay) to abody
 
+    PSegmentQueryResult = ^TSegmentQueryResult;
     TSegmentQueryResult = record
       dist: Single; // <0: nothing was hit
       flesh: TTreeFlesh;
@@ -209,6 +204,33 @@ type
       function valid (): Boolean; inline;
     end;
 
+  private
+    mNodes: array of TTreeNode; // nodes of the tree
+    mRootNodeId: Integer; // id of the root node of the tree
+    mFreeNodeId: Integer; // id of the first node of the list of free (allocated) nodes in the tree that we can use
+    mAllocCount: Integer; // number of allocated nodes in the tree
+    mNodeCount: Integer; // number of nodes in the tree
+
+    // extra AABB Gap used to allow the collision shape to move a little bit
+    // without triggering a large modification of the tree which can be costly
+    mExtraGap: TreeNumber;
+
+    chkAABB: AABB2D; // for checkers
+    qSRes: PSegmentQueryResult; // for queries
+    // for segment query
+    maxFraction: Single;
+    curax, curay: Single;
+    curbx, curby: Single;
+    dirx, diry: Single;
+    sqcb: TSegQueryCallback;
+
+    function checkerAABB (node: PTreeNode): Boolean;
+    function checkerPoint (node: PTreeNode): Boolean;
+    function checkerRay (node: PTreeNode): Boolean;
+    function visitorRay (flesh: TTreeFlesh; tag: Integer): Boolean;
+
+    type TQueryOverlapDg = function (abody: TTreeFlesh; atag: Integer): Boolean of object;
+
   private
     function allocateNode (): Integer;
     procedure releaseNode (nodeId: Integer);
@@ -218,7 +240,9 @@ type
     function computeHeight (nodeId: Integer): Integer;
     function insertObjectInternal (var aabb: AABB2D; staticObject: Boolean): Integer;
     procedure setup ();
-    function visit (var caabb: AABB2D; mode: Integer; checker: TVisitCheckerCB; visitor: TQueryOverlapCB; tagmask: Integer=-1): Integer;
+    function visit (var caabb: AABB2D; mode: Integer; checker: TVisitCheckerCB; visitor: TQueryOverlapCB; visdg: TQueryOverlapDg; tagmask: Integer): Integer;
+
+    function forEachNode (nodeId: Integer; dg: TForEachLeafCB): Boolean;
 
   public
     {$IFDEF aabbtree_query_count}
@@ -243,7 +267,7 @@ type
     function getNodeXY (nodeid: Integer; out x, y: Integer): Boolean; inline;
 
     // return `false` for invalid flesh
-    function getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean; virtual; abstract;
+    function getFleshAABB (out aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean; virtual; abstract;
 
     // insert an object into the tree
     // this method creates a new leaf node in the tree and returns the id of the corresponding node or -1 on error
@@ -274,8 +298,8 @@ type
     function updateObject (nodeId: Integer; forceReinsert: Boolean=false): Boolean; overload;
 
     function aabbQuery (ax, ay, aw, ah: TreeNumber; cb: TQueryOverlapCB; tagmask: Integer=-1): TTreeFlesh;
-    function pointQuery (ax, ay: TreeNumber; cb: TQueryOverlapCB): TTreeFlesh;
-    function segmentQuery (var qr: TSegmentQueryResult; ax, ay, bx, by: TreeNumber; cb: TSegQueryCallback): Boolean;
+    function pointQuery (ax, ay: TreeNumber; cb: TQueryOverlapCB; tagmask: Integer=-1): TTreeFlesh;
+    function segmentQuery (var qr: TSegmentQueryResult; ax, ay, bx, by: TreeNumber; cb: TSegQueryCallback; tagmask: Integer=-1): Boolean;
 
     function computeTreeHeight (): Integer; // compute the height of the tree
 
@@ -292,6 +316,13 @@ type
   end;
 
 
+function dtMinI (a, b: Integer): Integer; inline;
+function dtMaxI (a, b: Integer): Integer; inline;
+
+function dtMinF (a, b: TreeNumber): TreeNumber; inline;
+function dtMaxF (a, b: TreeNumber): TreeNumber; inline;
+
+
 implementation
 
 uses
@@ -299,11 +330,11 @@ uses
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-function minI (a, b: Integer): Integer; inline; begin if (a < b) then result := a else result := b; end;
-function maxI (a, b: Integer): Integer; inline; begin if (a > b) then result := a else result := b; end;
+function dtMinI (a, b: Integer): Integer; inline; begin if (a < b) then result := a else result := b; end;
+function dtMaxI (a, b: Integer): Integer; inline; begin if (a > b) then result := a else result := b; end;
 
-function minF (a, b: TreeNumber): TreeNumber; inline; begin if (a < b) then result := a else result := b; end;
-function maxF (a, b: TreeNumber): TreeNumber; inline; begin if (a > b) then result := a else result := b; end;
+function dtMinF (a, b: TreeNumber): TreeNumber; inline; begin if (a < b) then result := a else result := b; end;
+function dtMaxF (a, b: TreeNumber): TreeNumber; inline; begin if (a > b) then result := a else result := b; end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
@@ -363,7 +394,7 @@ begin
   setMergeTwo(aabb0, aabb1);
 end;
 
-function AABB2D.getvalid (): Boolean; inline; begin result := (minX < maxX) and (minY < maxY); end;
+function AABB2D.getvalid (): Boolean; inline; begin result := (minX <= maxX) and (minY <= maxY); end;
 
 {$IFDEF aabbtree_use_floats}
 function AABB2D.getcenterX (): TreeNumber; inline; begin result := (minX+maxX)/2.0; end;
@@ -372,8 +403,8 @@ function AABB2D.getcenterY (): TreeNumber; inline; begin result := (minY+maxY)/2
 function AABB2D.getcenterX (): TreeNumber; inline; begin result := (minX+maxX) div 2; end;
 function AABB2D.getcenterY (): TreeNumber; inline; begin result := (minY+maxY) div 2; end;
 {$ENDIF}
-function AABB2D.getextentX (): TreeNumber; inline; begin result := (maxX-minX); end;
-function AABB2D.getextentY (): TreeNumber; inline; begin result := (maxY-minY); end;
+function AABB2D.getextentX (): TreeNumber; inline; begin result := maxX-minX+1; end;
+function AABB2D.getextentY (): TreeNumber; inline; begin result := maxY-minY+1; end;
 
 procedure AABB2D.copyFrom (var aabb: AABB2D); inline;
 begin
@@ -389,10 +420,10 @@ end;
 
 procedure AABB2D.setDims (x0, y0, x1, y1: TreeNumber); inline;
 begin
-  minX := minF(x0, x1);
-  minY := minF(y0, y1);
-  maxX := maxF(x0, x1);
-  maxY := maxF(y0, y1);
+  minX := dtMinF(x0, x1);
+  minY := dtMinF(y0, y1);
+  maxX := dtMaxF(x0, x1);
+  maxY := dtMaxF(y0, y1);
   {$IF DEFINED(D2F_DEBUG)}
   if not valid then raise Exception.Create('setDims: result is fucked');
   {$ENDIF}
@@ -405,10 +436,10 @@ begin
   if not aabb0.valid then raise Exception.Create('setMergeTwo: aabb0 is fucked');
   if not aabb1.valid then raise Exception.Create('setMergeTwo: aabb0 is fucked');
   {$ENDIF}
-  minX := minF(aabb0.minX, aabb1.minX);
-  minY := minF(aabb0.minY, aabb1.minY);
-  maxX := maxF(aabb0.maxX, aabb1.maxX);
-  maxY := maxF(aabb0.maxY, aabb1.maxY);
+  minX := dtMinF(aabb0.minX, aabb1.minX);
+  minY := dtMinF(aabb0.minY, aabb1.minY);
+  maxX := dtMaxF(aabb0.maxX, aabb1.maxX);
+  maxY := dtMaxF(aabb0.maxY, aabb1.maxY);
   {$IF DEFINED(D2F_DEBUG)}
   if not valid then raise Exception.Create('setMergeTwo: result is fucked');
   {$ENDIF}
@@ -417,7 +448,7 @@ end;
 
 function AABB2D.volume (): TreeNumber; inline;
 begin
-  result := (maxX-minX)*(maxY-minY);
+  result := (maxX-minX+1)*(maxY-minY+1);
 end;
 
 
@@ -426,10 +457,10 @@ begin
   {$IF DEFINED(D2F_DEBUG)}
   if not aabb.valid then raise Exception.Create('merge: aabb is fucked');
   {$ENDIF}
-  minX := minF(minX, aabb.minX);
-  minY := minF(minY, aabb.minY);
-  maxX := maxF(maxX, aabb.maxX);
-  maxY := maxF(maxY, aabb.maxY);
+  minX := dtMinF(minX, aabb.minX);
+  minY := dtMinF(minY, aabb.minY);
+  maxX := dtMaxF(maxX, aabb.maxX);
+  maxY := dtMaxF(maxY, aabb.maxY);
   {$IF DEFINED(D2F_DEBUG)}
   if not valid then raise Exception.Create('setMergeTwo: result is fucked');
   {$ENDIF}
@@ -527,20 +558,20 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure TDynAABBTree.TSegmentQueryResult.reset (); inline; begin dist := -1; flesh := nil; end;
-function TDynAABBTree.TSegmentQueryResult.valid (): Boolean; inline; begin result := (dist >= 0) and (flesh <> nil); end;
+procedure TDynAABBTreeBase.TSegmentQueryResult.reset (); inline; begin dist := -1; flesh := Default(ITP); end;
+function TDynAABBTreeBase.TSegmentQueryResult.valid (): Boolean; inline; begin result := (dist >= 0) and (flesh <> Default(ITP)); end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-function TDynAABBTree.TTreeNode.leaf (): Boolean; inline; begin result := (height = 0); end;
-function TDynAABBTree.TTreeNode.isfree (): Boolean; inline; begin result := (height = -1); end;
+function TDynAABBTreeBase.TTreeNode.leaf (): Boolean; inline; begin result := (height = 0); end;
+function TDynAABBTreeBase.TTreeNode.isfree (): Boolean; inline; begin result := (height = -1); end;
 
-procedure TDynAABBTree.TTreeNode.clear (); inline;
+procedure TDynAABBTreeBase.TTreeNode.clear (); inline;
 begin
   parentId := 0;
   children[0] := 0;
   children[1] := 0;
-  flesh := nil;
+  flesh := Default(ITP);
   tag := 0;
   height := 0;
   aabb.minX := 0;
@@ -549,7 +580,7 @@ begin
   aabb.maxY := 0;
 end;
 
-procedure TDynAABBTree.TTreeNode.dumpToLog ();
+procedure TDynAABBTreeBase.TTreeNode.dumpToLog ();
 begin
   e_WriteLog(Format('NODE: parentId=%d; children=[%d,%d]; height=%d; tag=%d; fleshX=%d; fleshY=%d; aabb=(%d,%d)-(%d,%d)',
     [parentId, children[0], children[1], Integer(height), tag, fleshX, fleshY, aabb.minX, aabb.minY, aabb.maxX, aabb.maxY]),
@@ -559,7 +590,7 @@ end;
 
 // ////////////////////////////////////////////////////////////////////////// //
 // allocate and return a node to use in the tree
-function TDynAABBTree.allocateNode (): Integer;
+function TDynAABBTreeBase.allocateNode (): Integer;
 var
   i, newsz, freeNodeId: Integer;
   node: PTreeNode;
@@ -597,14 +628,14 @@ end;
 
 
 // release a node
-procedure TDynAABBTree.releaseNode (nodeId: Integer);
+procedure TDynAABBTreeBase.releaseNode (nodeId: Integer);
 begin
   {$IFDEF aabbtree_many_asserts}assert(mNodeCount > 0);{$ENDIF}
   {$IFDEF aabbtree_many_asserts}assert((nodeId >= 0) and (nodeId < mAllocCount));{$ENDIF}
   {$IFDEF aabbtree_many_asserts}assert(mNodes[nodeId].height >= 0);{$ENDIF}
   mNodes[nodeId].nextNodeId := mFreeNodeId;
   mNodes[nodeId].height := -1;
-  mNodes[nodeId].flesh := nil;
+  mNodes[nodeId].flesh := Default(ITP);
   mFreeNodeId := nodeId;
   Dec(mNodeCount);
 
@@ -614,7 +645,7 @@ end;
 
 // insert a leaf node in the tree
 // the process of inserting a new leaf node in the dynamic tree is described in the book "Introduction to Game Physics with Box2D" by Ian Parberry
-procedure TDynAABBTree.insertLeafNode (nodeId: Integer);
+procedure TDynAABBTreeBase.insertLeafNode (nodeId: Integer);
 var
   newNodeAABB, mergedAABBs, currentAndLeftAABB, currentAndRightAABB: AABB2D;
   currentNodeId: Integer;
@@ -723,7 +754,7 @@ begin
     {$IFDEF aabbtree_many_asserts}assert(rightChild <> TTreeNode.NullTreeNode);{$ENDIF}
 
     // recompute the height of the node in the tree
-    mNodes[currentNodeId].height := maxI(mNodes[leftChild].height, mNodes[rightChild].height)+1;
+    mNodes[currentNodeId].height := dtMaxI(mNodes[leftChild].height, mNodes[rightChild].height)+1;
     {$IFDEF aabbtree_many_asserts}assert(mNodes[currentNodeId].height > 0);{$ENDIF}
 
     // recompute the AABB of the node
@@ -737,7 +768,7 @@ end;
 
 
 // remove a leaf node from the tree
-procedure TDynAABBTree.removeLeafNode (nodeId: Integer);
+procedure TDynAABBTreeBase.removeLeafNode (nodeId: Integer);
 var
   currentNodeId, parentNodeId, grandParentNodeId, siblingNodeId: Integer;
   leftChildId, rightChildId: Integer;
@@ -791,7 +822,7 @@ begin
 
       // recompute the AABB and the height of the current node
       mNodes[currentNodeId].aabb.setMergeTwo(mNodes[leftChildId].aabb, mNodes[rightChildId].aabb);
-      mNodes[currentNodeId].height := maxI(mNodes[leftChildId].height, mNodes[rightChildId].height)+1;
+      mNodes[currentNodeId].height := dtMaxI(mNodes[leftChildId].height, mNodes[rightChildId].height)+1;
       {$IFDEF aabbtree_many_asserts}assert(mNodes[currentNodeId].height > 0);{$ENDIF}
 
       currentNodeId := mNodes[currentNodeId].parentId;
@@ -810,7 +841,7 @@ end;
 // balance the sub-tree of a given node using left or right rotations
 // the rotation schemes are described in the book "Introduction to Game Physics with Box2D" by Ian Parberry
 // this method returns the new root node id
-function TDynAABBTree.balanceSubTreeAtNode (nodeId: Integer): Integer;
+function TDynAABBTreeBase.balanceSubTreeAtNode (nodeId: Integer): Integer;
 var
   nodeA, nodeB, nodeC, nodeF, nodeG: PTreeNode;
   nodeBId, nodeCId, nodeFId, nodeGId: Integer;
@@ -882,8 +913,8 @@ begin
       nodeC.aabb.setMergeTwo(nodeA.aabb, nodeF.aabb);
 
       // recompute the height of node A and C
-      nodeA.height := maxI(nodeB.height, nodeG.height)+1;
-      nodeC.height := maxI(nodeA.height, nodeF.height)+1;
+      nodeA.height := dtMaxI(nodeB.height, nodeG.height)+1;
+      nodeC.height := dtMaxI(nodeA.height, nodeF.height)+1;
       {$IFDEF aabbtree_many_asserts}assert(nodeA.height > 0);{$ENDIF}
       {$IFDEF aabbtree_many_asserts}assert(nodeC.height > 0);{$ENDIF}
     end
@@ -899,8 +930,8 @@ begin
       nodeC.aabb.setMergeTwo(nodeA.aabb, nodeG.aabb);
 
       // recompute the height of node A and C
-      nodeA.height := maxI(nodeB.height, nodeF.height)+1;
-      nodeC.height := maxI(nodeA.height, nodeG.height)+1;
+      nodeA.height := dtMaxI(nodeB.height, nodeF.height)+1;
+      nodeC.height := dtMaxI(nodeA.height, nodeG.height)+1;
       {$IFDEF aabbtree_many_asserts}assert(nodeA.height > 0);{$ENDIF}
       {$IFDEF aabbtree_many_asserts}assert(nodeC.height > 0);{$ENDIF}
     end;
@@ -958,8 +989,8 @@ begin
       nodeB.aabb.setMergeTwo(nodeA.aabb, nodeF.aabb);
 
       // recompute the height of node A and B
-      nodeA.height := maxI(nodeC.height, nodeG.height)+1;
-      nodeB.height := maxI(nodeA.height, nodeF.height)+1;
+      nodeA.height := dtMaxI(nodeC.height, nodeG.height)+1;
+      nodeB.height := dtMaxI(nodeA.height, nodeF.height)+1;
       {$IFDEF aabbtree_many_asserts}assert(nodeA.height > 0);{$ENDIF}
       {$IFDEF aabbtree_many_asserts}assert(nodeB.height > 0);{$ENDIF}
     end
@@ -975,8 +1006,8 @@ begin
       nodeB.aabb.setMergeTwo(nodeA.aabb, nodeG.aabb);
 
       // recompute the height of node A and B
-      nodeA.height := maxI(nodeC.height, nodeF.height)+1;
-      nodeB.height := maxI(nodeA.height, nodeG.height)+1;
+      nodeA.height := dtMaxI(nodeC.height, nodeF.height)+1;
+      nodeB.height := dtMaxI(nodeA.height, nodeG.height)+1;
       {$IFDEF aabbtree_many_asserts}assert(nodeA.height > 0);{$ENDIF}
       {$IFDEF aabbtree_many_asserts}assert(nodeB.height > 0);{$ENDIF}
     end;
@@ -992,7 +1023,7 @@ end;
 
 
 // compute the height of a given node in the tree
-function TDynAABBTree.computeHeight (nodeId: Integer): Integer;
+function TDynAABBTreeBase.computeHeight (nodeId: Integer): Integer;
 var
   node: PTreeNode;
   leftHeight, rightHeight: Integer;
@@ -1008,12 +1039,12 @@ begin
   rightHeight := computeHeight(node.children[TTreeNode.Right]);
 
   // return the height of the node
-  result := 1+maxI(leftHeight, rightHeight);
+  result := 1+dtMaxI(leftHeight, rightHeight);
 end;
 
 
 // internally add an object into the tree
-function TDynAABBTree.insertObjectInternal (var aabb: AABB2D; staticObject: Boolean): Integer;
+function TDynAABBTreeBase.insertObjectInternal (var aabb: AABB2D; staticObject: Boolean): Integer;
 var
   nodeId: Integer;
   node: PTreeNode;
@@ -1048,7 +1079,7 @@ end;
 
 
 // initialize the tree
-procedure TDynAABBTree.setup ();
+procedure TDynAABBTreeBase.setup ();
 var
   i: Integer;
 begin
@@ -1072,88 +1103,88 @@ end;
 
 
 // also, checks if the tree structure is valid (for debugging purpose)
-function TDynAABBTree.forEachLeaf (dg: TForEachLeafCB): Boolean;
-  function forEachNode (nodeId: Integer): Boolean;
-  var
-    pNode: PTreeNode;
-    leftChild, rightChild, height: Integer;
-    aabb: AABB2D;
+function TDynAABBTreeBase.forEachNode (nodeId: Integer; dg: TForEachLeafCB): Boolean;
+var
+  pNode: PTreeNode;
+  leftChild, rightChild, height: Integer;
+  aabb: AABB2D;
+begin
+  result := false;
+  if (nodeId = TTreeNode.NullTreeNode) then exit;
+  // if it is the root
+  if (nodeId = mRootNodeId) then assert(mNodes[nodeId].parentId = TTreeNode.NullTreeNode);
+  // get the children nodes
+  pNode := @mNodes[nodeId];
+  assert(pNode.height >= 0);
+  if (not pNode.aabb.valid) then
   begin
-    result := false;
-    if (nodeId = TTreeNode.NullTreeNode) then exit;
-    // if it is the root
-    if (nodeId = mRootNodeId) then assert(mNodes[nodeId].parentId = TTreeNode.NullTreeNode);
-    // get the children nodes
-    pNode := @mNodes[nodeId];
-    assert(pNode.height >= 0);
-    if (not pNode.aabb.valid) then
+    {$IFDEF aabbtree_use_floats}
+    e_WriteLog(Format('AABB:(%f,%f)-(%f,%f); volume=%f; valid=%d; height=%d; leaf=%d', [pNode.aabb.minX, pNode.aabb.minY, pNode.aabb.maxX, pNode.aabb.maxY, pNode.aabb.volume, Integer(pNode.aabb.valid), pNode.height, Integer(pNode.leaf)]), MSG_NOTIFY);
+    {$ELSE}
+    e_WriteLog(Format('AABB:(%d,%d)-(%d,%d); volume=%d; valid=%d; height=%d; leaf=%d', [pNode.aabb.minX, pNode.aabb.minY, pNode.aabb.maxX, pNode.aabb.maxY, pNode.aabb.volume, Integer(pNode.aabb.valid), pNode.height, Integer(pNode.leaf)]), MSG_NOTIFY);
+    {$ENDIF}
+    if pNode.leaf then
     begin
+      getFleshAABB(aabb, pNode.flesh, pNode.tag);
       {$IFDEF aabbtree_use_floats}
-      e_WriteLog(Format('AABB:(%f,%f)-(%f,%f); volume=%f; valid=%d; height=%d; leaf=%d', [pNode.aabb.minX, pNode.aabb.minY, pNode.aabb.maxX, pNode.aabb.maxY, pNode.aabb.volume, Integer(pNode.aabb.valid), pNode.height, Integer(pNode.leaf)]), MSG_NOTIFY);
+      e_WriteLog(Format('  LEAF AABB:(%f,%f)-(%f,%f); valid=%d; volume=%f', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, Integer(aabb.valid), aabb.volume]), MSG_NOTIFY);
       {$ELSE}
-      e_WriteLog(Format('AABB:(%d,%d)-(%d,%d); volume=%d; valid=%d; height=%d; leaf=%d', [pNode.aabb.minX, pNode.aabb.minY, pNode.aabb.maxX, pNode.aabb.maxY, pNode.aabb.volume, Integer(pNode.aabb.valid), pNode.height, Integer(pNode.leaf)]), MSG_NOTIFY);
+      e_WriteLog(Format('  LEAF AABB:(%d,%d)-(%d,%d); valid=%d; volume=%d', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, Integer(aabb.valid), aabb.volume]), MSG_NOTIFY);
       {$ENDIF}
-      if pNode.leaf then
-      begin
-        getFleshAABB(aabb, pNode.flesh, pNode.tag);
-        {$IFDEF aabbtree_use_floats}
-        e_WriteLog(Format('  LEAF AABB:(%f,%f)-(%f,%f); valid=%d; volume=%f', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, Integer(aabb.valid), aabb.volume]), MSG_NOTIFY);
-        {$ELSE}
-        e_WriteLog(Format('  LEAF AABB:(%d,%d)-(%d,%d); valid=%d; volume=%d', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, Integer(aabb.valid), aabb.volume]), MSG_NOTIFY);
-        {$ENDIF}
-      end;
-    end;
-    assert(pNode.aabb.valid);
-    assert(pNode.aabb.volume > 0);
-    // if the current node is a leaf
-    if (pNode.leaf) then
-    begin
-      assert(pNode.height = 0);
-      if assigned(dg) then result := dg(pNode.flesh, pNode.aabb);
-    end
-    else
-    begin
-      leftChild := pNode.children[TTreeNode.Left];
-      rightChild := pNode.children[TTreeNode.Right];
-      // check that the children node Ids are valid
-      assert((0 <= leftChild) and (leftChild < mAllocCount));
-      assert((0 <= rightChild) and (rightChild < mAllocCount));
-      // check that the children nodes have the correct parent node
-      assert(mNodes[leftChild].parentId = nodeId);
-      assert(mNodes[rightChild].parentId = nodeId);
-      // check the height of node
-      height := 1+maxI(mNodes[leftChild].height, mNodes[rightChild].height);
-      assert(mNodes[nodeId].height = height);
-      // check the AABB of the node
-      aabb := AABB2D.Create(mNodes[leftChild].aabb, mNodes[rightChild].aabb);
-      assert(aabb.minX = mNodes[nodeId].aabb.minX);
-      assert(aabb.minY = mNodes[nodeId].aabb.minY);
-      assert(aabb.maxX = mNodes[nodeId].aabb.maxX);
-      assert(aabb.maxY = mNodes[nodeId].aabb.maxY);
-      // recursively check the children nodes
-      result := forEachNode(leftChild);
-      if not result then result := forEachNode(rightChild);
     end;
   end;
+  assert(pNode.aabb.valid);
+  assert(pNode.aabb.volume > 0);
+  // if the current node is a leaf
+  if (pNode.leaf) then
+  begin
+    assert(pNode.height = 0);
+    if assigned(dg) then result := dg(pNode.flesh, pNode.aabb);
+  end
+  else
+  begin
+    leftChild := pNode.children[TTreeNode.Left];
+    rightChild := pNode.children[TTreeNode.Right];
+    // check that the children node Ids are valid
+    assert((0 <= leftChild) and (leftChild < mAllocCount));
+    assert((0 <= rightChild) and (rightChild < mAllocCount));
+    // check that the children nodes have the correct parent node
+    assert(mNodes[leftChild].parentId = nodeId);
+    assert(mNodes[rightChild].parentId = nodeId);
+    // check the height of node
+    height := 1+dtMaxI(mNodes[leftChild].height, mNodes[rightChild].height);
+    assert(mNodes[nodeId].height = height);
+    // check the AABB of the node
+    aabb := AABB2D.Create(mNodes[leftChild].aabb, mNodes[rightChild].aabb);
+    assert(aabb.minX = mNodes[nodeId].aabb.minX);
+    assert(aabb.minY = mNodes[nodeId].aabb.minY);
+    assert(aabb.maxX = mNodes[nodeId].aabb.maxX);
+    assert(aabb.maxY = mNodes[nodeId].aabb.maxY);
+    // recursively check the children nodes
+    result := forEachNode(leftChild, dg);
+    if not result then result := forEachNode(rightChild, dg);
+  end;
+end;
+
 
+// also, checks if the tree structure is valid (for debugging purpose)
+function TDynAABBTreeBase.forEachLeaf (dg: TForEachLeafCB): Boolean;
 begin
   // recursively check each node
-  result := forEachNode(mRootNodeId);
+  result := forEachNode(mRootNodeId, dg);
 end;
 
 
 // return `true` from visitor to stop immediately
 // checker should check if this node should be considered to further checking
 // returns tree node if visitor says stop or -1
-const ModeNoChecks = 0;
-const ModeAABB = 1;
-const ModePoint = 2;
-function TDynAABBTree.visit (var caabb: AABB2D; mode: Integer; checker: TVisitCheckerCB; visitor: TQueryOverlapCB; tagmask: Integer=-1): Integer;
+function TDynAABBTreeBase.visit (var caabb: AABB2D; mode: Integer; checker: TVisitCheckerCB; visitor: TQueryOverlapCB; visdg: TQueryOverlapDg; tagmask: Integer): Integer;
 var
   stack: array [0..2048] of Integer; // stack with the nodes to visit
   bigstack: array of Integer = nil;
   sp: Integer = 0;
 
+  (*
   procedure spush (id: Integer); inline;
   var
     xsp: Integer;
@@ -1199,15 +1230,16 @@ var
       result := bigstack[sp-length(stack)];
     end;
   end;
+  *)
 
 var
   nodeId: Integer;
   node: PTreeNode;
   doNode: Boolean = false;
+  xsp: Integer;
 begin
   if not assigned(checker) then begin result := -1; exit; end;
-  if not assigned(visitor) then raise Exception.Create('dyntree: empty visitors aren''t supported');
-  //if not assigned(visitor) then begin result := -1; exit; end;
+  //if not assigned(visitor) and not assigned(visdg) then raise Exception.Create('dyntree: empty visitors aren''t supported');
   //try
     {$IFDEF aabbtree_query_count}
     mNodesVisited := 0;
@@ -1215,27 +1247,25 @@ begin
     {$ENDIF}
 
     // start from root node
-    spush(mRootNodeId);
+    {$IF FALSE}
+      spush(mRootNodeId);
+    {$ELSE}
+      if (sp < length(stack)) then begin stack[sp] := mRootNodeId; Inc(sp); end
+      else begin xsp := sp-length(stack); if (xsp < length(bigstack)) then bigstack[xsp] := mRootNodeId
+      else begin SetLength(bigstack, length(bigstack)+1); bigstack[high(bigstack)] := mRootNodeId; end;
+      end;
+      Inc(sp);
+    {$ENDIF}
 
     // while there are still nodes to visit
     while (sp > 0) do
     begin
       // get the next node id to visit
-      {$IF TRUE}
+      {$IF FALSE}
         nodeId := spop();
       {$ELSE}
-        if (sp <= length(stack)) then
-        begin
-          // use "small stack"
-          Dec(sp);
-          nodeId := stack[sp];
-        end
-        else
-        begin
-          // use "big stack"
-          Dec(sp);
-          nodeId := bigstack[sp-length(stack)];
-        end;
+        if (sp <= length(stack)) then begin Dec(sp); nodeId := stack[sp]; end
+        else begin Dec(sp); nodeId := bigstack[sp-length(stack)]; end;
       {$ENDIF}
       // skip it if it is a nil node
       if (nodeId = TTreeNode.NullTreeNode) then continue;
@@ -1270,14 +1300,36 @@ begin
           {$IFDEF aabbtree_query_count}Inc(mNodesDeepVisited);{$ENDIF}
           if ((node.tag and tagmask) <> 0) then
           begin
-            if (visitor(node.flesh, node.tag)) then begin result := nodeId; bigstack := nil; exit; end;
+            if assigned(visitor) then
+            begin
+              if (visitor(node.flesh, node.tag)) then begin result := nodeId; bigstack := nil; exit; end;
+            end;
+            if assigned(visdg) then
+            begin
+              if (visdg(node.flesh, node.tag)) then begin result := nodeId; bigstack := nil; exit; end;
+            end;
           end;
         end
         else
         begin
           // if the node is not a leaf, we need to visit its children
-          spush(node.children[TTreeNode.Left]);
-          spush(node.children[TTreeNode.Right]);
+          {$IF FALSE}
+            spush(node.children[TTreeNode.Left]);
+            spush(node.children[TTreeNode.Right]);
+          {$ELSE}
+            if (sp < length(stack)) then begin stack[sp] := node.children[TTreeNode.Left]; Inc(sp); end
+            else begin xsp := sp-length(stack); if (xsp < length(bigstack)) then bigstack[xsp] := node.children[TTreeNode.Left]
+            else begin SetLength(bigstack, length(bigstack)+1); bigstack[high(bigstack)] := node.children[TTreeNode.Left]; end;
+            end;
+            Inc(sp);
+
+            if (sp < length(stack)) then begin stack[sp] := node.children[TTreeNode.Right]; Inc(sp); end
+            else begin xsp := sp-length(stack); if (xsp < length(bigstack)) then bigstack[xsp] := node.children[TTreeNode.Right]
+            else begin SetLength(bigstack, length(bigstack)+1); bigstack[high(bigstack)] := node.children[TTreeNode.Right]; end;
+            end;
+            Inc(sp);
+
+          {$ENDIF}
         end;
       end;
     end;
@@ -1292,14 +1344,14 @@ end;
 
 // add `extraAABBGap` to bounding boxes so slight object movement won't cause tree rebuilds
 // extra AABB Gap used to allow the collision shape to move a little bit without triggering a large modification of the tree which can be costly
-constructor TDynAABBTree.Create (extraAABBGap: TreeNumber=0);
+constructor TDynAABBTreeBase.Create (extraAABBGap: TreeNumber=0);
 begin
   mExtraGap := extraAABBGap;
   setup();
 end;
 
 
-destructor TDynAABBTree.Destroy ();
+destructor TDynAABBTreeBase.Destroy ();
 begin
   mNodes := nil;
   inherited;
@@ -1307,18 +1359,18 @@ end;
 
 
 // clear all the nodes and reset the tree
-procedure TDynAABBTree.reset ();
+procedure TDynAABBTreeBase.reset ();
 begin
   mNodes := nil;
   setup();
 end;
 
 
-function TDynAABBTree.computeTreeHeight (): Integer; begin result := computeHeight(mRootNodeId); end;
+function TDynAABBTreeBase.computeTreeHeight (): Integer; begin result := computeHeight(mRootNodeId); end;
 
 
 // return the root AABB of the tree
-procedure TDynAABBTree.getRootAABB (var aabb: AABB2D);
+procedure TDynAABBTreeBase.getRootAABB (var aabb: AABB2D);
 begin
   {$IFDEF aabbtree_many_asserts}assert((mRootNodeId >= 0) and (mRootNodeId < mAllocCount));{$ENDIF}
   aabb := mNodes[mRootNodeId].aabb;
@@ -1327,25 +1379,25 @@ end;
 
 // does the given id represents a valid object?
 // WARNING: ids of removed objects can be reused on later insertions!
-function TDynAABBTree.isValidId (id: Integer): Boolean;
+function TDynAABBTreeBase.isValidId (id: Integer): Boolean;
 begin
   result := (id >= 0) and (id < mAllocCount) and (mNodes[id].leaf);
 end;
 
 
 // get object by nodeid; can return nil for invalid ids
-function TDynAABBTree.getNodeObjectId (nodeid: Integer): TTreeFlesh;
+function TDynAABBTreeBase.getNodeObjectId (nodeid: Integer): TTreeFlesh;
 begin
-  if (nodeid >= 0) and (nodeid < mAllocCount) and (mNodes[nodeid].leaf) then result := mNodes[nodeid].flesh else result := nil;
+  if (nodeid >= 0) and (nodeid < mAllocCount) and (mNodes[nodeid].leaf) then result := mNodes[nodeid].flesh else result := Default(ITP);
 end;
 
 // get fat object AABB by nodeid; returns random shit for invalid ids
-procedure TDynAABBTree.getNodeFatAABB (var aabb: AABB2D; nodeid: Integer);
+procedure TDynAABBTreeBase.getNodeFatAABB (var aabb: AABB2D; nodeid: Integer);
 begin
   if (nodeid >= 0) and (nodeid < mAllocCount) and (not mNodes[nodeid].isfree) then aabb.copyFrom(mNodes[nodeid].aabb) else aabb.setDims(0, 0, 0, 0);
 end;
 
-function TDynAABBTree.getNodeXY (nodeid: Integer; out x, y: Integer): Boolean; inline;
+function TDynAABBTreeBase.getNodeXY (nodeid: Integer; out x, y: Integer): Boolean; inline;
 begin
   if (nodeid >= 0) and (nodeid < mAllocCount) and (mNodes[nodeid].leaf) then
   begin
@@ -1372,7 +1424,7 @@ end;
 // this method creates a new leaf node in the tree and returns the id of the corresponding node or -1 on error
 // AABB for static object will not be "fat" (simple optimization)
 // WARNING! inserting the same object several times *WILL* break everything!
-function TDynAABBTree.insertObject (flesh: TTreeFlesh; tag: Integer; staticObject: Boolean=false): Integer;
+function TDynAABBTreeBase.insertObject (flesh: TTreeFlesh; tag: Integer; staticObject: Boolean=false): Integer;
 var
   aabb: AABB2D;
   nodeId, fx, fy: Integer;
@@ -1414,24 +1466,24 @@ end;
 
 // remove an object from the tree
 // WARNING: ids of removed objects can be reused on later insertions!
-procedure TDynAABBTree.removeObject (nodeId: Integer);
+procedure TDynAABBTreeBase.removeObject (nodeId: Integer);
 begin
-  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTree');
+  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTreeBase');
   // remove the node from the tree
   removeLeafNode(nodeId);
   releaseNode(nodeId);
 end;
 
 
-function TDynAABBTree.updateObject (nodeId: Integer; forceReinsert: Boolean=false): Boolean; overload;
+function TDynAABBTreeBase.updateObject (nodeId: Integer; forceReinsert: Boolean=false): Boolean; overload;
 var
   newAABB: AABB2D;
   dispX, dispY: TreeNumber;
 begin
-  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTree.updateObject');
+  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTreeBase.updateObject');
 
-  if not getFleshAABB(newAABB, mNodes[nodeId].flesh, mNodes[nodeId].tag) then raise Exception.Create('invalid flesh dimensions in TDynAABBTree.updateObject');
-  if not newAABB.valid then raise Exception.Create('invalid flesh aabb in TDynAABBTree.updateObject');
+  if not getFleshAABB(newAABB, mNodes[nodeId].flesh, mNodes[nodeId].tag) then raise Exception.Create('invalid flesh dimensions in TDynAABBTreeBase.updateObject');
+  if not newAABB.valid then raise Exception.Create('invalid flesh aabb in TDynAABBTreeBase.updateObject');
 
   dispX := newAABB.minX-mNodes[nodeId].fleshX;
   dispY := newAABB.minY-mNodes[nodeId].fleshY;
@@ -1442,16 +1494,16 @@ begin
   result := updateObject(nodeId, dispX, dispY, forceReinsert);
 end;
 
-function TDynAABBTree.updateObject (nodeId: Integer; dispX, dispY: TreeNumber; forceReinsert: Boolean=false): Boolean; overload;
+function TDynAABBTreeBase.updateObject (nodeId: Integer; dispX, dispY: TreeNumber; forceReinsert: Boolean=false): Boolean; overload;
 var
   newAABB: AABB2D;
   fx, fy: Integer;
   node: PTreeNode;
 begin
-  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTree.updateObject');
+  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTreeBase.updateObject');
 
-  if not getFleshAABB(newAABB, mNodes[nodeId].flesh, mNodes[nodeId].tag) then raise Exception.Create('invalid flesh dimensions in TDynAABBTree.updateObject');
-  if not newAABB.valid then raise Exception.Create('invalid flesh aabb in TDynAABBTree.updateObject');
+  if not getFleshAABB(newAABB, mNodes[nodeId].flesh, mNodes[nodeId].tag) then raise Exception.Create('invalid flesh dimensions in TDynAABBTreeBase.updateObject');
+  if not newAABB.valid then raise Exception.Create('invalid flesh aabb in TDynAABBTreeBase.updateObject');
 
   fx := newAABB.minX;
   fy := newAABB.minY;
@@ -1512,114 +1564,143 @@ begin
 end;
 
 
+function TDynAABBTreeBase.checkerAABB (node: PTreeNode): Boolean;
+begin
+  result := chkAABB.overlaps(node.aabb);
+end;
+
+
 // report all shapes overlapping with the AABB given in parameter
-function TDynAABBTree.aabbQuery (ax, ay, aw, ah: TreeNumber; cb: TQueryOverlapCB; tagmask: Integer=-1): TTreeFlesh;
-var
-  caabb: AABB2D;
-  function checker (node: PTreeNode): Boolean;
-  begin
-    result := caabb.overlaps(node.aabb);
-  end;
+function TDynAABBTreeBase.aabbQuery (ax, ay, aw, ah: TreeNumber; cb: TQueryOverlapCB; tagmask: Integer=-1): TTreeFlesh;
 var
   nid: Integer;
+  oldaabb: AABB2D;
 begin
-  result := nil;
+  result := Default(ITP);
   if not assigned(cb) then exit;
   if (aw < 1) or (ah < 1) then exit;
-  //caabb := AABB2D.Create(ax, ay, ax+aw, ay+ah);
-  caabb.minX := ax;
-  caabb.minY := ay;
-  caabb.maxX := ax+aw;
-  caabb.maxY := ay+ah;
-  nid := visit(caabb, ModeAABB, checker, cb, tagmask);
-  if (nid >= 0) then result := mNodes[nid].flesh else result := nil;
+  //chkAABB := AABB2D.Create(ax, ay, ax+aw, ay+ah);
+  oldaabb := chkAABB;
+  chkAABB.minX := ax;
+  chkAABB.minY := ay;
+  chkAABB.maxX := ax+aw;
+  chkAABB.maxY := ay+ah;
+  nid := visit(chkAABB, ModeAABB, checkerAABB, cb, nil, tagmask);
+  chkAABB := oldaabb;
+  if (nid >= 0) then result := mNodes[nid].flesh else result := Default(ITP);
+end;
+
+
+function TDynAABBTreeBase.checkerPoint (node: PTreeNode): Boolean;
+begin
+  result := node.aabb.contains(chkAABB.minX, chkAABB.minY);
 end;
 
 
 // report body that contains the given point, or nil
-function TDynAABBTree.pointQuery (ax, ay: TreeNumber; cb: TQueryOverlapCB): TTreeFlesh;
-  function checker (node: PTreeNode): Boolean;
-  begin
-    result := node.aabb.contains(ax, ay);
-  end;
-  function dummycb (abody: TTreeFlesh; atag: Integer): Boolean; begin result := false; end;
+function TDynAABBTreeBase.pointQuery (ax, ay: TreeNumber; cb: TQueryOverlapCB; tagmask: Integer=-1): TTreeFlesh;
 var
   nid: Integer;
-  caabb: AABB2D;
+  oldaabb: AABB2D;
 begin
-  if not assigned(cb) then cb := dummycb;
-  caabb := AABB2D.Create(ax, ay, ax+1, ay+1);
-  nid := visit(caabb, ModePoint, checker, cb);
+  oldaabb := chkAABB;
+  chkAABB := AABB2D.Create(ax, ay, ax+1, ay+1);
+  nid := visit(chkAABB, ModePoint, checkerPoint, cb, nil, tagmask);
   {$IFDEF aabbtree_many_asserts}assert((nid < 0) or ((nid >= 0) and (nid < mAllocCount) and (mNodes[nid].leaf)));{$ENDIF}
-  if (nid >= 0) then result := mNodes[nid].flesh else result := nil;
+  chkAABB := oldaabb;
+  if (nid >= 0) then result := mNodes[nid].flesh else result := Default(ITP);
 end;
 
 
-// segment querying method
-function TDynAABBTree.segmentQuery (var qr: TSegmentQueryResult; ax, ay, bx, by: TreeNumber; cb: TSegQueryCallback): Boolean;
-var
-  maxFraction: Single = 1.0e100; // infinity
-  curax, curay: Single;
-  curbx, curby: Single;
-  dirx, diry: Single;
-  invlen: Single;
-  caabb: AABB2D;
+function TDynAABBTreeBase.checkerRay (node: PTreeNode): Boolean;
+begin
+  result := node.aabb.intersects(curax, curay, curbx, curby);
+end;
 
-  function checker (node: PTreeNode): Boolean;
+function TDynAABBTreeBase.visitorRay (flesh: TTreeFlesh; tag: Integer): Boolean;
+var
+  hitFraction: Single;
+begin
+  hitFraction := sqcb(flesh, curax, curay, curbx, curby);
+  // if the user returned a hitFraction of zero, it means that the raycasting should stop here
+  if (hitFraction = 0.0) then
   begin
-    result := node.aabb.intersects(curax, curay, curbx, curby);
+    qSRes.dist := 0;
+    qSRes.flesh := flesh;
+    result := true;
+    exit;
   end;
-
-  function visitor (flesh: TTreeFlesh; tag: Integer): Boolean;
-  var
-    hitFraction: Single;
+  // if the user returned a positive fraction
+  if (hitFraction > 0.0) then
   begin
-    hitFraction := cb(flesh, curax, curay, curbx, curby);
-    // if the user returned a hitFraction of zero, it means that the raycasting should stop here
-    if (hitFraction = 0.0) then
-    begin
-      qr.dist := 0;
-      qr.flesh := flesh;
-      result := true;
-      exit;
-    end;
-    // if the user returned a positive fraction
-    if (hitFraction > 0.0) then
+    // we update the maxFraction value and the ray AABB using the new maximum fraction
+    if (hitFraction < maxFraction) then
     begin
-      // we update the maxFraction value and the ray AABB using the new maximum fraction
-      if (hitFraction < maxFraction) then
-      begin
-        maxFraction := hitFraction;
-        qr.dist := hitFraction;
-        qr.flesh := flesh;
-        // fix curb here
-        //curb := cura+dir*hitFraction;
-        curbx := curax+dirx*hitFraction;
-        curby := curay+diry*hitFraction;
-      end;
+      maxFraction := hitFraction;
+      qSRes.dist := hitFraction;
+      qSRes.flesh := flesh;
+      // fix curb here
+      //curb := cura+dir*hitFraction;
+      curbx := curax+dirx*hitFraction;
+      curby := curay+diry*hitFraction;
     end;
-    result := false; // continue
   end;
+  result := false; // continue
+end;
+
 
+// segment querying method
+function TDynAABBTreeBase.segmentQuery (var qr: TSegmentQueryResult; ax, ay, bx, by: TreeNumber; cb: TSegQueryCallback; tagmask: Integer=-1): Boolean;
+var
+  oldmaxFraction: Single;
+  oldcurax, oldcuray: Single;
+  oldcurbx, oldcurby: Single;
+  olddirx, olddiry: Single;
+  invlen: Single;
+  osres: PSegmentQueryResult;
+  osqcb: TSegQueryCallback;
 begin
   qr.reset();
 
   if (ax >= bx) or (ay >= by) then begin result := false; exit; end;
 
+  oldmaxFraction := maxFraction;
+  oldcurax := curax;
+  oldcuray := curay;
+  oldcurbx := curbx;
+  oldcurby := curby;
+  olddirx := dirx;
+  olddiry := diry;
+
+  maxFraction := 1.0e100; // infinity
   curax := ax;
   curay := ay;
   curbx := bx;
   curby := by;
 
-  dirx := (curbx-curax);
-  diry := (curby-curay);
+  dirx := curbx-curax;
+  diry := curby-curay;
   // normalize
   invlen := 1.0/sqrt(dirx*dirx+diry*diry);
   dirx *= invlen;
   diry *= invlen;
 
-  caabb := AABB2D.Create(0, 0, 1, 1);
-  visit(caabb, ModeNoChecks, checker, visitor);
+  //chkAABB := AABB2D.Create(0, 0, 1, 1);
+  osres := qSRes;
+  qSRes := @qr;
+  osqcb := sqcb;
+  sqcb := cb;
+  visit(chkAABB, ModeNoChecks, checkerRay, nil, visitorRay, tagmask);
+  qSRes := osres;
+  sqcb := osqcb;
+
+  curax := oldcurax;
+  curay := oldcuray;
+  curbx := oldcurbx;
+  curby := oldcurby;
+  dirx := olddirx;
+  diry := olddiry;
+  maxFraction := oldmaxFraction;
 
   result := qr.valid;
 end;