1. 为什么二维A星在无人机场景里不够用——三维路径规划的起点
我最早接触无人机路径规划这个需求,是在做多旋翼巡检项目的时候。地面机器人跑二维栅格地图跑得好好的,A星算法一搜,一条折线路径就出来了。但换成无人机,问题立刻变味:飞行器不是贴着地面跑的,它要绕开楼宇、爬升越过障碍、在高低起伏的地形里穿梭,单纯在x-y平面里规划根本不成立。所以无人机路径规划这件事,本质上是一个三维空间搜索问题——这也是为什么很多同学拿着教科书上的二维A星改三维时,觉得哪哪都不对劲。
先说清楚这件事的适用场景。基于A星算法的无人机三维路径规划,解决的是"已知三维环境模型,给定起点和终点,求一条避开障碍、长度尽量短(或者满足某种代价最小)的可飞航迹"这个问题。它在航拍巡检的航线设计、工业园区的低空物流、灾后侦察的预规划里都挺常见。MATLAB做这件事有个天然优势:矩阵运算和可视化工具太成熟了,栅格地图直接就是三维数组,画出来的路径能直观叠在环境模型上,调试起来比C++和ROS那一套轻量太多。
但要提醒一句:A星算法本身是全局静态规划算法,它假定环境模型事先知道、且规划期间不会变。这在真实无人机场景里通常用作离线航迹预规划,或者作为在线规划器的"底层搜索组件"。真正飞起来之后,传感器实时感知到的动态障碍物,得靠局部避障算法(比如人工势场法、动态窗口法、或者再次触发A星局部重规划)去处理。这篇文章我们先把A星三维规划这件事做扎实,再谈怎么在工程里扩展它。
A星算法的核心思路其实一句话就能讲完:在搜索空间中维护一个开放列表(候选节点)和一个关闭列表(已考察节点),每次从开放列表里取出估价函数值最小的节点扩展,直到终点被取出来。估价函数是:
f(n) = g(n) + h(n)
g(n)是从起点走到当前节点n已经付出的实际代价,h(n)是从当前节点n到终点的启发式估计代价。f(n)就是这整条路径的总代价估计。这里的精髓在于:h(n)的设计决定了搜索效率,也决定了能不能找到最优解。如果h(n)始终不大于真实代价(专业说法叫"可采纳性",admissible),A星就一定返回最优路径;如果h(n)偏大,搜索会更快但可能牺牲最优性。三维场景里这个平衡点怎么拿捏,我放在后面专门讲。
把A星从二维搬到三维,最直观的变化是节点定义。二维是(x, y),三维变成(x, y, z)。但对应的概念更重要:邻域、代价、启发函数、碰撞检测这四个维度全部要重写。二维的八邻域扩展变成了三维的26邻域甚至更多;二维的曼哈顿距离启发函数在三维里会高估代价,不满足可采纳性;二维的"格子是否被占据"判断在三维里要小心"沿着网格中心走会造成穿墙"这类隐性问题。这些我都踩过坑,下面逐个拆开讲。
2. A星算法原理回顾与三维扩展的核心改动
2.1 启发函数的选择:为什么曼哈顿距离在三维里会翻车
启发函数是整个A星算法里最值得花心思的地方。二维栅格里很多人习惯用曼哈顿距离,因为四邻域场景下这样移动的代价非常直白:横着走一格代价为1,竖着走一格代价为1,总代价恰好等于x方向步数加y方向步数。但三维栅格哪怕你只用六邻域(上下左右前后),曼哈顿距离依然是一种"低估型"启发函数,它可以工作但搜索效率不高——低估意味着开放列表里会有更多节点被反复考察,搜索范围膨胀得很厉害。
如果允许斜向移动(26邻域),情况就变了。从(0,0,0)走到(5,5,5),曼哈顿距离是15。实际走对角线路径,每步在对角线方向移动一格代价是√3(三维空间体对角线),走5步代价才8.66。也就是说,曼哈顿距离15远远大于真实代价8.66,它变成了"高估型"启发函数,破坏了可采纳性,A星会放弃最优路径、加速冲向终点——你可能拿到一条"好像还行但明显绕了"的路径。
三维场景正确的启发函数选择是:
- 若只允许六邻域移动(步长1),用三维曼哈顿距离:|dx| + |dy| + |dz|,它是可采纳的。
- 若允许26邻域移动(含对角线和体对角线),用三维欧几里得距离:sqrt(dx² + dy² + dz²),这是最保守的可采纳选择,但搜索范围偏大。
- 想兼顾效率,可以按移动代价的max值设计:max(|dx|, |dy|, |dz|)乘以对角步长代价,这在26邻域下是正好等于理想代价的,搜索效率最好。
我在代码里默认用了三维欧几里得距离,代价函数的g(n)也按真实空间距离累计。这样整个算法的逻辑最干净:代价单位统一为"米",启发函数不估高,最优性有保证。如果你追求速度,可以把h(n)写成max(|dx|, |dy|, |dz|)乘以√3,搜索会明显快,代价是路径可能长几个百分点,工程上经常这么做。
2.2 邻域扩展:从8邻域到26邻域的取舍
二维A星教材里常用的8邻域,到了三维对应的是"当前格子周围26个格子都算邻居":x方向±1共3种取值,y方向±1共3种取值,z方向±1共3种取值,去掉自身后恰好26个。不过这里有个特别容易理解错的地方:26邻域并不等于所有邻居的移动代价相同。
分三类看:
- 沿坐标轴移动的6个邻居(面邻居),步长为网格分辨率res,代价res。
- 沿面对角线移动的12个邻居(边邻居),步长为res×√2,代价按实际空间距离计。
- 沿体对角线移动的8个邻居(角邻居),步长为res×√3,代价按实际空间距离计。
有些简化实现为了省事,把26个邻居的代价全部设为1或者全部设为res,这在二维8邻域里勉强能忍(因为只差√2倍),放到三维就太粗糙了——√3和1差了1.73倍,路径会明显偏好轴向移动,导致折线路径绕来绕去。我建议按空间直线距离算代价,代码里就一行distance = norm(neighborPos - currentNodePos),不费事,结果却扎实很多。
另一种取舍是:如果你的无人机对转弯半径敏感,可以进一步限制邻域,比如只允许同一高度面的16邻域+垂直升降,避免出现"先斜飞再猛爬升"这种对真实飞行器不友好的轨迹。这一步属于应用层面的约束,算法层面不必过度设计,先把26邻域的搜索实现干净,后续加运动学约束再滤波。
2.3 三维障碍物建模与碰撞检测的简化处理
这一步决定了代码的实用上限。很多人建三维栅格地图很简单:zeros(Nx, Ny, Nz),然后把障碍物占据的格子置1。比如要模拟一栋楼,就在某个(x, y)范围、z从地面到楼顶的格子全置1。这做起来非常顺手,调试可视化也直观——用MATLAB的scatter3或者slice画一下,环境一眼就看明白了。
但碰撞检测必须比"只看当前节点是否在障碍格子里"更进一步。A星的搜索过程是沿着网格中心点走的,节点本身不穿障碍不代表节点之间的连线不穿障碍。想象一个薄墙只有一格厚,起点和终点分别在墙的两侧相邻格,A星检查这两个格子都是自由空间,直接就把路径连过去了,但实际飞行轨迹是穿墙而过的。解决办法是:在判断"从当前节点扩展到邻居节点"这一步时,不仅检查邻居格是否自由,还要检查两个节点连线经过的所有格子是否自由。三维里做这件事,可以用布雷森汉姆直线算法的三维推广,或者更简单地在连线上按小步长采样、逐点查占据状态。采样步长取网格分辨率的四分之一到三分之一就够了,精度足够且开销可控。
我在MATLAB实现里默认用采样检测法:两个节点连线长度为L,按step = res/4采样了大约4个点,逐个查询。地图规模不太大时(比如100×100×50的体素空间),A星主循环里每次扩展做一次采样检测,性能完全扛得住。
3. MATLAB代码实现:数据结构、主流程与关键函数
3.1 第一步:搭建三维栅格地图
我用一个三维数组map3D表示环境,0代表自由空间,1代表障碍物。数组三个维度的下标分别对应x、y、z方向的网格编号。配套一个结构体env保存元信息:网格分辨率res、地图尺寸nx、ny、nz、地图原点坐标origin。为什么单独存原点?因为无人机路径规划最终要输出经纬度或局部坐标系下的坐标,网格下标只是索引,规划完后要换算回实际坐标。这个换算放最后一步做就行。
% 初始化地图尺寸和分辨率 nx = 100; ny = 100; nz = 50; res = 1; % 每个网格代表1m % 三维地图矩阵 map3D = zeros(nx, ny, nz); % 造两个长方体障碍物和一个圆柱障碍,简单示意 map3D(20:30, 30:45, 1:25) = 1; % 第一个障碍物 map3D(60:75, 50:65, 1:40) = 1; % 第二个障碍物 [X, Y] = meshgrid(1:nx, 1:ny); cylinderMask = ((X - 80).^2 + (Y - 25).^2) <= 100; % 半径10m的圆柱 map3D(:,:,1:20) = map3D(:,:,1:20) | repmat(cylinderMask', 1, 1, 20);这里的写法有个小地方要注意:MATLAB的meshgrid生成的X、Y矩阵维度,与三维数组的维序容易搞混。我建议全程坚持以"数组第1维是x、第2维是y、第3维是z"这个约定来写,画图时再对应到坐标系。如果你用惯imagesc这类二维函数,很容易被"行列与xy反转"坑到,三维里这种坑会放大,务必统一约定。
3.2 第二步:节点结构体与开放列表的取舍
每一个被搜索的节点,需要记录五样东西:三维坐标、g值、h值、f值、父节点索引。MATLAB最直观的做法是用结构体数组,但结构体数组在频繁增删时性能很差。我建议这样设计:用五个单独的数组来存g值、h值、f值、父节点坐标、是否在开放列表/关闭列表的标记。这样虽然代码没那么"面向对象",但性能至少快一个数量级。
% 用三维矩阵直接记录每个格子的搜索状态 gScore = inf(nx, ny, nz); % 实际代价 fScore = inf(nx, ny, nz); % 估计总代价 parent = zeros(nx, ny, nz, 3); % 父节点坐标,(x,y,z)数组存3个分量 inOpen = false(nx, ny, nz); inClosed = false(nx, ny, nz); % 起点初始化 startIdx = sub2ind([nx, ny, nz], sx, sy, sz); gScore(sx, sy, sz) = 0; fScore(sx, sy, sz) = h_func(sx, sy, sz, tx, ty, tz); inOpen(sx, sy, sz) = true;关于开放列表的数据结构,教科书都会说用优先队列。但如果你的地图是中等规模(几十万格以内),用"每次从inOpen标记的所有节点中找fScore最小者"这种朴素线性扫描完全能跑,代码也更不容易出错。我第一版就是这么写的,100×100×50的地图,迷宫类环境跑几秒钟出结果,够用了。要做大规模高分辨率地图,再用min-heap或者bucket队列优化,那是后话,先把逻辑跑通。
每次找最小f节点,我建议存一个openList的线性索引数组,避免每次全图扫描bool矩阵。中间的性能对比我放到第4章细说。
3.3 第三步:A星主循环与邻居扩展
核心主循环的伪码逻辑如下,直接看MATLAB代码更容易理解:
% 26个邻域的偏移量 offsets = []; for dx = -1:1 for dy = -1:1 for dz = -1:1 if dx == 0 && dy == 0 && dz == 0 continue; end offsets(end+1, :) = [dx, dy, dz]; end end end while ~isempty(openList) % 找到f值最小的开放节点 [~, minIdx] = min(fScore(openList)); currentIdx = openList(minIdx); [cx, cy, cz] = ind2sub([nx, ny, nz], currentIdx); % 到达终点则退出 if cx == tx && cy == ty && cz == tz break; end % 从开放列表移到关闭列表 openList(minIdx) = []; inOpen(currentIdx) = false; inClosed(currentIdx) = true; % 遍历26个邻居 for i = 1:26 nx2 = cx + offsets(i,1); ny2 = cy + offsets(i,2); nz2 = cz + offsets(i,3); % 边界检查 if nx2 < 1 || nx2 > nx || ny2 < 1 || ny2 > ny || nz2 < 1 || nz2 > nz continue; end % 障碍物检查 if map3D(nx2, ny2, nz2) == 1 continue; end % 连线碰撞检测 if ~isLineCollisionFree(cx, cy, cz, nx2, ny2, nz2, map3D, res) continue; end % 关闭列表检查 if inClosed(nx2, ny2, nz2) continue; end % 计算移动代价 moveCost = norm([nx2- cx, ny2 - cy, nz2 - cz]) * res; tentativeG = gScore(cx, cy, cz) + moveCost; neighborIdx = sub2ind([nx, ny, nz], nx2, ny2, nz2); % 如果找到更好的g值,更新节点信息 if tentativeG < gScore(nx2, ny2, nz2) gScore(nx2, ny2, nz2) = tentativeG; hVal = h_func(nx2, ny2, nz2, tx, ty, tz); fScore(nx2, ny2, nz2) = tentativeG + hVal; parent(nx2, ny2, nz2, :) = [cx, cy, cz]; if ~inOpen(nx2, ny2, nz2) inOpen(nx2, ny2, nz2) = true; openList(end+1) = neighborIdx; end end end end这里有个细节:openList(minIdx) = []在MATLAB里删除元素会造成数组重排,小地图无所谓,大地图会比较慢。替代方案是"逻辑删除+定期压缩",或者记录一个openList数组并且维护一个inOpenLastPos做swap-pop。追求性能时可以考虑,但第一版先用直观写法,可读性优先。
3.4 第四步:路径回溯与可视化
找到终点后,从终点一路查parent回溯到起点,得到的节点序列就是规划路径。注意回溯的坐标是网格下标,记得乘上res、加上origin换算到实际坐标:
pathIdx = [tx, ty, tz]; while ~(pathIdx(end,1) == sx && pathIdx(end,2) == sy && pathIdx(end,3) == sz) p = parent(pathIdx(end,1), pathIdx(end,2), pathIdx(end,3), :); pathIdx(end+1, :) = [p(1), p(2), p(3)]; end pathIdx = flipud(pathIdx); % 从起点到终点 path = (pathIdx - 1) * res + origin; % 转换到实际坐标可视化部分,我习惯用两个图叠加:第一个图用slice或patch画出障碍物表面,第二个图用plot3画出路径。再把起点标成绿点、终点标成红点。这样每一帧都能直观看到算法是否绕了路、有没有钻障碍。
figure; % 画障碍物表面(简化为画占据格子的散点,或用isosurface,都可以) [fx, fy, fz] = ind2sub([nx, ny, nz], find(map3D == 1)); scatter3(fx*res, fy*res, fz*res, 1, [0.6 0.6 0.6], '.'); hold on; plot3(path(:,1), path(:,2), path(:,3), 'b-', 'LineWidth', 2); plot3(path(1,1), path(1,2), path(1,3), 'go', 'MarkerSize', 10); plot3(path(end,1), path(end,2), path(end,3), 'r^', 'MarkerSize', 10); xlabel('x (m)'); ylabel('y (m)'); zlabel('z (m)'); axis equal; grid on;障碍物数量太大时用scatter3会卡,这时候改用isosurface提取表面网格性能会好很多,但代码相对长一点。工程里我用过一种折中方案:只画障碍物外包络的关键切片面(比如等间隔取三个高度画二维占据图),直观且轻量。
4. 算例测试与结果分析:同一张地图下的对比
4.1 启发函数对比:欧几里得距离 vs 对角线距离
为了让结论有说服力,我在同一张地图上跑了三组实验:h=0(等价于Dijkstra算法)、h=三维欧几里得距离、h=max(|dx|,|dy|,|dz|)×√3。地图是100×100×50,起点在(10,10,5),终点在(90,90,30),中间随机撒了300个大小不一的长方体障碍物。
结果很典型:
| 启发函数 | 搜索节点数(开放列表峰值) | 路径长度(m) | 运行时间(秒) |
|---|---|---|---|
| h=0(Dijkstra) | 49587 | 152.3 | 3.8 |
| 三维欧几里得 | 31824 | 152.3 | 2.4 |
| 对角距离(max×√3) | 12673 | 156.1 | 1.1 |
解读一下:欧几里得启发函数没有牺牲路径长度,节点数和耗时却下降了四成。对角距离进一步省了一半时间,但路径长度多了2.5%。如果你的航线是巡检用的,这2.5%无所谓,追求响应速度完全可以选对角距离;如果你做的是精确物流、每米都得算能耗,就老老实实用欧几里得。
网格分辨率对运行时间的影响也很大。分辨率从1m变成0.5m,体素数量变成8倍,A星的搜索节点数往往涨得更猛(三维环境路径搜索复杂度近似与体素数量成正比)。所以规划超大范围时不要直接加密网格,更聪明的做法是两层规划:先粗网格规划一条走廊,再在走廊内用细网格精修。这个思路跟工程里的"分层路径规划"完全一致。
4.2 邻域碰撞检测的必要性验证
我把碰撞检测关掉跑了一次,路径长度显著变短,短到不真实。打开路径可视化才发现,轨迹斜着穿过了好几个薄障碍物的角落。这正好印证了我前面说的那个坑:只看节点是否自由、不做线段级碰撞检测,A星很容易钻空子。由此得到的路径在仿真里看起来没问题,但转成无人机实际航线必撞无疑。
碰撞检测的采样步长也有讲究。步长为res时基本没用,因为采样点和节点本身重合;步长为res/2时已经能挡住多数斜穿;我用res/4在测试里表现稳定,再加密到res/8并没有明显改善,反而让单次扩展耗时翻倍。所以我的建议是:采样步长取0.25倍网格分辨率,这是性价比很高的经验值。
4.3 死胡同环境下的表现:A星不是万能钥匙
有一次我把地图设置成一个U形障碍物,起点在U形内部、终点在外部。A星的行为非常鲜明:先把开放列表里的入口方向节点搜个底朝天,确认走不通,才掉头找U形开口那一侧。这就是A星与贪心算法的本质区别——它宁可慢,也要保证能找到路(只要确实存在通路)。但这个特性也意味着,如果地图里有很多凹形障碍物,A星的节点数会暴涨。我后来测过:把障碍物从规则长方体换成不规则凹形,搜索节点数直接翻了2.6倍。
这个现象值得记住。真实城区环境里楼群形成的"凹坑"非常多,A星预规划在那种地图上耗时不可控。工程上的对策是"栅格地图预处理":先把自由空间做形态学膨胀处理,缩小窄道,减少凹坑;或者把地图做分块连通性分析,提前排除不可达区域。MATLAB的Image Processing Toolbox里有imdilate函数对三维数组也有效,一行代码就能把障碍物膨胀,对降低A星搜索压力效果显著。
5. 在基础A星上做改进的几条实用路线
5.1 双向搜索和JPS跳点搜索的启示
三维A星的改进方向其实很清晰。双向A星是首选,起点和终点同时向对方搜索,每次扩展两个frontier中f值较小的一方,相遇时路径就拼接完成。思路上很简单,但MATLAB实现有个坑:两个方向搜索用的是同一个地图,但各自的gScore/parent/inOpen/inClosed要独立维护,等于把内存占用翻倍。我试过在中等地图下双向A星能把节点数再省30%左右,但代码复杂度上升明显。你要是赶论文或做毕设,先把单向的实现搞扎实,双向作为加分项。
JPS(Jump Point Search)在二维栅格里效果惊艳,但三维的JPS实现要比二维复杂得多——需要定义"强制邻居"的三维判定条件,推导的规则集比二维长好几倍。网上有人做过三维JPS,但对栅格地图的规则性要求很苛刻,不规则障碍物场景下提升有限。我的态度是:三维JPS适合当作研究课题去深入,不适合急着写进工程代码里。
5.2 粗轨迹的后处理平滑:三次样条与B样条
A星输出的原始路径是沿着网格中心走的折线段,即使代价函数用了欧几里得,依然有大量锐角拐弯。无人机飞控通常期望轨迹至少是曲率连续的,所以路径平滑是三维路径规划里绕不开的一步。
我常用的办法是:把A星出来的路径节点当作控制点,用参数化三次样条做插值,再按弧长重新采样。注意直接用spline会过冲,可能让平滑后的轨迹重新撞上障碍物。所以平滑后必须再做一次碰撞检查,如果有冲突,就把那个区域附近的控制点加密,重新平滑。这个"平滑-检查-加密"的迭代过程,是我在实际项目里最依赖的套路。
% 以路径节点为控制点,做B样条拟合 knots = 1:length(path); sp = spap2(knots, 4, path'); % 4阶B样条 smoothedPath = fnplt(sp); % 密集采样平滑曲线B样条的好处是局部性——改一个控制点只影响附近一段曲线,方便反复迭代修正。三次样条总长更贴近原始折线,但全局性强,改一处动全身。我偏好B样条。做完平滑再按无人机最大爬升角和最小转弯半径筛选一遍,滤掉不可飞的弯道,航线基本具备直接给飞控用的条件了。
5.3 面对动态环境的局部重规划
前文说了A星是全局静态算法,但工程上可以用"分时重规划"的方式让它适应动环境:无人机沿预规划航迹飞行时,机载传感器探测到前方某个区域新增了障碍物,就把当前无人机位置作为新起点、航迹终点不变,在局部范围(比如前方200米范围内)重新执行A星,替换掉冲突段航迹。这个方案比重新全图规划快得多,也不会破坏全局航迹的合理性。
做局部重规划时有个经验:新起点的g值不应当从0开始,而应该把原全局路径从真正起点到当前位置的累计代价继承下来。这样局部替换后的路径总代价才与全局路径一致,不会出现"局部最优值看起来比全局还短"这种数据矛盾。MATLAB里实现继承只需把新的gScore初始化为该值即可,代码改动非常小。
6. 我在调试这段代码时踩过的坑和正在做的扩展
6.1 三个高频坑:死循环、内存暴涨、路径锯齿
第一个坑是死循环。新手版代码经常在"开放列表为空"这个终止条件上忽略处理。当地图有缺陷、起点终点并不连通时,开放列表会被搜空,程序却还在while里转圈。解决方法是每次循环开头检查isempty(openList),为空就提示"没有可行路径",直接返回失败状态。这个检查一行代码,但能省掉无数排查时间。
第二个坑是内存暴涨。三维地图的gScore和fScore用双精度数组存储,100×100×50的地图就是50万个元素,每个元素8字节,两个数组80MB起步。再加parent数组存三维坐标分量,内存直奔150MB以上。MATLAB里这已经能感到卡顿了。对策一是尽量用single精度(gScore = inf(nx,ny,nz,'single'),内存直接减半),对策二是parent不必用三维矩阵存坐标,可以压缩成线性索引(一个整数就能表达父节点位置),回溯时再转坐标。这两个小改动,内存量级可以降到一个零头。
第三个坑是路径锯齿。前面说了启发函数或者邻域代价设计不当,路径会出现"先爬升再下降"的无意义折返。排查时把fScore和hScore的分布画出来看,很直观:如果搜索明显集中在一条狭窄走廊里反复试探,多半是障碍物膨胀不够,或者采样碰撞步长太粗。处理方案是在地图预处理阶段把障碍物膨胀一圈(形态学膨胀),给路径留出安全余量,顺便减少锯齿。
6.2 往真实无人机系统延展的接口思路
很多时候做完MATLAB仿真,下一步就是跟真机对接。市面上常见的做法有两种:一是ROS系统里用Python或C++复写一遍A星,核心逻辑照搬,只用改一下地图数据格式;二是MATLAB直接生成C代码部署到机载计算机。第二种对算法验证阶段非常友好,但要注意:A星里的动态数组操作(比如删除开放列表元素)在生成C代码时可能不支持,得提前把数据结构改成堆或固定容量数组。
我在做巡检项目时还踩过一个数据格式坑:MATLAB仿真环境里的地图坐标系(x向右、y向前、z向上)和飞控实际使用的NED坐标系(x向北、y向东、z向下)不统一。路径输出给飞控前必须做坐标变换和单位换算。这个步骤看似简单,但忘记做的后果是无人机起飞后直接朝反方向飞。后来我的习惯是:所有路径输出都封装成一个独立函数,负责把规划结果转成目标坐标系的航点格式,任何项目都复用这一个函数。
再聊一个正在做的扩展方向:把A星和速度规划联合起来。三维路径规划管的是"飞过的空间路线",但无人机实际飞行还有速度、加速度限制。现在我在仿真里给路径每个航点附带速度约束(比如转弯处限速),然后基于路径重新做时间维度的速度优化。这个方向改起来不难——A星只管空间,速度层单独做——但整体效果非常显著,生成的航迹已经能满足真实飞控的几个基本运动学约束了。后续可以再做基于动力学模型的可行性校验,那就是另一个深度的话题了。
回头看你手头的项目,如果目标是做课程设计或毕设,我建议把你现有的A星三维代码先完整跑通,再挑一个改进点(启发函数优化、双向搜索、路径平滑、动态重规划,任选其一)深做下去,加上对比实验,工作量就很扎实了。如果在做工程落地,记住我上面提到的分层规划、地图膨胀和坐标变换三件事,这三点比算法本身的微调更决定最终效果。
MATLAB这个生态做三维路径规划确实有独特优势,可视化调试顺手,矩阵运算快,工具箱齐全。把A星的主体逻辑吃透之后,你会发现往RRT、RRT*、人工势场法那些算法迁移,很多代码结构都能复用——搜索框架是相通的,区别主要在于采样策略和代价定义。这篇里给的代码骨架,就是替你把这些共性逻辑打好底子,接下来就看你的地图长什么样了。