work on pathing
This commit is contained in:
parent
7d10f1d2a9
commit
3bb0315912
10 changed files with 729 additions and 113 deletions
|
|
@ -16,9 +16,15 @@ public class WorldMap : IDisposable
|
|||
private Mat? _prevWallMask; // for frame deduplication
|
||||
private readonly PathFinder _pathFinder = new();
|
||||
|
||||
// Checkpoint tracking (canvas coordinates)
|
||||
private readonly List<(Point Pos, long LastSeenMs)> _checkpointsOff = [];
|
||||
private readonly List<(Point Pos, long LastSeenMs)> _checkpointsOn = [];
|
||||
private const int CheckpointDedupRadius = 20;
|
||||
|
||||
public MapPosition Position => _position;
|
||||
public bool LastMatchSucceeded { get; private set; }
|
||||
public int CanvasSize => _canvasSize;
|
||||
internal List<Point>? LastBfsPath => _pathFinder.LastResult?.Path;
|
||||
|
||||
private const int GrowMargin = 500;
|
||||
private const int GrowAmount = 2000; // 1000px added per side
|
||||
|
|
@ -64,7 +70,8 @@ public class WorldMap : IDisposable
|
|||
/// Match current wall mask against the accumulated map to find position,
|
||||
/// then stitch walls and paint explored area.
|
||||
/// </summary>
|
||||
public MapPosition MatchAndStitch(Mat classifiedMat, Mat wallMask, MinimapMode mode = MinimapMode.Overlay)
|
||||
public MapPosition MatchAndStitch(Mat classifiedMat, Mat wallMask, MinimapMode mode = MinimapMode.Overlay,
|
||||
List<Point>? checkpointsOff = null, List<Point>? checkpointsOn = null)
|
||||
{
|
||||
EnsureCapacity();
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
|
@ -103,6 +110,7 @@ public class WorldMap : IDisposable
|
|||
|
||||
StitchWithConfidence(classifiedMat, _position, boosted: true, mode: mode);
|
||||
PaintExploredCircle(_position);
|
||||
MergeCheckpoints(_position, classifiedMat.Width, checkpointsOff, checkpointsOn);
|
||||
LastMatchSucceeded = true; // signal caller to update viewport
|
||||
if (_consecutiveMatchFails >= 30)
|
||||
{
|
||||
|
|
@ -140,6 +148,7 @@ public class WorldMap : IDisposable
|
|||
var stitchStart = sw.Elapsed.TotalMilliseconds;
|
||||
StitchWithConfidence(classifiedMat, _position, boosted: false, mode: mode);
|
||||
PaintExploredCircle(_position);
|
||||
MergeCheckpoints(_position, classifiedMat.Width, checkpointsOff, checkpointsOn);
|
||||
var stitchMs = sw.Elapsed.TotalMilliseconds - stitchStart;
|
||||
|
||||
var posDx = _position.X - prevPos.X;
|
||||
|
|
@ -323,24 +332,23 @@ public class WorldMap : IDisposable
|
|||
dstRoi.Set(row, col, (byte)MapCell.Explored);
|
||||
}
|
||||
|
||||
// Mark fog on canvas
|
||||
// Mark fog on canvas (on top of Unknown or Explored — not Wall)
|
||||
if (isCorner)
|
||||
{
|
||||
// Corner minimap: fog is clean, accept all fog pixels
|
||||
for (var row = 0; row < h; row++)
|
||||
for (var col = 0; col < w; col++)
|
||||
{
|
||||
if (srcRoi.At<byte>(row, col) != (byte)MapCell.Fog) continue;
|
||||
if (dstRoi.At<byte>(row, col) == (byte)MapCell.Unknown)
|
||||
var dst = dstRoi.At<byte>(row, col);
|
||||
if (dst == (byte)MapCell.Unknown || dst == (byte)MapCell.Explored)
|
||||
dstRoi.Set(row, col, (byte)MapCell.Fog);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Overlay: restrict fog to ring around player (filters spell effect noise)
|
||||
var fogInner2 = _config.ExploredRadius * _config.ExploredRadius;
|
||||
var fogOuter = _config.ExploredRadius + 5;
|
||||
var fogOuter2 = fogOuter * fogOuter;
|
||||
// Overlay: exclude small area near player center (spell effect noise)
|
||||
const int fogInner = 30;
|
||||
const int fogInner2 = fogInner * fogInner;
|
||||
|
||||
for (var row = 0; row < h; row++)
|
||||
for (var col = 0; col < w; col++)
|
||||
|
|
@ -349,10 +357,10 @@ public class WorldMap : IDisposable
|
|||
|
||||
var fx = srcX + col - halfSize;
|
||||
var fy = srcY + row - halfSize;
|
||||
var dist2 = fx * fx + fy * fy;
|
||||
if (dist2 < fogInner2 || dist2 > fogOuter2) continue;
|
||||
if (fx * fx + fy * fy < fogInner2) continue;
|
||||
|
||||
if (dstRoi.At<byte>(row, col) == (byte)MapCell.Unknown)
|
||||
var dst = dstRoi.At<byte>(row, col);
|
||||
if (dst == (byte)MapCell.Unknown || dst == (byte)MapCell.Explored)
|
||||
dstRoi.Set(row, col, (byte)MapCell.Fog);
|
||||
}
|
||||
}
|
||||
|
|
@ -360,8 +368,10 @@ public class WorldMap : IDisposable
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark explored area: circle around player position, overwrite Unknown and Fog cells.
|
||||
/// Called by MatchAndStitch after stitch in both bootstrap and match-success branches.
|
||||
/// Mark explored area: circle around player position.
|
||||
/// Unknown cells are marked Explored within the full radius.
|
||||
/// Fog cells are only cleared in the inner portion (fogClearRadius),
|
||||
/// preserving fog at the edge so it stays visible on the viewport.
|
||||
/// </summary>
|
||||
private void PaintExploredCircle(MapPosition position)
|
||||
{
|
||||
|
|
@ -369,6 +379,8 @@ public class WorldMap : IDisposable
|
|||
var pcy = (int)Math.Round(position.Y);
|
||||
var r = _config.ExploredRadius;
|
||||
var r2 = r * r;
|
||||
var fogClear = r - 20;
|
||||
var fogClear2 = fogClear * fogClear;
|
||||
|
||||
var y0 = Math.Max(0, pcy - r);
|
||||
var y1 = Math.Min(_canvasSize - 1, pcy + r);
|
||||
|
|
@ -380,9 +392,12 @@ public class WorldMap : IDisposable
|
|||
{
|
||||
var dx = x - pcx;
|
||||
var dy = y - pcy;
|
||||
if (dx * dx + dy * dy > r2) continue;
|
||||
var d2 = dx * dx + dy * dy;
|
||||
if (d2 > r2) continue;
|
||||
var cell = _canvas.At<byte>(y, x);
|
||||
if (cell == (byte)MapCell.Unknown || cell == (byte)MapCell.Fog)
|
||||
if (cell == (byte)MapCell.Unknown)
|
||||
_canvas.Set(y, x, (byte)MapCell.Explored);
|
||||
else if (cell == (byte)MapCell.Fog && d2 <= fogClear2)
|
||||
_canvas.Set(y, x, (byte)MapCell.Explored);
|
||||
}
|
||||
}
|
||||
|
|
@ -430,6 +445,9 @@ public class WorldMap : IDisposable
|
|||
public (double dirX, double dirY)? FindNearestUnexplored(MapPosition pos, int searchRadius = 400)
|
||||
=> _pathFinder.FindNearestUnexplored(_canvas, _canvasSize, pos, searchRadius);
|
||||
|
||||
public (double dirX, double dirY)? FindPathToTarget(MapPosition pos, Point target, int searchRadius = 400)
|
||||
=> _pathFinder.FindPathToTarget(_canvas, _canvasSize, pos, target, searchRadius);
|
||||
|
||||
public byte[] GetMapSnapshot()
|
||||
{
|
||||
Cv2.ImEncode(".png", _canvas, out var buf);
|
||||
|
|
@ -456,7 +474,7 @@ public class WorldMap : IDisposable
|
|||
else if (v == (byte)MapCell.Wall)
|
||||
colored.Set(r, c, new Vec3b(26, 45, 61));
|
||||
else if (v == (byte)MapCell.Fog)
|
||||
colored.Set(r, c, new Vec3b(55, 40, 28));
|
||||
colored.Set(r, c, new Vec3b(120, 70, 40));
|
||||
}
|
||||
|
||||
// BFS overlay: frontier cells + direction line
|
||||
|
|
@ -489,6 +507,32 @@ public class WorldMap : IDisposable
|
|||
var ey = (int)(py2 + bfs.DirY * lineLen);
|
||||
Cv2.ArrowedLine(colored, new Point(px2, py2), new Point(ex, ey),
|
||||
new Scalar(0, 220, 0), 2, tipLength: 0.3);
|
||||
|
||||
// BFS path polyline (bright green)
|
||||
if (bfs.Path.Count >= 2)
|
||||
{
|
||||
var viewPath = new Point[bfs.Path.Count];
|
||||
for (var i = 0; i < bfs.Path.Count; i++)
|
||||
viewPath[i] = new Point(bfs.Path[i].X - x0, bfs.Path[i].Y - y0);
|
||||
Cv2.Polylines(colored, [viewPath], isClosed: false,
|
||||
color: new Scalar(0, 255, 0), thickness: 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Checkpoint markers
|
||||
foreach (var (cp, _) in _checkpointsOff)
|
||||
{
|
||||
var cpx = cp.X - x0;
|
||||
var cpy = cp.Y - y0;
|
||||
if (cpx >= 0 && cpx < viewSize && cpy >= 0 && cpy < viewSize)
|
||||
Cv2.Circle(colored, new Point(cpx, cpy), 5, new Scalar(100, 100, 100), -1); // gray
|
||||
}
|
||||
foreach (var (cp, _) in _checkpointsOn)
|
||||
{
|
||||
var cpx = cp.X - x0;
|
||||
var cpy = cp.Y - y0;
|
||||
if (cpx >= 0 && cpx < viewSize && cpy >= 0 && cpy < viewSize)
|
||||
Cv2.Circle(colored, new Point(cpx, cpy), 5, new Scalar(220, 200, 0), -1); // bright cyan (BGR)
|
||||
}
|
||||
|
||||
// Player dot (orange, on top)
|
||||
|
|
@ -501,6 +545,90 @@ public class WorldMap : IDisposable
|
|||
return buf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert frame-relative checkpoint positions to canvas coords and merge with existing lists.
|
||||
/// When a checkpoint-on appears near a checkpoint-off, remove the off entry.
|
||||
/// </summary>
|
||||
private void MergeCheckpoints(MapPosition position, int frameSize,
|
||||
List<Point>? offPoints, List<Point>? onPoints)
|
||||
{
|
||||
var halfSize = frameSize / 2;
|
||||
var canvasX = (int)Math.Round(position.X) - halfSize;
|
||||
var canvasY = (int)Math.Round(position.Y) - halfSize;
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
var r2 = CheckpointDedupRadius * CheckpointDedupRadius;
|
||||
|
||||
if (offPoints != null)
|
||||
{
|
||||
foreach (var fp in offPoints)
|
||||
{
|
||||
var cp = new Point(canvasX + fp.X, canvasY + fp.Y);
|
||||
if (!IsDuplicate(_checkpointsOff, cp, r2))
|
||||
_checkpointsOff.Add((cp, now));
|
||||
}
|
||||
}
|
||||
|
||||
if (onPoints != null)
|
||||
{
|
||||
foreach (var fp in onPoints)
|
||||
{
|
||||
var cp = new Point(canvasX + fp.X, canvasY + fp.Y);
|
||||
|
||||
// Remove matching off-checkpoint (it got activated)
|
||||
for (var i = _checkpointsOff.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var dx = _checkpointsOff[i].Pos.X - cp.X;
|
||||
var dy = _checkpointsOff[i].Pos.Y - cp.Y;
|
||||
if (dx * dx + dy * dy <= r2)
|
||||
{
|
||||
_checkpointsOff.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsDuplicate(_checkpointsOn, cp, r2))
|
||||
_checkpointsOn.Add((cp, now));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDuplicate(List<(Point Pos, long LastSeenMs)> list, Point pt, int r2)
|
||||
{
|
||||
foreach (var (pos, _) in list)
|
||||
{
|
||||
var dx = pos.X - pt.X;
|
||||
var dy = pos.Y - pt.Y;
|
||||
if (dx * dx + dy * dy <= r2)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the nearest unactivated checkpoint within maxDist canvas pixels, or null.
|
||||
/// </summary>
|
||||
public Point? GetNearestCheckpointOff(MapPosition pos, int maxDist = 200)
|
||||
{
|
||||
var px = (int)Math.Round(pos.X);
|
||||
var py = (int)Math.Round(pos.Y);
|
||||
var maxDist2 = maxDist * maxDist;
|
||||
|
||||
Point? best = null;
|
||||
var bestDist2 = int.MaxValue;
|
||||
foreach (var (cp, _) in _checkpointsOff)
|
||||
{
|
||||
var dx = cp.X - px;
|
||||
var dy = cp.Y - py;
|
||||
var d2 = dx * dx + dy * dy;
|
||||
if (d2 <= maxDist2 && d2 < bestDist2)
|
||||
{
|
||||
bestDist2 = d2;
|
||||
best = cp;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_canvas.Dispose();
|
||||
|
|
@ -514,6 +642,8 @@ public class WorldMap : IDisposable
|
|||
_frameCount = 0;
|
||||
_consecutiveMatchFails = 0;
|
||||
LastMatchSucceeded = false;
|
||||
_checkpointsOff.Clear();
|
||||
_checkpointsOn.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue