From 6bc3fb69728f6771416477b516475a921ecfe7f7 Mon Sep 17 00:00:00 2001 From: Boki Date: Fri, 13 Feb 2026 12:30:13 -0500 Subject: [PATCH] work on minimap --- src/Poe2Trade.Navigation/MinimapCapture.cs | 58 ++++----------------- src/Poe2Trade.Navigation/NavigationTypes.cs | 10 ++-- src/Poe2Trade.Navigation/WorldMap.cs | 37 ++++++++----- 3 files changed, 39 insertions(+), 66 deletions(-) diff --git a/src/Poe2Trade.Navigation/MinimapCapture.cs b/src/Poe2Trade.Navigation/MinimapCapture.cs index 9e52fb7..a7d0d62 100644 --- a/src/Poe2Trade.Navigation/MinimapCapture.cs +++ b/src/Poe2Trade.Navigation/MinimapCapture.cs @@ -70,15 +70,11 @@ public class MinimapCapture : IDisposable // --- 3. Wall mask: bright OR saturated → structure lines --- using var wallMask = BuildWallMask(satChan, valueChan, playerMask); - // --- 4. Explored mask: brightness above fog-of-war, minus walls/player --- - using var exploredMask = BuildExploredMask(valueChan, wallMask, playerMask); - - // --- 5. Build raw classified mat --- + // --- 4. Build classified mat (walls only — explored is tracked by WorldMap) --- var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC1, Scalar.Black); - classified.SetTo(new Scalar((byte)MapCell.Explored), exploredMask); classified.SetTo(new Scalar((byte)MapCell.Wall), wallMask); - // --- 6. Temporal smoothing: majority vote --- + // --- 5. Temporal smoothing: majority vote on walls --- var smoothed = TemporalSmooth(classified); // classified goes into ring buffer // --- 7. Gray for phase correlation (player zeroed — it stays centered, walls shift with map) --- @@ -118,23 +114,6 @@ public class MinimapCapture : IDisposable return wallMask; } - private Mat BuildExploredMask(Mat valueChan, Mat wallMask, Mat playerMask) - { - // Explored = any pixel above fog-of-war darkness, minus walls and player - var exploredMask = new Mat(); - Cv2.Threshold(valueChan, exploredMask, _config.FloorMinValue, 255, ThresholdTypes.Binary); - - using var notWall = new Mat(); - Cv2.BitwiseNot(wallMask, notWall); - Cv2.BitwiseAnd(exploredMask, notWall, exploredMask); - - using var notPlayer = new Mat(); - Cv2.BitwiseNot(playerMask, notPlayer); - Cv2.BitwiseAnd(exploredMask, notPlayer, exploredMask); - - return exploredMask; - } - private static void FilterSmallComponents(Mat mask, int minArea) { if (minArea <= 0) return; @@ -172,31 +151,21 @@ public class MinimapCapture : IDisposable var size = classified.Size(); using var wallCount = new Mat(size, MatType.CV_8UC1, Scalar.Black); - using var exploredCount = new Mat(size, MatType.CV_8UC1, Scalar.Black); foreach (var frame in _frameBuffer) { using var isWall = new Mat(); Cv2.Compare(frame, new Scalar((byte)MapCell.Wall), isWall, CmpType.EQ); Cv2.Add(wallCount, new Scalar(1), wallCount, isWall); - - using var isExplored = new Mat(); - Cv2.Compare(frame, new Scalar((byte)MapCell.Explored), isExplored, CmpType.EQ); - Cv2.Add(exploredCount, new Scalar(1), exploredCount, isExplored); } - // Apply vote thresholds + // Apply vote threshold using var wallPass = new Mat(); Cv2.Threshold(wallCount, wallPass, _config.WallTemporalThreshold - 0.5, 255, ThresholdTypes.Binary); - using var exploredPass = new Mat(); - Cv2.Threshold(exploredCount, exploredPass, - _config.ExploredTemporalThreshold - 0.5, 255, ThresholdTypes.Binary); - // Build smoothed result (explored wins over wall) var result = new Mat(size, MatType.CV_8UC1, Scalar.Black); result.SetTo(new Scalar((byte)MapCell.Wall), wallPass); - result.SetTo(new Scalar((byte)MapCell.Explored), exploredPass); return result; } @@ -236,12 +205,8 @@ public class MinimapCapture : IDisposable using var wallMask = BuildWallMask(satChan, valueChan, playerMask); if (stage == MinimapDebugStage.Walls) return EncodePng(wallMask); - using var exploredMask = BuildExploredMask(valueChan, wallMask, playerMask); - if (stage == MinimapDebugStage.Explored) return EncodePng(exploredMask); - - // Classified + // Classified (walls + player only — explored is tracked by WorldMap) using var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC3, Scalar.Black); - classified.SetTo(new Scalar(104, 64, 31), exploredMask); classified.SetTo(new Scalar(26, 45, 61), wallMask); classified.SetTo(new Scalar(0, 165, 255), playerMask); return EncodePng(classified); @@ -275,25 +240,22 @@ public class MinimapCapture : IDisposable Cv2.InRange(hsv, _config.PlayerLoHSV, _config.PlayerHiHSV, playerMask); using var wallMask = BuildWallMask(satChan, valueChan, playerMask); - using var exploredMask = BuildExploredMask(valueChan, wallMask, playerMask); - // Colorized classified + // Colorized classified (walls + player) using var classified = new Mat(_config.CaptureSize, _config.CaptureSize, MatType.CV_8UC3, Scalar.Black); - classified.SetTo(new Scalar(104, 64, 31), exploredMask); // blue classified.SetTo(new Scalar(26, 45, 61), wallMask); // brown classified.SetTo(new Scalar(0, 165, 255), playerMask); // orange Cv2.ImWrite(Path.Combine(dir, "01-raw.png"), bgr); Cv2.ImWrite(Path.Combine(dir, "02-walls.png"), wallMask); - Cv2.ImWrite(Path.Combine(dir, "03-explored.png"), exploredMask); - Cv2.ImWrite(Path.Combine(dir, "04-player.png"), playerMask); - Cv2.ImWrite(Path.Combine(dir, "05-classified.png"), classified); + Cv2.ImWrite(Path.Combine(dir, "03-player.png"), playerMask); + Cv2.ImWrite(Path.Combine(dir, "04-classified.png"), classified); // HSV channels var channels = Cv2.Split(hsv); - Cv2.ImWrite(Path.Combine(dir, "06-hue.png"), channels[0]); - Cv2.ImWrite(Path.Combine(dir, "07-sat.png"), channels[1]); - Cv2.ImWrite(Path.Combine(dir, "08-val.png"), channels[2]); + Cv2.ImWrite(Path.Combine(dir, "05-hue.png"), channels[0]); + Cv2.ImWrite(Path.Combine(dir, "06-sat.png"), channels[1]); + Cv2.ImWrite(Path.Combine(dir, "07-val.png"), channels[2]); foreach (var c in channels) c.Dispose(); Log.Information("Debug minimap images saved to {Dir}", Path.GetFullPath(dir)); diff --git a/src/Poe2Trade.Navigation/NavigationTypes.cs b/src/Poe2Trade.Navigation/NavigationTypes.cs index 0854bb1..f6e6f31 100644 --- a/src/Poe2Trade.Navigation/NavigationTypes.cs +++ b/src/Poe2Trade.Navigation/NavigationTypes.cs @@ -29,7 +29,6 @@ public enum MinimapDebugStage WorldMap, Raw, Walls, - Explored, Player, Classified, Hue, @@ -73,16 +72,15 @@ public class MinimapConfig public int WallMinValue { get; set; } = 200; public int WallMinSat { get; set; } = 150; - // Floor detection: minimum V to distinguish explored floor from fog-of-war - public int FloorMinValue { get; set; } = 40; - // Connected components: minimum area to keep (kills speckle) public int WallMinArea { get; set; } = 30; - // Temporal smoothing: majority vote over ring buffer + // Explored radius: pixels around player position to mark as explored on world map + public int ExploredRadius { get; set; } = 100; + + // Temporal smoothing: majority vote over ring buffer (walls only) public int TemporalFrameCount { get; set; } = 5; public int WallTemporalThreshold { get; set; } = 3; - public int ExploredTemporalThreshold { get; set; } = 2; // Capture rate (~30 fps) public int CaptureIntervalMs { get; set; } = 33; diff --git a/src/Poe2Trade.Navigation/WorldMap.cs b/src/Poe2Trade.Navigation/WorldMap.cs index 9b9454f..c779fe7 100644 --- a/src/Poe2Trade.Navigation/WorldMap.cs +++ b/src/Poe2Trade.Navigation/WorldMap.cs @@ -36,21 +36,34 @@ public class WorldMap : IDisposable var srcRoi = new Mat(classifiedMat, srcRect); var dstRoi = new Mat(_canvas, dstRect); - // Only paste non-Unknown pixels; don't overwrite Explored with Wall + // Paste wall pixels (walls always win) for (var row = 0; row < h; row++) + for (var col = 0; col < w; col++) { - for (var col = 0; col < w; col++) - { - var srcVal = srcRoi.At(row, col); - if (srcVal == (byte)MapCell.Unknown) continue; - - var dstVal = dstRoi.At(row, col); - // Don't overwrite Explored with Wall (Explored is more reliable) - if (dstVal == (byte)MapCell.Explored && srcVal == (byte)MapCell.Wall) - continue; - + var srcVal = srcRoi.At(row, col); + if (srcVal == (byte)MapCell.Wall) dstRoi.Set(row, col, srcVal); - } + } + + // Mark explored area: circle around player, only overwrite Unknown + var pcx = (int)Math.Round(position.X); + var pcy = (int)Math.Round(position.Y); + var r = _config.ExploredRadius; + var r2 = r * r; + + var y0 = Math.Max(0, pcy - r); + var y1 = Math.Min(_config.CanvasSize - 1, pcy + r); + var x0 = Math.Max(0, pcx - r); + var x1 = Math.Min(_config.CanvasSize - 1, pcx + r); + + for (var y = y0; y <= y1; y++) + for (var x = x0; x <= x1; x++) + { + var dx = x - pcx; + var dy = y - pcy; + if (dx * dx + dy * dy > r2) continue; + if (_canvas.At(y, x) == (byte)MapCell.Unknown) + _canvas.Set(y, x, (byte)MapCell.Explored); } }