DEADSOFTWARE

slightly faster (i hope) particles
authorKetmar Dark <ketmar@ketmar.no-ip.org>
Fri, 1 Sep 2017 07:46:21 +0000 (10:46 +0300)
committerKetmar Dark <ketmar@ketmar.no-ip.org>
Fri, 1 Sep 2017 07:46:57 +0000 (10:46 +0300)
  particles won't trace vertical movement if they are on a ground, and trying to move down

src/game/g_gfx.pas
src/game/g_grid.pas

index 6ce5a66da7847fac8d4184d7b7908784c5503c5a..fc00d5855ae5c89d639250cf9c587d8c498df246 100644 (file)
@@ -70,7 +70,7 @@ implementation
 uses
   g_map, g_panel, g_basic, Math, e_graphics, GL, GLExt,
   g_options, g_console, SysUtils, g_triggers, MAPDEF,
-  g_game, g_language, g_net;
+  g_game, g_language, g_net, xprofiler;
 
 type
   PParticle = ^TParticle;
@@ -89,6 +89,12 @@ type
     liquidTopY: Integer; // don't float higher than this
     // for water
     stickDX: Integer;
+    // for blood
+    justSticked: Boolean;
+    stickEY: Integer;
+    // for all
+    onGround: Boolean;
+    awaken: Boolean;
 
     //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
 
@@ -192,7 +198,33 @@ end;
 
 procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);
 {$IF not DEFINED(HAS_COLLIDE_BITMAP)}
+var
+  part: PParticle;
+  f: Integer;
 begin
+  for f := 0 to High(Particles) do
+  begin
+    part := @Particles[f];
+    if part.alive and (part.onGround or (not part.justSticked and (part.State = STATE_STICK))) and
+       (part.X >= x-2) and (part.Y >= y-2) and (part.X < x+Width+4) and (part.Y < y+Height+4) then
+    begin
+      // wakup this particle
+      {
+      if (part.ParticleType = PARTICLE_SPARK) then
+      begin
+        e_LogWritefln('waking up particle of type %s; justSticked=%s; onGround=%s; VelY=%s; AccelY=%s', [part.ParticleType, part.justSticked, part.onGround, part.VelY, part.AccelY]);
+      end;
+      }
+      part.justSticked := true; // so sticked state will be re-evaluated
+      if part.onGround then
+      begin
+        if (part.VelY = 0) then part.VelY := 0.1;
+        if (part.AccelY = 0) then part.AccelY := 0.5;
+      end;
+      part.onGround := false; // so onground state will be re-evaluated
+      part.awaken := true;
+    end;
+  end;
 end;
 {$ELSE}
 var
@@ -385,7 +417,27 @@ begin
          (not isBlockedAt(X-1, Y)) and
          (not isBlockedAt(X+1, Y))
       {$ELSE}
-      if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
+      if justSticked then
+      begin
+        if not mapGrid.traceOrthoRayWhileIn(ex, ey, X+stickDX, Y, X+stickDX, mapGrid.gridY0+mapGrid.gridHeight, GridTagWall or GridTagDoor or GridTagStep) then
+        begin
+           // îòëèïëà
+          State := STATE_NORMAL;
+          //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
+        end
+        else
+        begin
+          stickEY := ey+1;
+          if (nil <> g_Map_traceToNearest(X, Y, X, stickEY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
+          begin
+            if (ey > stickEY) then stickEY := ey-1;
+          end;
+          justSticked := false;
+          //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
+        end;
+      end;
+      if (State <> STATE_STICK) or (Y >= stickEY)
+      //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
       {$ENDIF}
       then
       begin // Îòëèïëà - êàïàåò
@@ -490,6 +542,7 @@ begin
         AccelX := 0;
         AccelY := 0;
         State := STATE_STICK;
+        justSticked := true;
         if (dX > 0) then stickDX := 1 else stickDX := -1;
       end;
       if (X < 0) or (X >= w) then begin die(); exit; end;
@@ -497,27 +550,32 @@ begin
     // vertical
     if (dY <> 0) then
     begin
-      pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep), @ex, @ey);
-      Y := ey;
-      // free to ride?
-      if (pan <> nil) then
+      if (dY < 0) or not onGround then
       begin
-        // Ñòåíà/äâåðü
-        VelX := 0;
-        VelY := 0;
-        AccelX := 0;
-        AccelY := 0;
-        if (dY > 0) and (State <> STATE_STICK) then
+        pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep), @ex, @ey);
+        Y := ey;
+        // free to ride?
+        if (pan <> nil) then
         begin
-          State := STATE_NORMAL;
-        end
-        else
-        begin
-          State := STATE_STICK;
-               if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
-          else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
-          else stickDX := 0;
+          // Ñòåíà/äâåðü
+          VelX := 0;
+          VelY := 0;
+          AccelX := 0;
+          AccelY := 0;
+          if (dY > 0) and (State <> STATE_STICK) then
+          begin
+            State := STATE_NORMAL;
+          end
+          else
+          begin
+            State := STATE_STICK;
+                 if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
+            else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
+            else stickDX := 0;
+            justSticked := true;
+          end;
         end;
+        onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
       end;
       if (Y < 0) or (Y >= h) then begin die(); exit; end;
     end;
@@ -538,6 +596,7 @@ begin
           AccelX := 0;
           AccelY := 0;
           State := STATE_STICK;
+          justSticked := true;
           break;
         end;
         X := X+s;
@@ -559,6 +618,7 @@ begin
           AccelX := 0;
           AccelY := 0;
           if (s > 0) and (State <> STATE_STICK) then State := STATE_NORMAL else State := STATE_STICK;
+          justSticked := (State = STATE_STICK);
           break;
         end;
         Y := Y+s;
@@ -632,7 +692,30 @@ begin
     end
     else
     begin
-      if (g_Map_PanelAtPoint(X+stickDX, Y, (GridTagWall or GridTagDoor or GridTagStep)) = nil) then State := STATE_NORMAL;
+      if justSticked then
+      begin
+        if not mapGrid.traceOrthoRayWhileIn(ex, ey, X+stickDX, Y, X+stickDX, mapGrid.gridY0+mapGrid.gridHeight, GridTagWall or GridTagDoor or GridTagStep) then
+        begin
+           // îòëèïëà
+          State := STATE_NORMAL;
+          //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
+        end
+        else
+        begin
+          stickEY := ey+1;
+          justSticked := false;
+          if (nil <> g_Map_traceToNearest(X, Y, X, stickEY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
+          begin
+            if (ey > stickEY) then stickEY := ey-1;
+          end;
+          //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
+        end;
+      end
+      else
+      begin
+        if (Y >= stickEY) then State := STATE_NORMAL;
+      end;
+      //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
     end;
     {$ENDIF}
     exit;
@@ -736,6 +819,7 @@ begin
         AccelX := 0;
         AccelY := 0;
         State := STATE_STICK;
+        justSticked := true;
         if (dX > 0) then stickDX := 1 else stickDX := -1;
       end;
     end;
@@ -744,32 +828,37 @@ begin
   // vertical
   if (dY <> 0) then
   begin
-    pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
-    Y := ey;
-    // free to ride?
-    if (pan <> nil) then
+    if (dY < 0) or not onGround then
     begin
-      // nope
-      if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
-      // Ñòåíà/äâåðü?
-      if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
+      pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
+      Y := ey;
+      // free to ride?
+      if (pan <> nil) then
       begin
-        VelX := 0;
-        VelY := 0;
-        AccelX := 0;
-        AccelY := 0;
-        if (dY > 0) and (State <> STATE_STICK) then
-        begin
-          State := STATE_NORMAL;
-        end
-        else
+        // nope
+        if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
+        // Ñòåíà/äâåðü?
+        if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
         begin
-          State := STATE_STICK;
-               if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
-          else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
-          else stickDX := 0;
+          VelX := 0;
+          VelY := 0;
+          AccelX := 0;
+          AccelY := 0;
+          if (dY > 0) and (State <> STATE_STICK) then
+          begin
+            State := STATE_NORMAL;
+          end
+          else
+          begin
+            State := STATE_STICK;
+                 if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
+            else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
+            else stickDX := 0;
+            justSticked := true;
+          end;
         end;
       end;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
     end;
     if (Y < 0) or (Y >= gMapInfo.Height) then begin die(); exit; end;
   end;
@@ -792,6 +881,7 @@ begin
         AccelX := 0;
         AccelY := 0;
         State := STATE_STICK;
+        justSticked := true;
         Break;
       end;
       X := X+s;
@@ -815,6 +905,7 @@ begin
         AccelX := 0;
         AccelY := 0;
         if (s > 0) and (State <> STATE_STICK) then State := STATE_NORMAL else State := STATE_STICK;
+        justSticked := (State = STATE_STICK);
         break;
       end;
       Y := Y+s;
@@ -903,37 +994,36 @@ begin
   if (dY <> 0) then
   begin
     {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
-    pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
-    //e_WriteLog(Format('spark y-trace: (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hit=%d', [X, Y, X, Y+dY, dY, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
-    (*
-    if (pan <> nil) then
+    if (dY < 0) or not onGround then
     begin
-      e_WriteLog(Format('spark y-trace: %08x (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hittag=%04x', [LongWord(@self), X, Y, X, Y+dY, dY, ex, ey, pan.tag]), MSG_NOTIFY);
-    end
-    else
-    begin
-      e_WriteLog(Format('spark y-trace: %08x (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hit=%d', [LongWord(@self), X, Y, X, Y+dY, dY, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
-    end;
-    *)
-    Y := ey;
-    // free to ride?
-    if (pan <> nil) then
-    begin
-      //die(); exit;
-      // nope
-      if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
-      if (dY < 0) then
+      pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
+      Y := ey;
+      {
+      if awaken then
       begin
-        VelY := -VelY;
-        AccelY := abs(AccelY);
-      end
-      else
+        awaken := false;
+        e_LogWritefln('AWAKEN particle of type %s; justSticked=%s; onGround=%s; VelY=%s; AccelY=%s; Y=%s; ey=%s', [ParticleType, justSticked, onGround, VelY, AccelY, Y, ey]);
+      end;
+      }
+      // free to ride?
+      if (pan <> nil) then
       begin
-        VelX := 0;
-        AccelX := 0;
-        VelY := 0;
-        AccelY := 0.8;
+        // nope
+        if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
+        if (dY < 0) then
+        begin
+          VelY := -VelY;
+          AccelY := abs(AccelY);
+        end
+        else
+        begin
+          VelX := 0;
+          AccelX := 0;
+          VelY := 0;
+          AccelY := 0.8;
+        end;
       end;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
     end;
     if (Y < 0) or (Y >= gMapInfo.Height) then begin die(); exit; end;
     {$ELSE}
@@ -1073,6 +1163,9 @@ begin
       Time := 0;
       LiveTime := 30+Random(60);
       ParticleType := PARTICLE_SPARK;
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -1169,6 +1262,10 @@ begin
       Time := 0;
       LiveTime := 120+Random(40);
       ParticleType := PARTICLE_BLOOD;
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
+      //stickEY := 0;
     end;
 
     if CurrentParticle >= MaxParticles-1 then
@@ -1230,6 +1327,9 @@ begin
       Time := 0;
       LiveTime := 30+Random(60);
       ParticleType := PARTICLE_SPARK;
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -1311,6 +1411,9 @@ begin
       Time := 0;
       LiveTime := 60+Random(60);
       ParticleType := PARTICLE_WATER;
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -1395,6 +1498,9 @@ begin
       Time := 0;
       LiveTime := 60+Random(60);
       ParticleType := PARTICLE_WATER;
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -1405,12 +1511,17 @@ begin
 end;
 
 
+{.$DEFINE D2F_DEBUG_BUBBLES}
 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
 var
   a: Integer;
   DevX1, DevX2,
   DevY1, DevY2: Byte;
   l, liquidx: Integer;
+  {$IF DEFINED(D2F_DEBUG_BUBBLES)}
+  stt: UInt64;
+  nptr, ptr: Boolean;
+  {$ENDIF}
 begin
   if not gpart_dbg_enabled then Exit;
   l := Length(Particles);
@@ -1443,7 +1554,21 @@ begin
 
       // trace liquid, so we'll know where it ends; do it in 8px steps for speed
       // tracer will return `false` if we started outside of the liquid
+
+      {$IF DEFINED(D2F_DEBUG_BUBBLES)}
+      stt := curTimeMicro();
+      ptr := mapGrid.traceOrthoRayWhileIn(liquidx, liquidTopY, X, Y, X, 0, GridTagWater or GridTagAcid1 or GridTagAcid2);
+      stt := curTimeMicro()-stt;
+      e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt), ptr, liquidTopY]);
+      //
+      stt := curTimeMicro();
+      nptr := g_Map_TraceLiquidNonPrecise(X, Y, 0, -8, liquidx, liquidTopY);
+      stt := curTimeMicro()-stt;
+      e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt), nptr, liquidTopY]);
+      if not nptr then continue;
+      {$ELSE}
       if not g_Map_TraceLiquidNonPrecise(X, Y, 0, -8, liquidx, liquidTopY) then continue;
+      {$ENDIF}
 
       VelX := 0;
       VelY := -1-Random;
@@ -1459,6 +1584,9 @@ begin
       Time := 0;
       LiveTime := 65535;
       ParticleType := PARTICLE_BUBBLES;
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
index a20fbfc9509b12815178a3261ecccaf30750431f..049868c7821e7784afa08dbe18e68ab97c0d6a31 100644 (file)
@@ -197,8 +197,9 @@ type
     function traceRay (const x0, y0, x1, y1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP; overload;
     function traceRay (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; cb: TGridRayQueryCB; tagmask: Integer=-1): ITP;
 
-    //function traceOrthoRayWhileIn (const x0, y0, x1, y1: Integer; tagmask: Integer=-1): ITP; overload;
-    //function traceOrthoRayWhileIn (out ex, ey: Integer; const ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): ITP;
+    // return `false` if we're still inside at the end
+    // line should be either strict horizontal, or strict vertical, otherwise an exception will be thrown
+    function traceOrthoRayWhileIn (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): Boolean;
 
     //WARNING: don't modify grid while any query is in progress (no checks are made!)
     //         you can set enabled/disabled flag, tho (but iterator can still return objects disabled inside it)
@@ -240,7 +241,7 @@ function maxInt (a, b: Integer): Integer; inline;
 implementation
 
 uses
-  SysUtils, e_log, g_console;
+  SysUtils, e_log, g_console, utils;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
@@ -2441,4 +2442,135 @@ begin
 end;
 
 
+{.$DEFINE D2F_DEBUG_OTR}
+function TBodyGridBase.traceOrthoRayWhileIn (out ex, ey: Integer; ax0, ay0, ax1, ay1: Integer; tagmask: Integer=-1): Boolean;
+var
+  ccidx: Integer;
+  cc: PGridCell;
+  px: PBodyProxyRec;
+  ptag: Integer;
+  minx, miny: Integer;
+  f, c0, c1: Integer;
+  x0, y0, x1, y1: Integer;
+  celly0, celly1: Integer;
+  dy: Integer;
+  filled: array[0..mTileSize-1] of Byte;
+  {$IF DEFINED(D2F_DEBUG_OTR)}
+  s: AnsiString = '';
+  {$ENDIF}
+begin
+  result := false;
+  ex := ax1;
+  ey := ay1;
+  if not ((ax0 = ax1) or (ay0 = ay1)) then raise Exception.Create('orthoray is not orthogonal');
+
+  tagmask := tagmask and TagFullMask;
+  if (tagmask = 0) then exit;
+
+  if (forEachAtPoint(ax0, ay0, nil, tagmask) = nil) then exit;
+
+  minx := mMinX;
+  miny := mMinY;
+
+  // offset query coords to (0,0)-based
+  x0 := ax0-minx;
+  y0 := ay0-miny;
+  x1 := ax1-minx;
+  y1 := ay1-miny;
+
+  if (x0 = x1) then
+  begin
+    if (x0 < 0) or (x0 >= mWidth*mTileSize) then exit; // oops
+    // vertical
+    if (y0 < y1) then
+    begin
+      // down
+      if (y1 < 0) or (y0 >= mHeight*mTileSize) then exit;
+      //if (ay0 < 0) then ay0 := 0;
+      if (y0 < 0) then exit;
+      if (y1 >= mHeight*mTileSize) then y1 := mHeight*mTileSize-1;
+      dy := 1;
+    end
+    else
+    begin
+      // up
+      if (y0 < 0) or (y1 >= mHeight*mTileSize) then exit;
+      //if (ay1 < 0) then ay1 := 0;
+      if (y1 < 0) then exit;
+      if (y0 >= mHeight*mTileSize) then y0 := mHeight*mTileSize-1;
+      dy := -1;
+    end;
+    // check tile
+    while true do
+    begin
+      ccidx := mGrid[(y0 div mTileSize)*mWidth+(x0 div mTileSize)];
+      FillChar(filled, sizeof(filled), 0);
+      celly0 := y0 and (not (mTileSize-1));
+      celly1 := celly0+mTileSize-1;
+      while (ccidx <> -1) do
+      begin
+        cc := @mCells[ccidx];
+        for f := 0 to GridCellBucketSize-1 do
+        begin
+          if (cc.bodies[f] = -1) then break;
+          px := @mProxies[cc.bodies[f]];
+          ptag := px.mTag;
+          if ((ptag and TagDisabled) = 0) and ((ptag and tagmask) <> 0) and
+             (ax0 >= px.x0) and (ax0 <= px.x1) then
+          begin
+            // bound c0 and c1 to cell
+            c0 := nclamp(px.y0-miny, celly0, celly1);
+            c1 := nclamp(px.y1-miny, celly0, celly1);
+            // fill the thing
+            {$IF DEFINED(D2F_DEBUG_OTR)}
+            e_LogWritefln('**px.y0=%s; px.y1=%s; c0=%s; c1=%s; celly0=%s; celly1=%s; [%s..%s]', [px.y0-miny, px.y1-miny, c0, c1, celly0, celly1, c0-celly0, (c0-celly0)+(c1-c0)]);
+            {$ENDIF}
+            //assert(c0 <= c1);
+            FillChar(filled[c0-celly0], c1-c0+1, 1);
+          end;
+        end;
+        // next cell
+        ccidx := cc.next;
+      end;
+      {$IF DEFINED(D2F_DEBUG_OTR)}
+      s := formatstrf('  x=%s; ay0=%s; ay1=%s; y0=%s; celly0=%s; celly1=%s; dy=%s; [', [ax0, ay0, ay1, y0, celly0, celly1, dy]);
+      for f := 0 to High(filled) do if (filled[f] <> 0) then s += '1' else s += '0';
+      s += ']';
+      e_LogWriteln(s);
+      {$ENDIF}
+      // now go till we hit cell boundary or empty space
+      if (dy < 0) then
+      begin
+        // up
+        while (y0 >= celly0) and (filled[y0-celly0] <> 0) do
+        begin
+          {$IF DEFINED(D2F_DEBUG_OTR)}
+          e_LogWritefln('   filled: cdy=%s; y0=%s; celly0=%s; ay0=%s; ay1=%s', [y0-celly0, y0, celly0, ay0, ay1]);
+          {$ENDIF}
+          Dec(y0);
+          Dec(ay0);
+        end;
+        {$IF DEFINED(D2F_DEBUG_OTR)}
+        e_LogWritefln('   span done: cdy=%s; y0=%s; celly0=%s; ay0=%s; ay1=%s', [y0-celly0, y0, celly0, ay0, ay1]);
+        {$ENDIF}
+        if (ay0 <= ay1) then begin ey := ay1; result := false; exit; end;
+        if (y0 >= celly0) then begin ey := ay0+1; {assert(forEachAtPoint(ex, ey, nil, tagmask) <> nil);} result := true; exit; end;
+      end
+      else
+      begin
+        // down
+        while (y0 <= celly1) and (filled[y1-celly0] <> 0) do begin Inc(y0); Inc(ay0); end;
+        if (ay0 >= ay1) then begin ey := ay1; result := false; exit; end;
+        if (y0 <= celly1) then begin ey := ay0-1; result := true; exit; end;
+      end;
+    end;
+  end
+  else
+  begin
+    // horizontal
+    assert(false);
+  end;
+end;
+
+
 end.