started adding navigation

This commit is contained in:
Boki 2026-02-13 10:43:35 -05:00
parent 32781b1462
commit 468e0a7246
20 changed files with 844 additions and 31 deletions

View file

@ -0,0 +1,158 @@
using OpenCvSharp;
using Serilog;
namespace Poe2Trade.Navigation;
public class WorldMap : IDisposable
{
private readonly MinimapConfig _config;
private readonly Mat _canvas;
public WorldMap(MinimapConfig config)
{
_config = config;
_canvas = new Mat(config.CanvasSize, config.CanvasSize, MatType.CV_8UC1, Scalar.Black);
}
public void StitchFrame(Mat classifiedMat, MapPosition position)
{
var halfSize = _config.CaptureSize / 2;
var canvasX = (int)Math.Round(position.X) - halfSize;
var canvasY = (int)Math.Round(position.Y) - halfSize;
// Clamp to canvas bounds
var srcX = Math.Max(0, -canvasX);
var srcY = Math.Max(0, -canvasY);
var dstX = Math.Max(0, canvasX);
var dstY = Math.Max(0, canvasY);
var w = Math.Min(_config.CaptureSize - srcX, _config.CanvasSize - dstX);
var h = Math.Min(_config.CaptureSize - srcY, _config.CanvasSize - dstY);
if (w <= 0 || h <= 0) return;
var srcRect = new Rect(srcX, srcY, w, h);
var dstRect = new Rect(dstX, dstY, w, h);
var srcRoi = new Mat(classifiedMat, srcRect);
var dstRoi = new Mat(_canvas, dstRect);
// Only paste non-Unknown pixels; don't overwrite Explored with Wall
for (var row = 0; row < h; row++)
{
for (var col = 0; col < w; col++)
{
var srcVal = srcRoi.At<byte>(row, col);
if (srcVal == (byte)MapCell.Unknown) continue;
var dstVal = dstRoi.At<byte>(row, col);
// Don't overwrite Explored with Wall (Explored is more reliable)
if (dstVal == (byte)MapCell.Explored && srcVal == (byte)MapCell.Wall)
continue;
dstRoi.Set(row, col, srcVal);
}
}
}
public (double dirX, double dirY)? FindNearestUnexplored(MapPosition pos, int searchRadius = 200)
{
var cx = (int)Math.Round(pos.X);
var cy = (int)Math.Round(pos.Y);
// Scan in angular sectors to find direction with most Unknown cells at frontier
var bestAngle = double.NaN;
var bestScore = 0;
const int sectorCount = 16;
var fogRadius = _config.FogRadius;
for (var sector = 0; sector < sectorCount; sector++)
{
var angle = 2 * Math.PI * sector / sectorCount;
var score = 0;
// Sample along a cone in this direction, at the fog boundary and beyond
for (var r = fogRadius - 20; r <= fogRadius + searchRadius; r += 5)
{
for (var spread = -15; spread <= 15; spread += 5)
{
var sampleAngle = angle + spread * Math.PI / 180;
var sx = cx + (int)(r * Math.Cos(sampleAngle));
var sy = cy + (int)(r * Math.Sin(sampleAngle));
if (sx < 0 || sx >= _config.CanvasSize || sy < 0 || sy >= _config.CanvasSize)
continue;
if (_canvas.At<byte>(sy, sx) == (byte)MapCell.Unknown)
score++;
}
}
if (score > bestScore)
{
bestScore = score;
bestAngle = angle;
}
}
if (bestScore == 0 || double.IsNaN(bestAngle))
{
Log.Information("No unexplored area found within search radius");
return null;
}
var dirX = Math.Cos(bestAngle);
var dirY = Math.Sin(bestAngle);
Log.Debug("Best exploration direction: angle={Angle:F1}deg score={Score}",
bestAngle * 180 / Math.PI, bestScore);
return (dirX, dirY);
}
public byte[] GetMapSnapshot()
{
Cv2.ImEncode(".png", _canvas, out var buf);
return buf;
}
public byte[] GetViewportSnapshot(MapPosition center, int viewSize = 400)
{
var cx = (int)Math.Round(center.X);
var cy = (int)Math.Round(center.Y);
var half = viewSize / 2;
// Clamp viewport to canvas
var x0 = Math.Clamp(cx - half, 0, _config.CanvasSize - viewSize);
var y0 = Math.Clamp(cy - half, 0, _config.CanvasSize - viewSize);
var roi = new Mat(_canvas, new Rect(x0, y0, viewSize, viewSize));
// Colorize: Unknown=#0d1117, Explored=#1f4068, Wall=#3d2d1a
using var colored = new Mat(viewSize, viewSize, MatType.CV_8UC3, new Scalar(23, 17, 13)); // BGR #0d1117
for (var r = 0; r < viewSize; r++)
for (var c = 0; c < viewSize; c++)
{
var v = roi.At<byte>(r, c);
if (v == (byte)MapCell.Explored)
colored.Set(r, c, new Vec3b(104, 64, 31)); // BGR #1f4068
else if (v == (byte)MapCell.Wall)
colored.Set(r, c, new Vec3b(26, 45, 61)); // BGR #3d2d1a
}
// Draw player dot
var px = cx - x0;
var py = cy - y0;
if (px >= 0 && px < viewSize && py >= 0 && py < viewSize)
Cv2.Circle(colored, new Point(px, py), 4, new Scalar(0, 140, 255), -1); // orange
Cv2.ImEncode(".png", colored, out var buf);
return buf;
}
public void Reset()
{
_canvas.SetTo(Scalar.Black);
}
public void Dispose()
{
_canvas.Dispose();
}
}