DEADSOFTWARE

tree: desperate attempts to win several microseconds...
[d2df-sdl.git] / src / game / z_aabbtree.pas
index 4f3e9938c0fe9c5f5acb617621a6242e890a5983..7d346de06fce48d5c5529bc6dd478c7fccf06b15 100644 (file)
@@ -15,7 +15,8 @@
  *)
 {$INCLUDE ../shared/a_modes.inc}
 {.$DEFINE aabbtree_many_asserts}
-{.$DEFINE aabbtree_query_count}
+{$DEFINE aabbtree_query_count}
+{.$DEFINE aabbtree_use_floats}
 unit z_aabbtree;
 
 interface
@@ -25,8 +26,7 @@ uses e_log;
 
 // ////////////////////////////////////////////////////////////////////////// //
 type
-  Float = Single;
-  PFloat = ^Float;
+  {$IFDEF aabbtree_use_floats}TreeNumber = Single;{$ELSE}TreeNumber = Integer;{$ENDIF}
 
   TTreeFlesh = TObject;
 
@@ -35,66 +35,66 @@ type
 type
   Ray2D = record
   public
-    origX, origY: Float;
-    dirX, dirY: Float;
+    origX, origY: Single;
+    dirX, dirY: Single;
 
   public
-    constructor Create (ax, ay: Float; aangle: Float); overload;
-    constructor Create (ax0, ay0, ax1, ay1: Float); overload;
-    constructor Create (const aray: Ray2D); overload;
+    constructor Create (ax, ay: Single; aangle: Single); overload;
+    constructor Create (ax0, ay0, ax1, ay1: Single); overload;
+    constructor Create (var aray: Ray2D); overload;
 
-    procedure copyFrom (const aray: Ray2D); inline;
+    procedure copyFrom (var aray: Ray2D); inline;
 
     procedure normalizeDir (); inline;
 
-    procedure setXYAngle (ax, ay: Float; aangle: Float); inline;
-    procedure setX0Y0X1Y1 (ax0, ay0, ax1, ay1: Float); inline;
+    procedure setXYAngle (ax, ay: Single; aangle: Single); inline;
+    procedure setX0Y0X1Y1 (ax0, ay0, ax1, ay1: Single); inline;
   end;
 
 // ////////////////////////////////////////////////////////////////////////// //
 type
   AABB2D = record
   public
-    minX, minY, maxX, maxY: Float;
+    minX, minY, maxX, maxY: TreeNumber;
 
   private
     function getvalid (): Boolean; inline;
-    function getcenterX (): Float; inline;
-    function getcenterY (): Float; inline;
-    function getextentX (): Float; inline;
-    function getextentY (): Float; inline;
+    function getcenterX (): TreeNumber; inline;
+    function getcenterY (): TreeNumber; inline;
+    function getextentX (): TreeNumber; inline;
+    function getextentY (): TreeNumber; inline;
 
   public
-    constructor Create (x0, y0, x1, y1: Float); overload;
-    constructor Create (const aabb: AABB2D); overload;
-    constructor Create (const aabb0, aabb1: AABB2D); overload;
+    constructor Create (x0, y0, x1, y1: TreeNumber); overload;
+    constructor Create (var aabb: AABB2D); overload;
+    constructor Create (var aabb0, aabb1: AABB2D); overload;
 
-    procedure copyFrom (const aabb: AABB2D); inline;
-    procedure setDims (x0, y0, x1, y1: Float); inline;
+    procedure copyFrom (var aabb: AABB2D); inline;
+    procedure setDims (x0, y0, x1, y1: TreeNumber); inline;
 
-    procedure setMergeTwo (const aabb0, aabb1: AABB2D); inline;
+    procedure setMergeTwo (var aabb0, aabb1: AABB2D); inline;
 
-    function volume (): Float; inline;
+    function volume (): TreeNumber; inline;
 
-    procedure merge (const aabb: AABB2D); inline;
+    procedure merge (var aabb: AABB2D); inline;
 
     // return true if the current AABB contains the AABB given in parameter
-    function contains (const aabb: AABB2D): Boolean; inline; overload;
-    function contains (ax, ay: Float): Boolean; inline; overload;
+    function contains (var aabb: AABB2D): Boolean; inline; overload;
+    function contains (ax, ay: TreeNumber): Boolean; inline; overload;
 
     // return true if the current AABB is overlapping with the AABB in parameter
     // two AABBs overlap if they overlap in the two axes at the same time
-    function overlaps (const aabb: AABB2D): Boolean; inline; overload;
+    function overlaps (var aabb: AABB2D): Boolean; inline; overload;
 
     // ray direction must be normalized
-    function intersects (const ray: Ray2D; tmino: PFloat=nil; tmaxo: PFloat=nil): Boolean; overload;
-    function intersects (ax, ay, bx, by: Float): Boolean; inline; overload;
+    function intersects (var ray: Ray2D; tmino: PSingle=nil; tmaxo: PSingle=nil): Boolean; overload;
+    function intersects (ax, ay, bx, by: Single): Boolean; inline; overload;
 
     property valid: Boolean read getvalid;
-    property centerX: Float read getcenterX;
-    property centerY: Float read getcenterY;
-    property extentX: Float read getextentX;
-    property extentY: Float read getextentY;
+    property centerX: TreeNumber read getcenterX;
+    property centerY: TreeNumber read getcenterY;
+    property extentX: TreeNumber read getextentX;
+    property extentY: TreeNumber read getextentY;
   end;
 
 
@@ -153,6 +153,7 @@ type
         height: SmallInt;
         // fat axis aligned bounding box (AABB) corresponding to the node
         aabb: AABB2D;
+        tag: Integer; // just a user-defined tag
       public
         // return true if the node is a leaf of the tree
         procedure clear (); inline;
@@ -163,17 +164,21 @@ type
       end;
 
       TVisitCheckerCB = function (node: PTreeNode): Boolean is nested;
-      TVisitVisitorCB = function (abody: TTreeFlesh): Boolean is nested;
+      //TVisitVisitorCB = function (abody: TTreeFlesh; atag: Integer): Boolean is nested;
 
   public
     // return `true` to stop
-    type TForEachLeafCB = function (abody: TTreeFlesh; const aabb: AABB2D): Boolean is nested; // WARNING! don't modify AABB here!
+    type TForEachLeafCB = function (abody: TTreeFlesh; var aabb: AABB2D): Boolean is nested; // WARNING! don't modify AABB here!
 
   public
     // in the broad-phase collision detection (dynamic AABB tree), the AABBs are
     // also inflated in direction of the linear motion of the body by mutliplying the
     // followin constant with the linear velocity and the elapsed time between two frames
+    {$IFDEF aabbtree_use_floats}
     const LinearMotionGapMultiplier = 1.7;
+    {$ELSE}
+    const LinearMotionGapMultiplier = 17; // *10
+    {$ENDIF}
 
   private
     mNodes: array of TTreeNode; // nodes of the tree
@@ -184,7 +189,21 @@ type
 
     // 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: Float;
+    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
+
+    TSegmentQueryResult = record
+      dist: Single; // <0: nothing was hit
+      flesh: TTreeFlesh;
+
+      procedure reset (); inline;
+      function valid (): Boolean; inline;
+    end;
 
   private
     function allocateNode (): Integer;
@@ -195,7 +214,7 @@ type
     function computeHeight (nodeId: Integer): Integer;
     function insertObjectInternal (var aabb: AABB2D; staticObject: Boolean): Integer;
     procedure setup ();
-    function visit (checker: TVisitCheckerCB; visitor: TVisitVisitorCB): Integer;
+    function visit (var caabb: AABB2D; mode: Integer; checker: TVisitCheckerCB; visitor: TQueryOverlapCB; tagmask: Integer=-1): Integer;
 
   public
     {$IFDEF aabbtree_query_count}
@@ -203,21 +222,7 @@ type
     {$ENDIF}
 
   public
-    // called when a overlapping node has been found during the call to forEachAABBOverlap()
-    // return `true` to stop
-    type TQueryOverlapCB = function (abody: TTreeFlesh): Boolean is nested;
-    type TSegQueryCallback = function (abody: TTreeFlesh; ax, ay, bx, by: Float): Float is nested; // return dist from (ax,ay) to abody
-
-    TSegmentQueryResult = record
-      dist: Float; // <0: nothing was hit
-      flesh: TTreeFlesh;
-
-      procedure reset (); inline;
-      function valid (): Boolean; inline;
-    end;
-
-  public
-    constructor Create (extraAABBGap: Float=0.0);
+    constructor Create (extraAABBGap: TreeNumber=0);
     destructor Destroy (); override;
 
     // clear all the nodes and reset the tree
@@ -237,7 +242,7 @@ type
     // 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 insertObject (flesh: TTreeFlesh; staticObject: Boolean=false): Integer;
+    function insertObject (flesh: TTreeFlesh; tag: Integer; staticObject: Boolean=false): Integer;
 
     // remove an object from the tree
     // WARNING: ids of removed objects can be reused on later insertions!
@@ -258,15 +263,15 @@ type
      *
      * return `true` if the tree was modified.
      *)
-    function updateObject (nodeId: Integer; dispX, dispY: Float; forceReinsert: Boolean=false): Boolean;
+    function updateObject (nodeId: Integer; dispX, dispY: TreeNumber; forceReinsert: Boolean=false): Boolean;
 
-    function aabbQuery (ax, ay, aw, ah: Float; cb: TQueryOverlapCB): Boolean;
-    function pointQuery (ax, ay: Float; cb: TQueryOverlapCB): TTreeFlesh;
-    function segmentQuery (var qr: TSegmentQueryResult; ax, ay, bx, by: Float; cb: TSegQueryCallback): Boolean;
+    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 computeTreeHeight (): Integer; // compute the height of the tree
 
-    property extraGap: Float read mExtraGap write mExtraGap;
+    property extraGap: TreeNumber read mExtraGap write mExtraGap;
     property nodeCount: Integer read mNodeCount;
     property nodeAlloced: Integer read mAllocCount;
     {$IFDEF aabbtree_query_count}
@@ -289,17 +294,17 @@ 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 minF (a, b: Float): Float; inline; begin if (a < b) then result := a else result := b; end;
-function maxF (a, b: Float): Float; 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;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-constructor Ray2D.Create (ax, ay: Float; aangle: Float); begin setXYAngle(ax, ay, aangle); end;
-constructor Ray2D.Create (ax0, ay0, ax1, ay1: Float); begin setX0Y0X1Y1(ax0, ay0, ax1, ay1); end;
-constructor Ray2D.Create (const aray: Ray2D); overload; begin copyFrom(aray); end;
+constructor Ray2D.Create (ax, ay: Single; aangle: Single); begin setXYAngle(ax, ay, aangle); end;
+constructor Ray2D.Create (ax0, ay0, ax1, ay1: Single); begin setX0Y0X1Y1(ax0, ay0, ax1, ay1); end;
+constructor Ray2D.Create (var aray: Ray2D); overload; begin copyFrom(aray); end;
 
 
-procedure Ray2D.copyFrom (const aray: Ray2D); inline;
+procedure Ray2D.copyFrom (var aray: Ray2D); inline;
 begin
   origX := aray.origX;
   origY := aray.origY;
@@ -309,14 +314,14 @@ end;
 
 procedure Ray2D.normalizeDir (); inline;
 var
-  invlen: Float;
+  invlen: Single;
 begin
   invlen := 1.0/sqrt(dirX*dirX+dirY*dirY);
   dirX *= invlen;
   dirY *= invlen;
 end;
 
-procedure Ray2D.setXYAngle (ax, ay: Float; aangle: Float); inline;
+procedure Ray2D.setXYAngle (ax, ay: Single; aangle: Single); inline;
 begin
   origX := ax;
   origY := ay;
@@ -324,7 +329,7 @@ begin
   dirY := sin(aangle);
 end;
 
-procedure Ray2D.setX0Y0X1Y1 (ax0, ay0, ax1, ay1: Float); inline;
+procedure Ray2D.setX0Y0X1Y1 (ax0, ay0, ax1, ay1: Single); inline;
 begin
   origX := ax0;
   origY := ay0;
@@ -335,30 +340,34 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-constructor AABB2D.Create (x0, y0, x1, y1: Float); overload;
+constructor AABB2D.Create (x0, y0, x1, y1: TreeNumber); overload;
 begin
   setDims(x0, y0, x1, y1);
 end;
 
-constructor AABB2D.Create (const aabb: AABB2D); overload;
+constructor AABB2D.Create (var aabb: AABB2D); overload;
 begin
   copyFrom(aabb);
 end;
 
-constructor AABB2D.Create (const aabb0, aabb1: AABB2D); overload;
+constructor AABB2D.Create (var aabb0, aabb1: AABB2D); overload;
 begin
   setMergeTwo(aabb0, aabb1);
 end;
 
 function AABB2D.getvalid (): Boolean; inline; begin result := (minX < maxX) and (minY < maxY); end;
 
-function AABB2D.getcenterX (): Float; inline; begin result := (minX+maxX)/2.0; end;
-function AABB2D.getcenterY (): Float; inline; begin result := (minY+maxY)/2.0; end;
-function AABB2D.getextentX (): Float; inline; begin result := (maxX-minX)+1.0; end;
-function AABB2D.getextentY (): Float; inline; begin result := (maxY-minY)+1.0; end;
-
-
-procedure AABB2D.copyFrom (const aabb: AABB2D); inline;
+{$IFDEF aabbtree_use_floats}
+function AABB2D.getcenterX (): TreeNumber; inline; begin result := (minX+maxX)/2.0; end;
+function AABB2D.getcenterY (): TreeNumber; inline; begin result := (minY+maxY)/2.0; end;
+{$ELSE}
+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;
+
+procedure AABB2D.copyFrom (var aabb: AABB2D); inline;
 begin
   minX := aabb.minX;
   minY := aabb.minY;
@@ -370,7 +379,7 @@ begin
 end;
 
 
-procedure AABB2D.setDims (x0, y0, x1, y1: Float); inline;
+procedure AABB2D.setDims (x0, y0, x1, y1: TreeNumber); inline;
 begin
   minX := minF(x0, x1);
   minY := minF(y0, y1);
@@ -382,7 +391,7 @@ begin
 end;
 
 
-procedure AABB2D.setMergeTwo (const aabb0, aabb1: AABB2D); inline;
+procedure AABB2D.setMergeTwo (var aabb0, aabb1: AABB2D); inline;
 begin
   {$IF DEFINED(D2F_DEBUG)}
   if not aabb0.valid then raise Exception.Create('setMergeTwo: aabb0 is fucked');
@@ -398,13 +407,13 @@ begin
 end;
 
 
-function AABB2D.volume (): Float; inline;
+function AABB2D.volume (): TreeNumber; inline;
 begin
   result := (maxX-minX)*(maxY-minY);
 end;
 
 
-procedure AABB2D.merge (const aabb: AABB2D); inline;
+procedure AABB2D.merge (var aabb: AABB2D); inline;
 begin
   {$IF DEFINED(D2F_DEBUG)}
   if not aabb.valid then raise Exception.Create('merge: aabb is fucked');
@@ -419,7 +428,7 @@ begin
 end;
 
 
-function AABB2D.contains (const aabb: AABB2D): Boolean; inline; overload;
+function AABB2D.contains (var aabb: AABB2D): Boolean; inline; overload;
 begin
   result :=
     (aabb.minX >= minX) and (aabb.minY >= minY) and
@@ -427,13 +436,13 @@ begin
 end;
 
 
-function AABB2D.contains (ax, ay: Float): Boolean; inline; overload;
+function AABB2D.contains (ax, ay: TreeNumber): Boolean; inline; overload;
 begin
   result := (ax >= minX) and (ay >= minY) and (ax <= maxX) and (ay <= maxY);
 end;
 
 
-function AABB2D.overlaps (const aabb: AABB2D): Boolean; inline; overload;
+function AABB2D.overlaps (var aabb: AABB2D): Boolean; inline; overload;
 begin
   result := false;
   // exit with no intersection if found separated along any axis
@@ -445,10 +454,10 @@ end;
 
 // something to consider here is that 0 * inf =nan which occurs when the ray starts exactly on the edge of a box
 // https://tavianator.com/fast-branchless-raybounding-box-intersections-part-2-nans/
-function AABB2D.intersects (const ray: Ray2D; tmino: PFloat=nil; tmaxo: PFloat=nil): Boolean; overload;
+function AABB2D.intersects (var ray: Ray2D; tmino: PSingle=nil; tmaxo: PSingle=nil): Boolean; overload;
 var
-  dinv, t1, t2, tmp: Float;
-  tmin, tmax: Float;
+  dinv, t1, t2, tmp: Single;
+  tmin, tmax: Single;
 begin
   // ok with coplanars
   tmin := -1.0e100;
@@ -490,9 +499,9 @@ begin
   end;
 end;
 
-function AABB2D.intersects (ax, ay, bx, by: Float): Boolean; inline; overload;
+function AABB2D.intersects (ax, ay, bx, by: Single): Boolean; inline; overload;
 var
-  tmin: Float;
+  tmin: Single;
   ray: Ray2D;
 begin
   result := true;
@@ -524,6 +533,7 @@ begin
   children[0] := 0;
   children[1] := 0;
   flesh := nil;
+  tag := 0;
   height := 0;
   aabb.minX := 0;
   aabb.minY := 0;
@@ -591,8 +601,8 @@ var
   currentNodeId: Integer;
   leftChild, rightChild, siblingNode: Integer;
   oldParentNode, newParentNode: Integer;
-  volumeAABB, mergedVolume: Float;
-  costS, costI, costLeft, costRight: Float;
+  volumeAABB, mergedVolume: TreeNumber;
+  costS, costI, costLeft, costRight: TreeNumber;
 begin
   // if the tree is empty
   if (mRootNodeId = TTreeNode.NullTreeNode) then
@@ -618,10 +628,10 @@ begin
     mergedVolume := mergedAABBs.volume;
 
     // compute the cost of making the current node the sibling of the new node
-    costS := 2.0*mergedVolume;
+    costS := 2*mergedVolume;
 
     // compute the minimum cost of pushing the new node further down the tree (inheritance cost)
-    costI := 2.0*(mergedVolume-volumeAABB);
+    costI := 2*(mergedVolume-volumeAABB);
 
     // compute the cost of descending into the left child
     currentAndLeftAABB := AABB2D.Create(newNodeAABB, mNodes[leftChild].aabb);
@@ -806,7 +816,7 @@ begin
   balanceFactor := nodeC.height-nodeB.height;
 
   // if the right node C is 2 higher than left node B
-  if (balanceFactor > 1.0) then
+  if (balanceFactor > 1) then
   begin
     {$IFDEF aabbtree_many_asserts}assert(not nodeC.leaf);{$ENDIF}
 
@@ -1056,11 +1066,19 @@ function TDynAABBTree.forEachLeaf (dg: TForEachLeafCB): Boolean;
     assert(pNode.height >= 0);
     if (not pNode.aabb.valid) then
     begin
+      {$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);
+        {$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);
@@ -1105,9 +1123,12 @@ 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
-function TDynAABBTree.visit (checker: TVisitCheckerCB; visitor: TVisitVisitorCB): Integer;
+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;
 var
-  stack: array [0..255] of Integer; // stack with the nodes to visit
+  stack: array [0..2048] of Integer; // stack with the nodes to visit
   bigstack: array of Integer = nil;
   sp: Integer = 0;
 
@@ -1140,10 +1161,9 @@ var
     end;
   end;
 
-  (*
   function spop (): Integer; inline;
   begin
-    {$IFDEF aabbtree_many_asserts}assert(sp > 0);{$ENDIF}
+    //{$IFDEF aabbtree_many_asserts}assert(sp > 0);{$ENDIF}
     if (sp <= length(stack)) then
     begin
       // use "small stack"
@@ -1157,15 +1177,16 @@ var
       result := bigstack[sp-length(stack)];
     end;
   end;
-  *)
 
 var
   nodeId: Integer;
   node: PTreeNode;
+  doNode: Boolean = false;
 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;
-  try
+  //try
     {$IFDEF aabbtree_query_count}
     mNodesVisited := 0;
     mNodesDeepVisited := 0;
@@ -1178,37 +1199,56 @@ begin
     while (sp > 0) do
     begin
       // get the next node id to visit
-      //nodeId := spop();
-      {$IFDEF aabbtree_many_asserts}assert(sp > 0);{$ENDIF}
-      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 TRUE}
+        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;
+      {$ENDIF}
       // skip it if it is a nil node
       if (nodeId = TTreeNode.NullTreeNode) then continue;
       {$IFDEF aabbtree_query_count}Inc(mNodesVisited);{$ENDIF}
       // get the corresponding node
       node := @mNodes[nodeId];
       // should we investigate this node?
-      if (checker(node)) then
+      case mode of
+        ModeNoChecks: doNode := checker(node);
+        ModeAABB:
+          begin
+            //doNode := caabb.overlaps(node.aabb);
+            // this gives small speedup (or not...)
+            // exit with no intersection if found separated along any axis
+                 if (caabb.maxX < node.aabb.minX) or (caabb.minX > node.aabb.maxX) then doNode := false
+            else if (caabb.maxY < node.aabb.minY) or (caabb.minY > node.aabb.maxY) then doNode := false
+            else doNode := true;
+          end;
+        ModePoint:
+          begin
+            //doNode := node.aabb.contains(caabb.minX, caabb.minY);
+            // this gives small speedup
+            doNode := (caabb.minX >= node.aabb.minX) and (caabb.minY >= node.aabb.minY) and (caabb.minX <= node.aabb.maxX) and (caabb.minY <= node.aabb.maxY);
+          end;
+      end;
+      if doNode then
       begin
         // if the node is a leaf
         if (node.leaf) then
         begin
           // call visitor on it
           {$IFDEF aabbtree_query_count}Inc(mNodesDeepVisited);{$ENDIF}
-          if assigned(visitor) then
+          if ((node.tag and tagmask) <> 0) then
           begin
-            if (visitor(node.flesh)) then begin result := nodeId; exit; end;
+            if (visitor(node.flesh, node.tag)) then begin result := nodeId; bigstack := nil; exit; end;
           end;
         end
         else
@@ -1221,15 +1261,16 @@ begin
     end;
 
     result := -1; // oops
-  finally
     bigstack := nil;
-  end;
+  //finally
+  //  bigstack := nil;
+  //end;
 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: Float=0.0);
+constructor TDynAABBTree.Create (extraAABBGap: TreeNumber=0);
 begin
   mExtraGap := extraAABBGap;
   setup();
@@ -1287,21 +1328,29 @@ 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; staticObject: Boolean=false): Integer;
+function TDynAABBTree.insertObject (flesh: TTreeFlesh; tag: Integer; staticObject: Boolean=false): Integer;
 var
   aabb: AABB2D;
   nodeId: Integer;
 begin
   if not getFleshAABB(aabb, flesh) then
   begin
+    {$IFDEF aabbtree_use_floats}
     e_WriteLog(Format('trying to insert FUCKED FLESH:(%f,%f)-(%f,%f); volume=%f; valid=%d', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, aabb.volume, Integer(aabb.valid)]), MSG_WARNING);
+    {$ELSE}
+    e_WriteLog(Format('trying to insert FUCKED FLESH:(%d,%d)-(%d,%d); volume=%d; valid=%d', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, aabb.volume, Integer(aabb.valid)]), MSG_WARNING);
+    {$ENDIF}
     //raise Exception.Create('trying to insert invalid flesh in dyntree');
     result := -1;
     exit;
   end;
   if not aabb.valid then
   begin
+    {$IFDEF aabbtree_use_floats}
     e_WriteLog(Format('trying to insert FUCKED AABB:(%f,%f)-(%f,%f); volume=%f; valid=%d', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, aabb.volume, Integer(aabb.valid)]), MSG_WARNING);
+    {$ELSE}
+    e_WriteLog(Format('trying to insert FUCKED AABB:(%d,%d)-(%d,%d); volume=%d; valid=%d', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, aabb.volume, Integer(aabb.valid)]), MSG_WARNING);
+    {$ENDIF}
     raise Exception.Create('trying to insert invalid aabb in dyntree');
     result := -1;
     exit;
@@ -1310,6 +1359,7 @@ begin
   nodeId := insertObjectInternal(aabb, staticObject);
   {$IFDEF aabbtree_many_asserts}assert(mNodes[nodeId].leaf);{$ENDIF}
   mNodes[nodeId].flesh := flesh;
+  mNodes[nodeId].tag := tag;
   result := nodeId;
 end;
 
@@ -1325,7 +1375,7 @@ begin
 end;
 
 
-function TDynAABBTree.updateObject (nodeId: Integer; dispX, dispY: Float; forceReinsert: Boolean=false): Boolean;
+function TDynAABBTree.updateObject (nodeId: Integer; dispX, dispY: TreeNumber; forceReinsert: Boolean=false): Boolean;
 var
   newAABB: AABB2D;
 begin
@@ -1351,21 +1401,21 @@ begin
   end;
 
   // inflate the fat AABB in direction of the linear motion of the AABB
-  if (dispX < 0.0) then
+  if (dispX < 0) then
   begin
-    mNodes[nodeId].aabb.minX := mNodes[nodeId].aabb.minX+LinearMotionGapMultiplier*dispX;
+    mNodes[nodeId].aabb.minX := mNodes[nodeId].aabb.minX+LinearMotionGapMultiplier*dispX {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
   end
   else
   begin
-    mNodes[nodeId].aabb.maxX := mNodes[nodeId].aabb.maxX+LinearMotionGapMultiplier*dispX;
+    mNodes[nodeId].aabb.maxX := mNodes[nodeId].aabb.maxX+LinearMotionGapMultiplier*dispX {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
   end;
-  if (dispY < 0.0) then
+  if (dispY < 0) then
   begin
-    mNodes[nodeId].aabb.minY := mNodes[nodeId].aabb.minY+LinearMotionGapMultiplier*dispY;
+    mNodes[nodeId].aabb.minY := mNodes[nodeId].aabb.minY+LinearMotionGapMultiplier*dispY {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
   end
   else
   begin
-    mNodes[nodeId].aabb.maxY := mNodes[nodeId].aabb.maxY+LinearMotionGapMultiplier*dispY;
+    mNodes[nodeId].aabb.maxY := mNodes[nodeId].aabb.maxY+LinearMotionGapMultiplier*dispY {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
   end;
 
   {$IFDEF aabbtree_many_asserts}assert(mNodes[nodeId].aabb.contains(newAABB));{$ENDIF}
@@ -1378,53 +1428,66 @@ end;
 
 
 // report all shapes overlapping with the AABB given in parameter
-function TDynAABBTree.aabbQuery (ax, ay, aw, ah: Float; cb: TQueryOverlapCB): Boolean;
+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;
+var
+  nid: Integer;
 begin
+  result := nil;
   if not assigned(cb) then exit;
   if (aw < 1) or (ah < 1) then exit;
-  caabb := AABB2D.Create(ax, ay, ax+aw, ay+ah);
-  result := (visit(checker, cb) <> -1);
+  //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;
 end;
 
 
 // report body that contains the given point, or nil
-function TDynAABBTree.pointQuery (ax, ay: Float; cb: TQueryOverlapCB): TTreeFlesh;
-var
-  nid: Integer;
+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;
+var
+  nid: Integer;
+  caabb: AABB2D;
 begin
-  nid := visit(checker, cb);
+  if not assigned(cb) then cb := dummycb;
+  caabb := AABB2D.Create(ax, ay, ax+1, ay+1);
+  nid := visit(caabb, ModePoint, checker, cb);
   {$IFDEF aabbtree_many_asserts}assert((nid < 0) or ((nid >= 0) and (nid < mNodeCount) and (mNodes[nid].leaf)));{$ENDIF}
   if (nid >= 0) then result := mNodes[nid].flesh else result := nil;
 end;
 
 
 // segment querying method
-function TDynAABBTree.segmentQuery (var qr: TSegmentQueryResult; ax, ay, bx, by: Float; cb: TSegQueryCallback): Boolean;
+function TDynAABBTree.segmentQuery (var qr: TSegmentQueryResult; ax, ay, bx, by: TreeNumber; cb: TSegQueryCallback): Boolean;
 var
-  maxFraction: Float = 1.0e100; // infinity
-  curax, curay: Float;
-  curbx, curby: Float;
-  dirx, diry: Float;
-  invlen: Float;
+  maxFraction: Single = 1.0e100; // infinity
+  curax, curay: Single;
+  curbx, curby: Single;
+  dirx, diry: Single;
+  invlen: Single;
+  caabb: AABB2D;
 
   function checker (node: PTreeNode): Boolean;
   begin
     result := node.aabb.intersects(curax, curay, curbx, curby);
   end;
 
-  function visitor (flesh: TTreeFlesh): Boolean;
+  function visitor (flesh: TTreeFlesh; tag: Integer): Boolean;
   var
-    hitFraction: Float;
+    hitFraction: Single;
   begin
     hitFraction := cb(flesh, curax, curay, curbx, curby);
     // if the user returned a hitFraction of zero, it means that the raycasting should stop here
@@ -1470,7 +1533,8 @@ begin
   dirx *= invlen;
   diry *= invlen;
 
-  visit(checker, visitor);
+  caabb := AABB2D.Create(0, 0, 1, 1);
+  visit(caabb, ModeNoChecks, checker, visitor);
 
   result := qr.valid;
 end;