gazebo

[turtlebot4][path] rrt planner__2 ......... tf관련 에러

정지홍 2025. 6. 10. 13:29

https://jihong.tistory.com/651

 

[turtlebot4][path] rrt planner__1

1. 패키지 설치mkdir rrt_testcd rrt_testros2 pkg create --build-type ament_cmake rrt_planner --dependencies rclcpp nav2_core pluginlib nav2_costmap_2d geometry_msgs nav2_msgs nav_msgs 2. rrt_planner / include / rrt_planner.hpp#ifndef RRT_PLANNER__RRT_P

jihong.tistory.com

 

 

위의 planner는 현재 controller_server에서 에러가 발생한다...

__ID__CONTROLLER_SERVER__
[INFO] [1749528739.922357963] [controller_server]: 
	controller_server lifecycle node launched. 
	Waiting on external lifecycle transitions to activate
	See https://design.ros2.org/articles/node_lifecycle.html for more information.
[INFO] [1749528740.287452338] [controller_server]: Creating controller server
[INFO] [1749528740.331993132] [local_costmap.local_costmap]: 
	local_costmap lifecycle node launched. 
	Waiting on external lifecycle transitions to activate
	See https://design.ros2.org/articles/node_lifecycle.html for more information.
[INFO] [1749528740.417867734] [local_costmap.local_costmap]: Creating Costmap
[INFO] [1749529224.489922937] [controller_server]: Configuring controller interface
[INFO] [1749529224.490366100] [controller_server]: getting goal checker plugins..
[INFO] [1749529224.490574081] [controller_server]: Controller frequency set to 20.0000Hz
[INFO] [1749529224.490614421] [local_costmap.local_costmap]: Configuring
[INFO] [1749529224.502574746] [local_costmap.local_costmap]: Using plugin "static_layer"
[INFO] [1749529226.535232607] [local_costmap.local_costmap]: Subscribing to the map topic (/map) with transient local durability
[INFO] [1749529226.538675546] [local_costmap.local_costmap]: Initialized plugin "static_layer"
[INFO] [1749529226.538732756] [local_costmap.local_costmap]: Using plugin "voxel_layer"
[INFO] [1749529226.540243344] [local_costmap.local_costmap]: Subscribed to Topics: scan
[INFO] [1749529226.601249286] [local_costmap.local_costmap]: Initialized plugin "voxel_layer"
[INFO] [1749529226.601288036] [local_costmap.local_costmap]: Using plugin "inflation_layer"
[INFO] [1749529226.602569503] [local_costmap.local_costmap]: Initialized plugin "inflation_layer"
[INFO] [1749529226.641373444] [local_costmap.local_costmap]: StaticLayer: Resizing static layer to 620 X 1138 at 0.050000 m/pix
[INFO] [1749529226.670882785] [controller_server]: Created progress_checker : progress_checker of type nav2_controller::SimpleProgressChecker
[INFO] [1749529226.712877283] [controller_server]: Created goal checker : general_goal_checker of type nav2_controller::SimpleGoalChecker
[INFO] [1749529226.713972559] [controller_server]: Controller Server has general_goal_checker  goal checkers available.
[INFO] [1749529227.072415038] [controller_server]: Created controller : FollowPath of type dwb_core::DWBLocalPlanner
[INFO] [1749529227.076313039] [controller_server]: Setting transform_tolerance to 0.200000
[INFO] [1749529228.189372880] [controller_server]: Using critic "RotateToGoal" (dwb_critics::RotateToGoalCritic)
[INFO] [1749529228.190383776] [controller_server]: Critic plugin initialized
[INFO] [1749529228.190613707] [controller_server]: Using critic "Oscillation" (dwb_critics::OscillationCritic)
[INFO] [1749529228.191492632] [controller_server]: Critic plugin initialized
[INFO] [1749529228.192114195] [controller_server]: Using critic "BaseObstacle" (dwb_critics::BaseObstacleCritic)
[INFO] [1749529228.192532327] [controller_server]: Critic plugin initialized
[INFO] [1749529228.192736308] [controller_server]: Using critic "GoalAlign" (dwb_critics::GoalAlignCritic)
[INFO] [1749529228.193412322] [controller_server]: Critic plugin initialized
[INFO] [1749529228.193614073] [controller_server]: Using critic "PathAlign" (dwb_critics::PathAlignCritic)
[INFO] [1749529228.194767710] [controller_server]: Critic plugin initialized
[INFO] [1749529228.195079441] [controller_server]: Using critic "PathDist" (dwb_critics::PathDistCritic)
[INFO] [1749529228.196175957] [controller_server]: Critic plugin initialized
[INFO] [1749529228.196950501] [controller_server]: Using critic "GoalDist" (dwb_critics::GoalDistCritic)
[INFO] [1749529228.197410054] [controller_server]: Critic plugin initialized
[INFO] [1749529228.197442284] [controller_server]: Controller Server has FollowPath  controllers available.
[INFO] [1749529239.515215876] [controller_server]: Activating
[INFO] [1749529239.515270366] [local_costmap.local_costmap]: Activating
[INFO] [1749529239.515285667] [local_costmap.local_costmap]: Checking transform
[INFO] [1749529239.515388227] [local_costmap.local_costmap]: start
[INFO] [1749529239.716995233] [controller_server]: Creating bond (controller_server) to lifecycle manager.




[INFO] [1749529372.906331112] [controller_server]: Received a goal, begin computing control effort.
[WARN] [1749529372.906420013] [controller_server]: No goal checker was specified in parameter 'current_goal_checker'. Server will use only plugin loaded general_goal_checker . This warning will appear once.
[ERROR] [1749529372.910746426] [tf_help]: Transform data too old when converting from map to odom
[ERROR] [1749529372.910805096] [tf_help]: Data time: 1749529372s 884181932ns, Transform time: 537s 946000000ns
[INFO] [1749529372.910822617] [controller_server]: Reached the goal!

[INFO] [1749529388.532860450] [controller_server]: Received a goal, begin computing control effort.
[ERROR] [1749529388.535635895] [tf_help]: Transform data too old when converting from map to odom
[ERROR] [1749529388.535651455] [tf_help]: Data time: 1749529388s 511332613ns, Transform time: 552s 745000000ns
[INFO] [1749529388.535660385] [controller_server]: Reached the goal!

[INFO] [1749529415.058869655] [controller_server]: Received a goal, begin computing control effort.
[ERROR] [1749529415.061857191] [tf_help]: Transform data too old when converting from map to odom
[ERROR] [1749529415.061905982] [tf_help]: Data time: 1749529415s 37542769ns, Transform time: 578s 137000000ns
[ERROR] [1749529415.112193855] [tf_help]: Transform data too old when converting from map to odom
[ERROR] [1749529415.112220995] [tf_help]: Data time: 1749529415s 37542769ns, Transform time: 578s 185000000ns
[ERROR] [1749529415.161587444] [tf_help]: Transform data too old when converting from map to odom
[ERROR] [1749529415.161613964] [tf_help]: Data time: 1749529415s 37542769ns, Transform time: 578s 233000000ns
[ERROR] [1749529415.211943187] [tf_help]: Transform data too old when converting from map to odom

 

 

 

 

예상 원인 1 : isCollisionFree함수

  • 매번 경로 후보를 생성하기 위해서, 수십~수백번 costmap에 접근함...
    위의 부분이 루프 안에서 지나치게 많이 호출이 된다면 병목이 발생할 수 있음.
  • 그래서 아래 파일처럼 우선 수정함.

#include "rrt_planner/rrt_planner.hpp"
#include <random>

namespace rrt_planner
{

void RRTPlanner::configure(const rclcpp_lifecycle::LifecycleNode::WeakPtr &,
                           std::string, std::shared_ptr<tf2_ros::Buffer>,
                           std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
{
  costmap_ros_ = costmap_ros;
  RCLCPP_INFO(logger_, "RRTPlanner configured");
}

void RRTPlanner::cleanup()
{
  RCLCPP_INFO(logger_, "RRTPlanner cleanup");
}

void RRTPlanner::activate()
{
  RCLCPP_INFO(logger_, "RRTPlanner activated");
}

void RRTPlanner::deactivate()
{
  RCLCPP_INFO(logger_, "RRTPlanner deactivated");
}

// 두 점 (x0 , y0)에서 (x1 , y1)까지 직선 경로가 장애물 없이 통과 가능한지 체크
bool RRTPlanner::isCollisionFree(double x0, double y0, double x1, double y1)
{
  unsigned int mx, my;  // 코스트맵의 좌표계 변수
  double distance = hypot(x1 - x0, y1 - y0);   // 거리 구하기
  int steps = static_cast<int>(distance / 0.5); //  0.5 = 50cm 

  // 직선 경로를 5cm 단위로 샘플링하면서 각각의 점이 장애물과 겹치는지 확인
  for (int i = 0; i <= steps; i++) {
    double x = x0 + (x1 - x0) * i / steps;
    double y = y0 + (y1 - y0) * i / steps;
    // 지도 좌표계에서 costmap 좌표계로 변환 불가한다면( 이 경우는 맵 밖인 경우 ).... 충돌로 간주
    if (!costmap_ros_->getCostmap()->worldToMap(x, y, mx, my)) return false;
    // 해당 셀의 코스트가 254이상인 경우에..... 충돌로 간주 
    if (costmap_ros_->getCostmap()->getCost(mx, my) >= 254) return false;
  }
  return true;
}

// 실제 RRT 알고리즘을 통해서 start --> goal 경로를 생성하는 함수 
nav_msgs::msg::Path RRTPlanner::createPlan(
  const geometry_msgs::msg::PoseStamped & start,
  const geometry_msgs::msg::PoseStamped & goal)
{
  nav_msgs::msg::Path path; // 이는 ros2에서 path정보를 담아서 전송할때 사용하는 메시지 타입이다.
  // path메시지에의 헤더에는 stamp와 frame_id가 있다.
  path.header.stamp = rclcpp::Clock().now(); // 경로 메시지의 타임 스탬프
  path.header.frame_id = "map"; // 전역 좌표계....( frame_id는 map )

  // node 구조체이다. 트리의 한 노드이며 위치와 부모 인덱스를 저장.
  // node.pose.pose.position.x에서 첫번째 pose는 PostStamped메시지(타임스탬프 +프레임 + pose)를 의미
  //            두번째 pose는 Pose 메시지 (Point position + Quaternion orientation)
  struct Node {
    geometry_msgs::msg::PoseStamped pose;
    int parent;
    double cost;
  };
/*
  // 내부 람다: 두 점간의 충돌 검사를 위한 함수.
  auto isCollisionFree = [this](double x0, double y0, double x1, double y1) {
    unsigned int mx, my;
    double distance = hypot(x1 - x0, y1 - y0);
    int steps = static_cast<int>(distance / 0.05); 

    for (int i = 0; i <= steps; i++) {
      double x = x0 + (x1 - x0) * i / steps;
      double y = y0 + (y1 - y0) * i / steps;
      if (!costmap_ros_->getCostmap()->worldToMap(x, y, mx, my)) return false;
      if (costmap_ros_->getCostmap()->getCost(mx, my) >= 254) return false;
    }
    return true;
  };*/
  

  // 노드(트리) 저장 벡터이다. 첫 노드는 시작위치를 의미한다.
  std::vector<Node> nodes;
  nodes.push_back({start, -1 , 0}); // 시작 노드는 부모가 없다.

  // 랜덤 샘플링을 위한 난수 생성기. 분포를 설정
  std::random_device rd;
  std::mt19937 gen(rd());

  // 지도 범위 내에서 x,y를 무작위로 샘플링한다.
  // uniform_real_distribution<> name(a,b)은 c++ 표준 라이브러리에 포함된 난수 분포 클래스다.
  //    이는 실수형 난수를 균등하게 생성해주는 역할을 한다. 즉, 지정한 범위 내에서 "모든 값이 동일한 확률"로 나올수 있게 실수형 난수를 뽑아쥼
  //    getOriginX()는 costmap의 x최솟값
  //    getSizeInMetersX()는 costmap의 x축 길이
  std::uniform_real_distribution<> dis_x(
    costmap_ros_->getCostmap()->getOriginX(),
    costmap_ros_->getCostmap()->getOriginX() + costmap_ros_->getCostmap()->getSizeInMetersX());
  std::uniform_real_distribution<> dis_y(
    costmap_ros_->getCostmap()->getOriginY(),
    costmap_ros_->getCostmap()->getOriginY() + costmap_ros_->getCostmap()->getSizeInMetersY());
  // 목표점으로의 bias ( 확률적 목표점 샘플링 )
  std::uniform_real_distribution<> dis_goal(0.0, 1.0);

  // RRT의 파라미터 
  const int MAX_ITERATIONS = 300;
  const double STEP_SIZE = 0.2;  // 한번에 뻗어 나가는 거리 
  const double GOAL_THRESHOLD = 0.3; // 목표 지점에 도달했다고 보는 거리
  const double GOAL_BIAS = 0.1; // goal을 샘플링 할 확률... 10 %

  auto start_time = rclcpp::Clock().now(); // 경로 생성 시작 시간 
  RCLCPP_INFO(logger_, "RRT start time is %.6f" , start_time.seconds());

  // RRT 트리 생성 반복 루프..... ( max_iterations 혹은 timeLimits까지 반복 )
  for (int i = 0; i < MAX_ITERATIONS; ++i) {
    // 경로 탐색이 0.5초 초과 시 타임아웃 ( 실시간성을 보장 , 무한루프 방지 )
    if ((rclcpp::Clock().now() - start_time).seconds() > 0.5) {
      RCLCPP_INFO(logger_, "RRT timeout  %.6f " , start_time.seconds());
      RCLCPP_WARN(logger_, "RRT planner timeout");
      break;
    }

    // 1. 랜덤 샘플링 
    geometry_msgs::msg::PoseStamped random_pose;
    // 20%확률로 goal 위치를 직접 샘플링
    if (dis_goal(gen) < GOAL_BIAS)
      random_pose = goal;
    else {
      random_pose.pose.position.x = dis_x(gen);
      random_pose.pose.position.y = dis_y(gen);
    }

    // 2. q_rand에서 가장 가까운 노드인 q_near을 찾는다.
    int nearest_idx = -1;
    double min_dist = std::numeric_limits<double>::max();
    for (size_t j = 0; j < nodes.size(); ++j) {
      double dx = random_pose.pose.position.x - nodes[j].pose.pose.position.x;
      double dy = random_pose.pose.position.y - nodes[j].pose.pose.position.y;
      double dist = sqrt(dx*dx + dy*dy);
      if (dist < min_dist) {
        min_dist = dist;
        nearest_idx = j;
      }
    }

    // 3. q_near에서 q_rand로 뻗어날 방향을 구한다. 그리고 step_size만큼 이동한 위치의 지점을 찾는다.
    double angle = atan2(random_pose.pose.position.y - nodes[nearest_idx].pose.pose.position.y,
                         random_pose.pose.position.x - nodes[nearest_idx].pose.pose.position.x);
    double new_x = nodes[nearest_idx].pose.pose.position.x + STEP_SIZE * cos(angle);
    double new_y = nodes[nearest_idx].pose.pose.position.y + STEP_SIZE * sin(angle);

    if (!isCollisionFree(nodes[nearest_idx].pose.pose.position.x,
                         nodes[nearest_idx].pose.pose.position.y,
                         new_x, new_y)) {
      //RCLCPP_INFO(logger_ , "노드 생성중 장애물 충돌...");
      continue;
    }

    Node new_node;
    new_node.pose.pose.position.x = new_x;
    new_node.pose.pose.position.y = new_y;
    new_node.parent = nearest_idx;
    new_node.cost = nodes[new_node.parent].cost + hypot(
      new_x-nodes[new_node.parent].pose.pose.position.x , new_y-nodes[new_node.parent].pose.pose.position.y);
    nodes.push_back(new_node);

    if (hypot(goal.pose.position.x - new_x, goal.pose.position.y - new_y) < GOAL_THRESHOLD) {
      
      Node goal_node;
      goal_node.pose = goal;
      goal_node.parent = nodes.size() - 1;
      goal_node.cost = new_node.cost + hypot(
          goal.pose.position.x - new_x,
          goal.pose.position.y - new_y
      );
      nodes.push_back(goal_node);

      int current_idx = nodes.size()-1;
      RCLCPP_INFO( logger_ , "최종적인 %.6f 입니다." , nodes.back().cost );
      while (current_idx != -1) {
        path.poses.insert(path.poses.begin(), nodes[current_idx].pose);
        current_idx = nodes[current_idx].parent;
      }
      RCLCPP_INFO(logger_, "Path size: %ld", path.poses.size());
      return path;
    }
  }
  RCLCPP_WARN(logger_, "Failed to find path!");
  return path;
}




}  // namespace rrt_planner
#include <pluginlib/class_list_macros.hpp>

PLUGINLIB_EXPORT_CLASS(rrt_planner::RRTPlanner, nav2_core::GlobalPlanner)

 

 

 

예상 원인 2 : 지속적인 path return으로 인한 계산량 증가

  • createPlan에서 path가 반환되어도 계속해서 path를 계산하고 있다.
  • navigate_to_pose_w_replanning_and_recovery.xml을 수정해야 한다.
    •  그중 RateController hz="1.0"이 핵심이다. 이것의 의미는 1초에 1번 computePathToPose 노드가 실행된다.
      이로 인해서 경로가 지속적으로 재계획된다.
    •   RateController hz="0.1"로 수정하면 10초에 한번씩 계산하게 할수있다.

 

위의 파일의 navigate_to_pose_w_replanning_and_recovery.xml을 수정해야한다.

 

 

 



 

예상 원인 3 : planner에서 유효한 경로를 못만들거나, timestamp가 잘못된 경우

  • 만약에 empty한 path를 받으면, 당연히 controller는 움질일수 없으니 실패한다.
    • 동시에, 오래된 TF 데이터를 참조하는 오류가 발생한다.
  • 우선 코드상에 path.header.stamp = rclcpp::Clock().now()라고 된 부분은 시스템의 시간을 받아온다. 그래서 이를 노드의 시간으로 가져오게   auto start_time = node_->now();같이 변경하자.

 

 


 

 

예상 원인 4 :  path.pose의 header.stamp와 header.frame_id가 일치않아서 발생하는 TF오류

  • 즉, path.poses의 header.stamp/frame_id가 각자 따로였다.
  • 그래서 “controller가 참조하는 모든 pose의 시간과 프레임이, path의 header와 같게 설정한다.”
#include "rrt_planner/rrt_planner.hpp"
#include <random>

namespace rrt_planner
{

void RRTPlanner::configure(
  const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
  std::string name, 
  std::shared_ptr<tf2_ros::Buffer> tf,
  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
{
  node_ = parent.lock(); // 여기를 추가해야 함.
  costmap_ros_ = costmap_ros;
  RCLCPP_INFO(logger_, "RRTPlanner configured");
}


void RRTPlanner::cleanup()
{
  RCLCPP_INFO(logger_, "RRTPlanner cleanup");
}

void RRTPlanner::activate()
{
  RCLCPP_INFO(logger_, "RRTPlanner activated");
}

void RRTPlanner::deactivate()
{
  RCLCPP_INFO(logger_, "RRTPlanner deactivated");
}

// 두 점 (x0 , y0)에서 (x1 , y1)까지 직선 경로가 장애물 없이 통과 가능한지 체크
bool RRTPlanner::isCollisionFree(double x0, double y0, double x1, double y1)
{
  unsigned int mx, my;  // 코스트맵의 좌표계 변수
  double distance = hypot(x1 - x0, y1 - y0);   // 거리 구하기
  int steps = static_cast<int>(distance / 0.05); //  0.5 = 50cm 

  // 직선 경로를 5cm 단위로 샘플링하면서 각각의 점이 장애물과 겹치는지 확인
  for (int i = 0; i <= steps; i++) {
    double x = x0 + (x1 - x0) * i / steps;
    double y = y0 + (y1 - y0) * i / steps;
    // 지도 좌표계에서 costmap 좌표계로 변환 불가한다면( 이 경우는 맵 밖인 경우 ).... 충돌로 간주
    if (!costmap_ros_->getCostmap()->worldToMap(x, y, mx, my)) {
      RCLCPP_WARN(logger_, "좌표가 코스트맵 바깥임: (%f,%f)", x, y);
      return false;
    }
    // 해당 셀의 코스트가 254이상인 경우에..... 충돌로 간주 
    double cell_cost = costmap_ros_ -> getCostmap() -> getCost(mx,my);
    if (cell_cost >= 254){
      RCLCPP_WARN(logger_, "충돌 감지됨! 코스트(%f) 좌표:(%u,%u)", cell_cost , mx, my);
      return false;
    }
    else if( cell_cost >= 200){
      RCLCPP_WARN(logger_ , "미지의 영역! 코스트 (%f) 좌표:(%u,%u)", cell_cost , mx, my);
    }
  }
  return true;
}

// 실제 RRT 알고리즘을 통해서 start --> goal 경로를 생성하는 함수 
nav_msgs::msg::Path RRTPlanner::createPlan(
  const geometry_msgs::msg::PoseStamped & start,
  const geometry_msgs::msg::PoseStamped & goal)
{
  nav_msgs::msg::Path path; // 이는 ros2에서 path정보를 담아서 전송할때 사용하는 메시지 타입이다.
  // path메시지에의 헤더에는 stamp와 frame_id가 있다.
//  path.header.stamp = rclcpp::Clock().now(); // 경로 메시지의 타임 스탬프
  //path.header.stamp = this->now(); 
 // path.header.stamp = node_->now();
  path.header.frame_id = "map"; // 전역 좌표계....( frame_id는 map )

  // node 구조체이다. 트리의 한 노드이며 위치와 부모 인덱스를 저장.
  // node.pose.pose.position.x에서 첫번째 pose는 PostStamped메시지(타임스탬프 +프레임 + pose)를 의미
  //            두번째 pose는 Pose 메시지 (Point position + Quaternion orientation)
  struct Node {
    geometry_msgs::msg::PoseStamped pose;
    int parent;
    double cost;
  };

  // 노드(트리) 저장 벡터이다. 첫 노드는 시작위치를 의미한다.
  std::vector<Node> nodes;
  nodes.push_back({start, -1 , 0}); // 시작 노드는 부모가 없다.

  // 랜덤 샘플링을 위한 난수 생성기. 분포를 설정
  std::random_device rd;
  std::mt19937 gen(rd());

  // 지도 범위 내에서 x,y를 무작위로 샘플링한다.
  // uniform_real_distribution<> name(a,b)은 c++ 표준 라이브러리에 포함된 난수 분포 클래스다.
  //    이는 실수형 난수를 균등하게 생성해주는 역할을 한다. 즉, 지정한 범위 내에서 "모든 값이 동일한 확률"로 나올수 있게 실수형 난수를 뽑아쥼
  //    getOriginX()는 costmap의 x최솟값
  //    getSizeInMetersX()는 costmap의 x축 길이
  std::uniform_real_distribution<> dis_x(
    costmap_ros_->getCostmap()->getOriginX(),
    costmap_ros_->getCostmap()->getOriginX() + costmap_ros_->getCostmap()->getSizeInMetersX());
  std::uniform_real_distribution<> dis_y(
    costmap_ros_->getCostmap()->getOriginY(),
    costmap_ros_->getCostmap()->getOriginY() + costmap_ros_->getCostmap()->getSizeInMetersY());
  // 목표점으로의 bias ( 확률적 목표점 샘플링 )
  std::uniform_real_distribution<> dis_goal(0.0, 1.0);

  // RRT의 파라미터 
  const int MAX_ITERATIONS = 300;
  const double STEP_SIZE = 0.2;  // 한번에 뻗어 나가는 거리 
  const double GOAL_THRESHOLD = 0.3; // 목표 지점에 도달했다고 보는 거리
  const double GOAL_BIAS = 0.1; // goal을 샘플링 할 확률... 10 %

 
  auto start_time = node_->now();
  RCLCPP_INFO(logger_, "RRT start time is %.6f" , start_time.seconds());

  // RRT 트리 생성 반복 루프..... ( max_iterations 혹은 timeLimits까지 반복 )
  for (int i = 0; i < MAX_ITERATIONS; ++i) {
    // 경로 탐색이 0.5초 초과 시 타임아웃 ( 실시간성을 보장 , 무한루프 방지 )
    if ((node_->now() - start_time).seconds() > 0.5) {
      RCLCPP_INFO(logger_, "RRT timeout  %.6f " , start_time.seconds());
      RCLCPP_WARN(logger_, "RRT planner timeout");
      break;
    }

    // 1. 랜덤 샘플링 
    geometry_msgs::msg::PoseStamped random_pose;
    // 20%확률로 goal 위치를 직접 샘플링
    if (dis_goal(gen) < GOAL_BIAS)
      random_pose = goal;
    else {
      random_pose.pose.position.x = dis_x(gen);
      random_pose.pose.position.y = dis_y(gen);
    }

    // 2. q_rand에서 가장 가까운 노드인 q_near을 찾는다.
    int nearest_idx = -1;
    double min_dist = std::numeric_limits<double>::max();
    for (size_t j = 0; j < nodes.size(); ++j) {
      double dx = random_pose.pose.position.x - nodes[j].pose.pose.position.x;
      double dy = random_pose.pose.position.y - nodes[j].pose.pose.position.y;
      double dist = sqrt(dx*dx + dy*dy);
      if (dist < min_dist) {
        min_dist = dist;
        nearest_idx = j;
      }
    }

    // 3. q_near에서 q_rand로 뻗어날 방향을 구한다. 그리고 step_size만큼 이동한 위치의 지점을 찾는다.
    double angle = atan2(random_pose.pose.position.y - nodes[nearest_idx].pose.pose.position.y,
                         random_pose.pose.position.x - nodes[nearest_idx].pose.pose.position.x);
    double new_x = nodes[nearest_idx].pose.pose.position.x + STEP_SIZE * cos(angle);
    double new_y = nodes[nearest_idx].pose.pose.position.y + STEP_SIZE * sin(angle);

    if (!isCollisionFree(nodes[nearest_idx].pose.pose.position.x,
                         nodes[nearest_idx].pose.pose.position.y,
                         new_x, new_y)) {
      //RCLCPP_INFO(logger_ , "노드 생성중 장애물 충돌...");
      continue;
    }

    Node new_node;
    new_node.pose.pose.position.x = new_x;
    new_node.pose.pose.position.y = new_y;
    new_node.parent = nearest_idx;
    new_node.cost = nodes[new_node.parent].cost + hypot(
      new_x-nodes[new_node.parent].pose.pose.position.x , new_y-nodes[new_node.parent].pose.pose.position.y);
    nodes.push_back(new_node);
  //  new_node.pose.header.stamp = node_->now();

    if (hypot(goal.pose.position.x - new_x, goal.pose.position.y - new_y) < GOAL_THRESHOLD) {
      
      Node goal_node;
      goal_node.pose = goal;
      goal_node.parent = nodes.size() - 1;
      goal_node.cost = new_node.cost + hypot(
          goal.pose.position.x - new_x,
          goal.pose.position.y - new_y
      );
      nodes.push_back(goal_node);
      
      int current_idx = nodes.size()-1;
      RCLCPP_INFO( logger_ , "최종적인 %.6f 입니다." , nodes.back().cost );
      while (current_idx != -1) {
        path.poses.insert( path.poses.begin() , nodes[current_idx].pose);
        current_idx = nodes[current_idx].parent;
      }
      RCLCPP_INFO(logger_, "Path size: %ld", path.poses.size());
      path.header.stamp = node_->now();
      path.header.frame_id = "map";
      for (auto &pose : path.poses) {
          pose.header.stamp = path.header.stamp;
          pose.header.frame_id = path.header.frame_id;
      }
      return path;
    }
  }
  RCLCPP_WARN(logger_, "Failed to find path!");
  return path;
}




}  // namespace rrt_planner
#include <pluginlib/class_list_macros.hpp>

PLUGINLIB_EXPORT_CLASS(rrt_planner::RRTPlanner, nav2_core::GlobalPlanner)