279 lines
7.2 KiB
Markdown
279 lines
7.2 KiB
Markdown
# 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`
|