started adding navigation
This commit is contained in:
parent
32781b1462
commit
468e0a7246
20 changed files with 844 additions and 31 deletions
158
src/Poe2Trade.Navigation/WorldMap.cs
Normal file
158
src/Poe2Trade.Navigation/WorldMap.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue