#include <ros/ros.h>

#include <nav_core/base_global_planner.h>
#include <costmap_2d/costmap_2d_ros.h>
#include <costmap_2d/cost_values.h>
#include <geometry_msgs/PoseStamped.h>
#include <nav_msgs/Path.h>
#include <pluginlib/class_list_macros.h>

#include <algorithm>
#include <cmath>
#include <limits>
#include <random>
#include <string>
#include <vector>

namespace rrt_star_global_planner
{

struct Point2D
{
  double x;
  double y;
};

struct TreeNode
{
  Point2D point;
  int parent;
  double cost;
};

class RRTStarGlobalPlanner : public nav_core::BaseGlobalPlanner
{
public:
  RRTStarGlobalPlanner()
    : initialized_(false),
      costmap_ros_(nullptr),
      costmap_(nullptr),
      rng_(std::random_device{}())
  {
  }

  RRTStarGlobalPlanner(std::string name, costmap_2d::Costmap2DROS* costmap_ros)
    : initialized_(false),
      costmap_ros_(nullptr),
      costmap_(nullptr),
      rng_(std::random_device{}())
  {
    initialize(name, costmap_ros);
  }

  void initialize(std::string name, costmap_2d::Costmap2DROS* costmap_ros) override
  {
    if (initialized_)
    {
      ROS_WARN("RRTStarGlobalPlanner has already been initialized.");
      return;
    }

    if (costmap_ros == nullptr)
    {
      ROS_ERROR("RRTStarGlobalPlanner received null costmap_ros.");
      return;
    }

    costmap_ros_ = costmap_ros;
    costmap_ = costmap_ros_->getCostmap();
    global_frame_ = costmap_ros_->getGlobalFrameID();

    ros::NodeHandle private_nh("~/" + name);
    ros::NodeHandle nh;

    plan_pub_ = nh.advertise<nav_msgs::Path>("/move_base/RRTStarGlobalPlanner/plan", 1, true);

    private_nh.param("max_iter", max_iter_, 3000);
    private_nh.param("step_size", step_size_, 0.30);
    private_nh.param("search_radius", search_radius_, 0.80);
    private_nh.param("goal_radius", goal_radius_, 0.50);
    private_nh.param("normal_width_ratio", normal_width_ratio_, 0.45);
    private_nh.param("max_sample_retry", max_sample_retry_, 1000);
    private_nh.param("d_dichotomy", d_dichotomy_, 0.20);
    private_nh.param("stop_at_first_path", stop_at_first_path_, true);
    private_nh.param("allow_unknown", allow_unknown_, false);
    private_nh.param("collision_check_step", collision_check_step_, 0.05);

    initialized_ = true;

    ROS_INFO("RRTStarGlobalPlanner initialized.");
    ROS_INFO("global_frame = %s", global_frame_.c_str());
    ROS_INFO("full path topic = /move_base/RRTStarGlobalPlanner/plan");
  }

  bool makePlan(const geometry_msgs::PoseStamped& start,
                const geometry_msgs::PoseStamped& goal,
                std::vector<geometry_msgs::PoseStamped>& plan) override
  {
    plan.clear();

    if (!initialized_)
    {
      ROS_ERROR("RRTStarGlobalPlanner has not been initialized.");
      return false;
    }

    Point2D start_pt{start.pose.position.x, start.pose.position.y};
    Point2D goal_pt{goal.pose.position.x, goal.pose.position.y};

    if (!isPointFree(start_pt))
    {
      ROS_WARN("RRTStarGlobalPlanner: start point is not free.");
      return false;
    }

    if (!isPointFree(goal_pt))
    {
      ROS_WARN("RRTStarGlobalPlanner: goal point is not free.");
      return false;
    }

    std::vector<Point2D> path_points;
    bool success = planRRTStar(start_pt, goal_pt, path_points);

    if (!success || path_points.empty())
    {
      ROS_WARN("RRTStarGlobalPlanner: failed to find path.");
      return false;
    }

    ros::Time stamp = ros::Time::now();

    for (size_t i = 0; i < path_points.size(); ++i)
    {
      geometry_msgs::PoseStamped pose;
      pose.header.stamp = stamp;
      pose.header.frame_id = global_frame_;
      pose.pose.position.x = path_points[i].x;
      pose.pose.position.y = path_points[i].y;
      pose.pose.position.z = 0.0;
      pose.pose.orientation = goal.pose.orientation;
      plan.push_back(pose);
    }

    nav_msgs::Path path_msg;
    path_msg.header.stamp = stamp;
    path_msg.header.frame_id = global_frame_;
    path_msg.poses = plan;
    plan_pub_.publish(path_msg);

    ROS_INFO("RRTStarGlobalPlanner: generated path with %lu poses.", plan.size());
    return true;
  }

private:
  bool planRRTStar(const Point2D& start,
                   const Point2D& goal,
                   std::vector<Point2D>& final_path)
  {
    std::vector<TreeNode> tree;
    tree.push_back(TreeNode{start, -1, 0.0});

    std::vector<int> goal_candidates;

    int best_goal_idx = -1;
    double best_goal_cost = std::numeric_limits<double>::infinity();

    int success_count_in_period = 0;

    for (int iter = 1; iter <= max_iter_; ++iter)
    {
      if (iter % 10 == 0)
      {
        success_count_in_period = 0;
      }

      double alpha;
      if (best_goal_idx < 0)
      {
        alpha = 1.0 / (1.0 + std::exp(-(static_cast<double>(success_count_in_period) - 5.0)));
      }
      else
      {
        alpha = 0.0;
      }

      Point2D sample = sampleHybrid(start, goal, alpha);

      if (!isPointFree(sample))
      {
        continue;
      }

      int nearest_idx = nearestNode(tree, sample);
      Point2D nearest_point = tree[nearest_idx].point;
      Point2D new_point = steerTowards(nearest_point, sample, step_size_);

      if (!isPointFree(new_point))
      {
        continue;
      }

      if (edgeInCollision(nearest_point, new_point))
      {
        continue;
      }

      success_count_in_period++;

      std::vector<int> neighbor_idx = nearNodes(tree, new_point, search_radius_);

      int reachest_idx = findReachest(tree, nearest_idx, new_point);

      bool has_create = false;
      Point2D x_create;
      int x_create_idx = -1;

      createNode(tree, reachest_idx, new_point, x_create, has_create);

      if (has_create)
      {
        int parent_idx = tree[reachest_idx].parent;

        if (parent_idx >= 0 && !edgeInCollision(tree[parent_idx].point, x_create))
        {
          double create_cost = tree[parent_idx].cost + distance(tree[parent_idx].point, x_create);
          tree.push_back(TreeNode{x_create, parent_idx, create_cost});
          x_create_idx = static_cast<int>(tree.size()) - 1;
        }
        else
        {
          has_create = false;
        }
      }

      int best_parent = nearest_idx;
      double best_cost = tree[nearest_idx].cost + distance(nearest_point, new_point);

      for (size_t i = 0; i < neighbor_idx.size(); ++i)
      {
        int idx = neighbor_idx[i];
        double candidate_cost = tree[idx].cost + distance(tree[idx].point, new_point);

        if (candidate_cost < best_cost && !edgeInCollision(tree[idx].point, new_point))
        {
          best_parent = idx;
          best_cost = candidate_cost;
        }
      }

      tree.push_back(TreeNode{new_point, best_parent, best_cost});
      int new_idx = static_cast<int>(tree.size()) - 1;

      for (size_t i = 0; i < neighbor_idx.size(); ++i)
      {
        int idx = neighbor_idx[i];

        if (idx == new_idx || idx == best_parent)
        {
          continue;
        }

        bool rewired = false;

        if (has_create && x_create_idx >= 0)
        {
          double create_cost = tree[x_create_idx].cost + distance(tree[x_create_idx].point, tree[idx].point);

          if (create_cost < tree[idx].cost && !edgeInCollision(tree[x_create_idx].point, tree[idx].point))
          {
            tree[idx].parent = x_create_idx;
            tree[idx].cost = create_cost;
            updateDescendantCosts(tree, idx);
            rewired = true;
          }
        }

        if (rewired)
        {
          continue;
        }

        double new_cost = tree[new_idx].cost + distance(tree[new_idx].point, tree[idx].point);

        if (new_cost < tree[idx].cost && !edgeInCollision(tree[new_idx].point, tree[idx].point))
        {
          tree[idx].parent = new_idx;
          tree[idx].cost = new_cost;
          updateDescendantCosts(tree, idx);
        }
      }

      if (distance(new_point, goal) <= goal_radius_ && !edgeInCollision(new_point, goal))
      {
        goal_candidates.push_back(new_idx);
      }

      best_goal_idx = -1;
      best_goal_cost = std::numeric_limits<double>::infinity();

      for (size_t i = 0; i < goal_candidates.size(); ++i)
      {
        int idx = goal_candidates[i];

        if (edgeInCollision(tree[idx].point, goal))
        {
          continue;
        }

        double candidate_cost = tree[idx].cost + distance(tree[idx].point, goal);

        if (candidate_cost < best_goal_cost)
        {
          best_goal_cost = candidate_cost;
          best_goal_idx = idx;
        }
      }

      if (stop_at_first_path_ && best_goal_idx >= 0)
      {
        break;
      }
    }

    if (best_goal_idx < 0)
    {
      return false;
    }

    final_path = backtrackPath(tree, best_goal_idx, goal);
    return true;
  }

  Point2D sampleUniform()
  {
    double origin_x = costmap_->getOriginX();
    double origin_y = costmap_->getOriginY();
    double size_x = costmap_->getSizeInMetersX();
    double size_y = costmap_->getSizeInMetersY();

    std::uniform_real_distribution<double> dist_x(origin_x, origin_x + size_x);
    std::uniform_real_distribution<double> dist_y(origin_y, origin_y + size_y);

    return Point2D{dist_x(rng_), dist_y(rng_)};
  }

  Point2D sampleNormal(const Point2D& start, const Point2D& goal)
  {
    Point2D L{goal.x - start.x, goal.y - start.y};
    double L_length = std::sqrt(L.x * L.x + L.y * L.y);

    if (L_length < 1e-9)
    {
      return start;
    }

    double sigma = normal_width_ratio_ * L_length / 4.0;

    std::uniform_real_distribution<double> uniform_01(0.0, 1.0);
    std::normal_distribution<double> normal_dist(0.0, sigma);

    for (int retry = 0; retry < max_sample_retry_; ++retry)
    {
      double t = uniform_01(rng_);

      Point2D lrand{
        start.x + t * L.x,
        start.y + t * L.y
      };

      Point2D sample{
        lrand.x + normal_dist(rng_),
        lrand.y + normal_dist(rng_)
      };

      if (isInsideMap(sample))
      {
        return sample;
      }
    }

    return sampleUniform();
  }

  Point2D sampleHybrid(const Point2D& start, const Point2D& goal, double alpha)
  {
    std::uniform_real_distribution<double> uniform_01(0.0, 1.0);
    double r = uniform_01(rng_);

    if (r > alpha)
    {
      Point2D qrand1 = sampleNormal(start, goal);
      Point2D qrand2 = sampleUniform();

      if (distance(qrand1, goal) <= distance(qrand2, goal))
      {
        return qrand1;
      }
      else
      {
        return qrand2;
      }
    }
    else
    {
      return goal;
    }
  }

  int nearestNode(const std::vector<TreeNode>& tree, const Point2D& point)
  {
    int best_idx = 0;
    double best_dist = std::numeric_limits<double>::infinity();

    for (size_t i = 0; i < tree.size(); ++i)
    {
      double d = distanceSquared(tree[i].point, point);

      if (d < best_dist)
      {
        best_dist = d;
        best_idx = static_cast<int>(i);
      }
    }

    return best_idx;
  }

  std::vector<int> nearNodes(const std::vector<TreeNode>& tree,
                             const Point2D& point,
                             double radius)
  {
    std::vector<int> result;
    double r2 = radius * radius;

    for (size_t i = 0; i < tree.size(); ++i)
    {
      if (distanceSquared(tree[i].point, point) <= r2)
      {
        result.push_back(static_cast<int>(i));
      }
    }

    return result;
  }

  Point2D steerTowards(const Point2D& from, const Point2D& to, double step_size)
  {
    double dx = to.x - from.x;
    double dy = to.y - from.y;
    double d = std::sqrt(dx * dx + dy * dy);

    if (d <= step_size)
    {
      return to;
    }

    return Point2D{
      from.x + dx / d * step_size,
      from.y + dy / d * step_size
    };
  }

  int findReachest(const std::vector<TreeNode>& tree,
                   int nearest_idx,
                   const Point2D& x_rand)
  {
    int reachest_idx = nearest_idx;

    while (tree[reachest_idx].parent >= 0)
    {
      int parent_idx = tree[reachest_idx].parent;
      Point2D parent_point = tree[parent_idx].point;

      if (edgeInCollision(x_rand, parent_point))
      {
        return reachest_idx;
      }

      reachest_idx = parent_idx;
    }

    return reachest_idx;
  }

  void createNode(const std::vector<TreeNode>& tree,
                  int reachest_idx,
                  const Point2D& x_rand,
                  Point2D& x_create,
                  bool& has_create)
  {
    has_create = false;

    int parent_idx = tree[reachest_idx].parent;

    if (parent_idx < 0)
    {
      return;
    }

    Point2D x_allow = tree[reachest_idx].point;
    Point2D x_forbid = tree[parent_idx].point;

    while (distance(x_allow, x_forbid) > d_dichotomy_)
    {
      Point2D x_mid{
        (x_allow.x + x_forbid.x) / 2.0,
        (x_allow.y + x_forbid.y) / 2.0
      };

      if (!edgeInCollision(x_rand, x_mid))
      {
        x_allow = x_mid;
      }
      else
      {
        x_forbid = x_mid;
      }
    }

    x_forbid = x_rand;
    Point2D parent_point = tree[parent_idx].point;

    while (distance(x_allow, x_forbid) > d_dichotomy_)
    {
      Point2D x_mid{
        (x_allow.x + x_forbid.x) / 2.0,
        (x_allow.y + x_forbid.y) / 2.0
      };

      if (!edgeInCollision(x_mid, parent_point))
      {
        x_allow = x_mid;
      }
      else
      {
        x_forbid = x_mid;
      }
    }

    if (distance(x_allow, tree[reachest_idx].point) > 1e-9)
    {
      x_create = x_allow;
      has_create = true;
    }
  }

  void updateDescendantCosts(std::vector<TreeNode>& tree, int root_idx)
  {
    for (size_t i = 0; i < tree.size(); ++i)
    {
      if (tree[i].parent == root_idx)
      {
        tree[i].cost = tree[root_idx].cost + distance(tree[root_idx].point, tree[i].point);
        updateDescendantCosts(tree, static_cast<int>(i));
      }
    }
  }

  std::vector<Point2D> backtrackPath(const std::vector<TreeNode>& tree,
                                     int goal_parent_idx,
                                     const Point2D& goal)
  {
    std::vector<Point2D> reversed_path;
    reversed_path.push_back(goal);

    int idx = goal_parent_idx;

    while (idx >= 0)
    {
      reversed_path.push_back(tree[idx].point);
      idx = tree[idx].parent;
    }

    std::reverse(reversed_path.begin(), reversed_path.end());
    return reversed_path;
  }

  bool isInsideMap(const Point2D& p)
  {
    unsigned int mx;
    unsigned int my;
    return costmap_->worldToMap(p.x, p.y, mx, my);
  }

  bool isPointFree(const Point2D& p)
  {
    unsigned int mx;
    unsigned int my;

    if (!costmap_->worldToMap(p.x, p.y, mx, my))
    {
      return false;
    }

    unsigned char cost = costmap_->getCost(mx, my);

    if (cost == costmap_2d::LETHAL_OBSTACLE)
    {
      return false;
    }

    if (cost == costmap_2d::INSCRIBED_INFLATED_OBSTACLE)
    {
      return false;
    }

    if (!allow_unknown_ && cost == costmap_2d::NO_INFORMATION)
    {
      return false;
    }

    return true;
  }

  bool edgeInCollision(const Point2D& p1, const Point2D& p2)
  {
    double d = distance(p1, p2);

    if (d < 1e-9)
    {
      return !isPointFree(p1);
    }

    int steps = std::max(2, static_cast<int>(std::ceil(d / collision_check_step_)));

    for (int i = 0; i <= steps; ++i)
    {
      double t = static_cast<double>(i) / static_cast<double>(steps);

      Point2D p{
        p1.x + t * (p2.x - p1.x),
        p1.y + t * (p2.y - p1.y)
      };

      if (!isPointFree(p))
      {
        return true;
      }
    }

    return false;
  }

  double distance(const Point2D& a, const Point2D& b)
  {
    return std::sqrt(distanceSquared(a, b));
  }

  double distanceSquared(const Point2D& a, const Point2D& b)
  {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return dx * dx + dy * dy;
  }

private:
  bool initialized_;

  costmap_2d::Costmap2DROS* costmap_ros_;
  costmap_2d::Costmap2D* costmap_;
  std::string global_frame_;
  ros::Publisher plan_pub_;

  int max_iter_;
  double step_size_;
  double search_radius_;
  double goal_radius_;
  double normal_width_ratio_;
  int max_sample_retry_;
  double d_dichotomy_;
  bool stop_at_first_path_;
  bool allow_unknown_;
  double collision_check_step_;

  std::mt19937 rng_;
};

}  // namespace rrt_star_global_planner

PLUGINLIB_EXPORT_CLASS(rrt_star_global_planner::RRTStarGlobalPlanner,
                       nav_core::BaseGlobalPlanner)

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐