DEADSOFTWARE

Sweep-And-Prune broad phase implementation; not working yet
authorKetmar Dark <ketmar@ketmar.no-ip.org>
Thu, 17 Aug 2017 18:25:12 +0000 (21:25 +0300)
committerKetmar Dark <ketmar@ketmar.no-ip.org>
Fri, 18 Aug 2017 18:28:23 +0000 (21:28 +0300)
src/game/Doom2DF.dpr
src/game/g_console.pas
src/game/g_game.pas
src/game/g_grid.pas
src/game/g_map.pas
src/game/g_sap.pas [new file with mode: 0644]
src/game/g_window.pas
src/shared/binheap.pas [new file with mode: 0644]

index e6f4740839272900d528b1cc9774202d30130ff2..3980b1bb39be0b1913b292ab6aa3c87f12a7139b 100644 (file)
@@ -70,6 +70,8 @@ uses
   g_nethandler in 'g_nethandler.pas',
   g_netmaster in 'g_netmaster.pas',
   g_res_downloader in 'g_res_downloader.pas',
+  g_grid in 'g_grid.pas',
+  g_sap in 'g_sap.pas',
   g_game in 'g_game.pas',
   g_gfx in 'g_gfx.pas',
   g_gui in 'g_gui.pas',
@@ -96,6 +98,7 @@ uses
   fmodtypes in '../lib/FMOD/fmodtypes.pas',
 {$ENDIF}
   xprofiler in '../shared/xprofiler.pas',
+  binheap in '../shared/binheap.pas',
   BinEditor in '../shared/BinEditor.pas',
   envvars in '../shared/envvars.pas',
   g_panel in 'g_panel.pas',
index 0231729f7bcd8655f9bf088489c1fcab74e30879..c41cb23c8ab8c41de9c1eb5233f7135d73d0b933 100644 (file)
@@ -394,6 +394,9 @@ begin
   AddCommand('r_gridrender', ProfilerCommands);
   AddCommand('dbg_coldet_grid', ProfilerCommands);
 
+  AddCommand('sq_use_grid', ProfilerCommands);
+  AddCommand('sq_use_sap', ProfilerCommands);
+
   AddCommand('p1_name', GameCVars);
   AddCommand('p2_name', GameCVars);
   AddCommand('p1_color', GameCVars);
index cc97e4c65d63700db25e1dd6489179803ef8d0de..802425a68977ff8074cc0b4ba967bf6dcdfdafbb 100644 (file)
@@ -475,7 +475,7 @@ function drawProfiles (x, y: Integer; prof: TProfiler): Integer;
 var
   wdt, hgt: Integer;
   yy: Integer;
-  ii, idx: Integer;
+  ii: Integer;
 begin
   result := 0;
   if (prof = nil) then exit;
@@ -5017,21 +5017,25 @@ var
 begin
   //if not gDebugMode then exit;
   cmd := LowerCase(P[0]);
+
   if cmd = 'dpp' then
   begin
     g_profile_frame_draw := not g_profile_frame_draw;
     exit;
   end;
+
   if cmd = 'dpu' then
   begin
     g_profile_frame_update := not g_profile_frame_update;
     exit;
   end;
+
   if cmd = 'dpc' then
   begin
     g_profile_collision := not g_profile_collision;
     exit;
   end;
+
   if cmd = 'r_gridrender' then
   begin
     case getBool(1) of
@@ -5042,6 +5046,7 @@ begin
     if gdbg_map_use_grid_render then g_Console_Add('grid rendering: tan') else g_Console_Add('grid rendering: ona');
     exit;
   end;
+
   if cmd = 'dbg_coldet_grid' then
   begin
     case getBool(1) of
@@ -5052,6 +5057,17 @@ begin
     if gdbg_map_use_grid_coldet then g_Console_Add('grid coldet: tan') else g_Console_Add('grid coldet: ona');
     exit;
   end;
+
+  if (cmd = 'sq_use_grid') or (cmd = 'sq_use_sap') then
+  begin
+    case getBool(1) of
+      -1: begin end;
+       0: gdbg_map_use_sap := (cmd = 'sq_use_grid');
+       1: gdbg_map_use_sap := (cmd = 'sq_use_sap');
+    end;
+    if gdbg_map_use_sap then g_Console_Add('coldet: sap') else g_Console_Add('coldet: grid');
+    exit;
+  end;
 end;
 
 procedure DebugCommands(P: SArray);
index aeeba590205eed25b63aaf4ecab8fcd708609222..56d8ecbc42bbebf2ac1c9075c588b5725f68f227 100644 (file)
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *)
+// universal spatial grid
 {$INCLUDE ../shared/a_modes.inc}
 unit g_grid;
 
 interface
 
-uses e_log;
-
 const
   GridDefaultTileSize = 32;
 
@@ -73,7 +72,7 @@ type
     mGrid: array of Integer; // mWidth*mHeight, index in mCells
     mCells: array of TGridCell; // cell pool
     mFreeCell: Integer; // first free cell index or -1
-    mLastQuery: Integer;
+    mLastQuery: DWord;
     mUsedCells: Integer;
     mProxies: array of TBodyProxyRec;
     mProxyFree: TBodyProxy; // free
@@ -111,55 +110,13 @@ type
   end;
 
 
-type
-  TBinaryHeapLessFn = function (a, b: TObject): Boolean;
-
-  TBinaryHeapObj = class(TObject)
-  private
-    elem: array of TObject;
-    elemUsed: Integer;
-    lessfn: TBinaryHeapLessFn;
-
-  private
-    procedure heapify (root: Integer);
-
-  public
-    constructor Create (alessfn: TBinaryHeapLessFn);
-    destructor Destroy (); override;
-
-    procedure clear ();
-
-    procedure insert (val: TObject);
-
-    function front (): TObject;
-    procedure popFront ();
-
-    property count: Integer read elemUsed;
-  end;
-
-
 implementation
 
 uses
-  SysUtils;
+  SysUtils, e_log;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-(*
-constructor TBodyProxy.Create (aGrid: TBodyGrid; aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer);
-begin
-  mGrid := aGrid;
-  setup(aX, aY, aWidth, aHeight, aObj, aTag);
-end;
-
-
-destructor TBodyProxy.Destroy ();
-begin
-  inherited;
-end;
-*)
-
-
 procedure TBodyProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer);
 begin
   mX := aX;
@@ -213,8 +170,6 @@ end;
 
 
 destructor TBodyGrid.Destroy ();
-var
-  px: TBodyProxy;
 begin
   mCells := nil;
   mGrid := nil;
@@ -520,83 +475,4 @@ begin
 end;
 
 
-// ////////////////////////////////////////////////////////////////////////// //
-constructor TBinaryHeapObj.Create (alessfn: TBinaryHeapLessFn);
-begin
-  if not assigned(alessfn) then raise Exception.Create('wutafuck?!');
-  lessfn := alessfn;
-  SetLength(elem, 8192); // 'cause why not?
-  elemUsed := 0;
-end;
-
-
-destructor TBinaryHeapObj.Destroy ();
-begin
-  inherited;
-end;
-
-
-procedure TBinaryHeapObj.clear ();
-begin
-  elemUsed := 0;
-end;
-
-
-procedure TBinaryHeapObj.heapify (root: Integer);
-var
-  smallest, right: Integer;
-  tmp: TObject;
-begin
-  while true do
-  begin
-    smallest := 2*root+1; // left child
-    if (smallest >= elemUsed) then break; // anyway
-    right := smallest+1; // right child
-    if not lessfn(elem[smallest], elem[root]) then smallest := root;
-    if (right < elemUsed) and (lessfn(elem[right], elem[smallest])) then smallest := right;
-    if (smallest = root) then break;
-    // swap
-    tmp := elem[root];
-    elem[root] := elem[smallest];
-    elem[smallest] := tmp;
-    root := smallest;
-  end;
-end;
-
-
-procedure TBinaryHeapObj.insert (val: TObject);
-var
-  i, par: Integer;
-begin
-  if (val = nil) then exit;
-  i := elemUsed;
-  if (i = Length(elem)) then SetLength(elem, Length(elem)+16384); // arbitrary number
-  Inc(elemUsed);
-  while (i <> 0) do
-  begin
-    par := (i-1) div 2; // parent
-    if not lessfn(val, elem[par]) then break;
-    elem[i] := elem[par];
-    i := par;
-  end;
-  elem[i] := val;
-end;
-
-function TBinaryHeapObj.front (): TObject;
-begin
-  if elemUsed > 0 then result := elem[0] else result := nil;
-end;
-
-
-procedure TBinaryHeapObj.popFront ();
-begin
-  if (elemUsed > 0) then
-  begin
-    Dec(elemUsed);
-    elem[0] := elem[elemUsed];
-    heapify(0);
-  end;
-end;
-
-
 end.
index 29750c92009aeb5a36a4bb13fd2601bf22c521ff..c9851ee57bb6ec63e8822a8aeae444c19f4442eb 100644 (file)
@@ -20,7 +20,7 @@ interface
 
 uses
   e_graphics, g_basic, MAPSTRUCT, g_textures, Classes,
-  g_phys, wadreader, BinEditor, g_panel, g_grid, md5, xprofiler;
+  g_phys, wadreader, BinEditor, g_panel, g_grid, g_sap, md5, xprofiler;
 
 type
   TMapInfo = record
@@ -136,6 +136,7 @@ var
 
   gdbg_map_use_grid_render: Boolean = true;
   gdbg_map_use_grid_coldet: Boolean = true;
+  gdbg_map_use_sap: Boolean = false;
   profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY!
 
 implementation
@@ -145,7 +146,7 @@ uses
   GL, GLExt, g_weapons, g_game, g_sound, e_sound, CONFIG,
   g_options, MAPREADER, g_triggers, g_player, MAPDEF,
   Math, g_monsters, g_saveload, g_language, g_netmsg,
-  utils, sfs,
+  utils, sfs, binheap,
   ImagingTypes, Imaging, ImagingUtility,
   ImagingGif, ImagingNetworkGraphics;
 
@@ -196,6 +197,7 @@ var
   FlagPoints:    Array [FLAG_RED..FLAG_BLUE] of PFlagPoint;
   //DOMFlagPoints: Array of TFlagPoint;
   gMapGrid: TBodyGrid = nil;
+  gMapSAP: TSweepAndPrune = nil;
 
 
 procedure g_Map_ProfilersBegin ();
@@ -984,12 +986,15 @@ var
     for idx := High(panels) downto 0 do
     begin
       gMapGrid.insertBody(panels[idx], panels[idx].X, panels[idx].Y, panels[idx].Width, panels[idx].Height, tag);
+      gMapSAP.insertBody(panels[idx], panels[idx].X, panels[idx].Y, panels[idx].Width, panels[idx].Height, tag);
     end;
   end;
 
 begin
   gMapGrid.Free();
   gMapGrid := nil;
+  gMapSAP.Free();
+  gMapSAP := nil;
 
   fixMinMax(gWalls);
   fixMinMax(gRenderBackgrounds);
@@ -1008,6 +1013,7 @@ begin
   end;
 
   gMapGrid := TBodyGrid.Create(mapX0, mapY0, mapX1-mapX0+1, mapY1-mapY0+1);
+  gMapSAP := TSweepAndPrune.Create();
 
   addPanelsToGrid(gWalls, PANEL_WALL); // and PANEL_CLOSEDOOR
   addPanelsToGrid(gRenderBackgrounds, PANEL_BACK);
@@ -1020,6 +1026,7 @@ begin
   addPanelsToGrid(gBlockMon, PANEL_BLOCKMON);
 
   gMapGrid.dumpStats();
+  gMapSAP.dumpStats();
 end;
 
 function g_Map_Load(Res: String): Boolean;
@@ -1056,6 +1063,8 @@ var
 begin
   gMapGrid.Free();
   gMapGrid := nil;
+  gMapSAP.Free();
+  gMapSAP := nil;
 
   Result := False;
   gMapInfo.Map := Res;
@@ -1917,7 +1926,14 @@ begin
 
   if gdbg_map_use_grid_render then
   begin
-    gMapGrid.forEachInAABB(x0, y0, wdt, hgt, checker);
+    if gdbg_map_use_sap then
+    begin
+      gMapSAP.forEachInAABB(x0, y0, wdt, hgt, checker);
+    end
+    else
+    begin
+      gMapGrid.forEachInAABB(x0, y0, wdt, hgt, checker);
+    end;
     // sort and draw the list (we need to sort it, or rendering is fucked)
     while gDrawPanelList.count > 0 do
     begin
@@ -1956,7 +1972,14 @@ procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius:
   end;
 
 begin
-  gMapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker);
+  if gdbg_map_use_sap then
+  begin
+    gMapSAP.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker);
+  end
+  else
+  begin
+    gMapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker);
+  end;
 end;
 
 
@@ -2218,7 +2241,14 @@ begin
   try
     if gdbg_map_use_grid_coldet then
     begin
-      result := gMapGrid.forEachInAABB(X, Y, Width, Height, checker);
+      if gdbg_map_use_sap then
+      begin
+        gMapSAP.forEachInAABB(X, Y, Width, Height, checker);
+      end
+      else
+      begin
+        result := gMapGrid.forEachInAABB(X, Y, Width, Height, checker);
+      end;
     end
     else
     begin
@@ -2283,7 +2313,14 @@ begin
     if gdbg_map_use_grid_coldet then
     begin
       texid := TEXTURE_NONE;
-      gMapGrid.forEachInAABB(X, Y, Width, Height, checker);
+      if gdbg_map_use_sap then
+      begin
+        gMapSAP.forEachInAABB(X, Y, Width, Height, checker);
+      end
+      else
+      begin
+        gMapGrid.forEachInAABB(X, Y, Width, Height, checker);
+      end;
       result := texid;
     end
     else
diff --git a/src/game/g_sap.pas b/src/game/g_sap.pas
new file mode 100644 (file)
index 0000000..5baf514
--- /dev/null
@@ -0,0 +1,406 @@
+(* Copyright (C)  DooM 2D:Forever Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *)
+{$INCLUDE ../shared/a_modes.inc}
+// universal sweep-and-prune broad phase
+unit g_sap;
+
+interface
+
+type
+  TSAPQueryCB = function (obj: TObject; tag: Integer): Boolean is nested; // return `true` to stop
+
+type
+  TSAPProxy = Integer;
+
+  PSAPProxyRec = ^TSAPProxyRec;
+  TSAPProxyRec = record
+  private
+    mX, mY, mWidth, mHeight: Integer; // aabb
+    mQueryMark: DWord; // was this object visited at this query?
+    mObj: TObject;
+    mTag: Integer;
+    //nextLink: TSAPProxy; // next free or nothing
+    mIIdx: array [0..1] of Integer; // indicies in corresponding intervals
+
+  private
+    procedure setup (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer);
+
+  public
+    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;
+
+
+  TSweepAndPrune = class(TObject)
+  private
+    type
+      //PInterval = ^TInterval;
+      TInterval = record
+      public
+        min, max: Integer;
+        pidx: Integer; // proxy idx
+
+      public
+        function less (var i: TInterval): Boolean; inline;
+        function intersects (v0, v1: Integer): Boolean; inline;
+      end;
+
+  private
+    mLastQuery: DWord;
+    mIntrs: array[0..1] of array of TInterval;
+    mIntrsUsed: array[0..1] of Integer;
+    mProxies: array of TSAPProxyRec;
+    mProxyFree: TSAPProxy; // free
+    mProxyCount: Integer; // currently used
+    mProxyMaxCount: Integer;
+    mUpdateBlocked: Integer; // >0: updates are blocked
+
+  private
+    function allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer): TSAPProxy;
+    procedure freeProxy (body: TSAPProxy);
+
+    procedure sortIntervals ();
+
+    procedure insert (body: TSAPProxy);
+    //procedure remove (body: TSAPProxy);
+
+  public
+    constructor Create ();
+    destructor Destroy (); override;
+
+    function insertBody (aObj: TObject; ax, ay, aWidth, aHeight: Integer; aTag: Integer=0): TSAPProxy;
+    //procedure removeBody (aObj: TSAPProxy); // WARNING! this WILL destroy proxy!
+
+    //procedure moveBody (body: TSAPProxy; dx, dy: Integer);
+    //procedure resizeBody (body: TSAPProxy; sx, sy: Integer);
+    //procedure moveResizeBody (body: TSAPProxy; dx, dy, sx, sy: Integer);
+
+    function forEachInAABB (x, y, w, h: Integer; cb: TSAPQueryCB): Boolean;
+
+    //function getProxyForBody (aObj: TObject; x, y, w, h: Integer): TSAPProxy;
+
+    // call these functions before massive update (it may, or may be not faster)
+    procedure batchUpdateBegin ();
+    procedure batchUpdateEnd ();
+
+    procedure dumpStats ();
+  end;
+
+
+implementation
+
+uses
+  SysUtils, e_log;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+procedure TSAPProxyRec.setup (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer);
+begin
+  mX := aX;
+  mY := aY;
+  mWidth := aWidth;
+  mHeight := aHeight;
+  mQueryMark := 0;
+  mObj := aObj;
+  mTag := aTag;
+  //nextLink := -1;
+  mIIdx[0] := -1;
+  mIIdx[1] := -1;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+function TSweepAndPrune.TInterval.less (var i: TInterval): Boolean;
+var
+  n: Integer;
+begin
+  n := min-i.min;
+  if (n <> 0) then begin result := (n < 0); exit; end;
+  n := max-i.max;
+  if (n <> 0) then begin result := (n < 0); exit; end;
+  result := (pidx < i.pidx);
+end;
+
+
+// v0 MUST be <= v1!
+function TSweepAndPrune.TInterval.intersects (v0, v1: Integer): Boolean;
+begin
+  result := false;
+  if (v1 < min) or (v0 > max) then exit;
+  result := true;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+constructor TSweepAndPrune.Create ();
+var
+  idx: Integer;
+begin
+  mLastQuery := 0;
+
+  // init intervals
+  for idx := 0 to High(mIntrs) do
+  begin
+    SetLength(mIntrs[idx], 8192);
+    mIntrsUsed[idx] := 0;
+  end;
+
+  // init proxies
+  SetLength(mProxies, 8192);
+  for idx := 0 to High(mProxies) do
+  begin
+    mProxies[idx].mIIdx[0] := idx+1;
+    mProxies[idx].mIIdx[1] := -1;
+  end;
+  mProxies[High(mProxies)].mIIdx[0] := -1;
+
+  mProxyFree := 0;
+  mProxyCount := 0;
+  mProxyMaxCount := 0;
+
+  mUpdateBlocked := 0;
+
+  //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 TSweepAndPrune.Destroy ();
+var
+  idx: Integer;
+begin
+  mProxies := nil;
+  for idx := 0 to High(mIntrs) do mIntrs[idx] := nil;
+  inherited;
+end;
+
+
+procedure TSweepAndPrune.dumpStats ();
+begin
+  e_WriteLog(Format('used intervals: %d; max proxies allocated: %d; proxies used: %d', [mIntrsUsed[0], mProxyMaxCount, mProxyCount]), MSG_NOTIFY);
+end;
+
+
+procedure TSweepAndPrune.batchUpdateBegin ();
+begin
+  Inc(mUpdateBlocked);
+end;
+
+
+procedure TSweepAndPrune.batchUpdateEnd ();
+begin
+  if (mUpdateBlocked > 0) then
+  begin
+    Dec(mUpdateBlocked);
+    if (mUpdateBlocked = 0) then sortIntervals();
+  end;
+end;
+
+
+function TSweepAndPrune.allocProxy (aX, aY, aWidth, aHeight: Integer; aObj: TObject; aTag: Integer): TSAPProxy;
+var
+  olen, idx: Integer;
+  px: PSAPProxyRec;
+begin
+  if (mProxyFree = -1) then
+  begin
+    // no free proxies, resize list
+    olen := Length(mProxies);
+    SetLength(mProxies, olen+8192); // arbitrary number
+    for idx := olen to High(mProxies) do mProxies[idx].mIIdx[0] := idx+1;
+    mProxies[High(mProxies)].mIIdx[0] := -1;
+    mProxyFree := olen;
+  end;
+  // get one from list
+  result := mProxyFree;
+  px := @mProxies[result];
+  mProxyFree := px.mIIdx[0];
+  px.setup(aX, aY, aWidth, aHeight, aObj, aTag);
+  // add to used list
+  px.mIIdx[0] := -1;
+  // statistics
+  Inc(mProxyCount);
+  if (mProxyMaxCount < mProxyCount) then mProxyMaxCount := mProxyCount;
+end;
+
+procedure TSweepAndPrune.freeProxy (body: TSAPProxy);
+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?)');
+  // add to free list
+  mProxies[body].mObj := nil;
+  mProxies[body].mIIdx[0] := mProxyFree;
+  mProxies[body].mIIdx[1] := -1;
+  mProxyFree := body;
+  Dec(mProxyCount);
+end;
+
+
+procedure TSweepAndPrune.sortIntervals ();
+  procedure insSort (var arr: array of TInterval; iidx: Integer);
+  var
+    i, j: Integer;
+    x: TInterval;
+  begin
+    if (Length(arr) < 2) then exit; // nothing to do
+    i := 1;
+    while (i < Length(arr)) do
+    begin
+      if (arr[i].less(arr[i-1])) then
+      begin
+        x := arr[i];
+        j := i-1;
+        while (j >= 0) and (x.less(arr[j])) do
+        begin
+          arr[j+1] := arr[j];
+          mProxies[arr[j+1].pidx].mIIdx[iidx] := j+1;
+          Dec(j);
+        end;
+        mProxies[x.pidx].mIIdx[iidx] := j+1;
+        arr[j+1] := x;
+      end;
+      Inc(i);
+    end;
+  end;
+
+begin
+  insSort(mIntrs[0], 0);
+  insSort(mIntrs[1], 1);
+end;
+
+
+procedure TSweepAndPrune.insert (body: TSAPProxy);
+var
+  px: PSAPProxyRec;
+
+  procedure insIntr (v0, v1, iidx: Integer);
+  var
+    i: Integer;
+  begin
+    i := mIntrsUsed[iidx];
+    if (i >= Length(mIntrs[iidx])) then SetLength(mIntrs[iidx], i+8192);
+    mIntrs[iidx][i].min := v0;
+    mIntrs[iidx][i].max := v1;
+    mIntrs[iidx][i].pidx := i;
+    Inc(mIntrsUsed[iidx]);
+  end;
+
+begin
+  if (body < 0) or (body > High(mProxies)) then exit; // just in case
+  px := @mProxies[body];
+  insIntr(px.mX, px.mX+px.mWidth-1, 0);
+  insIntr(px.mY, px.mY+px.mHeight-1, 1);
+end;
+
+
+function TSweepAndPrune.insertBody (aObj: TObject; aX, aY, aWidth, aHeight: Integer; aTag: Integer=0): TSAPProxy;
+begin
+  if (aObj = nil) or (aWidth < 1) or (aHeight < 1) then begin result := -1; exit; end;
+  result := allocProxy(aX, aY, aWidth, aHeight, aObj, aTag);
+  insert(result);
+end;
+
+
+function TSweepAndPrune.forEachInAABB (x, y, w, h: Integer; cb: TSAPQueryCB): Boolean;
+
+  function walkInterval (val0, val1, lq, iidx: Integer): Boolean;
+  var
+    i, bot, mid, cmp: Integer;
+    px: PSAPProxyRec;
+  begin
+    result := false;
+    if (mIntrsUsed[iidx] < 1) then exit; // nothing to do
+    if (mIntrsUsed[iidx] = 1) then
+    begin
+      // one element
+      i := 0;
+    end
+    else
+    begin
+      // do search
+      bot := 0;
+      i := mIntrsUsed[iidx]-1;
+      while (bot <> i) do
+      begin
+        mid := i-(i-bot) div 2;
+        cmp := val0-mIntrs[iidx][mid].min;
+        if (cmp = 0) then break;
+        if (cmp < 0) then i := mid-1 else bot := mid;
+      end;
+      //return (cmpfn(lines+i) == 0 ? i : -1);
+      if (i > 0) and not mIntrs[iidx][i].intersects(val0, val1) and mIntrs[iidx][i-1].intersects(val0, val1) then Dec(i);
+      if (i+1 < mIntrsUsed[iidx]) and not mIntrs[iidx][i].intersects(val0, val1) and mIntrs[iidx][i+1].intersects(val0, val1) then Inc(i);
+      while (i > 0) and mIntrs[iidx][i].intersects(val0, val1) do Dec(i);
+      if (iidx = 0) then
+      begin
+        // first pass
+        while (i < mIntrsUsed[iidx]) and mIntrs[iidx][i].intersects(val0, val1) do
+        begin
+          mProxies[mIntrs[iidx][i].pidx].mQueryMark := lq;
+          Inc(i);
+        end;
+      end
+      else
+      begin
+        // second pass
+        while (i < mIntrsUsed[iidx]) and mIntrs[iidx][i].intersects(val0, val1) do
+        begin
+          px := @mProxies[mIntrs[iidx][i].pidx];
+          if (px.mQueryMark = lq) then
+          begin
+            result := cb(px.mObj, px.mTag);
+            if result then exit;
+          end;
+          Inc(i);
+        end;
+      end;
+    end;
+  end;
+
+var
+  idx: Integer;
+begin
+  result := false;
+  if not assigned(cb) then exit; // no callback, not interesting
+  if (w < 1) or (h < 1) then exit; // nothing to do
+
+  // increase query counter
+  Inc(mLastQuery);
+  if (mLastQuery = 0) then
+  begin
+    // just in case of overflow
+    mLastQuery := 1;
+    for idx := 0 to High(mProxies) do mProxies[idx].mQueryMark := 0;
+  end;
+
+  (*
+   * the algorithm is simple:
+   *   find start for first interval (binary search will do)
+   *   walk the interval, marking proxies with mLastQuery
+   *   increment mLastQuery
+   *   find start for second interval (binary search will do)
+   *   walk the interval, returning proxies marked with mLastQuery
+   *)
+  walkInterval(x, x+w-1, mLastQuery, 0);
+  result := walkInterval(x, x+w-1, mLastQuery, 1);
+end;
+
+
+end.
index 450afde4334c352e216dab131aaf1ae687d647b3..582c2d98973da6f38b5819829fc5c807d10e1a3b 100644 (file)
@@ -709,6 +709,8 @@ begin
     if ParamStr(idx) = '--opengl-dump-exts' then gwin_dump_extensions := true;
     if ParamStr(idx) = '--twinkletwinkle' then gwin_k8_enable_light_experiments := true;
     if ParamStr(idx) = '--jah' then g_profile_history_size := 100;
+    if ParamStr(idx) = '--sap' then gdbg_map_use_sap := true;
+    if ParamStr(idx) = '--grid' then gdbg_map_use_sap := false;
   end;
 
   e_WriteLog('Initializing OpenGL', MSG_NOTIFY);
diff --git a/src/shared/binheap.pas b/src/shared/binheap.pas
new file mode 100644 (file)
index 0000000..968c5f9
--- /dev/null
@@ -0,0 +1,136 @@
+(* Copyright (C)  DooM 2D:Forever Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *)
+{$INCLUDE a_modes.inc}
+// binary heap
+unit binheap;
+
+interface
+
+
+type
+  TBinaryHeapLessFn = function (a, b: TObject): Boolean;
+
+  TBinaryHeapObj = class(TObject)
+  private
+    elem: array of TObject;
+    elemUsed: Integer;
+    lessfn: TBinaryHeapLessFn;
+
+  private
+    procedure heapify (root: Integer);
+
+  public
+    constructor Create (alessfn: TBinaryHeapLessFn);
+    destructor Destroy (); override;
+
+    procedure clear ();
+
+    procedure insert (val: TObject);
+
+    function front (): TObject;
+    procedure popFront ();
+
+    property count: Integer read elemUsed;
+  end;
+
+
+implementation
+
+uses
+  SysUtils;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+constructor TBinaryHeapObj.Create (alessfn: TBinaryHeapLessFn);
+begin
+  if not assigned(alessfn) then raise Exception.Create('wutafuck?!');
+  lessfn := alessfn;
+  SetLength(elem, 8192); // 'cause why not?
+  elemUsed := 0;
+end;
+
+
+destructor TBinaryHeapObj.Destroy ();
+begin
+  elem := nil;
+  inherited;
+end;
+
+
+procedure TBinaryHeapObj.clear ();
+begin
+  elemUsed := 0;
+end;
+
+
+procedure TBinaryHeapObj.heapify (root: Integer);
+var
+  smallest, right: Integer;
+  tmp: TObject;
+begin
+  while true do
+  begin
+    smallest := 2*root+1; // left child
+    if (smallest >= elemUsed) then break; // anyway
+    right := smallest+1; // right child
+    if not lessfn(elem[smallest], elem[root]) then smallest := root;
+    if (right < elemUsed) and (lessfn(elem[right], elem[smallest])) then smallest := right;
+    if (smallest = root) then break;
+    // swap
+    tmp := elem[root];
+    elem[root] := elem[smallest];
+    elem[smallest] := tmp;
+    root := smallest;
+  end;
+end;
+
+
+procedure TBinaryHeapObj.insert (val: TObject);
+var
+  i, par: Integer;
+begin
+  if (val = nil) then exit;
+  i := elemUsed;
+  if (i = Length(elem)) then SetLength(elem, Length(elem)+16384); // arbitrary number
+  Inc(elemUsed);
+  while (i <> 0) do
+  begin
+    par := (i-1) div 2; // parent
+    if not lessfn(val, elem[par]) then break;
+    elem[i] := elem[par];
+    i := par;
+  end;
+  elem[i] := val;
+end;
+
+function TBinaryHeapObj.front (): TObject;
+begin
+  if elemUsed > 0 then result := elem[0] else result := nil;
+end;
+
+
+procedure TBinaryHeapObj.popFront ();
+begin
+  if (elemUsed > 0) then
+  begin
+    Dec(elemUsed);
+    elem[0] := elem[elemUsed];
+    heapify(0);
+  end;
+end;
+
+
+end.