Add optimize path
This commit is contained in:
@@ -3,79 +3,59 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
// Filename : CMoveAgent.cs
|
||||
// Creator : ported/simplified from C++ (AutoPFImp/AutoMoveImp/MoveAgent.*)
|
||||
// Creator : ported from C++ (AutoPFImp/AutoMoveImp/MoveAgent.*)
|
||||
// Date : 2026/01/09
|
||||
|
||||
namespace AutoMove
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimal MoveAgent: loads movemap and provides A* path on layer0 rmap.
|
||||
/// 最小 MoveAgent:加载 movemap,并在第0层 rmap 上执行 A* 路径搜索。
|
||||
/// MoveAgent: A* on layer0 rmap + COptimizePath (matches C++ CMoveAgent flow).
|
||||
/// </summary>
|
||||
public class CMoveAgent
|
||||
{
|
||||
// Debug switch (enable temporarily while validating routes).
|
||||
// 调试开关(验证寻路时可临时开启)。
|
||||
// NOTE: must not be const, otherwise Unity compiler warns about unreachable code.
|
||||
// 注意:不要用 const,否则 Unity 编译会报“不可达代码”警告。
|
||||
private static bool DEBUG_AUTOPF = false;
|
||||
|
||||
public abstract class BrushTest
|
||||
{
|
||||
// from.y/to.y store DH in original engine.
|
||||
// 原版中 from.y/to.y 存储 DH(相对地形高度差)。
|
||||
public abstract bool Collide(Vector3 from, Vector3 to);
|
||||
}
|
||||
|
||||
private readonly CMoveMap m_pMoveMap = new CMoveMap();
|
||||
private Vector3 m_vOriginOverride;
|
||||
private COptimizePath m_pPathOptimizer;
|
||||
|
||||
private Vector2Int m_ptStart;
|
||||
private Vector2Int m_ptGoal;
|
||||
private int m_iLayerStart;
|
||||
private int m_iLayerGoal;
|
||||
|
||||
private readonly List<Vector3> m_path3D = new List<Vector3>(1024);
|
||||
private readonly List<Vector2Int> m_pathMap = new List<Vector2Int>(1024);
|
||||
private int m_optimizeCurIndex;
|
||||
private const int DefaultOptimizeCatchCount = 10;
|
||||
|
||||
public bool Load(string basePathNoExt, Func<string, byte[]> resolver, Vector3? originOverride)
|
||||
{
|
||||
// basePathNoExt corresponds to "maps\\<map>\\movemap\\r.._..-c.._..-l0" in C++.
|
||||
// basePathNoExt 对应 C++ 的 "maps\\<map>\\movemap\\r.._..-c.._..-l0"。
|
||||
string cfgName = basePathNoExt;
|
||||
if (!cfgName.EndsWith(".cfg", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cfgName += ".cfg";
|
||||
}
|
||||
|
||||
byte[] cfgBytes = resolver(cfgName);
|
||||
if (cfgBytes == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolver for referenced files: in cfg, references are baseName + ".prmap" etc.
|
||||
// cfg 内引用:baseName + ".prmap" 等。
|
||||
bool ok = m_pMoveMap.Load(cfgBytes, resolver, basePathNoExt);
|
||||
if (!ok)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (originOverride.HasValue)
|
||||
{
|
||||
m_vOriginOverride = originOverride.Value;
|
||||
m_pMoveMap.SetOrigin(m_vOriginOverride);
|
||||
}
|
||||
m_pMoveMap.SetOrigin(originOverride.Value);
|
||||
|
||||
CreateOptimizer();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CreateOptimizer()
|
||||
{
|
||||
m_pPathOptimizer = new COptimizePath();
|
||||
}
|
||||
|
||||
public bool IsReady()
|
||||
{
|
||||
// Original C++ requires MultiCluGraph; here we only require layer0 RMap.
|
||||
// 原版 C++ 依赖 MultiCluGraph;这里最小实现只要求第0层 RMap。
|
||||
var layer0 = m_pMoveMap.GetLayer(0);
|
||||
return layer0 != null && layer0.GetRMap() != null;
|
||||
}
|
||||
@@ -101,8 +81,6 @@ namespace AutoMove
|
||||
|
||||
public int WhichLayer(Vector3 vWld, float dH, out float layerDist)
|
||||
{
|
||||
// Minimal: single layer 0 if passable.
|
||||
// 最小实现:如果可通行则使用单层0。
|
||||
layerDist = 0.0f;
|
||||
var pt = TransWld2Map(vWld);
|
||||
var layer0 = m_pMoveMap.GetLayer(0);
|
||||
@@ -119,71 +97,72 @@ namespace AutoMove
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetPathCount() => m_path3D.Count;
|
||||
public List<PathNode> Get2DPath()
|
||||
{
|
||||
return m_pPathOptimizer?.GetPath();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches C++ CMoveAgent::GetOptimizeCatchCount (COptimizePath::m_CatchCount, default 10).
|
||||
/// </summary>
|
||||
public int GetOptimizeCatchCount() => DefaultOptimizeCatchCount;
|
||||
public int GetPathCount()
|
||||
{
|
||||
if (m_pPathOptimizer != null)
|
||||
{
|
||||
var path = m_pPathOptimizer.GetPath();
|
||||
if (path != null)
|
||||
return path.Count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int GetOptimizeCatchCount()
|
||||
{
|
||||
return m_pPathOptimizer != null ? m_pPathOptimizer.GetCatchCount() : 0;
|
||||
}
|
||||
|
||||
public Vector3 Get2DPathNode(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_path3D.Count) return Vector3.zero;
|
||||
Vector3 v = m_path3D[index];
|
||||
return new Vector3(v.x, 0f, v.z);
|
||||
var path2d = Get2DPath();
|
||||
if (path2d == null || index < 0 || index >= path2d.Count)
|
||||
return Vector3.zero;
|
||||
PathNode pathNode = path2d[index];
|
||||
Vector2 ptWld = m_pMoveMap.TransMap2Wld(pathNode.ptMap.x, pathNode.ptMap.y);
|
||||
return new Vector3(ptWld.x, 0f, ptWld.y);
|
||||
}
|
||||
|
||||
public Vector3 Get3DPathNode(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_path3D.Count) return Vector3.zero;
|
||||
return m_path3D[index];
|
||||
var path2d = Get2DPath();
|
||||
if (path2d == null || index < 0 || index >= path2d.Count)
|
||||
return Vector3.zero;
|
||||
PathNode pathNode = path2d[index];
|
||||
Vector2 ptWld = m_pMoveMap.TransMap2Wld(pathNode.ptMap.x, pathNode.ptMap.y);
|
||||
float dh = m_pMoveMap.GetDH(pathNode.layer, (int)pathNode.ptMap.x, (int)pathNode.ptMap.y);
|
||||
return new Vector3(ptWld.x, dh, ptWld.y);
|
||||
}
|
||||
|
||||
/// <summary>Ported from C++ CMoveAgent::Optimize — incremental path smoothing while walking.</summary>
|
||||
/// <summary>Ported from C++ CMoveAgent::Optimize.</summary>
|
||||
public bool Optimize(int moveIndex)
|
||||
{
|
||||
if (moveIndex < 0 || moveIndex >= m_pathMap.Count - 1)
|
||||
if (m_pPathOptimizer == null)
|
||||
return false;
|
||||
|
||||
var layer0 = m_pMoveMap.GetLayer(0);
|
||||
var rmap = layer0?.GetRMap();
|
||||
if (rmap == null)
|
||||
if (!m_pPathOptimizer.NeedOptimize(moveIndex))
|
||||
return false;
|
||||
|
||||
int from = Mathf.Max(moveIndex, m_optimizeCurIndex);
|
||||
if (from >= m_pathMap.Count - 1)
|
||||
return false;
|
||||
|
||||
int best = from + 1;
|
||||
for (int j = m_pathMap.Count - 1; j > from; j--)
|
||||
{
|
||||
if (IsLinePassable(rmap, m_pathMap[from], m_pathMap[j]))
|
||||
{
|
||||
best = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (best <= from + 1)
|
||||
return false;
|
||||
|
||||
m_pathMap.RemoveRange(from + 1, best - from - 1);
|
||||
m_path3D.RemoveRange(from + 1, best - from - 1);
|
||||
m_optimizeCurIndex = from + 1;
|
||||
m_pPathOptimizer.StepOptimize();
|
||||
return true;
|
||||
}
|
||||
|
||||
public System.Collections.Generic.List<Vector3> GetFullPath()
|
||||
public List<Vector3> GetFullPath()
|
||||
{
|
||||
return new System.Collections.Generic.List<Vector3>(m_path3D);
|
||||
var path = new List<Vector3>();
|
||||
var path2d = Get2DPath();
|
||||
if (path2d == null)
|
||||
return path;
|
||||
for (int i = 0; i < path2d.Count; i++)
|
||||
path.Add(Get2DPathNode(i));
|
||||
return path;
|
||||
}
|
||||
|
||||
public bool Search(int nMaxExpand = -1)
|
||||
{
|
||||
// A* on layer0 rmap
|
||||
// 在第0层 rmap 上执行 A*
|
||||
m_path3D.Clear();
|
||||
|
||||
var layer0 = m_pMoveMap.GetLayer(0);
|
||||
var rmap = layer0?.GetRMap();
|
||||
if (rmap == null) return false;
|
||||
@@ -192,30 +171,18 @@ namespace AutoMove
|
||||
if (w <= 0 || h <= 0) return false;
|
||||
|
||||
if (!InBounds(m_ptStart, w, h) || !InBounds(m_ptGoal, w, h))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If goal or start is not passable, try to find nearest passable (original MoveAgent does this).
|
||||
// 如果起点或终点不可通行,尝试寻找最近可通行点(原版 MoveAgent 会这样做)。
|
||||
if (!rmap.GetPixel(m_ptStart.x, m_ptStart.y))
|
||||
{
|
||||
if (!TryFindNearestPassable(rmap, m_ptStart, w, h, 64, out var newStart))
|
||||
{
|
||||
if (DEBUG_AUTOPF) Debug.LogWarning($"[CMoveAgent] start not passable and no nearest passable found. start={m_ptStart}");
|
||||
return false;
|
||||
}
|
||||
if (DEBUG_AUTOPF) Debug.Log($"[CMoveAgent] Adjust start {m_ptStart} -> {newStart}");
|
||||
m_ptStart = newStart;
|
||||
}
|
||||
if (!rmap.GetPixel(m_ptGoal.x, m_ptGoal.y))
|
||||
{
|
||||
if (!TryFindNearestPassable(rmap, m_ptGoal, w, h, 64, out var newGoal))
|
||||
{
|
||||
if (DEBUG_AUTOPF) Debug.LogWarning($"[CMoveAgent] goal not passable and no nearest passable found. goal={m_ptGoal}");
|
||||
return false;
|
||||
}
|
||||
if (DEBUG_AUTOPF) Debug.Log($"[CMoveAgent] Adjust goal {m_ptGoal} -> {newGoal}");
|
||||
m_ptGoal = newGoal;
|
||||
}
|
||||
|
||||
@@ -227,8 +194,6 @@ namespace AutoMove
|
||||
open.Push(m_ptStart, Heuristic(m_ptStart, m_ptGoal));
|
||||
|
||||
int expands = 0;
|
||||
//ToDo: need use another method to caculate the value of maxExpand
|
||||
//800000 is a magic number, need to be optimized
|
||||
int maxExpand = nMaxExpand > 0 ? nMaxExpand : 800000;
|
||||
|
||||
while (open.Count > 0 && expands < maxExpand)
|
||||
@@ -271,16 +236,12 @@ namespace AutoMove
|
||||
|
||||
if (rmap == null) return false;
|
||||
|
||||
// Check origin first
|
||||
// 先检查原点
|
||||
if (origin.x >= 0 && origin.x < w && origin.y >= 0 && origin.y < h && rmap.GetPixel(origin.x, origin.y))
|
||||
{
|
||||
best = origin;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Expand square rings
|
||||
// 按方形“圈”扩展搜索
|
||||
for (int r = 1; r <= maxRadius; r++)
|
||||
{
|
||||
int minX = Math.Max(0, origin.x - r);
|
||||
@@ -288,14 +249,11 @@ namespace AutoMove
|
||||
int minY = Math.Max(0, origin.y - r);
|
||||
int maxY = Math.Min(h - 1, origin.y + r);
|
||||
|
||||
// Top/bottom edges
|
||||
for (int x = minX; x <= maxX; x++)
|
||||
{
|
||||
TryConsider(x, minY);
|
||||
TryConsider(x, maxY);
|
||||
}
|
||||
|
||||
// Left/right edges (excluding corners already checked)
|
||||
for (int y = minY + 1; y <= maxY - 1; y++)
|
||||
{
|
||||
TryConsider(minX, y);
|
||||
@@ -328,123 +286,35 @@ namespace AutoMove
|
||||
|
||||
private void ReconstructPath(Dictionary<Vector2Int, Vector2Int> cameFrom, Vector2Int cur)
|
||||
{
|
||||
List<Vector2Int> rev = new List<Vector2Int>(1024) { cur };
|
||||
var rev = new List<Vector2Int>(1024) { cur };
|
||||
while (cameFrom.TryGetValue(cur, out var prev))
|
||||
{
|
||||
cur = prev;
|
||||
rev.Add(cur);
|
||||
}
|
||||
|
||||
rev.Reverse();
|
||||
var layer0 = m_pMoveMap.GetLayer(0);
|
||||
var rmap = layer0?.GetRMap();
|
||||
List<Vector2Int> smoothed = rmap != null ? SmoothPathMap(rev, rmap) : rev;
|
||||
|
||||
m_pathMap.Clear();
|
||||
m_path3D.Clear();
|
||||
m_optimizeCurIndex = 0;
|
||||
for (int i = 0; i < smoothed.Count; i++)
|
||||
var initPath = new List<PathNode>(rev.Count);
|
||||
for (int i = 0; i < rev.Count; i++)
|
||||
{
|
||||
m_pathMap.Add(smoothed[i]);
|
||||
m_path3D.Add(Map2Wld(smoothed[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>String-pull on passable grid (same goal as C++ COptimizePath::SetupOptimize).</summary>
|
||||
static List<Vector2Int> SmoothPathMap(List<Vector2Int> raw, CBitImage rmap)
|
||||
{
|
||||
if (raw == null || raw.Count <= 2)
|
||||
return raw;
|
||||
|
||||
var result = new List<Vector2Int>(raw.Count) { raw[0] };
|
||||
int i = 0;
|
||||
while (i < raw.Count - 1)
|
||||
{
|
||||
int furthest = i + 1;
|
||||
for (int j = raw.Count - 1; j > i; j--)
|
||||
initPath.Add(new PathNode
|
||||
{
|
||||
if (IsLinePassable(rmap, raw[i], raw[j]))
|
||||
{
|
||||
furthest = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (furthest != i)
|
||||
{
|
||||
if (result[result.Count - 1] != raw[furthest])
|
||||
result.Add(raw[furthest]);
|
||||
i = furthest;
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
if (result[result.Count - 1] != raw[i])
|
||||
result.Add(raw[i]);
|
||||
}
|
||||
ptMap = new Vector2(rev[i].x, rev[i].y),
|
||||
layer = m_iLayerStart,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
if (m_pPathOptimizer == null)
|
||||
CreateOptimizer();
|
||||
|
||||
/// <summary>Matches C++ COptimizePath::_LineTo strict diagonal checks on rmap.</summary>
|
||||
static bool IsLinePassable(CBitImage rmap, Vector2Int from, Vector2Int to)
|
||||
{
|
||||
if (from == to)
|
||||
return true;
|
||||
|
||||
rmap.GetImageSize(out int w, out int h);
|
||||
int x0 = from.x, y0 = from.y;
|
||||
int x1 = to.x, y1 = to.y;
|
||||
int dx = Mathf.Abs(x1 - x0);
|
||||
int dy = Mathf.Abs(y1 - y0);
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
int lastX = x0, lastY = y0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (x0 < 0 || x0 >= w || y0 < 0 || y0 >= h || !rmap.GetPixel(x0, y0))
|
||||
return false;
|
||||
|
||||
if (x0 == x1 && y0 == y1)
|
||||
break;
|
||||
|
||||
int e2 = err << 1;
|
||||
int nextX = x0, nextY = y0;
|
||||
if (e2 > -dy)
|
||||
{
|
||||
err -= dy;
|
||||
nextX += sx;
|
||||
}
|
||||
if (e2 < dx)
|
||||
{
|
||||
err += dx;
|
||||
nextY += sy;
|
||||
}
|
||||
|
||||
if (nextX != lastX && nextY != lastY)
|
||||
{
|
||||
if (!rmap.GetPixel(lastX, nextY) || !rmap.GetPixel(nextX, lastY))
|
||||
return false;
|
||||
}
|
||||
|
||||
x0 = nextX;
|
||||
y0 = nextY;
|
||||
lastX = x0;
|
||||
lastY = y0;
|
||||
}
|
||||
|
||||
return true;
|
||||
// C++: m_pPathOptimizer->SetupOptimize(GetMoveMap(), path);
|
||||
m_pPathOptimizer.SetupOptimize(m_pMoveMap, initPath);
|
||||
}
|
||||
|
||||
private static bool InBounds(Vector2Int p, int w, int h) => p.x >= 0 && p.x < w && p.y >= 0 && p.y < h;
|
||||
|
||||
private static int Heuristic(Vector2Int a, Vector2Int b)
|
||||
{
|
||||
// Octile distance * 10
|
||||
// 八方向启发式(octile)*10
|
||||
int dx = Mathf.Abs(a.x - b.x);
|
||||
int dy = Mathf.Abs(a.y - b.y);
|
||||
int min = Math.Min(dx, dy);
|
||||
@@ -454,8 +324,6 @@ namespace AutoMove
|
||||
|
||||
private static int Cost(Vector2Int a, Vector2Int b)
|
||||
{
|
||||
// Diagonal=14, straight=10
|
||||
// 斜向=14,直向=10
|
||||
int dx = Mathf.Abs(a.x - b.x);
|
||||
int dy = Mathf.Abs(a.y - b.y);
|
||||
return (dx + dy == 2) ? 14 : 10;
|
||||
@@ -475,35 +343,13 @@ namespace AutoMove
|
||||
|
||||
public void ResetSearch()
|
||||
{
|
||||
// Çå³ýµ±Ç°ËÑË÷״̬
|
||||
//if (m_iStat == PF_STATE_UNKNOWN)
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
//if (m_pPfAlg)
|
||||
//{
|
||||
// m_pPfAlg->Reset();
|
||||
//}
|
||||
//if (m_pPathOptimizer)
|
||||
//{
|
||||
// m_pPathOptimizer.Reset();
|
||||
//}
|
||||
m_ptStart.x = m_ptStart.y = 0;
|
||||
m_ptStart = Vector2Int.zero;
|
||||
m_iLayerStart = -1;
|
||||
m_ptGoal.x = m_ptGoal.y = 0;
|
||||
m_ptGoal = Vector2Int.zero;
|
||||
m_iLayerGoal = -1;
|
||||
//m_pBrushTest = null;
|
||||
|
||||
//m_iStat = PF_STATE_UNKNOWN;
|
||||
m_path3D.Clear();
|
||||
m_pathMap.Clear();
|
||||
m_optimizeCurIndex = 0;
|
||||
m_pPathOptimizer?.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Very small min-heap for A*.
|
||||
/// A* 用的小型最小堆。
|
||||
/// </summary>
|
||||
private sealed class MinHeap
|
||||
{
|
||||
private struct Node
|
||||
@@ -559,5 +405,3 @@ namespace AutoMove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -56,8 +56,13 @@ namespace AutoMove
|
||||
|
||||
public Vector2 TransMap2Wld(int x, int y)
|
||||
{
|
||||
float wx = m_vOrigin.x + (x + 0.5f) * m_fPixelSize;
|
||||
float wz = m_vOrigin.z + (y + 0.5f) * m_fPixelSize;
|
||||
return TransMap2Wld((float)x, (float)y);
|
||||
}
|
||||
|
||||
public Vector2 TransMap2Wld(float mapX, float mapY)
|
||||
{
|
||||
float wx = m_vOrigin.x + (mapX + 0.5f) * m_fPixelSize;
|
||||
float wz = m_vOrigin.z + (mapY + 0.5f) * m_fPixelSize;
|
||||
return new Vector2(wx, wz);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
// Ported from C++ OptimizePath.h / OptimizePath.cpp (Kui Wu, 2007).
|
||||
|
||||
namespace AutoMove
|
||||
{
|
||||
/// <summary>Line sampler for map-space path optimization (C++ CLine).</summary>
|
||||
sealed class CLine
|
||||
{
|
||||
Vector2 m_from;
|
||||
Vector2 m_dir;
|
||||
int m_count;
|
||||
|
||||
public void Init(Vector2 from, Vector2 dir)
|
||||
{
|
||||
m_from = from;
|
||||
m_dir = dir;
|
||||
float len = m_dir.magnitude;
|
||||
if (len > 1e-8f)
|
||||
{
|
||||
m_dir.x /= len;
|
||||
m_dir.y /= len;
|
||||
}
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
public void Init(Vector2 from, int dirX, int dirY)
|
||||
{
|
||||
Init(from, new Vector2(dirX, dirY));
|
||||
}
|
||||
|
||||
public Vector2 Next()
|
||||
{
|
||||
m_count++;
|
||||
return new Vector2(m_from.x + m_dir.x * m_count, m_from.y + m_dir.y * m_count);
|
||||
}
|
||||
|
||||
public int GetCount() => m_count;
|
||||
public Vector2 GetFrom() => m_from;
|
||||
public void Reset() => m_count = 0;
|
||||
}
|
||||
|
||||
/// <summary>Path optimizer — port of C++ COptimizePath.</summary>
|
||||
public sealed class COptimizePath
|
||||
{
|
||||
const int LookAhead = 60;
|
||||
const int LookStep = 3;
|
||||
|
||||
CMoveMap m_pMoveMap;
|
||||
int m_mapWidth;
|
||||
int m_mapHeight;
|
||||
readonly Dictionary<int, short> m_lookUp = new Dictionary<int, short>(4096);
|
||||
readonly List<PathNode> m_path = new List<PathNode>(1024);
|
||||
int m_curIndex = -1;
|
||||
int m_catchCount = 10;
|
||||
int m_curLayer = -1;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
m_pMoveMap = null;
|
||||
m_mapWidth = 0;
|
||||
m_mapHeight = 0;
|
||||
m_lookUp.Clear();
|
||||
m_path.Clear();
|
||||
m_curIndex = -1;
|
||||
m_curLayer = -1;
|
||||
}
|
||||
|
||||
public int GetCatchCount() => m_catchCount;
|
||||
|
||||
public List<PathNode> GetPath() => m_path;
|
||||
|
||||
public bool NeedOptimize(int moveIndex)
|
||||
{
|
||||
if (m_curIndex < m_path.Count
|
||||
&& moveIndex < m_path.Count
|
||||
&& m_curIndex - moveIndex < m_catchCount)
|
||||
{
|
||||
if (moveIndex > m_curIndex)
|
||||
{
|
||||
SetFootprintRange(m_curIndex, moveIndex - 1, 0);
|
||||
m_curIndex = moveIndex;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetupOptimize(CMoveMap pMoveMap, List<PathNode> initPath, int catchCount = 10)
|
||||
{
|
||||
m_path.Clear();
|
||||
if (initPath != null && initPath.Count > 0)
|
||||
m_path.AddRange(initPath);
|
||||
|
||||
m_pMoveMap = pMoveMap;
|
||||
m_mapWidth = pMoveMap != null ? pMoveMap.GetMapWidth() : 0;
|
||||
m_mapHeight = pMoveMap != null ? pMoveMap.GetMapLength() : 0;
|
||||
m_curIndex = 0;
|
||||
m_catchCount = catchCount;
|
||||
m_curLayer = -1;
|
||||
|
||||
if (m_path.Count == 0)
|
||||
return;
|
||||
|
||||
CheckLayer();
|
||||
}
|
||||
|
||||
public void StepOptimize()
|
||||
{
|
||||
CheckLayer();
|
||||
int step = m_catchCount * 2;
|
||||
if (step <= 0)
|
||||
return;
|
||||
|
||||
int i = 0;
|
||||
while (i < step && m_curIndex < m_path.Count)
|
||||
{
|
||||
LocalOptimize();
|
||||
i++;
|
||||
m_curIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
void CheckLayer()
|
||||
{
|
||||
if (m_path.Count == 0 || m_curIndex < 0 || m_curIndex >= m_path.Count)
|
||||
return;
|
||||
|
||||
if (m_path[m_curIndex].layer == m_curLayer)
|
||||
return;
|
||||
|
||||
m_curLayer = m_path[m_curIndex].layer;
|
||||
m_lookUp.Clear();
|
||||
|
||||
int i = m_curIndex;
|
||||
while (i < m_path.Count && m_path[i].layer == m_curLayer)
|
||||
{
|
||||
SetFootprint(m_path[i].ptMap, (short)(i + 1));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocalOptimize()
|
||||
{
|
||||
int toIndex = Mathf.Min(m_curIndex + LookAhead, m_path.Count - 1);
|
||||
var line = new CLine();
|
||||
Vector2 dir;
|
||||
int newCount = -1;
|
||||
|
||||
while (toIndex - m_curIndex > LookStep)
|
||||
{
|
||||
if (GetFootprint(m_path[toIndex].ptMap) == 0)
|
||||
{
|
||||
toIndex -= LookStep;
|
||||
continue;
|
||||
}
|
||||
if (m_path[toIndex].layer != m_curLayer)
|
||||
{
|
||||
toIndex -= LookStep;
|
||||
continue;
|
||||
}
|
||||
|
||||
dir = m_path[toIndex].ptMap - m_path[m_curIndex].ptMap;
|
||||
if ((int)dir.x == 0 && (int)dir.y == 0)
|
||||
{
|
||||
PathIntersect(toIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
line.Init(m_path[m_curIndex].ptMap, dir);
|
||||
if (LineTo(line, m_path[toIndex].ptMap))
|
||||
{
|
||||
newCount = line.GetCount();
|
||||
break;
|
||||
}
|
||||
toIndex -= LookStep;
|
||||
}
|
||||
|
||||
if (newCount > 0)
|
||||
{
|
||||
line.Reset();
|
||||
AddPathPortion(line, toIndex - m_curIndex, newCount);
|
||||
}
|
||||
}
|
||||
|
||||
CBitImage GetRMap(int layer)
|
||||
{
|
||||
if (m_pMoveMap == null)
|
||||
return null;
|
||||
var layerMap = m_pMoveMap.GetLayer(layer);
|
||||
return layerMap?.GetRMap();
|
||||
}
|
||||
|
||||
bool LineTo(CLine line, Vector2 to)
|
||||
{
|
||||
int toX = (int)to.x;
|
||||
int toY = (int)to.y;
|
||||
int curX = (int)line.GetFrom().x;
|
||||
int curY = (int)line.GetFrom().y;
|
||||
|
||||
CBitImage pRMap = GetRMap(m_curLayer);
|
||||
if (pRMap == null)
|
||||
return false;
|
||||
|
||||
int lastX = curX;
|
||||
int lastY = curY;
|
||||
|
||||
while (curX != toX || curY != toY)
|
||||
{
|
||||
Vector2 cur = line.Next();
|
||||
curX = (int)cur.x;
|
||||
curY = (int)cur.y;
|
||||
|
||||
if (!pRMap.GetPixel(curX, curY))
|
||||
return false;
|
||||
|
||||
bool bNeighbor1 = pRMap.GetPixel(lastX, curY);
|
||||
bool bNeighbor2 = pRMap.GetPixel(curX, lastY);
|
||||
|
||||
if (toX == lastX && toY == curY)
|
||||
break;
|
||||
if (toX == curX && toY == lastY)
|
||||
break;
|
||||
|
||||
if (curX != lastX && curY != lastY && (!bNeighbor1 || !bNeighbor2))
|
||||
return false;
|
||||
|
||||
lastX = curX;
|
||||
lastY = curY;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PathIntersect(int toIndex)
|
||||
{
|
||||
SetFootprintRange(m_curIndex + 1, toIndex, 0);
|
||||
int removeCount = toIndex - m_curIndex;
|
||||
if (removeCount > 0 && m_curIndex + 1 < m_path.Count)
|
||||
m_path.RemoveRange(m_curIndex + 1, removeCount);
|
||||
}
|
||||
|
||||
void AddPathPortion(CLine line, int oldCount, int newCount)
|
||||
{
|
||||
SetFootprintRange(m_curIndex + 1, m_curIndex + oldCount, 0);
|
||||
|
||||
if (oldCount > newCount)
|
||||
{
|
||||
m_path.RemoveRange(m_curIndex + 1, oldCount - newCount);
|
||||
}
|
||||
else if (oldCount < newCount)
|
||||
{
|
||||
int insertCount = newCount - oldCount;
|
||||
for (int k = 0; k < insertCount; k++)
|
||||
m_path.Insert(m_curIndex + 1, new PathNode());
|
||||
}
|
||||
|
||||
int index = m_curIndex + 1;
|
||||
while (line.GetCount() < newCount && index < m_path.Count)
|
||||
{
|
||||
var node = m_path[index];
|
||||
node.ptMap = line.Next();
|
||||
node.layer = m_curLayer;
|
||||
m_path[index] = node;
|
||||
index++;
|
||||
}
|
||||
|
||||
SetFootprintRange(m_curIndex + 1, m_curIndex + newCount, 1);
|
||||
}
|
||||
|
||||
int GetLookUpKey(int x, int y) => y * m_mapWidth + x;
|
||||
|
||||
int GetFootprint(int x, int y)
|
||||
{
|
||||
return m_lookUp.TryGetValue(GetLookUpKey(x, y), out short v) ? v : 0;
|
||||
}
|
||||
|
||||
int GetFootprint(Vector2 pt) => GetFootprint((int)pt.x, (int)pt.y);
|
||||
|
||||
void SetFootprint(int x, int y, int val)
|
||||
{
|
||||
int key = GetLookUpKey(x, y);
|
||||
if (val == 0)
|
||||
m_lookUp.Remove(key);
|
||||
else
|
||||
m_lookUp[key] = (short)val;
|
||||
}
|
||||
|
||||
void SetFootprint(Vector2 pt, int val) => SetFootprint((int)pt.x, (int)pt.y, val);
|
||||
|
||||
void SetFootprintRange(int fromIndex, int toIndex, int val)
|
||||
{
|
||||
for (int i = fromIndex; i <= toIndex && i < m_path.Count; ++i)
|
||||
SetFootprint(m_path[i].ptMap, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96e45cded56a0bc4188f329f2cf3b676
|
||||
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AutoMove
|
||||
{
|
||||
/// <summary>Matches C++ AutoMove::PathNode (PfConstant.h).</summary>
|
||||
public struct PathNode
|
||||
{
|
||||
public Vector2 ptMap;
|
||||
public int layer;
|
||||
|
||||
public Vector2Int GetPtI()
|
||||
{
|
||||
return new Vector2Int(Mathf.FloorToInt(ptMap.x), Mathf.FloorToInt(ptMap.y));
|
||||
}
|
||||
|
||||
public void SetI(Vector2Int pt, int iLayer)
|
||||
{
|
||||
ptMap = new Vector2(pt.x, pt.y);
|
||||
layer = iLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98c1e0dc325b8e64bbda287a0128aeed
|
||||
@@ -19,7 +19,7 @@ namespace BrewMonster.Scripts
|
||||
/// </summary>
|
||||
public sealed class CECIntelligentRoute
|
||||
{
|
||||
private const bool DEBUG_AUTOPF = true;
|
||||
private const bool DEBUG_AUTOPF = false;
|
||||
/// <summary>cos(5°) for FindFarthestNode — same as C++ EC_IntelligentRoute.cpp.</summary>
|
||||
private static readonly float s_cos5Deg = Mathf.Cos(5f * Mathf.PI / 180f);
|
||||
public enum SearchResult
|
||||
@@ -478,10 +478,8 @@ namespace BrewMonster.Scripts
|
||||
}
|
||||
|
||||
m_iCurDest = FindNextNode(pos, m_iCurDest + 1);
|
||||
m_dist2CurDest = (GetCurDest() - pos).MagnitudeH();
|
||||
agent.Optimize(m_iCurDest);
|
||||
int newPathCount = agent.GetPathCount();
|
||||
if (m_iCurDest >= newPathCount)
|
||||
m_iCurDest = Mathf.Max(0, newPathCount - 1);
|
||||
m_dist2CurDest = (GetCurDest() - pos).MagnitudeH();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user