Categories

Tags

Table of Contents

  1. 算法介绍
  2. A*搜索
  3. 裁剪
    1. 跳跃
  4. 学习总结

算法介绍

JPS算法是基于A*算法的扩展实现,也就是说,我们还是会使用到A*的启发方式来挑选下一个扩展节点,只不过,JPS会对候选的扩展节点进行裁剪。例如下面两张图。x 表示当前检测的节点,箭头表示它的前继方向(即它是从哪一个节点来的)。灰色的地方表示将会裁剪掉的邻居节点,白色的地方才是需要的扩展节点。

也就是说,上图左边的进行裁剪后,实际上只剩下5号节点,右图裁剪后实际只剩下2,3,5号节点。我们简单起见,现在只是介绍了向右和向右上扩展时的情况(另外6 个方向其实道理一样)。

另外,需要注意强制邻居节点。 当x沿着箭头方向进行扩展时,当x和障碍物相邻时,图中被圈起来的节点无法被裁剪,该节点也被称为强制邻居节点。

为什么叫做强制邻居节点?因为他会强制结束本次跳跃的进行。例如,节点x一直在朝着右边进行扩展,突然遇到一个障碍出现在它的上面(出现在下面的情况时,强制邻居为8),此时我们会结束跳跃,把当前x节点和强制邻居节点压入OpenSet。这样一来,我们就直接略过中间大部分节点的扩展搜索,并直接从新压入的跳点开始搜索。

其实,以上就算JPS的核心思想,下面我们就基于A*算法对其进行改造,将我们的GeiNeighbors方法进行修改,不在时获取8个方向的邻居,而是经过裁剪后的邻居。

A*搜索

function Search(){
  if(done){
    return;
  }

  //执行搜索中
  var lowest_id = 0;
  var lowest_f = openSet[lowest_id].f;
  for (var i = 1; i < openSet.length; i++) {
    if(openSet[i].f < lowest_f){
      lowest_id = i;
      lowest_f = openSet[i].f;
    }
  }
  var current = openSet[lowest_id];
  RemoveFormArray(openSet,current);
  closedSet.push(current);
  if(current == end){
    done = true;
    console.log("Done!");
    return;
  }

  if(current.cameFrom != null){
    //取得移动方向
    var dir_x = constrain(current.x - current.cameFrom.x ,-1,1);
    var dir_y = constrain(current.y - current.cameFrom.y ,-1,1);
    GetNeighbors_WithCut(current,dir_x,dir_y);
  }else{
    GetNeighbors(current);
  }

  for (var i =  0; i < neighbors.length ; i++) {
    var neighbor = neighbors[i];

    var jumpNode = jump(neighbor,current,grid);
    if(jumpNode != null && !closedSet.includes(jumpNode)){

      var g_score = current.g + floor( dist(jumpNode.x,jumpNode.y,current.x,current.y) * 10) / 10;

      if(openSet.includes(jumpNode)){
        if(g_score < jumpNode.g){
          jumpNode.g = g_score;//是一个更加近的节点
          jumpNode.cameFrom = current;
        }
      }else{
        jumpNode.g = g_score;
        jumpNode.cameFrom = current;
        openSet.push(jumpNode);
      }

      jumpNode.h = Heuristic(jumpNode,end) ;
      jumpNode.f = jumpNode.g + jumpNode.h;
    }
  }

}

function Heuristic(_spot,_end){
  return abs(_spot.x - _end.x) + abs(_spot.y - _end.y) ;
}

获取邻居


function GetNeighbors(_spot){
  neighbors.length = 0;
  for (var i = 0; i < 8; i++) {
    var dir = grid.drections[i];
    var neighbor = grid.getNeighbor(_spot,dir.x,dir.y);
    if(neighbor != null && neighbor.cost != 0){
      if((i % 2) != 0){//对角
        var last_dir =  grid.drections[(i - 1) % 8];
        var next_dir =  grid.drections[(i + 1) % 8];

        var last = grid.getNeighbor(_spot,last_dir.x,last_dir.y);
        var next = grid.getNeighbor(_spot,next_dir.x,next_dir.y);
        if(last != null && last.cost == 0 && next != null && next.cost == 0){
          console.log("对角");
        }else{
          neighbors.push(neighbor);
        }

      }else{
        neighbors.push(neighbor);
      }
    }
  }
}

function GetNeighbors_WithCut(_spot,_dirx,_diry){
  neighbors.length = 0;
  if(_dirx != 0 && _diry != 0){
    //斜角
    neighbors = DiagonalTest(grid,_spot,_dirx,_diry);
  }else{
    //非斜角
    if(_dirx != 0){
      neighbors = HorizontalTest(grid,_spot,_dirx);
    }else{
      neighbors = VerticalTest(grid,_spot,_diry);
    }
  }
}

裁剪

function AddForceNeighbor(_cell,_grid,list,_dirx,_diry){
  var forceNeighbor = _grid.getNeighbor(_cell,_dirx,_diry);
  if(forceNeighbor != null && forceNeighbor.cost != 0){
      list.push(forceNeighbor);
  }
}


function HorizontalTest(_grid,_currentCell,_dir){
  var res = [];
  var nextCell = _grid.getNeighbor(_currentCell,_dir,0);
  if(nextCell == null){
    return res;
  }else{
    if(nextCell.cost != 0){
      res.push(nextCell);
    }
  }
  var upCell = _grid.getNeighbor(_currentCell,0,-1);
  var downCell = _grid.getNeighbor(_currentCell,0,1);

  //发现强制邻居节点
  if(upCell != null){
    if(_dir == 1){
      AddForceNeighbor_D(_currentCell,_grid,res,0,2,1);
    }else{
      AddForceNeighbor_D(_currentCell,_grid,res,0,6,7);
    }
  }
  if(downCell != null ){
    if(_dir == 1){
      AddForceNeighbor_D(_currentCell,_grid,res,4,2,3);
    }else{
      AddForceNeighbor_D(_currentCell,_grid,res,4,6,5);
    }
  }
  return res;
}
function VerticalTest(_grid,_currentCell,_dir){
  var res = [];
  var nextCell = _grid.getNeighbor(_currentCell,0,_dir);
  if(nextCell == null){
    return res;
  }else{
    if(nextCell.cost != 0){
      res.push(nextCell);
    }
  }
  var leftCell = _grid.getNeighbor(_currentCell,-1,0);
  var rightCell = _grid.getNeighbor(_currentCell,1,0);

  //发现强制邻居节点
  if(leftCell != null){
    if(_dir == 1){
      AddForceNeighbor_D(_currentCell,_grid,res,6,4,5);
    }else{
      AddForceNeighbor_D(_currentCell,_grid,res,6,0,7);
    }
  }
  if(rightCell != null){
    if(_dir == 1){
      AddForceNeighbor_D(_currentCell,_grid,res,2,4,3);
    }else{
      AddForceNeighbor_D(_currentCell,_grid,res,2,0,1);
    }
  }
  return res;
}
function DiagonalTest(_grid,_currentCell,_dirx,_diry){
  var res = [];
  var nextCell = _grid.getNeighbor(_currentCell,_dirx,_diry);


  var nextVCell = _grid.getNeighbor(_currentCell,0,_diry);//垂直方向
  var nextHCell = _grid.getNeighbor(_currentCell,_dirx,0);//水平方向

  if(nextVCell != null && nextVCell.cost != 0){
    res.push(nextVCell);
  }
  if(nextHCell != null && nextHCell.cost != 0){
    res.push(nextHCell);
  }
  if(nextVCell != null && nextVCell.cost == 0 &&
    nextHCell != null &&  nextHCell.cost == 0){
    //不允许穿过两个不可通行的节点之间
    return res;
  }else{
    if(nextCell != null && nextCell.cost != 0){
      res.push(nextCell);
    }
  }

  //发现强制邻居节点
  if(_dirx == 1 && _diry == -1){
    //右上
    AddForceNeighbor_D(_currentCell,_grid,res,6,0,7);
    AddForceNeighbor_D(_currentCell,_grid,res,4,2,3);
  }else if(_dirx == 1 && _diry == 1){
    //右下
    AddForceNeighbor_D(_currentCell,_grid,res,0,2,1);
    AddForceNeighbor_D(_currentCell,_grid,res,6,4,5);
  }else if(_dirx == -1 && _diry == 1){
    //左下
    AddForceNeighbor_D(_currentCell,_grid,res,0,6,7);
    AddForceNeighbor_D(_currentCell,_grid,res,2,4,3);
  }else{
    AddForceNeighbor_D(_currentCell,_grid,res,2,0,1);
    AddForceNeighbor_D(_currentCell,_grid,res,4,6,5);
  }

  return res;
}
//考虑斜角,不需从两个墙之间穿过
function AddForceNeighbor_D(_cell,_grid,list,_close,_open,_add){
  var closeCell = _grid.getNeighbor_byDirID(_cell,_close);
  var openCell = _grid.getNeighbor_byDirID(_cell,_open);
  if(openCell == null)
    return;
  if(closeCell.cost == 0 &&
    openCell.cost != 0){
    var dir = _grid.drections[_add];
    AddForceNeighbor(_cell,_grid,list,dir.x,dir.y);
  }
}

跳跃

function jump(_neighbor,_current,_grid){
  if(_neighbor == null || _neighbor.cost == 0){
    return null;
  }

  if(_neighbor == end){
    return _neighbor;
  }

  var dx = _neighbor.x - _current.x;
  var dy = _neighbor.y - _current.y;

  var forceNeighbor1 = null;
  var obstacle1 = null;
  var forceNeighbor2 = null;
  var obstacle2 = null;

  if((dx & dy) != 0){
    //斜角
    forceNeighbor1 = _grid.getNeighbor(_neighbor,-dx,dy);
    obstacle1 = _grid.getNeighbor(_neighbor,-dx,0);
    forceNeighbor2 = _grid.getNeighbor(_neighbor,dx,-dy);
    obstacle2 = _grid.getNeighbor(_neighbor,0,-dy);

    if(HasForceNeighbor(forceNeighbor1,obstacle1,forceNeighbor2,obstacle2)){
      return _neighbor;
    }
    if(jump(_grid.getNeighbor(_neighbor,dx,0) ,_neighbor,_grid) != null ||
       jump(_grid.getNeighbor(_neighbor,0,dy) ,_neighbor,_grid) != null){
      return _neighbor;
    }
  }else{
    if(dx != 0){
      forceNeighbor1 = _grid.getNeighbor(_neighbor,dx,1);
      obstacle1 = _grid.getNeighbor(_neighbor,0,1);
      forceNeighbor2 = _grid.getNeighbor(_neighbor,dx,-1);
      obstacle2 = _grid.getNeighbor(_neighbor,0,-1);

    }else if(dy != 0){
      forceNeighbor1 = _grid.getNeighbor(_neighbor,-1,dy);
      obstacle1 = _grid.getNeighbor(_neighbor,-1,0);
      forceNeighbor2 = _grid.getNeighbor(_neighbor,1,dy);
      obstacle2 = _grid.getNeighbor(_neighbor,1,0);
    }
    if(HasForceNeighbor(forceNeighbor1,obstacle1,forceNeighbor2,obstacle2)){
      return _neighbor;
    }
  }

  var H_cell = _grid.getNeighbor(_neighbor,dx,0);
  var V_cell = _grid.getNeighbor(_neighbor,0,dy);

  if((H_cell != null && H_cell.cost != 0) || (V_cell != null && V_cell.cost != 0)){
    var D_Cell =  _grid.getNeighbor(_neighbor,dx,dy);
    return jump(D_Cell,_neighbor,_grid);
  }else{
    return null;
  }
}

function HasForceNeighbor(forceNeighbor1 ,obstacle1,forceNeighbor2,obstacle2){
  if((obstacle1 != null && obstacle1.cost == 0 &&
     forceNeighbor1 != null && forceNeighbor1.cost != 0) ||
  (obstacle2 != null && obstacle2.cost == 0 &&
     forceNeighbor2 != null && forceNeighbor2.cost != 0)){
    return true;
  }
}

学习总结


参考文献