JumpPoint Search算法(JPS算法)
JPS算法全称为Jump PointSearch,也就是跳点算法,可以视为A*算法的一种改进算法,它保留了A*算法的主体框架,区别在于:A*算法是将当前节点的所有未访问邻居节点加入openlist,而JPS则是使用一些方法将有“价值”的节点加入openlist,具体方法就是本文的内容。
了解A*算法再来看JPS算法将会事半功倍
原理: 如果p经过x点到达n, 比不经过x点到达n的距离代价都小, 那么n是x点的强迫邻居, x为跳点,
x的父亲为p
开销
公式: f = gCost + hCost
gCost: 开始点到当前点的实际距离代价
hCos: 当前点到目标点的估计距离代价
f: 总开销
二.方向(8个方向)

分别为右, 右上, 上, 左上, 左, 左下, 下, 右下
图一:
p为openList找出来行走的点

因为x点上方有阻碍物, 并且右上方有强迫邻居x1, 所以x为跳点, x的(方向)向右, x的(父亲)为p, 返回判断是否加入openList, 重新在openList找行走的点
图二:

因为p点右上方和右下方都有强迫邻居,
1. p向右走, x3点有邻居, 所以x3是跳点
所以x1, x2, x3都为跳点, (方向)分别是右上, 右下, 右, (父亲)都为p点
图三:

图四:

如图四, 单个点分析
1.因为p为斜方向的点, 所以有三个方向要走分别是上(l点开始), 右上(d点开始), 右(r点开始),
2.如果d点有强迫邻居返回d点, d点位跳点, d点的(方向)为右上, (父亲)为p, 返回, 如果没有进入第三步
3.那么就需要d点的上方向和右方向判断是否有跳点, 如果有, d点为跳点, d点的方向为右上, (父亲)为p, 返回, 如果没有进入第四步
4.d点向右上方向, 前进一步到达d2, 重复2步骤
如图三
因为p点上, 右上和右都有邻居, 所以p1(方向左上), p3(方向右上), p6(方向右下)都为跳点, (父亲)都为p点, 返回判断是否加入openList 重新在openList找行走的点
图五:

1.因为p点上方向, 左方向堵了, 所以只有p1点左上方向可以做跳点判断
因为p点有两个邻居p2, p3, 所以p1为跳点, 方向左上, 父亲为p, 返回判断是否加入openList, 重新在openList找行走的点
图六:

因为p点有邻居p1, p2, 所以p1(方向左下), p2(方向右上), 都为跳点, 父亲为p点
1.因为p为斜方向的点, 所以有三个方向要走分别是上,左上, 左
2.因为p5有邻居, 所以p5为跳点(方向左)
2.因为p3左方向p4有邻居, 所以p3为跳点(方向左上)
所以p1(方向左下), p2(方向右上), p5(方向左), p3(方向左上)都为跳点, (父亲)都为p点
代码:
//enum 方向 右 右上 上 左上 左 左下 下 右下
const int32 DirX[8] = { 1, 1, 0, -1, -1, -1, 0, 1 };
const int32 DirY[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };
1.跳点算法获取最短的开销路径:
void JpsFind::CalcPath()
{
while( !openL_.empty() )
{
NodePtr current = openL_.front();
if( !current )
return;
if( endNode_->x == current->x && endNode_->y == current->y )
{
endNode_ = current;
return;
}
map_mgr_.TryViewEveryPointPath(current);//(忽略,该函数用于展示轨迹)
openL_.pop_front();
closeL_.insert((current->x<<16)|current->y);
std::vector<NodePtr> nL = GetSuccessJumpNodeL(current);
if( !nL.empty() )
{
for( const auto& m : nL )
{
if( FindByCloseL(m) )//是否在closeList
continue;
NodePtr is_open = FindByOpenL(m);
int32 newGcost = current->gCost + GetDistance(current->x, current->y, m->x, m->y);
if( newGcost < m->gCost || !is_open )
{
m->gCost = newGcost;
m->hCost = GetDistance(m->x, m->y, endNode_->x, endNode_->y);
m->parent = current;//设置父亲归属
if( !is_open )
{
AddToOpenL(m);
}
}
}
}
}
}
获取当前点的有关跳点列表
std::vector<NodePtr> JpsFind::GetSuccessJumpNodeL( const NodePtr& node )
{
std::vector<NodePtr> nL = GetForceNodeL(node);//强迫邻居
for( const auto& m : GetNeighbours(node) )//当前点可以行走的方向
{
NodePtr jps_node = Jump(m, m->dir);//递归行走点, 返回跳点
if( !jps_node )
continue;
nL.push_back(jps_node);
}
return nL;
}
递归返回对应跳点
NodePtr JpsFind::Jump( const NodePtr& node, int32 dir )
{
map_mgr_.TryViewEveryPointPath(node);//(忽略,该函数用于展示轨迹)
if( !node || !Walk(node->x, node->y) )//是否可行走
return NodePtr();
if( node->x == endNode_->x && node->y == endNode_->y )
{
jpsN_ = true;
return node;
}
jpsN_ = false;//用于判断斜线方向是否有跳点
if( dir%2 == 1 )
{
return JumpInclined(node, dir);//斜线方向
}
return JumpStr(node, dir);
}
斜线方向递归
NodePtr JpsFind::JumpInclined( const NodePtr& node, int32 dir )
{
//斜方向
int32 l_dir = (dir + 3) % 8;
int32 r_dir = (dir + 5) % 8;
//斜线交叉方向是否墙
bool l_force_ = Walk(node->x + DirX[l_dir], node->y + DirY[l_dir]);
bool r_force_ = Walk(node->x + DirX[r_dir], node->y + DirY[r_dir]);
if( !l_force_ || !r_force_ )
{//斜线方向有强迫邻居
l_dir = (dir + 2) % 8;
r_dir = (dir + 6) % 8;
if( !l_force_ && Walk(node->x + DirX[l_dir], node->y + DirY[l_dir]) )
{
jpsN_ = true;
return node;
}
if( !r_force_ && Walk(node->x + DirX[r_dir], node->y + DirY[r_dir]) )
{
jpsN_ = true;
return node;
}
}
//斜线两个方向是否有跳点
l_dir = (dir + 1) % 8;
r_dir = (dir + 7) % 8;
bool l_force = false;
bool r_force = false;
NodePtr l_node = GetNode(node->x + DirX[l_dir], node->y + DirY[l_dir]);
if( l_node && l_node->dir == -1 )
Jump(l_node, l_dir);
l_force = jpsN_;
NodePtr r_node = GetNode(node->x + DirX[r_dir], node->y + DirY[r_dir]);
if( r_node && r_node->dir == -1 )
Jump(r_node, r_dir);
r_force = jpsN_;
if( l_force || r_force )
{
return node;
}
NodePtr next_node = GetNode(node->x + DirX[dir], node->y + DirY[dir]);
if( next_node )
next_node->dir = dir;
return Jump(next_node, dir);
}
直线方向递归
NodePtr JpsFind::JumpStr( const NodePtr& node, int32 dir )
{
//直线方向 上下左右
int32 l_dir = (dir + 2) % 8;
int32 r_dir = (dir + 6) % 8;
bool l_force_ = Walk(node->x + DirX[l_dir], node->y + DirY[l_dir]);
bool r_force_ = Walk(node->x + DirX[r_dir], node->y + DirY[r_dir]);
if( !l_force_ || !r_force_ )
{
int32 l_force_dir = (dir + 1) % 8;
int32 r_force_dir = (dir + 7) % 8;
bool left_next = Walk(node->x + DirX[l_force_dir], node->y + DirY[l_force_dir]);
bool right_next = Walk(node->x + DirX[r_force_dir], node->y + DirY[r_force_dir]);
if( !l_force_ && left_next )
{
jpsN_ = true;
return node;
}
if( !r_force_ && right_next )
{
jpsN_ = true;
return node;
}
}
NodePtr next_node = GetNode(node->x + DirX[dir], node->y + DirY[dir]);
if( next_node )
next_node->dir = dir;
return Jump(next_node, dir);
}
完整效果图:

完整跳点图
