diff --git a/src/TurtleBot-RRT-Star/CMakeLists.txt b/src/TurtleBot-RRT-Star/CMakeLists.txt new file mode 100755 index 0000000..4700401 --- /dev/null +++ b/src/TurtleBot-RRT-Star/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.5) +project(nav2_rrtstar_planner) + +# Default to C99 +set(CMAKE_C_STANDARD 99) + + +# Default to C++14 +set(CMAKE_CXX_STANDARD 14) + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_action REQUIRED) +find_package(rclcpp_lifecycle REQUIRED) +find_package(std_msgs REQUIRED) +find_package(visualization_msgs REQUIRED) +find_package(nav2_util REQUIRED) +find_package(nav2_msgs REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(builtin_interfaces REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(nav2_costmap_2d REQUIRED) +find_package(nav2_core REQUIRED) +find_package(pluginlib REQUIRED) + +include_directories( + include +) + +set(library_name ${PROJECT_NAME}_plugin) + +set(dependencies + rclcpp + rclcpp_action + rclcpp_lifecycle + std_msgs + visualization_msgs + nav2_util + nav2_msgs + nav_msgs + geometry_msgs + builtin_interfaces + tf2_ros + nav2_costmap_2d + nav2_core + pluginlib +) + +add_library(${library_name} SHARED + src/rrtstar_planner.cpp +) + +ament_target_dependencies(${library_name} + ${dependencies} +) + +target_compile_definitions(${library_name} PUBLIC "PLUGINLIB__DISABLE_BOOST_FUNCTIONS") + + +pluginlib_export_plugin_description_file(nav2_core global_planner_plugin.xml) + +install(TARGETS ${library_name} + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION lib/${PROJECT_NAME} +) + +install(DIRECTORY include/ + DESTINATION include/ +) + +install(FILES global_planner_plugin.xml + DESTINATION share/${PROJECT_NAME} +) + +install(DIRECTORY launch/ + DESTINATION share/${PROJECT_NAME}/launch +) + +install(DIRECTORY scripts/ + DESTINATION share/${PROJECT_NAME}/scripts +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + + +ament_export_include_directories(include) +ament_export_libraries(${library_name}) +ament_export_dependencies(${dependencies}) +ament_package() diff --git a/src/TurtleBot-RRT-Star/global_planner_plugin.xml b/src/TurtleBot-RRT-Star/global_planner_plugin.xml new file mode 100755 index 0000000..aa3959e --- /dev/null +++ b/src/TurtleBot-RRT-Star/global_planner_plugin.xml @@ -0,0 +1,5 @@ + + + This is a plugin for RRT Star path planning. + + diff --git a/src/TurtleBot-RRT-Star/include/nav2_rrtstar_planner/rrtstar_planner.hpp b/src/TurtleBot-RRT-Star/include/nav2_rrtstar_planner/rrtstar_planner.hpp new file mode 100755 index 0000000..ea20f35 --- /dev/null +++ b/src/TurtleBot-RRT-Star/include/nav2_rrtstar_planner/rrtstar_planner.hpp @@ -0,0 +1,60 @@ +#ifndef NAV2_RRTSTAR_PLANNER__RRTSTAR_PLANNER_HPP_ +#define NAV2_RRTSTAR_PLANNER__RRTSTAR_PLANNER_HPP_ + +#include +#include +#include +#include "rclcpp/rclcpp.hpp" +#include "nav2_core/global_planner.hpp" +#include "nav2_costmap_2d/costmap_2d_ros.hpp" +#include "tf2_ros/buffer.h" +#include "geometry_msgs/msg/pose_stamped.hpp" +#include "nav_msgs/msg/path.hpp" + +namespace nav2_rrtstar_planner { + +struct Vertex { + double x, y, cost; + Vertex* parent; + Vertex(double x_val, double y_val, Vertex* p = nullptr, double travel_distance = 0) : + x(x_val), y(y_val), parent(p), cost(travel_distance) {} +}; + +class RRTStar : public nav2_core::GlobalPlanner { +public: + RRTStar() = default; + ~RRTStar() override = default; + + void configure(const rclcpp_lifecycle::LifecycleNode::WeakPtr& parent, + std::string name, std::shared_ptr tf, + std::shared_ptr costmap_ros) override; + void cleanup() override; + void activate() override; + void deactivate() override; + + nav_msgs::msg::Path createPlan(const geometry_msgs::msg::PoseStamped& start, + const geometry_msgs::msg::PoseStamped& goal) override; + +protected: + std::shared_ptr tf_; + nav2_util::LifecycleNode::SharedPtr node_; + nav2_costmap_2d::Costmap2D* costmap_; + std::string global_frame_; + std::string name_; + int max_iterations_; + double interpolation_resolution_; + std::vector> tree_; + double ball_radius_constant_; + + double calculate_distance(double x, double y, const Vertex& vertex); + Vertex* nearest_neighbor(double x, double y); + bool connectible(const Vertex& start, const Vertex& end); + void calculateBallRadiusConstant(); + double calculateBallRadius(int tree_size, int dimensions, double max_connection_distance); + std::vector findVerticesInsideCircle(double center_x, double center_y, double radius); + double calculate_cost_from_start(const Vertex& vertex); +}; + +} // namespace nav2_rrtstar_planner + +#endif // NAV2_RRTSTAR_PLANNER__RRTSTAR_PLANNER_HPP_ \ No newline at end of file diff --git a/src/TurtleBot-RRT-Star/launch/bringup_localization_with_initial_pose.launch.py b/src/TurtleBot-RRT-Star/launch/bringup_localization_with_initial_pose.launch.py new file mode 100644 index 0000000..a684cb0 --- /dev/null +++ b/src/TurtleBot-RRT-Star/launch/bringup_localization_with_initial_pose.launch.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +""" +Launch localization with automatic AMCL initial pose setting. +No need to manually click 2D Pose Estimate in RViz. +""" + +from launch import LaunchDescription +from launch.actions import ExecuteProcess, IncludeLaunchDescription, TimerAction +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration +import os + +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + bringup_dir = get_package_share_directory('nav2_bringup') + script_path = os.path.join( + get_package_share_directory('nav2_rrtstar_planner'), + 'scripts', + 'publish_initial_pose.py' + ) + + # Launch arguments + use_sim_time = LaunchConfiguration('use_sim_time', default='true') + map_yaml_file = LaunchConfiguration('map') + + # Include original localization_launch.py + localization_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(bringup_dir, 'launch', 'localization_launch.py') + ), + launch_arguments={ + 'use_sim_time': use_sim_time, + 'map': map_yaml_file + }.items() + ) + + # Auto-publish initial pose (0, 0) after 3 seconds delay + auto_initial_pose = TimerAction( + period=3.0, + actions=[ + ExecuteProcess( + cmd=['python3', script_path], + output='screen' + ) + ] + ) + + ld = LaunchDescription() + ld.add_action(localization_launch) + ld.add_action(auto_initial_pose) + + return ld diff --git a/src/TurtleBot-RRT-Star/nav2_params.yaml b/src/TurtleBot-RRT-Star/nav2_params.yaml new file mode 100755 index 0000000..8dc3f8c --- /dev/null +++ b/src/TurtleBot-RRT-Star/nav2_params.yaml @@ -0,0 +1,364 @@ +amcl: + ros__parameters: + use_sim_time: True + # 设置初始位姿(x, y, z, yaw) + set_initial_pose: True + initial_pose: + x: 0.0 + y: 0.0 + z: 0.0 + yaw: 0.0 + alpha1: 0.2 + alpha2: 0.2 + alpha3: 0.2 + alpha4: 0.2 + alpha5: 0.2 + base_frame_id: "base_footprint" + beam_skip_distance: 0.5 + beam_skip_error_threshold: 0.9 + beam_skip_threshold: 0.3 + do_beamskip: false + global_frame_id: "map" + lambda_short: 0.1 + laser_likelihood_max_dist: 2.0 + laser_max_range: 100.0 + laser_min_range: -1.0 + laser_model_type: "likelihood_field" + max_beams: 60 + max_particles: 2000 + min_particles: 500 + odom_frame_id: "odom" + pf_err: 0.05 + pf_z: 0.99 + recovery_alpha_fast: 0.0 + recovery_alpha_slow: 0.0 + resample_interval: 1 + robot_model_type: "nav2_amcl::DifferentialMotionModel" + save_pose_rate: 0.5 + sigma_hit: 0.2 + tf_broadcast: true + transform_tolerance: 1.0 + update_min_a: 0.2 + update_min_d: 0.25 + z_hit: 0.5 + z_max: 0.05 + z_rand: 0.5 + z_short: 0.05 + scan_topic: scan + +bt_navigator: + ros__parameters: + use_sim_time: True + global_frame: map + robot_base_frame: base_link + odom_topic: /odom + bt_loop_duration: 10 + default_server_timeout: 20 + # 'default_nav_through_poses_bt_xml' and 'default_nav_to_pose_bt_xml' are use defaults: + # nav2_bt_navigator/navigate_to_pose_w_replanning_and_recovery.xml + # nav2_bt_navigator/navigate_through_poses_w_replanning_and_recovery.xml + # They can be set here or via a RewrittenYaml remap from a parent launch file to Nav2. + plugin_lib_names: + - nav2_compute_path_to_pose_action_bt_node + - nav2_compute_path_through_poses_action_bt_node + - nav2_smooth_path_action_bt_node + - nav2_follow_path_action_bt_node + - nav2_spin_action_bt_node + - nav2_wait_action_bt_node + - nav2_assisted_teleop_action_bt_node + - nav2_back_up_action_bt_node + - nav2_drive_on_heading_bt_node + - nav2_clear_costmap_service_bt_node + - nav2_is_stuck_condition_bt_node + - nav2_goal_reached_condition_bt_node + - nav2_goal_updated_condition_bt_node + - nav2_globally_updated_goal_condition_bt_node + - nav2_is_path_valid_condition_bt_node + - nav2_initial_pose_received_condition_bt_node + - nav2_reinitialize_global_localization_service_bt_node + - nav2_rate_controller_bt_node + - nav2_distance_controller_bt_node + - nav2_speed_controller_bt_node + - nav2_truncate_path_action_bt_node + - nav2_truncate_path_local_action_bt_node + - nav2_goal_updater_node_bt_node + - nav2_recovery_node_bt_node + - nav2_pipeline_sequence_bt_node + - nav2_round_robin_node_bt_node + - nav2_transform_available_condition_bt_node + - nav2_time_expired_condition_bt_node + - nav2_path_expiring_timer_condition + - nav2_distance_traveled_condition_bt_node + - nav2_single_trigger_bt_node + - nav2_goal_updated_controller_bt_node + - nav2_is_battery_low_condition_bt_node + - nav2_navigate_through_poses_action_bt_node + - nav2_navigate_to_pose_action_bt_node + - nav2_remove_passed_goals_action_bt_node + - nav2_planner_selector_bt_node + - nav2_controller_selector_bt_node + - nav2_goal_checker_selector_bt_node + - nav2_controller_cancel_bt_node + - nav2_path_longer_on_approach_bt_node + - nav2_wait_cancel_bt_node + - nav2_spin_cancel_bt_node + - nav2_back_up_cancel_bt_node + - nav2_assisted_teleop_cancel_bt_node + - nav2_drive_on_heading_cancel_bt_node + - nav2_is_battery_charging_condition_bt_node + +bt_navigator_navigate_through_poses_rclcpp_node: + ros__parameters: + use_sim_time: True + +bt_navigator_navigate_to_pose_rclcpp_node: + ros__parameters: + use_sim_time: True + +controller_server: + ros__parameters: + use_sim_time: True + controller_frequency: 20.0 + min_x_velocity_threshold: 0.001 + min_y_velocity_threshold: 0.5 + min_theta_velocity_threshold: 0.001 + failure_tolerance: 0.3 + progress_checker_plugin: "progress_checker" + goal_checker_plugins: ["general_goal_checker"] # "precise_goal_checker" + controller_plugins: ["FollowPath"] + + # Progress checker parameters + progress_checker: + plugin: "nav2_controller::SimpleProgressChecker" + required_movement_radius: 0.5 + movement_time_allowance: 10.0 + # Goal checker parameters + #precise_goal_checker: + # plugin: "nav2_controller::SimpleGoalChecker" + # xy_goal_tolerance: 0.25 + # yaw_goal_tolerance: 0.25 + # stateful: True + general_goal_checker: + stateful: True + plugin: "nav2_controller::SimpleGoalChecker" + xy_goal_tolerance: 0.25 + yaw_goal_tolerance: 0.25 + # DWB parameters + FollowPath: + plugin: "dwb_core::DWBLocalPlanner" + debug_trajectory_details: True + min_vel_x: 0.0 + min_vel_y: 0.0 + max_vel_x: 0.26 + max_vel_y: 0.0 + max_vel_theta: 1.0 + min_speed_xy: 0.0 + max_speed_xy: 0.26 + min_speed_theta: 0.0 + # Add high threshold velocity for turtlebot 3 issue. + # https://github.com/ROBOTIS-GIT/turtlebot3_simulations/issues/75 + acc_lim_x: 2.5 + acc_lim_y: 0.0 + acc_lim_theta: 3.2 + decel_lim_x: -2.5 + decel_lim_y: 0.0 + decel_lim_theta: -3.2 + vx_samples: 20 + vy_samples: 5 + vtheta_samples: 20 + sim_time: 1.7 + linear_granularity: 0.05 + angular_granularity: 0.025 + transform_tolerance: 0.2 + xy_goal_tolerance: 0.25 + trans_stopped_velocity: 0.25 + short_circuit_trajectory_evaluation: True + stateful: True + critics: ["RotateToGoal", "Oscillation", "BaseObstacle", "GoalAlign", "PathAlign", "PathDist", "GoalDist"] + BaseObstacle.scale: 0.02 + PathAlign.scale: 32.0 + PathAlign.forward_point_distance: 0.1 + GoalAlign.scale: 24.0 + GoalAlign.forward_point_distance: 0.1 + PathDist.scale: 32.0 + GoalDist.scale: 24.0 + RotateToGoal.scale: 32.0 + RotateToGoal.slowing_factor: 5.0 + RotateToGoal.lookahead_time: -1.0 + +local_costmap: + local_costmap: + ros__parameters: + update_frequency: 5.0 + publish_frequency: 2.0 + global_frame: odom + robot_base_frame: base_link + use_sim_time: True + rolling_window: true + width: 3 + height: 3 + resolution: 0.05 + robot_radius: 0.22 + plugins: ["voxel_layer", "inflation_layer"] + inflation_layer: + plugin: "nav2_costmap_2d::InflationLayer" + cost_scaling_factor: 1.0 + inflation_radius: 0.55 + voxel_layer: + plugin: "nav2_costmap_2d::VoxelLayer" + enabled: True + publish_voxel_map: True + origin_z: 0.0 + z_resolution: 0.05 + z_voxels: 16 + max_obstacle_height: 2.0 + mark_threshold: 0 + observation_sources: scan + scan: + topic: /scan + max_obstacle_height: 2.0 + clearing: True + marking: True + data_type: "LaserScan" + raytrace_max_range: 3.0 + raytrace_min_range: 0.0 + obstacle_max_range: 2.5 + obstacle_min_range: 0.0 + static_layer: + plugin: "nav2_costmap_2d::StaticLayer" + map_subscribe_transient_local: True + always_send_full_costmap: True + +global_costmap: + global_costmap: + ros__parameters: + update_frequency: 1.0 + publish_frequency: 1.0 + global_frame: map + robot_base_frame: base_link + use_sim_time: True + robot_radius: 0.22 + resolution: 0.05 + track_unknown_space: true + plugins: ["static_layer", "obstacle_layer", "inflation_layer"] + obstacle_layer: + plugin: "nav2_costmap_2d::ObstacleLayer" + enabled: True + observation_sources: scan + scan: + topic: /scan + max_obstacle_height: 2.0 + clearing: True + marking: True + data_type: "LaserScan" + raytrace_max_range: 3.0 + raytrace_min_range: 0.0 + obstacle_max_range: 2.5 + obstacle_min_range: 0.0 + static_layer: + plugin: "nav2_costmap_2d::StaticLayer" + map_subscribe_transient_local: True + inflation_layer: + plugin: "nav2_costmap_2d::InflationLayer" + cost_scaling_factor: 3.0 + inflation_radius: 0.25 + always_send_full_costmap: True + +map_server: + ros__parameters: + use_sim_time: True + # Overridden in launch by the "map" launch configuration or provided default value. + # To use in yaml, remove the default "map" value in the tb3_simulation_launch.py file & provide full path to map below. + yaml_filename: "" + +map_saver: + ros__parameters: + use_sim_time: True + save_map_timeout: 5.0 + free_thresh_default: 0.25 + occupied_thresh_default: 0.65 + map_subscribe_transient_local: True + +planner_server: + ros__parameters: + expected_planner_frequency: 0.5 + use_sim_time: True + planner_plugins: ['GridBased'] + # GridBased: + # plugin: 'nav2_navfn_planner/NavfnPlanner' + # tolerance: 0.5 + # use_astar: false + # allow_unknown: true + + planner_plugin_types: ['nav2_rrtstar_planner::RRTStar'] # For Foxy and earlier + planner_plugin_ids: ['GridBased'] # For Foxy and earlier + plugins: ['GridBased'] # For Galactic and later + use_sim_time: True + GridBased: + plugin: nav2_rrtstar_planner/RRTStar # For Galactic and later + interpolation_resolution: 0.01 + +smoother_server: + ros__parameters: + use_sim_time: True + smoother_plugins: ["simple_smoother"] + simple_smoother: + plugin: "nav2_smoother::SimpleSmoother" + tolerance: 1.0e-10 + max_its: 1000 + do_refinement: True + +behavior_server: + ros__parameters: + costmap_topic: local_costmap/costmap_raw + footprint_topic: local_costmap/published_footprint + cycle_frequency: 10.0 + behavior_plugins: ["spin", "backup", "drive_on_heading", "assisted_teleop", "wait"] + spin: + plugin: "nav2_behaviors/Spin" + backup: + plugin: "nav2_behaviors/BackUp" + drive_on_heading: + plugin: "nav2_behaviors/DriveOnHeading" + wait: + plugin: "nav2_behaviors/Wait" + assisted_teleop: + plugin: "nav2_behaviors/AssistedTeleop" + global_frame: odom + robot_base_frame: base_link + transform_tolerance: 0.1 + use_sim_time: true + simulate_ahead_time: 2.0 + max_rotational_vel: 1.0 + min_rotational_vel: 0.4 + rotational_acc_lim: 3.2 + +robot_state_publisher: + ros__parameters: + use_sim_time: True + +waypoint_follower: + ros__parameters: + use_sim_time: True + loop_rate: 20 + stop_on_failure: false + waypoint_task_executor_plugin: "wait_at_waypoint" + wait_at_waypoint: + plugin: "nav2_waypoint_follower::WaitAtWaypoint" + enabled: True + waypoint_pause_duration: 200 + +velocity_smoother: + ros__parameters: + use_sim_time: True + smoothing_frequency: 20.0 + scale_velocities: False + feedback: "OPEN_LOOP" + max_velocity: [0.26, 0.0, 1.0] + min_velocity: [-0.26, 0.0, -1.0] + max_accel: [2.5, 0.0, 3.2] + max_decel: [-2.5, 0.0, -3.2] + odom_topic: "odom" + odom_duration: 0.1 + deadband_velocity: [0.0, 0.0, 0.0] + velocity_timeout: 1.0 diff --git a/src/TurtleBot-RRT-Star/package.xml b/src/TurtleBot-RRT-Star/package.xml new file mode 100755 index 0000000..118575b --- /dev/null +++ b/src/TurtleBot-RRT-Star/package.xml @@ -0,0 +1,38 @@ + + + + nav2_rrtstar_planner + 1.0.0 + RRT Star path planner. + echo + BSD-3-Clause + + ament_cmake + + rclcpp + rclcpp_action + rclcpp_lifecycle + std_msgs + visualization_msgs + nav2_util + nav2_msgs + nav_msgs + geometry_msgs + builtin_interfaces + tf2_ros + nav2_costmap_2d + nav2_core + pluginlib + nav2_bringup + random + vector + limits + + ament_lint_auto + ament_lint_common + + + ament_cmake + + + diff --git a/src/TurtleBot-RRT-Star/scripts/publish_initial_pose.py b/src/TurtleBot-RRT-Star/scripts/publish_initial_pose.py new file mode 100755 index 0000000..4bf9f98 --- /dev/null +++ b/src/TurtleBot-RRT-Star/scripts/publish_initial_pose.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Publish initial pose for AMCL localization.""" + +import rclpy +from rclpy.node import Node +from geometry_msgs.msg import PoseWithCovarianceStamped + + +class InitialPosePublisher(Node): + def __init__(self): + super().__init__('initial_pose_publisher') + self.publisher = self.create_publisher( + PoseWithCovarianceStamped, + '/initialpose', + 10 + ) + self.timer = self.create_timer(1.0, self.publish_pose) + self.get_logger().info('Initial pose publisher started') + self.published = False + + def publish_pose(self): + if self.published: + return + + msg = PoseWithCovarianceStamped() + msg.header.stamp.sec = 0 + msg.header.stamp.nanosec = 0 + msg.header.frame_id = 'map' + msg.pose.pose.position.x = 0.0 + msg.pose.pose.position.y = 0.0 + msg.pose.pose.position.z = 0.0 + msg.pose.pose.orientation.x = 0.0 + msg.pose.pose.orientation.y = 0.0 + msg.pose.pose.orientation.z = 0.0 + msg.pose.pose.orientation.w = 1.0 + msg.pose.covariance = [ + 0.25, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.25, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.06853891945200942 + ] + + self.publisher.publish(msg) + self.get_logger().info('Published initial pose (0, 0)') + self.published = True + # Shutdown after publishing + self.get_logger().info('Shutting down initial pose publisher') + raise rclpy.shutdown() + + +def main(args=None): + rclpy.init(args=args) + try: + node = InitialPosePublisher() + rclpy.spin(node) + except KeyboardInterrupt: + pass + finally: + if rclpy.ok(): + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/TurtleBot-RRT-Star/src/rrtstar_planner.cpp b/src/TurtleBot-RRT-Star/src/rrtstar_planner.cpp new file mode 100755 index 0000000..d7dc836 --- /dev/null +++ b/src/TurtleBot-RRT-Star/src/rrtstar_planner.cpp @@ -0,0 +1,321 @@ +#include +#include +#include +#include "nav2_util/node_utils.hpp" +#include +#include +#include + +#include "nav2_rrtstar_planner/rrtstar_planner.hpp" + +namespace nav2_rrtstar_planner +{ + +void RRTStar::configure( + const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent, + std::string name, std::shared_ptr tf, + std::shared_ptr costmap_ros) +{ + node_ = parent.lock(); + if (!node_) { + RCLCPP_ERROR(rclcpp::get_logger("RRTStar"), "Failed to lock parent node in configure; parent is expired."); + return; + } + name_ = name; + tf_ = tf; + costmap_ = costmap_ros->getCostmap(); + global_frame_ = costmap_ros->getGlobalFrameID(); + max_iterations_ = 1000; + + // Parameter initialization + nav2_util::declare_parameter_if_not_declared( + node_, name_ + ".interpolation_resolution", rclcpp::ParameterValue(0.01)); + node_->get_parameter(name_ + ".interpolation_resolution", interpolation_resolution_); +} + +void RRTStar::cleanup() +{ + RCLCPP_INFO( + node_->get_logger(), "CleaningUp plugin %s of type NavfnPlanner", + name_.c_str()); +} + +void RRTStar::activate() +{ + RCLCPP_INFO( + node_->get_logger(), "Activating plugin %s of type NavfnPlanner", + name_.c_str()); +} + +void RRTStar::deactivate() +{ + RCLCPP_INFO( + node_->get_logger(), "Deactivating plugin %s of type NavfnPlanner", + name_.c_str()); +} + +void RRTStar::calculateBallRadiusConstant() { + double resolution = costmap_->getResolution(); + double cellArea = resolution * resolution; + unsigned int numFreeCells = 0; + + for (unsigned int x = 0; x < costmap_->getSizeInCellsX(); x++) { + for (unsigned int y = 0; y < costmap_->getSizeInCellsY(); y++) { + if (costmap_->getCost(x, y) == nav2_costmap_2d::FREE_SPACE) { + numFreeCells++; + } + } + } + + double freeVolume = cellArea * numFreeCells; + int dimensions = 2; + double vUnitBall = M_PI; + ball_radius_constant_ = 2.0 * (1 + 1.0 / dimensions) * std::pow((freeVolume / vUnitBall), (1.0 / dimensions)); +} + +double RRTStar::calculateBallRadius(int tree_size, int dimensions, double max_connection_distance) { + double term1 = (ball_radius_constant_ * std::log(tree_size)) / tree_size; + double term2 = std::pow(term1, 1.0 / dimensions); + return std::min(term2, max_connection_distance); +} + +std::vector RRTStar::findVerticesInsideCircle(double center_x, double center_y, double radius) { + std::vector vertices_inside_circle; + double radius_squared = radius * radius; + + for (int i = 0; i < tree_.size(); ++i) { + // Dereference unique_ptr to access x and y + double distance_squared = std::pow((*tree_[i]).x - center_x, 2) + std::pow((*tree_[i]).y - center_y, 2); + if (distance_squared <= radius_squared) { + vertices_inside_circle.push_back(i); + } + } + return vertices_inside_circle; +} + + +double RRTStar::calculate_distance(double x, double y, const Vertex& vertex) { + return std::sqrt(std::pow(vertex.x - x, 2) + std::pow(vertex.y - y, 2)); +} + +Vertex* RRTStar::nearest_neighbor(double x, double y) { + Vertex* nearest_vertex = nullptr; + double min_dist = std::numeric_limits::infinity(); + + for (const auto& vertex : tree_) { + // Dereference unique_ptr to pass Vertex reference to calculate_distance + double dist = calculate_distance(x, y, *vertex); + if (dist < min_dist) { + min_dist = dist; + nearest_vertex = vertex.get(); // Set to raw pointer of the unique_ptr + } + } + return nearest_vertex; +} + + +bool RRTStar::connectible(const Vertex& start, const Vertex& end) { + double resolution = interpolation_resolution_; + double steps = std::ceil(std::hypot(end.x - start.x, end.y - start.y) / resolution); + if (steps > 0){ + double x_increment = (end.x - start.x) / steps; + double y_increment = (end.y - start.y) / steps; + + double x = start.x, y = start.y; + for (int i = 0; i < steps; ++i) { + unsigned int mx, my; + if (!costmap_->worldToMap(x, y, mx, my)) return false; + if (costmap_->getCost(mx, my) != nav2_costmap_2d::FREE_SPACE) return false; + x += x_increment; + y += y_increment; + } + } + return true; +} + +double RRTStar::calculate_cost_from_start(const Vertex& vertex) { + double total_cost = 0.0; + + const Vertex* cur_ver = &vertex; + + while (cur_ver != nullptr) { + total_cost += cur_ver->cost; + cur_ver = cur_ver->parent; + } + + return total_cost; +} + +nav_msgs::msg::Path RRTStar::createPlan( + const geometry_msgs::msg::PoseStamped & start, + const geometry_msgs::msg::PoseStamped & goal) +{ + nav_msgs::msg::Path global_path; + + // Checking if the goal and start state is in the global frame + if (start.header.frame_id != global_frame_) { + RCLCPP_ERROR( + node_->get_logger(), "Planner will only accept start position from %s frame", + global_frame_.c_str()); + return global_path; + } + + if (goal.header.frame_id != global_frame_) { + RCLCPP_INFO( + node_->get_logger(), "Planner will only accept goal position from %s frame", + global_frame_.c_str()); + return global_path; + } + + global_path.poses.clear(); + global_path.header.stamp = node_->now(); + global_path.header.frame_id = global_frame_; + + // Set up a random position generator + calculateBallRadiusConstant(); + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> x_dis(costmap_->getOriginX(), costmap_->getOriginX() + costmap_->getSizeInCellsX() * costmap_->getResolution()); + std::uniform_real_distribution<> y_dis(costmap_->getOriginY(), costmap_->getOriginY() + costmap_->getSizeInCellsY() * costmap_->getResolution()); + + // Add start position to the tree + tree_.clear(); + tree_.reserve(max_iterations_); + auto start_vertex = std::make_unique(start.pose.position.x, start.pose.position.y); + start_vertex->cost = 0; + tree_.emplace_back(std::move(start_vertex)); + + // Create vertex for the end point + Vertex end_vertex(goal.pose.position.x, goal.pose.position.y); + + geometry_msgs::msg::PoseStamped pose; + pose.pose.position.x = goal.pose.position.x; + pose.pose.position.y = goal.pose.position.y; + pose.pose.position.z = 0.0; + pose.pose.orientation = goal.pose.orientation; + global_path.poses.insert(global_path.poses.begin(), pose); + + for (int i = 1; i <= max_iterations_ - 1; ++i) { + // Generate a random point + double rand_x = x_dis(gen); + double rand_y = y_dis(gen); + auto new_position = std::make_unique(rand_x, rand_y); + + // Find nearest neighbor and assign its parent to new_position + Vertex* nearest = nearest_neighbor(rand_x, rand_y); + new_position->parent = nearest; // Use raw pointer to nearest vertex + new_position->cost = calculate_distance(nearest->x, nearest->y, *new_position); + + if (connectible(*nearest, *new_position)) { + // Perform rewire operation + double ball_radius = calculateBallRadius(tree_.size(), 2, 2.0); + + std::vector vertices_inside_circle = findVerticesInsideCircle(new_position->x, new_position->y, ball_radius); + tree_.emplace_back(std::move(new_position)); + + RCLCPP_INFO(node_->get_logger(), "New vertex x: %.4f", tree_.back()->x); + RCLCPP_INFO(node_->get_logger(), "New vertex y: %.4f", tree_.back()->y); + + double total_cost_for_new_position = calculate_cost_from_start(*tree_.back()); + + int num_of_rewiring = 0; + + // Check if there is a better route from start towards the new position + for (size_t j = 0; j < vertices_inside_circle.size(); ++j) { + int index = vertices_inside_circle[j]; + double potential_cost = calculate_cost_from_start(*tree_[index]) + calculate_distance(tree_.back()->x, tree_.back()->y, *tree_[index]); + if (potential_cost < total_cost_for_new_position && connectible(*tree_.back(), *tree_[index])) { + tree_.back()->parent = tree_[index].get(); + tree_.back()->cost = calculate_distance(tree_.back()->x, tree_.back()->y, *tree_[index]); + total_cost_for_new_position = potential_cost; + num_of_rewiring += 1; + } + } + + // Check if any existing vertex may benefit from being connected by the new position + for (size_t j = 0; j < vertices_inside_circle.size(); ++j) { + int index = vertices_inside_circle[j]; + double current_cost = calculate_cost_from_start(*tree_[index]); + double potential_cost = calculate_cost_from_start(*tree_.back()) + calculate_distance(tree_.back()->x, tree_.back()->y, *tree_[index]); + if (potential_cost < current_cost && connectible(*tree_.back(), *tree_[index])) { + tree_[index]->parent = tree_.back().get(); + tree_[index]->cost = calculate_distance(tree_.back()->x, tree_.back()->y, *tree_[index]); + num_of_rewiring += 1; + } + } + } else { + i -= 1; + } + } + + // Find optimal way to the goal + double ball_radius = 2 * calculateBallRadius(tree_.size(), 2, 2.0); + std::vector vertices_inside_circle = findVerticesInsideCircle(goal.pose.position.x, goal.pose.position.y, ball_radius); + + while (true) { + double min_cost = std::numeric_limits::infinity(); + + for (size_t j = 0; j < vertices_inside_circle.size(); ++j) { + int index = vertices_inside_circle[j]; + double potential_cost = calculate_cost_from_start(*tree_[index]) + calculate_distance(goal.pose.position.x, goal.pose.position.y, *tree_[index]); + if (potential_cost < min_cost && connectible(end_vertex, *tree_[index])) { + end_vertex.parent = tree_[index].get(); + end_vertex.cost = calculate_distance(goal.pose.position.x, goal.pose.position.y, *tree_[index]); + min_cost = potential_cost; + } + } + + if (min_cost < 10000) { + auto end_vertex_ptr = std::make_unique(end_vertex); + tree_.emplace_back(std::move(end_vertex_ptr)); + + Vertex* cur_ver = &end_vertex; + while (cur_ver) { + geometry_msgs::msg::PoseStamped pose; + pose.pose.position.x = cur_ver->x; + pose.pose.position.y = cur_ver->y; + pose.pose.position.z = 0.0; + + global_path.poses.insert(global_path.poses.begin(), pose); + + // Add waypoints between the random points + if (cur_ver->parent != nullptr) { + double steps = std::ceil(std::hypot(cur_ver->x - cur_ver->parent->x, cur_ver->y - cur_ver->parent->y) * 10); + double x_increment = (cur_ver->parent->x - cur_ver->x) / steps; + double y_increment = (cur_ver->parent->y - cur_ver->y) / steps; + + double x = cur_ver->x; + double y = cur_ver->y; + + for (int i = 0; i < steps - 1; ++i) { + x += x_increment; + y += y_increment; + geometry_msgs::msg::PoseStamped pose; + pose.pose.position.x = x; + pose.pose.position.y = y; + pose.pose.position.z = 0.0; + + global_path.poses.insert(global_path.poses.begin(), pose); + } + } + cur_ver = cur_ver->parent; + } + break; + } + + if (ball_radius > 100) { + break; + } + + ball_radius += 0.5; + vertices_inside_circle = findVerticesInsideCircle(goal.pose.position.x, goal.pose.position.y, ball_radius); + } + + return global_path; +} + + +} // namespace nav2_rrtstar_planner + +#include "pluginlib/class_list_macros.hpp" +PLUGINLIB_EXPORT_CLASS(nav2_rrtstar_planner::RRTStar, nav2_core::GlobalPlanner)