换成了基于网格坐标的路径优化算法
This commit is contained in:
parent
1622d6cb90
commit
dd62a6dce4
@ -130,7 +130,8 @@
|
||||
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)",
|
||||
"Read(/C:\\Users\\Tellme\\apps\\NavisworksTransport\\src\\Core\\Collision/**)",
|
||||
"WebSearch",
|
||||
"Read(//c/Users/Tellme/apps/OpenSource/AStar-master/Roy-T.AStar/**)"
|
||||
"Read(//c/Users/Tellme/apps/OpenSource/AStar-master/Roy-T.AStar/**)",
|
||||
"Read(//c/Users/Tellme/apps/OpenSource/AStar-master/**)"
|
||||
],
|
||||
"deny": [],
|
||||
"additionalDirectories": [
|
||||
|
||||
278
doc/design/2026/AStar库的使用方法.md
Normal file
278
doc/design/2026/AStar库的使用方法.md
Normal file
@ -0,0 +1,278 @@
|
||||
# Roy-T.AStar库的使用方法与经验总结
|
||||
|
||||
## 概述
|
||||
|
||||
Roy-T.AStar是一个高性能的C# A*寻路算法库,位于`C:\Users\Tellme\apps\OpenSource\AStar-master`。本文档记录了在NavisworksTransport项目中集成和使用该库的经验教训。
|
||||
|
||||
## 核心概念理解
|
||||
|
||||
### 1. 坐标系统
|
||||
|
||||
**关键发现**:Roy-T.AStar使用**米坐标系统**,而不是网格索引。
|
||||
|
||||
```csharp
|
||||
// 创建网格时,传入的是物理尺寸
|
||||
var cellSize = new Size(
|
||||
Distance.FromMeters((float)cellSizeInMeters),
|
||||
Distance.FromMeters((float)cellSizeInMeters)
|
||||
);
|
||||
|
||||
// GridPosition构造函数接受的是网格索引
|
||||
var gridPos = new GridPosition(x, y); // x,y是网格索引,如(0,0), (1,0)等
|
||||
|
||||
// 但Node.Position返回的是米坐标!
|
||||
// 例如:网格(1,0)的Node.Position可能是(2.0, 0.0)米(假设cellSize=2米)
|
||||
```
|
||||
|
||||
### 2. 路径数据结构
|
||||
|
||||
**Path对象结构**:
|
||||
|
||||
- `Path.Edges`: 边的列表(IReadOnlyList<IEdge>)
|
||||
- 每条边包含:
|
||||
- `Start`: 起始节点
|
||||
- `End`: 终止节点
|
||||
- `Distance`: 边的长度
|
||||
- `TraversalVelocity`: 遍历速度
|
||||
|
||||
**重要特性**:连续边的关系
|
||||
|
||||
```
|
||||
Edge[0]: Start=A, End=B
|
||||
Edge[1]: Start=B, End=C // 注意:Edge[0].End == Edge[1].Start
|
||||
Edge[2]: Start=C, End=D
|
||||
```
|
||||
|
||||
## 常见陷阱与解决方案
|
||||
|
||||
### 陷阱1:坐标转换时的重复点问题
|
||||
|
||||
**错误做法**:
|
||||
|
||||
```csharp
|
||||
// ❌ 错误:会产生重复点
|
||||
var gridPath = new List<GridPoint2D>();
|
||||
gridPath.Add(ConvertToGrid(edges[0].Start)); // 添加起点
|
||||
foreach (var edge in edges) {
|
||||
gridPath.Add(ConvertToGrid(edge.End)); // 每条边的终点
|
||||
}
|
||||
// 结果:[A, B, B, C, C, D] - 转弯点重复!
|
||||
```
|
||||
|
||||
**正确做法**:
|
||||
|
||||
```csharp
|
||||
// ✅ 正确:避免重复
|
||||
var gridPath = new List<GridPoint2D>();
|
||||
if (edges.Count > 0) {
|
||||
gridPath.Add(ConvertToGrid(edges[0].Start)); // 只添加第一个起点
|
||||
foreach (var edge in edges) {
|
||||
var gridPoint = ConvertToGrid(edge.End);
|
||||
// 检查是否与上一个点重复
|
||||
if (gridPath.Count == 0 || !gridPath.Last().Equals(gridPoint)) {
|
||||
gridPath.Add(gridPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 结果:[A, B, C, D] - 完美的连续路径
|
||||
```
|
||||
|
||||
### 陷阱2:米坐标到网格坐标的转换
|
||||
|
||||
**关键代码**:
|
||||
|
||||
```csharp
|
||||
// 从A*的米坐标转换为网格索引
|
||||
double cellSizeInMeters = UnitsConverter.ConvertToMeters(gridMap.CellSize);
|
||||
int gridX = (int)Math.Floor(node.Position.X / cellSizeInMeters);
|
||||
int gridY = (int)Math.Floor(node.Position.Y / cellSizeInMeters);
|
||||
```
|
||||
|
||||
### 陷阱3:网格创建时的节点连接
|
||||
|
||||
**默认创建方法的限制**:
|
||||
|
||||
```csharp
|
||||
// Roy-T.AStar提供的默认方法会连接所有节点
|
||||
var grid = Grid.CreateGridWithLateralConnections(gridSize, cellSize, velocity);
|
||||
```
|
||||
|
||||
**自定义障碍物处理**:
|
||||
|
||||
```csharp
|
||||
// 1. 先断开所有连接
|
||||
for (int x = 0; x < gridMap.Width; x++) {
|
||||
for (int y = 0; y < gridMap.Height; y++) {
|
||||
grid.DisconnectNode(new GridPosition(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 只连接可通行的节点
|
||||
for (int x = 0; x < gridMap.Width; x++) {
|
||||
for (int y = 0; y < gridMap.Height; y++) {
|
||||
if (IsWalkable(x, y)) {
|
||||
// 连接到右侧邻居
|
||||
if (x + 1 < width && IsWalkable(x + 1, y)) {
|
||||
grid.AddEdge(new GridPosition(x, y),
|
||||
new GridPosition(x + 1, y), velocity);
|
||||
}
|
||||
// 连接到下方邻居
|
||||
if (y + 1 < height && IsWalkable(x, y + 1)) {
|
||||
grid.AddEdge(new GridPosition(x, y),
|
||||
new GridPosition(x, y + 1), velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 路径优化策略
|
||||
|
||||
### 1. 网格路径优化算法
|
||||
|
||||
**问题**:A*输出的路径包含大量中间点,需要优化。
|
||||
|
||||
**解决方案**:基于方向变化的路径简化
|
||||
|
||||
```csharp
|
||||
private List<GridPoint2D> SimplifyPath(List<GridPoint2D> path) {
|
||||
if (path.Count < 3) return path;
|
||||
|
||||
var simplified = new List<GridPoint2D> { path[0] };
|
||||
|
||||
// 计算初始方向(归一化)
|
||||
int dx = path[1].X - path[0].X;
|
||||
int dy = path[1].Y - path[0].Y;
|
||||
var prevDirection = new GridPoint2D(
|
||||
dx == 0 ? 0 : Math.Sign(dx),
|
||||
dy == 0 ? 0 : Math.Sign(dy)
|
||||
);
|
||||
|
||||
// 检测方向变化
|
||||
for (int i = 2; i < path.Count; i++) {
|
||||
dx = path[i].X - path[i-1].X;
|
||||
dy = path[i].Y - path[i-1].Y;
|
||||
var currentDirection = new GridPoint2D(
|
||||
dx == 0 ? 0 : Math.Sign(dx),
|
||||
dy == 0 ? 0 : Math.Sign(dy)
|
||||
);
|
||||
|
||||
// 方向改变时保留转弯点
|
||||
if (!currentDirection.Equals(prevDirection)) {
|
||||
simplified.Add(path[i - 1]);
|
||||
prevDirection = currentDirection;
|
||||
}
|
||||
}
|
||||
|
||||
simplified.Add(path.Last()); // 添加终点
|
||||
return simplified;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 方向归一化的重要性
|
||||
|
||||
**问题**:不同步长的移动被误判为转弯
|
||||
|
||||
- `(-1, 0)` → `(-2, 0)`:都是向左,但步长不同
|
||||
- `(0, -1)` → `(0, -3)`:都是向上,但步长不同
|
||||
|
||||
**解决**:使用`Math.Sign()`归一化方向向量
|
||||
|
||||
```csharp
|
||||
// 归一化为单位方向向量 (-1, 0, 1)
|
||||
int dirX = dx == 0 ? 0 : Math.Sign(dx);
|
||||
int dirY = dy == 0 ? 0 : Math.Sign(dy);
|
||||
```
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 路径缓存
|
||||
|
||||
对于频繁查询的路径,考虑缓存结果:
|
||||
|
||||
```csharp
|
||||
private Dictionary<(Point3D, Point3D), Path> _pathCache;
|
||||
```
|
||||
|
||||
### 2. 分层寻路
|
||||
|
||||
对于大型地图,可以使用分层A*算法:
|
||||
|
||||
- 高层:粗略网格,快速找到大致路径
|
||||
- 低层:精细网格,优化局部路径
|
||||
|
||||
### 3. 动态障碍物
|
||||
|
||||
Roy-T.AStar支持动态修改网格连接:
|
||||
|
||||
```csharp
|
||||
// 添加障碍物
|
||||
grid.DisconnectNode(position);
|
||||
|
||||
// 移除障碍物
|
||||
grid.AddEdge(from, to, velocity);
|
||||
```
|
||||
|
||||
## 2.5D路径规划扩展
|
||||
|
||||
### 高度约束处理
|
||||
|
||||
```csharp
|
||||
// 检查节点是否满足高度约束
|
||||
if (cell.PassableHeights != null && cell.PassableHeights.Any()) {
|
||||
bool heightOk = cell.PassableHeights.Any(
|
||||
interval => interval.GetSpan() >= vehicleHeight
|
||||
);
|
||||
if (!heightOk) {
|
||||
grid.DisconnectNode(position); // 不满足高度要求,断开连接
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 日志输出
|
||||
|
||||
```csharp
|
||||
LogManager.Info($"[A*执行] 找到路径,包含 {path.Edges.Count} 条边");
|
||||
LogManager.Debug($"[路径优化] 方向从({prev.X},{prev.Y})变为({curr.X},{curr.Y})");
|
||||
```
|
||||
|
||||
### 2. 路径验证
|
||||
|
||||
```csharp
|
||||
// 验证路径连续性
|
||||
for (int i = 1; i < path.Count; i++) {
|
||||
var dist = Math.Abs(path[i].X - path[i-1].X) +
|
||||
Math.Abs(path[i].Y - path[i-1].Y);
|
||||
if (dist > 1) {
|
||||
LogManager.Warning($"路径不连续:从{path[i-1]}到{path[i]}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 实际优化效果
|
||||
|
||||
在NavisworksTransport项目中的实测结果:
|
||||
|
||||
- **原始A*输出**:101个路径点
|
||||
- **优化后**:19个关键转弯点
|
||||
- **优化率**:81.2%
|
||||
- **处理时间**:约8ms
|
||||
|
||||
## 总结
|
||||
|
||||
使用Roy-T.AStar库的关键要点:
|
||||
|
||||
1. 理解米坐标系统,正确进行坐标转换
|
||||
2. 注意Path.Edges的连续性,避免重复点
|
||||
3. 使用方向归一化进行路径优化
|
||||
4. 灵活运用DisconnectNode和AddEdge处理障碍物
|
||||
5. 对于2.5D场景,在网格连接阶段处理高度约束
|
||||
|
||||
## 参考资源
|
||||
|
||||
- Roy-T.AStar源码:`C:\Users\Tellme\apps\OpenSource\AStar-master`
|
||||
- NavisworksTransport集成代码:
|
||||
- `src\PathPlanning\AutoPathFinder.cs`
|
||||
- `src\PathPlanning\PathOptimizer.cs`
|
||||
@ -517,7 +517,7 @@ namespace NavisworksTransport
|
||||
};
|
||||
visualization.PointMarkers.Add(grayEndMarker);
|
||||
|
||||
LogManager.WriteLog($"[路径可视化] 添加灰色未到达终点: ({originalEndPoint.X:F2}, {originalEndPoint.Y:F2}, {originalEndPoint.Z:F2}), 完成度: {visualization.PathRoute.CompletionPercentage:F1}%");
|
||||
LogManager.WriteLog($"[路径可视化] 添加未到达终点: ({originalEndPoint.X:F2}, {originalEndPoint.Y:F2}, {originalEndPoint.Z:F2}), 完成度: {visualization.PathRoute.CompletionPercentage:F1}%");
|
||||
}
|
||||
|
||||
visualization.LastUpdated = DateTime.Now;
|
||||
|
||||
@ -155,7 +155,7 @@ namespace NavisworksTransport.PathPlanning
|
||||
LogManager.Info($"[2.5D路径规划] 路径生成完成,最终包含 {finalPath.Count} 个路径点,完成度: {pathResult.CompletionPercentage:F1}%");
|
||||
|
||||
// 路径优化 - 去除共线点
|
||||
pathResult = OptimizePath(pathResult);
|
||||
pathResult = OptimizePath(pathResult, gridMap);
|
||||
|
||||
return pathResult;
|
||||
}
|
||||
@ -315,6 +315,44 @@ namespace NavisworksTransport.PathPlanning
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将A*路径的米坐标转换为网格坐标
|
||||
/// </summary>
|
||||
/// <param name="astarPath">A*路径</param>
|
||||
/// <param name="gridMap">网格地图</param>
|
||||
/// <returns>网格坐标路径</returns>
|
||||
public List<GridPoint2D> ConvertPathToGridCoordinates(Path astarPath, GridMap gridMap)
|
||||
{
|
||||
var gridPath = new List<GridPoint2D>();
|
||||
double cellSizeInMeters = UnitsConverter.ConvertToMeters(gridMap.CellSize);
|
||||
|
||||
// 只添加起始节点一次,然后只添加每条边的终点
|
||||
if (astarPath.Edges.Count > 0)
|
||||
{
|
||||
// 添加第一条边的起点
|
||||
var startNode = astarPath.Edges[0].Start;
|
||||
var startGridX = (int)Math.Floor(startNode.Position.X / cellSizeInMeters);
|
||||
var startGridY = (int)Math.Floor(startNode.Position.Y / cellSizeInMeters);
|
||||
gridPath.Add(new GridPoint2D(startGridX, startGridY));
|
||||
|
||||
// 添加每条边的终点(避免重复)
|
||||
foreach (var edge in astarPath.Edges)
|
||||
{
|
||||
var gridX = (int)Math.Floor(edge.End.Position.X / cellSizeInMeters);
|
||||
var gridY = (int)Math.Floor(edge.End.Position.Y / cellSizeInMeters);
|
||||
var gridPoint = new GridPoint2D(gridX, gridY);
|
||||
|
||||
// 检查是否与上一个点重复(理论上A*不应该产生重复,但保险起见)
|
||||
if (gridPath.Count == 0 || !gridPath[gridPath.Count - 1].Equals(gridPoint))
|
||||
{
|
||||
gridPath.Add(gridPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gridPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将A*路径转换为世界坐标
|
||||
/// </summary>
|
||||
@ -330,11 +368,11 @@ namespace NavisworksTransport.PathPlanning
|
||||
// 获取单位转换因子
|
||||
double cellSizeInMeters = UnitsConverter.ConvertToMeters(gridMap.CellSize);
|
||||
|
||||
// 添加起始节点
|
||||
var startNode = astarPath.Edges.Count > 0 ? astarPath.Edges[0].Start : null;
|
||||
if (startNode != null)
|
||||
// 添加起始节点一次,然后只添加边的终点(避免重复)
|
||||
if (astarPath.Edges.Count > 0)
|
||||
{
|
||||
// RoyT.AStar返回的Position是米坐标,需要转换为网格索引
|
||||
// 添加第一条边的起点
|
||||
var startNode = astarPath.Edges[0].Start;
|
||||
var startGridX = (int)Math.Floor(startNode.Position.X / cellSizeInMeters);
|
||||
var startGridY = (int)Math.Floor(startNode.Position.Y / cellSizeInMeters);
|
||||
var startGridPos = new GridPoint2D(startGridX, startGridY);
|
||||
@ -342,26 +380,37 @@ namespace NavisworksTransport.PathPlanning
|
||||
var startWorldPos = gridMap.GridToWorld3D(startGridPos, startCell?.WorldPosition.Z ?? 0);
|
||||
LogManager.Info($"[路径转换] 起始节点: A*米坐标({startNode.Position.X:F2}, {startNode.Position.Y:F2}) -> 网格索引({startGridPos.X}, {startGridPos.Y}) -> 世界坐标({startWorldPos.X:F2}, {startWorldPos.Y:F2}, {startWorldPos.Z:F2})");
|
||||
worldPath.Add(startWorldPos);
|
||||
}
|
||||
|
||||
// 添加路径上的每个节点(只输出前5个用于调试)
|
||||
int debugCount = 0;
|
||||
foreach (var edge in astarPath.Edges)
|
||||
{
|
||||
// RoyT.AStar返回的Position是米坐标,需要转换为网格索引
|
||||
var gridX = (int)Math.Floor(edge.End.Position.X / cellSizeInMeters);
|
||||
var gridY = (int)Math.Floor(edge.End.Position.Y / cellSizeInMeters);
|
||||
var gridPos = new GridPoint2D(gridX, gridY);
|
||||
var cell = gridMap.GetCell(gridPos);
|
||||
var worldPos = gridMap.GridToWorld3D(gridPos, cell?.WorldPosition.Z ?? 0);
|
||||
|
||||
if (debugCount < 5) // 只输出前5个点的详细信息
|
||||
// 添加每条边的终点(避免重复)
|
||||
int debugCount = 0;
|
||||
foreach (var edge in astarPath.Edges)
|
||||
{
|
||||
LogManager.Info($"[路径转换] 节点{debugCount}: A*米坐标({edge.End.Position.X:F2}, {edge.End.Position.Y:F2}) -> 网格索引({gridPos.X}, {gridPos.Y}) -> 世界坐标({worldPos.X:F2}, {worldPos.Y:F2}, {worldPos.Z:F2})");
|
||||
// RoyT.AStar返回的Position是米坐标,需要转换为网格索引
|
||||
var gridX = (int)Math.Floor(edge.End.Position.X / cellSizeInMeters);
|
||||
var gridY = (int)Math.Floor(edge.End.Position.Y / cellSizeInMeters);
|
||||
var gridPos = new GridPoint2D(gridX, gridY);
|
||||
var cell = gridMap.GetCell(gridPos);
|
||||
var worldPos = gridMap.GridToWorld3D(gridPos, cell?.WorldPosition.Z ?? 0);
|
||||
|
||||
// 检查是否与上一个点重复(理论上A*不应该产生重复,但保险起见)
|
||||
bool isDuplicate = worldPath.Count > 0 &&
|
||||
Math.Abs(worldPath[worldPath.Count - 1].X - worldPos.X) < 0.01 &&
|
||||
Math.Abs(worldPath[worldPath.Count - 1].Y - worldPos.Y) < 0.01;
|
||||
|
||||
if (!isDuplicate)
|
||||
{
|
||||
if (debugCount < 5) // 只输出前5个点的详细信息
|
||||
{
|
||||
LogManager.Info($"[路径转换] 节点{debugCount}: A*米坐标({edge.End.Position.X:F2}, {edge.End.Position.Y:F2}) -> 网格索引({gridPos.X}, {gridPos.Y}) -> 世界坐标({worldPos.X:F2}, {worldPos.Y:F2}, {worldPos.Z:F2})");
|
||||
}
|
||||
worldPath.Add(worldPos);
|
||||
debugCount++;
|
||||
}
|
||||
else if (debugCount < 5)
|
||||
{
|
||||
LogManager.Debug($"[路径转换] 跳过重复点: 网格索引({gridPos.X}, {gridPos.Y}) -> 世界坐标({worldPos.X:F2}, {worldPos.Y:F2}, {worldPos.Z:F2})");
|
||||
}
|
||||
}
|
||||
debugCount++;
|
||||
|
||||
worldPath.Add(worldPos);
|
||||
}
|
||||
|
||||
return worldPath;
|
||||
@ -947,8 +996,9 @@ namespace NavisworksTransport.PathPlanning
|
||||
/// 优化路径(去除共线点等)
|
||||
/// </summary>
|
||||
/// <param name="pathResult">原始路径查找结果</param>
|
||||
/// <param name="gridMap">网格地图</param>
|
||||
/// <returns>优化后的路径查找结果</returns>
|
||||
private PathFindingResult OptimizePath(PathFindingResult pathResult)
|
||||
private PathFindingResult OptimizePath(PathFindingResult pathResult, GridMap gridMap = null)
|
||||
{
|
||||
if (pathResult == null || pathResult.PathPoints.Count <= 2)
|
||||
{
|
||||
@ -974,31 +1024,33 @@ namespace NavisworksTransport.PathPlanning
|
||||
tempRoute.Points.Add(pathPoint);
|
||||
}
|
||||
|
||||
// 🔥 暂时关闭路径优化功能,避免斜线问题
|
||||
// TODO: 后续重新设计更可靠的路径优化算法
|
||||
/*
|
||||
// 创建路径优化器并执行优化
|
||||
var optimizerConfig = new PathOptimizer.OptimizationConfig
|
||||
// 使用基于网格的路径优化算法
|
||||
if (gridMap != null)
|
||||
{
|
||||
EnableSimplification = true,
|
||||
CollinearTolerance = 0.01
|
||||
};
|
||||
var optimizer = new PathOptimizer(optimizerConfig);
|
||||
var optimizedRoute = optimizer.OptimizePath(tempRoute);
|
||||
// 创建路径优化器并执行优化
|
||||
var optimizerConfig = new PathOptimizer.OptimizationConfig
|
||||
{
|
||||
EnableSimplification = true,
|
||||
CollinearTolerance = 0.01
|
||||
};
|
||||
var optimizer = new PathOptimizer(optimizerConfig);
|
||||
var optimizedRoute = optimizer.OptimizePath(tempRoute, gridMap);
|
||||
|
||||
if (optimizedRoute != null && optimizedRoute.Points.Count > 0)
|
||||
{
|
||||
// 提取优化后的点位置
|
||||
var optimizedPoints = optimizedRoute.Points.Select(p => p.Position).ToList();
|
||||
|
||||
LogManager.Info($"[路径优化] 点数变化:{pathResult.PathPoints.Count} -> {optimizedPoints.Count}");
|
||||
|
||||
// 更新路径结果
|
||||
pathResult.PathPoints = optimizedPoints;
|
||||
if (optimizedRoute != null && optimizedRoute.Points.Count > 0)
|
||||
{
|
||||
// 提取优化后的点位置
|
||||
var optimizedPoints = optimizedRoute.Points.Select(p => p.Position).ToList();
|
||||
|
||||
LogManager.Info($"[路径优化] 点数变化:{pathResult.PathPoints.Count} -> {optimizedPoints.Count}");
|
||||
|
||||
// 更新路径结果
|
||||
pathResult.PathPoints = optimizedPoints;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Info($"[路径生成] 未提供网格地图,跳过路径优化,点数: {pathResult.PathPoints.Count}");
|
||||
}
|
||||
*/
|
||||
|
||||
LogManager.Info($"[路径生成] 使用原始A*算法结果,未进行路径优化,点数: {pathResult.PathPoints.Count}");
|
||||
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
@ -73,8 +73,9 @@ namespace NavisworksTransport.PathPlanning
|
||||
/// 优化路径的主入口
|
||||
/// </summary>
|
||||
/// <param name="originalPath">原始路径</param>
|
||||
/// <param name="gridMap">网格地图(用于网格坐标转换)</param>
|
||||
/// <returns>优化后的路径</returns>
|
||||
public PathRoute OptimizePath(PathRoute originalPath)
|
||||
public PathRoute OptimizePath(PathRoute originalPath, GridMap gridMap = null)
|
||||
{
|
||||
if (originalPath == null)
|
||||
{
|
||||
@ -98,12 +99,21 @@ namespace NavisworksTransport.PathPlanning
|
||||
// 复制路径点进行优化
|
||||
var optimizedPoints = new List<PathPoint>(originalPath.Points);
|
||||
|
||||
// 1. 基础简化 - 去除共线点
|
||||
// 1. 基础简化 - 使用基于网格的算法
|
||||
if (_config.EnableSimplification)
|
||||
{
|
||||
LogManager.Info("[路径优化] 执行共线点简化");
|
||||
optimizedPoints = SimplifyCollinearPoints(optimizedPoints);
|
||||
LogManager.Info($"[路径优化] 简化完成,点数:{originalCount} -> {optimizedPoints.Count}");
|
||||
if (gridMap != null)
|
||||
{
|
||||
LogManager.Info("[路径优化] 执行基于网格的路径简化");
|
||||
optimizedPoints = SimplifyGridBasedPath(optimizedPoints, gridMap);
|
||||
LogManager.Info($"[路径优化] 简化完成,点数:{originalCount} -> {optimizedPoints.Count}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Info("[路径优化] 执行传统共线点简化");
|
||||
optimizedPoints = SimplifyCollinearPoints(optimizedPoints);
|
||||
LogManager.Info($"[路径优化] 简化完成,点数:{originalCount} -> {optimizedPoints.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 未来扩展:路径平滑
|
||||
@ -135,6 +145,116 @@ namespace NavisworksTransport.PathPlanning
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于网格坐标的路径简化算法
|
||||
/// </summary>
|
||||
/// <param name="points">原始路径点列表</param>
|
||||
/// <param name="gridMap">网格地图</param>
|
||||
/// <returns>简化后的路径点列表</returns>
|
||||
private List<PathPoint> SimplifyGridBasedPath(List<PathPoint> points, GridMap gridMap)
|
||||
{
|
||||
if (points == null || points.Count < 3)
|
||||
return points;
|
||||
|
||||
// 转换为网格坐标
|
||||
var worldPath = points.Select(p => p.Position).ToList();
|
||||
var gridPath = worldPath.Select(p => gridMap.WorldToGrid(p)).ToList();
|
||||
|
||||
// 检查重复点(调试用)
|
||||
int duplicateCount = 0;
|
||||
for (int i = 1; i < gridPath.Count; i++)
|
||||
{
|
||||
if (gridPath[i].Equals(gridPath[i-1]))
|
||||
{
|
||||
duplicateCount++;
|
||||
LogManager.Warning($"[路径优化] 发现重复网格点:索引 {i-1} 和 {i} 都是 ({gridPath[i].X}, {gridPath[i].Y})");
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateCount > 0)
|
||||
{
|
||||
LogManager.Warning($"[路径优化] 总共发现 {duplicateCount} 个重复网格点,开始去重处理");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Info("[路径优化] 未发现重复网格点,A*算法输出正常");
|
||||
}
|
||||
|
||||
// 步骤1:先去除重复点
|
||||
var dedupedGridPath = new List<GridPoint2D>();
|
||||
var dedupedPoints = new List<PathPoint>();
|
||||
for (int i = 0; i < gridPath.Count; i++)
|
||||
{
|
||||
if (i == 0 || !gridPath[i].Equals(gridPath[i-1]))
|
||||
{
|
||||
dedupedGridPath.Add(gridPath[i]);
|
||||
dedupedPoints.Add(points[i]);
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[路径优化] 去重完成:{gridPath.Count} -> {dedupedGridPath.Count} 个点");
|
||||
|
||||
if (dedupedGridPath.Count < 3)
|
||||
{
|
||||
LogManager.Info("[路径优化] 去重后点数不足3个,直接返回");
|
||||
return dedupedPoints;
|
||||
}
|
||||
|
||||
// 步骤2:基于去重后的网格路径进行方向优化
|
||||
var simplified = new List<GridPoint2D> { dedupedGridPath[0] };
|
||||
|
||||
// 获取初始方向(归一化)
|
||||
int initialDx = dedupedGridPath[1].X - dedupedGridPath[0].X;
|
||||
int initialDy = dedupedGridPath[1].Y - dedupedGridPath[0].Y;
|
||||
GridPoint2D prevDirection = new GridPoint2D(
|
||||
initialDx == 0 ? 0 : Math.Sign(initialDx),
|
||||
initialDy == 0 ? 0 : Math.Sign(initialDy)
|
||||
);
|
||||
|
||||
LogManager.Debug($"[路径优化] 初始方向:({prevDirection.X}, {prevDirection.Y})");
|
||||
|
||||
// 简化路径:只保留转弯点(使用归一化方向)
|
||||
for (int i = 2; i < dedupedGridPath.Count; i++)
|
||||
{
|
||||
// 计算当前线段的位移
|
||||
int dx = dedupedGridPath[i].X - dedupedGridPath[i-1].X;
|
||||
int dy = dedupedGridPath[i].Y - dedupedGridPath[i-1].Y;
|
||||
|
||||
// 归一化为单位方向向量 (-1, 0, 1)
|
||||
var currentDirection = new GridPoint2D(
|
||||
dx == 0 ? 0 : Math.Sign(dx),
|
||||
dy == 0 ? 0 : Math.Sign(dy)
|
||||
);
|
||||
|
||||
// 只有真正的方向改变时才保留转弯点
|
||||
if (!currentDirection.Equals(prevDirection))
|
||||
{
|
||||
simplified.Add(dedupedGridPath[i - 1]);
|
||||
LogManager.Debug($"[路径优化] 检测到转弯:位置({dedupedGridPath[i-1].X},{dedupedGridPath[i-1].Y}),方向从({prevDirection.X},{prevDirection.Y})变为({currentDirection.X},{currentDirection.Y})");
|
||||
prevDirection = currentDirection;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加终点
|
||||
simplified.Add(dedupedGridPath[dedupedGridPath.Count - 1]);
|
||||
|
||||
LogManager.Info($"[网格路径优化] 最终优化完成:{gridPath.Count} -> {dedupedGridPath.Count}(去重) -> {simplified.Count}(简化) 个点");
|
||||
|
||||
// 步骤3:转换回PathPoint格式
|
||||
var simplifiedPoints = new List<PathPoint>();
|
||||
foreach (var gridPoint in simplified)
|
||||
{
|
||||
// 在去重后的路径中找到对应的点
|
||||
int originalIndex = dedupedGridPath.FindIndex(g => g.Equals(gridPoint));
|
||||
if (originalIndex >= 0)
|
||||
{
|
||||
simplifiedPoints.Add(dedupedPoints[originalIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
return simplifiedPoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简化共线点(去除直线上的冗余点)
|
||||
/// </summary>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user