add humble-navigation2
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(nav2_constrained_smoother)
|
||||
|
||||
set(CMAKE_BUILD_TYPE Release) # significant Ceres optimization speedup
|
||||
|
||||
find_package(ament_cmake REQUIRED)
|
||||
find_package(nav2_core REQUIRED)
|
||||
find_package(nav2_common REQUIRED)
|
||||
find_package(angles REQUIRED)
|
||||
find_package(rclcpp REQUIRED)
|
||||
find_package(rclcpp_action REQUIRED)
|
||||
find_package(nav2_util REQUIRED)
|
||||
find_package(nav2_msgs REQUIRED)
|
||||
find_package(nav2_costmap_2d REQUIRED)
|
||||
find_package(pluginlib REQUIRED)
|
||||
find_package(Ceres REQUIRED COMPONENTS SuiteSparse)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(${CERES_VERSION} VERSION_LESS_EQUAL 2.0.0)
|
||||
add_definitions(-DUSE_OLD_CERES_API)
|
||||
endif()
|
||||
|
||||
nav2_package()
|
||||
|
||||
set(library_name nav2_constrained_smoother)
|
||||
|
||||
include_directories(
|
||||
include
|
||||
${CERES_INCLUDES}
|
||||
)
|
||||
|
||||
set(dependencies
|
||||
angles
|
||||
rclcpp
|
||||
rclcpp_action
|
||||
nav2_msgs
|
||||
nav2_costmap_2d
|
||||
nav2_util
|
||||
nav2_core
|
||||
pluginlib
|
||||
)
|
||||
|
||||
add_library(${library_name} SHARED src/constrained_smoother.cpp)
|
||||
target_link_libraries(${library_name} ${CERES_LIBRARIES})
|
||||
# prevent pluginlib from using boost
|
||||
target_compile_definitions(${library_name} PUBLIC "PLUGINLIB__DISABLE_BOOST_FUNCTIONS")
|
||||
ament_target_dependencies(${library_name} ${dependencies})
|
||||
|
||||
if(BUILD_TESTING)
|
||||
find_package(ament_lint_auto REQUIRED)
|
||||
# the following line skips the linter which checks for copyrights
|
||||
set(ament_cmake_copyright_FOUND TRUE)
|
||||
ament_lint_auto_find_test_dependencies()
|
||||
|
||||
find_package(ament_cmake_gtest REQUIRED)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
${library_name}
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
|
||||
install(DIRECTORY include/
|
||||
DESTINATION include/
|
||||
)
|
||||
|
||||
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})
|
||||
|
||||
pluginlib_export_plugin_description_file(nav2_core nav2_constrained_smoother.xml)
|
||||
|
||||
ament_package()
|
||||
@@ -0,0 +1,49 @@
|
||||
# Constrained Smoother
|
||||
|
||||
A smoother plugin for `nav2_smoother` based on the original deprecated smoother in `nav2_smac_planner` by [Steve Macenski](https://www.linkedin.com/in/steve-macenski-41a985101/) and put into operational state by [**RoboTech Vision**](https://robotechvision.com/). Suitable for applications which need planned global path to be pushed away from obstacles and/or for Reeds-Shepp motion models.
|
||||
|
||||
See documentation on navigation.ros.org: https://navigation.ros.org/configuration/packages/configuring-constrained-smoother.html
|
||||
|
||||
|
||||
Example of configuration (see indoor_navigation package of this repo for a full launch configuration):
|
||||
|
||||
```
|
||||
smoother_server:
|
||||
ros__parameters:
|
||||
use_sim_time: True
|
||||
smoother_plugins: ["SmoothPath"]
|
||||
|
||||
SmoothPath:
|
||||
plugin: "nav2_constrained_smoother/ConstrainedSmoother"
|
||||
reversing_enabled: true # whether to detect forward/reverse direction and cusps. Should be set to false for paths without orientations assigned
|
||||
path_downsampling_factor: 3 # every n-th node of the path is taken. Useful for speed-up
|
||||
path_upsampling_factor: 1 # 0 - path remains downsampled, 1 - path is upsampled back to original granularity using cubic bezier, 2... - more upsampling
|
||||
keep_start_orientation: true # whether to prevent the start orientation from being smoothed
|
||||
keep_goal_orientation: true # whether to prevent the gpal orientation from being smoothed
|
||||
minimum_turning_radius: 0.40 # minimum turning radius the robot can perform. Can be set to 0.0 (or w_curve can be set to 0.0 with the same effect) for diff-drive/holonomic robots
|
||||
w_curve: 30.0 # weight to enforce minimum_turning_radius
|
||||
w_dist: 0.0 # weight to bind path to original as optional replacement for cost weight
|
||||
w_smooth: 2000000.0 # weight to maximize smoothness of path
|
||||
w_cost: 0.015 # weight to steer robot away from collision and cost
|
||||
|
||||
# Parameters used to improve obstacle avoidance near cusps (forward/reverse movement changes)
|
||||
# See the [docs page](https://navigation.ros.org/configuration/packages/configuring-constrained-smoother) for further clarification
|
||||
w_cost_cusp_multiplier: 3.0 # option to have higher weight during forward/reverse direction change which is often accompanied with dangerous rotations
|
||||
cusp_zone_length: 2.5 # length of the section around cusp in which nodes use w_cost_cusp_multiplier (w_cost rises gradually inside the zone towards the cusp point, whose costmap weight equals w_cost*w_cost_cusp_multiplier)
|
||||
|
||||
# Points in robot frame to grab costmap values from. Format: [x1, y1, weight1, x2, y2, weight2, ...]
|
||||
# IMPORTANT: Requires much higher number of iterations to actually improve the path. Uncomment only if you really need it (highly elongated/asymmetric robots)
|
||||
# See the [docs page](https://navigation.ros.org/configuration/packages/configuring-constrained-smoother) for further clarification
|
||||
# cost_check_points: [-0.185, 0.0, 1.0]
|
||||
|
||||
optimizer:
|
||||
max_iterations: 70 # max iterations of smoother
|
||||
debug_optimizer: false # print debug info
|
||||
gradient_tol: 5e3
|
||||
fn_tol: 1.0e-15
|
||||
param_tol: 1.0e-20
|
||||
```
|
||||
|
||||
Note: Smoothing paths which contain multiple subsequent poses at one point (e.g. in-place rotations from Smac lattice planners) is currently not supported
|
||||
|
||||
Note: Constrained Smoother is recommended to be used on a path with a bounded length. TruncatePathLocal BT Node can be used for extracting a relevant path section around robot (in combination with DistanceController to achieve periodicity)
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2021 RoboTech Vision
|
||||
// Copyright (c) 2020 Shrijit Singh
|
||||
// Copyright (c) 2020 Samsung Research America
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef NAV2_CONSTRAINED_SMOOTHER__CONSTRAINED_SMOOTHER_HPP_
|
||||
#define NAV2_CONSTRAINED_SMOOTHER__CONSTRAINED_SMOOTHER_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
#include "nav2_core/smoother.hpp"
|
||||
#include "nav2_constrained_smoother/smoother.hpp"
|
||||
#include "rclcpp/rclcpp.hpp"
|
||||
#include "nav2_util/odometry_utils.hpp"
|
||||
#include "nav2_util/geometry_utils.hpp"
|
||||
#include "geometry_msgs/msg/pose2_d.hpp"
|
||||
|
||||
namespace nav2_constrained_smoother
|
||||
{
|
||||
|
||||
/**
|
||||
* @class nav2_constrained_smoother::ConstrainedSmoother
|
||||
* @brief Regulated pure pursuit controller plugin
|
||||
*/
|
||||
class ConstrainedSmoother : public nav2_core::Smoother
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for nav2_constrained_smoother::ConstrainedSmoother
|
||||
*/
|
||||
ConstrainedSmoother() = default;
|
||||
|
||||
/**
|
||||
* @brief Destrructor for nav2_constrained_smoother::ConstrainedSmoother
|
||||
*/
|
||||
~ConstrainedSmoother() override = default;
|
||||
|
||||
/**
|
||||
* @brief Configure smoother parameters and member variables
|
||||
* @param parent WeakPtr to node
|
||||
* @param name Name of plugin
|
||||
* @param tf TF buffer
|
||||
* @param costmap_ros Costmap2DROS object of environment
|
||||
*/
|
||||
void configure(
|
||||
const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
|
||||
std::string name, std::shared_ptr<tf2_ros::Buffer> tf,
|
||||
std::shared_ptr<nav2_costmap_2d::CostmapSubscriber> costmap_sub,
|
||||
std::shared_ptr<nav2_costmap_2d::FootprintSubscriber> footprint_sub) override;
|
||||
|
||||
/**
|
||||
* @brief Cleanup controller state machine
|
||||
*/
|
||||
void cleanup() override;
|
||||
|
||||
/**
|
||||
* @brief Activate controller state machine
|
||||
*/
|
||||
void activate() override;
|
||||
|
||||
/**
|
||||
* @brief Deactivate controller state machine
|
||||
*/
|
||||
void deactivate() override;
|
||||
|
||||
/**
|
||||
* @brief Method to smooth given path
|
||||
*
|
||||
* @param path In-out path to be optimized
|
||||
* @param max_time Maximum duration smoothing should take
|
||||
* @return Smoothed path
|
||||
*/
|
||||
bool smooth(
|
||||
nav_msgs::msg::Path & path,
|
||||
const rclcpp::Duration & max_time) override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<tf2_ros::Buffer> tf_;
|
||||
std::string plugin_name_;
|
||||
std::shared_ptr<nav2_costmap_2d::CostmapSubscriber> costmap_sub_;
|
||||
rclcpp::Logger logger_ {rclcpp::get_logger("ConstrainedSmoother")};
|
||||
|
||||
std::unique_ptr<nav2_constrained_smoother::Smoother> smoother_;
|
||||
SmootherParams smoother_params_;
|
||||
OptimizerParams optimizer_params_;
|
||||
};
|
||||
|
||||
} // namespace nav2_constrained_smoother
|
||||
|
||||
#endif // NAV2_CONSTRAINED_SMOOTHER__CONSTRAINED_SMOOTHER_HPP_
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2021 RoboTech Vision
|
||||
// Copyright (c) 2020, Samsung Research America
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License. Reserved.
|
||||
|
||||
#ifndef NAV2_CONSTRAINED_SMOOTHER__OPTIONS_HPP_
|
||||
#define NAV2_CONSTRAINED_SMOOTHER__OPTIONS_HPP_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "rclcpp_lifecycle/lifecycle_node.hpp"
|
||||
#include "nav2_util/node_utils.hpp"
|
||||
#include "ceres/ceres.h"
|
||||
|
||||
namespace nav2_constrained_smoother
|
||||
{
|
||||
|
||||
/**
|
||||
* @struct nav2_smac_planner::SmootherParams
|
||||
* @brief Parameters for the smoother cost function
|
||||
*/
|
||||
struct SmootherParams
|
||||
{
|
||||
/**
|
||||
* @brief A constructor for nav2_smac_planner::SmootherParams
|
||||
*/
|
||||
SmootherParams()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get params from ROS parameter
|
||||
* @param node_ Ptr to node
|
||||
* @param name Name of plugin
|
||||
*/
|
||||
void get(rclcpp_lifecycle::LifecycleNode * node, const std::string & name)
|
||||
{
|
||||
std::string local_name = name + std::string(".");
|
||||
|
||||
// Smoother params
|
||||
double minimum_turning_radius;
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, name + ".minimum_turning_radius", rclcpp::ParameterValue(0.4));
|
||||
node->get_parameter(name + ".minimum_turning_radius", minimum_turning_radius);
|
||||
max_curvature = 1.0f / minimum_turning_radius;
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "w_curve", rclcpp::ParameterValue(30.0));
|
||||
node->get_parameter(local_name + "w_curve", curvature_weight);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "w_cost", rclcpp::ParameterValue(0.015));
|
||||
node->get_parameter(local_name + "w_cost", costmap_weight);
|
||||
double cost_cusp_multiplier;
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "w_cost_cusp_multiplier", rclcpp::ParameterValue(3.0));
|
||||
node->get_parameter(local_name + "w_cost_cusp_multiplier", cost_cusp_multiplier);
|
||||
cusp_costmap_weight = costmap_weight * cost_cusp_multiplier;
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "cusp_zone_length", rclcpp::ParameterValue(2.5));
|
||||
node->get_parameter(local_name + "cusp_zone_length", cusp_zone_length);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "w_dist", rclcpp::ParameterValue(0.0));
|
||||
node->get_parameter(local_name + "w_dist", distance_weight);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "w_smooth", rclcpp::ParameterValue(2000000.0));
|
||||
node->get_parameter(local_name + "w_smooth", smooth_weight);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "cost_check_points", rclcpp::ParameterValue(std::vector<double>()));
|
||||
node->get_parameter(local_name + "cost_check_points", cost_check_points);
|
||||
if (cost_check_points.size() % 3 != 0) {
|
||||
RCLCPP_ERROR(
|
||||
rclcpp::get_logger(
|
||||
"constrained_smoother"),
|
||||
"cost_check_points parameter must contain values as follows: "
|
||||
"[x1, y1, weight1, x2, y2, weight2, ...]");
|
||||
throw std::runtime_error("Invalid parameter: cost_check_points");
|
||||
}
|
||||
// normalize check point weights so that their sum == 1.0
|
||||
double check_point_weights_sum = 0.0;
|
||||
for (size_t i = 2u; i < cost_check_points.size(); i += 3) {
|
||||
check_point_weights_sum += cost_check_points[i];
|
||||
}
|
||||
for (size_t i = 2u; i < cost_check_points.size(); i += 3) {
|
||||
cost_check_points[i] /= check_point_weights_sum;
|
||||
}
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "path_downsampling_factor", rclcpp::ParameterValue(1));
|
||||
node->get_parameter(local_name + "path_downsampling_factor", path_downsampling_factor);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "path_upsampling_factor", rclcpp::ParameterValue(1));
|
||||
node->get_parameter(local_name + "path_upsampling_factor", path_upsampling_factor);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "reversing_enabled", rclcpp::ParameterValue(true));
|
||||
node->get_parameter(local_name + "reversing_enabled", reversing_enabled);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "keep_goal_orientation", rclcpp::ParameterValue(true));
|
||||
node->get_parameter(local_name + "keep_goal_orientation", keep_goal_orientation);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "keep_start_orientation", rclcpp::ParameterValue(true));
|
||||
node->get_parameter(local_name + "keep_start_orientation", keep_start_orientation);
|
||||
}
|
||||
|
||||
double smooth_weight{0.0};
|
||||
double costmap_weight{0.0};
|
||||
double cusp_costmap_weight{0.0};
|
||||
double cusp_zone_length{0.0};
|
||||
double distance_weight{0.0};
|
||||
double curvature_weight{0.0};
|
||||
double max_curvature{0.0};
|
||||
double max_time{10.0}; // adjusted by action goal, not by parameters
|
||||
int path_downsampling_factor{1};
|
||||
int path_upsampling_factor{1};
|
||||
bool reversing_enabled{true};
|
||||
bool keep_goal_orientation{true};
|
||||
bool keep_start_orientation{true};
|
||||
std::vector<double> cost_check_points{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct nav2_smac_planner::OptimizerParams
|
||||
* @brief Parameters for the ceres optimizer
|
||||
*/
|
||||
struct OptimizerParams
|
||||
{
|
||||
OptimizerParams()
|
||||
: debug(false),
|
||||
max_iterations(50),
|
||||
param_tol(1e-8),
|
||||
fn_tol(1e-6),
|
||||
gradient_tol(1e-10)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get params from ROS parameter
|
||||
* @param node_ Ptr to node
|
||||
* @param name Name of plugin
|
||||
*/
|
||||
void get(rclcpp_lifecycle::LifecycleNode * node, const std::string & name)
|
||||
{
|
||||
std::string local_name = name + std::string(".optimizer.");
|
||||
|
||||
// Optimizer params
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "linear_solver_type", rclcpp::ParameterValue("SPARSE_NORMAL_CHOLESKY"));
|
||||
node->get_parameter(local_name + "linear_solver_type", linear_solver_type);
|
||||
if (solver_types.find(linear_solver_type) == solver_types.end()) {
|
||||
std::stringstream valid_types_str;
|
||||
for (auto type = solver_types.begin(); type != solver_types.end(); type++) {
|
||||
if (type != solver_types.begin()) {
|
||||
valid_types_str << ", ";
|
||||
}
|
||||
valid_types_str << type->first;
|
||||
}
|
||||
RCLCPP_ERROR(
|
||||
rclcpp::get_logger("constrained_smoother"),
|
||||
"Invalid linear_solver_type. Valid values are %s", valid_types_str.str().c_str());
|
||||
throw std::runtime_error("Invalid parameter: linear_solver_type");
|
||||
}
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "param_tol", rclcpp::ParameterValue(1e-15));
|
||||
node->get_parameter(local_name + "param_tol", param_tol);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "fn_tol", rclcpp::ParameterValue(1e-7));
|
||||
node->get_parameter(local_name + "fn_tol", fn_tol);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "gradient_tol", rclcpp::ParameterValue(1e-10));
|
||||
node->get_parameter(local_name + "gradient_tol", gradient_tol);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "max_iterations", rclcpp::ParameterValue(100));
|
||||
node->get_parameter(local_name + "max_iterations", max_iterations);
|
||||
nav2_util::declare_parameter_if_not_declared(
|
||||
node, local_name + "debug_optimizer", rclcpp::ParameterValue(false));
|
||||
node->get_parameter(local_name + "debug_optimizer", debug);
|
||||
}
|
||||
|
||||
const std::map<std::string, ceres::LinearSolverType> solver_types = {
|
||||
{"DENSE_QR", ceres::DENSE_QR},
|
||||
{"SPARSE_NORMAL_CHOLESKY", ceres::SPARSE_NORMAL_CHOLESKY}};
|
||||
|
||||
bool debug;
|
||||
std::string linear_solver_type;
|
||||
int max_iterations; // Ceres default: 50
|
||||
|
||||
double param_tol; // Ceres default: 1e-8
|
||||
double fn_tol; // Ceres default: 1e-6
|
||||
double gradient_tol; // Ceres default: 1e-10
|
||||
};
|
||||
|
||||
} // namespace nav2_constrained_smoother
|
||||
|
||||
#endif // NAV2_CONSTRAINED_SMOOTHER__OPTIONS_HPP_
|
||||
@@ -0,0 +1,401 @@
|
||||
// Copyright (c) 2021 RoboTech Vision
|
||||
// Copyright (c) 2020, Samsung Research America
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License. Reserved.
|
||||
|
||||
#ifndef NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_HPP_
|
||||
#define NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_HPP_
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
|
||||
#include "nav2_constrained_smoother/smoother_cost_function.hpp"
|
||||
#include "nav2_constrained_smoother/utils.hpp"
|
||||
|
||||
#include "ceres/ceres.h"
|
||||
#include "Eigen/Core"
|
||||
|
||||
namespace nav2_constrained_smoother
|
||||
{
|
||||
|
||||
/**
|
||||
* @class nav2_smac_planner::Smoother
|
||||
* @brief A Conjugate Gradient 2D path smoother implementation
|
||||
*/
|
||||
class Smoother
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief A constructor for nav2_smac_planner::Smoother
|
||||
*/
|
||||
Smoother() {}
|
||||
|
||||
/**
|
||||
* @brief A destructor for nav2_smac_planner::Smoother
|
||||
*/
|
||||
~Smoother() {}
|
||||
|
||||
/**
|
||||
* @brief Initialization of the smoother
|
||||
* @param params OptimizerParam struct
|
||||
*/
|
||||
void initialize(const OptimizerParams params)
|
||||
{
|
||||
debug_ = params.debug;
|
||||
|
||||
options_.linear_solver_type = params.solver_types.at(params.linear_solver_type);
|
||||
|
||||
options_.max_num_iterations = params.max_iterations;
|
||||
|
||||
options_.function_tolerance = params.fn_tol;
|
||||
options_.gradient_tolerance = params.gradient_tol;
|
||||
options_.parameter_tolerance = params.param_tol;
|
||||
|
||||
if (debug_) {
|
||||
options_.minimizer_progress_to_stdout = true;
|
||||
options_.logging_type = ceres::LoggingType::PER_MINIMIZER_ITERATION;
|
||||
} else {
|
||||
options_.logging_type = ceres::SILENT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Smoother method
|
||||
* @param path Reference to path
|
||||
* @param start_dir Orientation of the first pose
|
||||
* @param end_dir Orientation of the last pose
|
||||
* @param costmap Pointer to minimal costmap
|
||||
* @param params parameters weights
|
||||
* @return If smoothing was successful
|
||||
*/
|
||||
bool smooth(
|
||||
std::vector<Eigen::Vector3d> & path,
|
||||
const Eigen::Vector2d & start_dir,
|
||||
const Eigen::Vector2d & end_dir,
|
||||
const nav2_costmap_2d::Costmap2D * costmap,
|
||||
const SmootherParams & params)
|
||||
{
|
||||
// Path has always at least 2 points
|
||||
if (path.size() < 2) {
|
||||
throw std::runtime_error("Constrained smoother: Path must have at least 2 points");
|
||||
}
|
||||
|
||||
options_.max_solver_time_in_seconds = params.max_time;
|
||||
|
||||
ceres::Problem problem;
|
||||
std::vector<Eigen::Vector3d> path_optim;
|
||||
std::vector<bool> optimized;
|
||||
if (buildProblem(path, costmap, params, problem, path_optim, optimized)) {
|
||||
// solve the problem
|
||||
ceres::Solver::Summary summary;
|
||||
ceres::Solve(options_, &problem, &summary);
|
||||
if (debug_) {
|
||||
RCLCPP_INFO(rclcpp::get_logger("smoother_server"), "%s", summary.FullReport().c_str());
|
||||
}
|
||||
if (!summary.IsSolutionUsable() || summary.initial_cost - summary.final_cost < 0.0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
RCLCPP_INFO(rclcpp::get_logger("smoother_server"), "Path too short to optimize");
|
||||
}
|
||||
|
||||
upsampleAndPopulate(path_optim, optimized, start_dir, end_dir, params, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Build problem method
|
||||
* @param path Reference to path
|
||||
* @param costmap Pointer to costmap
|
||||
* @param params Smoother parameters
|
||||
* @param problem Output problem to solve
|
||||
* @param path_optim Output path on which the problem will be solved
|
||||
* @param optimized False for points skipped by downsampling
|
||||
* @return If there is a problem to solve
|
||||
*/
|
||||
bool buildProblem(
|
||||
const std::vector<Eigen::Vector3d> & path,
|
||||
const nav2_costmap_2d::Costmap2D * costmap,
|
||||
const SmootherParams & params,
|
||||
ceres::Problem & problem,
|
||||
std::vector<Eigen::Vector3d> & path_optim,
|
||||
std::vector<bool> & optimized)
|
||||
{
|
||||
// Create costmap grid
|
||||
costmap_grid_ = std::make_shared<ceres::Grid2D<u_char>>(
|
||||
costmap->getCharMap(), 0, costmap->getSizeInCellsY(), 0, costmap->getSizeInCellsX());
|
||||
auto costmap_interpolator = std::make_shared<ceres::BiCubicInterpolator<ceres::Grid2D<u_char>>>(
|
||||
*costmap_grid_);
|
||||
|
||||
// Create residual blocks
|
||||
const double cusp_half_length = params.cusp_zone_length / 2;
|
||||
ceres::LossFunction * loss_function = NULL;
|
||||
path_optim = path;
|
||||
optimized = std::vector<bool>(path.size());
|
||||
optimized[0] = true;
|
||||
int prelast_i = -1;
|
||||
int last_i = 0;
|
||||
double last_direction = path_optim[0][2];
|
||||
bool last_was_cusp = false;
|
||||
bool last_is_reversing = false;
|
||||
std::deque<std::pair<double, SmootherCostFunction *>> potential_cusp_funcs;
|
||||
double last_segment_len = EPSILON;
|
||||
double potential_cusp_funcs_len = 0;
|
||||
double len_since_cusp = std::numeric_limits<double>::infinity();
|
||||
|
||||
for (size_t i = 1; i < path_optim.size(); i++) {
|
||||
auto & pt = path_optim[i];
|
||||
bool is_cusp = false;
|
||||
if (i != path_optim.size() - 1) {
|
||||
is_cusp = pt[2] * last_direction < 0;
|
||||
last_direction = pt[2];
|
||||
|
||||
// skip to downsample if can be skipped (no forward/reverse direction change)
|
||||
if (!is_cusp &&
|
||||
i > (params.keep_start_orientation ? 1 : 0) &&
|
||||
i < path_optim.size() - (params.keep_goal_orientation ? 2 : 1) &&
|
||||
static_cast<int>(i - last_i) < params.path_downsampling_factor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// keep distance inequalities between poses
|
||||
// (some might have been downsampled while others might not)
|
||||
double current_segment_len = (path_optim[i] - path_optim[last_i]).block<2, 1>(0, 0).norm();
|
||||
|
||||
// forget cost functions which don't have chance to be part of a cusp zone
|
||||
potential_cusp_funcs_len += current_segment_len;
|
||||
while (!potential_cusp_funcs.empty() && potential_cusp_funcs_len > cusp_half_length) {
|
||||
potential_cusp_funcs_len -= potential_cusp_funcs.front().first;
|
||||
potential_cusp_funcs.pop_front();
|
||||
}
|
||||
|
||||
// update cusp zone costmap weights
|
||||
if (is_cusp) {
|
||||
double len_to_cusp = current_segment_len;
|
||||
for (int i = potential_cusp_funcs.size() - 1; i >= 0; i--) {
|
||||
auto & f = potential_cusp_funcs[i];
|
||||
double new_weight =
|
||||
params.cusp_costmap_weight * (1.0 - len_to_cusp / cusp_half_length) +
|
||||
params.costmap_weight * len_to_cusp / cusp_half_length;
|
||||
if (std::abs(new_weight - params.cusp_costmap_weight) <
|
||||
std::abs(f.second->getCostmapWeight() - params.cusp_costmap_weight))
|
||||
{
|
||||
f.second->setCostmapWeight(new_weight);
|
||||
}
|
||||
len_to_cusp += f.first;
|
||||
}
|
||||
potential_cusp_funcs_len = 0;
|
||||
potential_cusp_funcs.clear();
|
||||
len_since_cusp = 0;
|
||||
}
|
||||
|
||||
// add cost function
|
||||
optimized[i] = true;
|
||||
if (prelast_i != -1) {
|
||||
double costmap_weight = params.costmap_weight;
|
||||
if (len_since_cusp <= cusp_half_length) {
|
||||
costmap_weight =
|
||||
params.cusp_costmap_weight * (1.0 - len_since_cusp / cusp_half_length) +
|
||||
params.costmap_weight * len_since_cusp / cusp_half_length;
|
||||
}
|
||||
SmootherCostFunction * cost_function = new SmootherCostFunction(
|
||||
path[last_i].template block<2, 1>(
|
||||
0,
|
||||
0),
|
||||
(last_was_cusp ? -1 : 1) * last_segment_len / current_segment_len,
|
||||
last_is_reversing,
|
||||
costmap,
|
||||
costmap_interpolator,
|
||||
params,
|
||||
costmap_weight
|
||||
);
|
||||
problem.AddResidualBlock(
|
||||
cost_function->AutoDiff(), loss_function,
|
||||
path_optim[last_i].data(), pt.data(), path_optim[prelast_i].data());
|
||||
|
||||
potential_cusp_funcs.emplace_back(current_segment_len, cost_function);
|
||||
}
|
||||
|
||||
// shift current to last and last to pre-last
|
||||
last_was_cusp = is_cusp;
|
||||
last_is_reversing = last_direction < 0;
|
||||
prelast_i = last_i;
|
||||
last_i = i;
|
||||
len_since_cusp += current_segment_len;
|
||||
last_segment_len = std::max(EPSILON, current_segment_len);
|
||||
}
|
||||
|
||||
int posesToOptimize = problem.NumParameterBlocks() - 2; // minus start and goal
|
||||
if (params.keep_goal_orientation) {
|
||||
posesToOptimize -= 1; // minus goal orientation holder
|
||||
}
|
||||
if (params.keep_start_orientation) {
|
||||
posesToOptimize -= 1; // minus start orientation holder
|
||||
}
|
||||
if (posesToOptimize <= 0) {
|
||||
return false; // nothing to optimize
|
||||
}
|
||||
// first two and last two points are constant (to keep start and end direction)
|
||||
problem.SetParameterBlockConstant(path_optim.front().data());
|
||||
if (params.keep_start_orientation) {
|
||||
problem.SetParameterBlockConstant(path_optim[1].data());
|
||||
}
|
||||
if (params.keep_goal_orientation) {
|
||||
problem.SetParameterBlockConstant(path_optim[path_optim.size() - 2].data());
|
||||
}
|
||||
problem.SetParameterBlockConstant(path_optim.back().data());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate optimized points to path, assigning orientations and upsampling poses using cubic bezier
|
||||
* @param path_optim Path with optimized points
|
||||
* @param optimized False for points skipped by downsampling
|
||||
* @param start_dir Orientation of the first pose
|
||||
* @param end_dir Orientation of the last pose
|
||||
* @param params Smoother parameters
|
||||
* @param path Output path with upsampled optimized points
|
||||
*/
|
||||
void upsampleAndPopulate(
|
||||
const std::vector<Eigen::Vector3d> & path_optim,
|
||||
const std::vector<bool> & optimized,
|
||||
const Eigen::Vector2d & start_dir,
|
||||
const Eigen::Vector2d & end_dir,
|
||||
const SmootherParams & params,
|
||||
std::vector<Eigen::Vector3d> & path)
|
||||
{
|
||||
// Populate path, assign orientations, interpolate skipped/upsampled poses
|
||||
path.clear();
|
||||
if (params.path_upsampling_factor > 1) {
|
||||
path.reserve(params.path_upsampling_factor * (path_optim.size() - 1) + 1);
|
||||
}
|
||||
int last_i = 0;
|
||||
int prelast_i = -1;
|
||||
Eigen::Vector2d prelast_dir;
|
||||
for (int i = 1; i <= static_cast<int>(path_optim.size()); i++) {
|
||||
if (i == static_cast<int>(path_optim.size()) || optimized[i]) {
|
||||
if (prelast_i != -1) {
|
||||
Eigen::Vector2d last_dir;
|
||||
auto & prelast = path_optim[prelast_i];
|
||||
auto & last = path_optim[last_i];
|
||||
|
||||
// Compute orientation of last
|
||||
if (i < static_cast<int>(path_optim.size())) {
|
||||
auto & current = path_optim[i];
|
||||
Eigen::Vector2d tangent_dir = tangentDir<double>(
|
||||
prelast.block<2, 1>(0, 0),
|
||||
last.block<2, 1>(0, 0),
|
||||
current.block<2, 1>(0, 0),
|
||||
prelast[2] * last[2] < 0);
|
||||
|
||||
last_dir =
|
||||
tangent_dir.dot((current - last).block<2, 1>(0, 0) * last[2]) >= 0 ?
|
||||
tangent_dir :
|
||||
-tangent_dir;
|
||||
last_dir.normalize();
|
||||
} else if (params.keep_goal_orientation) {
|
||||
last_dir = end_dir;
|
||||
} else {
|
||||
last_dir = (last - prelast).block<2, 1>(0, 0) * last[2];
|
||||
last_dir.normalize();
|
||||
}
|
||||
double last_angle = atan2(last_dir[1], last_dir[0]);
|
||||
|
||||
// Interpolate poses between prelast and last
|
||||
int interp_cnt = (last_i - prelast_i) * params.path_upsampling_factor - 1;
|
||||
if (interp_cnt > 0) {
|
||||
Eigen::Vector2d last_pt = last.block<2, 1>(0, 0);
|
||||
Eigen::Vector2d prelast_pt = prelast.block<2, 1>(0, 0);
|
||||
double dist = (last_pt - prelast_pt).norm();
|
||||
Eigen::Vector2d pt1 = prelast_pt + prelast_dir * dist * 0.4 * prelast[2];
|
||||
Eigen::Vector2d pt2 = last_pt - last_dir * dist * 0.4 * prelast[2];
|
||||
for (int j = 1; j <= interp_cnt; j++) {
|
||||
double interp = j / static_cast<double>(interp_cnt + 1);
|
||||
Eigen::Vector2d pt = cubicBezier(prelast_pt, pt1, pt2, last_pt, interp);
|
||||
path.emplace_back(pt[0], pt[1], 0.0);
|
||||
}
|
||||
}
|
||||
path.emplace_back(last[0], last[1], last_angle);
|
||||
|
||||
// Assign orientations to interpolated points
|
||||
for (size_t j = path.size() - 1 - interp_cnt; j < path.size() - 1; j++) {
|
||||
Eigen::Vector2d tangent_dir = tangentDir<double>(
|
||||
path[j - 1].block<2, 1>(0, 0),
|
||||
path[j].block<2, 1>(0, 0),
|
||||
path[j + 1].block<2, 1>(0, 0),
|
||||
false);
|
||||
tangent_dir =
|
||||
tangent_dir.dot((path[j + 1] - path[j]).block<2, 1>(0, 0) * prelast[2]) >= 0 ?
|
||||
tangent_dir :
|
||||
-tangent_dir;
|
||||
path[j][2] = atan2(tangent_dir[1], tangent_dir[0]);
|
||||
}
|
||||
|
||||
prelast_dir = last_dir;
|
||||
} else { // start pose
|
||||
auto & start = path_optim[0];
|
||||
Eigen::Vector2d dir = params.keep_start_orientation ?
|
||||
start_dir :
|
||||
((path_optim[i] - start).block<2, 1>(0, 0) * start[2]).normalized();
|
||||
path.emplace_back(start[0], start[1], atan2(dir[1], dir[0]));
|
||||
prelast_dir = dir;
|
||||
}
|
||||
prelast_i = last_i;
|
||||
last_i = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Piecewise cubic bezier curve as defined by Adobe in Postscript
|
||||
The two end points are pt0 and pt3
|
||||
Their associated control points are pt1 and pt2
|
||||
*/
|
||||
static Eigen::Vector2d cubicBezier(
|
||||
Eigen::Vector2d & pt0, Eigen::Vector2d & pt1,
|
||||
Eigen::Vector2d & pt2, Eigen::Vector2d & pt3, double mu)
|
||||
{
|
||||
Eigen::Vector2d a, b, c, pt;
|
||||
|
||||
c[0] = 3 * (pt1[0] - pt0[0]);
|
||||
c[1] = 3 * (pt1[1] - pt0[1]);
|
||||
b[0] = 3 * (pt2[0] - pt1[0]) - c[0];
|
||||
b[1] = 3 * (pt2[1] - pt1[1]) - c[1];
|
||||
a[0] = pt3[0] - pt0[0] - c[0] - b[0];
|
||||
a[1] = pt3[1] - pt0[1] - c[1] - b[1];
|
||||
|
||||
pt[0] = a[0] * mu * mu * mu + b[0] * mu * mu + c[0] * mu + pt0[0];
|
||||
pt[1] = a[1] * mu * mu * mu + b[1] * mu * mu + c[1] * mu + pt0[1];
|
||||
|
||||
return pt;
|
||||
}
|
||||
|
||||
bool debug_;
|
||||
ceres::Solver::Options options_;
|
||||
std::shared_ptr<ceres::Grid2D<u_char>> costmap_grid_;
|
||||
};
|
||||
|
||||
} // namespace nav2_constrained_smoother
|
||||
|
||||
#endif // NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_HPP_
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
// Copyright (c) 2021 RoboTech Vision
|
||||
// Copyright (c) 2020, Samsung Research America
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License. Reserved.
|
||||
|
||||
#ifndef NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_COST_FUNCTION_HPP_
|
||||
#define NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_COST_FUNCTION_HPP_
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
#include "ceres/ceres.h"
|
||||
#include "ceres/cubic_interpolation.h"
|
||||
#include "Eigen/Core"
|
||||
#include "nav2_costmap_2d/costmap_2d.hpp"
|
||||
#include "nav2_constrained_smoother/options.hpp"
|
||||
#include "nav2_constrained_smoother/utils.hpp"
|
||||
|
||||
namespace nav2_constrained_smoother
|
||||
{
|
||||
|
||||
/**
|
||||
* @struct nav2_constrained_smoother::SmootherCostFunction
|
||||
* @brief Cost function for path smoothing with multiple terms
|
||||
* including curvature, smoothness, distance from original and obstacle avoidance.
|
||||
*/
|
||||
class SmootherCostFunction
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief A constructor for nav2_constrained_smoother::SmootherCostFunction
|
||||
* @param original_path Original position of the path node
|
||||
* @param next_to_last_length_ratio Ratio of next path segment compared to previous.
|
||||
* Negative if one of them represents reversing motion.
|
||||
* @param reversing Whether the path segment after this node represents reversing motion.
|
||||
* @param costmap A costmap to get values for collision and obstacle avoidance
|
||||
* @param params Optimization weights and parameters
|
||||
* @param costmap_weight Costmap cost weight. Can be params.costmap_weight or params.cusp_costmap_weight
|
||||
*/
|
||||
SmootherCostFunction(
|
||||
const Eigen::Vector2d & original_pos,
|
||||
double next_to_last_length_ratio,
|
||||
bool reversing,
|
||||
const nav2_costmap_2d::Costmap2D * costmap,
|
||||
const std::shared_ptr<ceres::BiCubicInterpolator<ceres::Grid2D<u_char>>> & costmap_interpolator,
|
||||
const SmootherParams & params,
|
||||
double costmap_weight)
|
||||
: original_pos_(original_pos),
|
||||
next_to_last_length_ratio_(next_to_last_length_ratio),
|
||||
reversing_(reversing),
|
||||
params_(params),
|
||||
costmap_weight_(costmap_weight),
|
||||
costmap_origin_(costmap->getOriginX(), costmap->getOriginY()),
|
||||
costmap_resolution_(costmap->getResolution()),
|
||||
costmap_interpolator_(costmap_interpolator)
|
||||
{
|
||||
}
|
||||
|
||||
ceres::CostFunction * AutoDiff()
|
||||
{
|
||||
return new ceres::AutoDiffCostFunction<SmootherCostFunction, 4, 2, 2, 2>(this);
|
||||
}
|
||||
|
||||
void setCostmapWeight(double costmap_weight)
|
||||
{
|
||||
costmap_weight_ = costmap_weight;
|
||||
}
|
||||
|
||||
double getCostmapWeight()
|
||||
{
|
||||
return costmap_weight_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Smoother cost function evaluation
|
||||
* @param pt X, Y coords of current point
|
||||
* @param pt_next X, Y coords of next point
|
||||
* @param pt_prev X, Y coords of previous point
|
||||
* @param pt_residual array of output residuals (smoothing, curvature, distance, cost)
|
||||
* @return if successful in computing values
|
||||
*/
|
||||
template<typename T>
|
||||
bool operator()(
|
||||
const T * const pt, const T * const pt_next, const T * const pt_prev,
|
||||
T * pt_residual) const
|
||||
{
|
||||
Eigen::Map<const Eigen::Matrix<T, 2, 1>> xi(pt);
|
||||
Eigen::Map<const Eigen::Matrix<T, 2, 1>> xi_next(pt_next);
|
||||
Eigen::Map<const Eigen::Matrix<T, 2, 1>> xi_prev(pt_prev);
|
||||
Eigen::Map<Eigen::Matrix<T, 4, 1>> residual(pt_residual);
|
||||
residual.setZero();
|
||||
|
||||
// compute cost
|
||||
addSmoothingResidual<T>(params_.smooth_weight, xi, xi_next, xi_prev, residual[0]);
|
||||
addCurvatureResidual<T>(params_.curvature_weight, xi, xi_next, xi_prev, residual[1]);
|
||||
addDistanceResidual<T>(
|
||||
params_.distance_weight, xi,
|
||||
original_pos_.template cast<T>(), residual[2]);
|
||||
addCostResidual<T>(costmap_weight_, xi, xi_next, xi_prev, residual[3]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Cost function term for smooth paths
|
||||
* @param weight Weight to apply to function
|
||||
* @param pt Point Xi for evaluation
|
||||
* @param pt_next Point Xi+1 for calculating Xi's cost
|
||||
* @param pt_prev Point Xi-1 for calculating Xi's cost
|
||||
* @param r Residual (cost) of term
|
||||
*/
|
||||
template<typename T>
|
||||
inline void addSmoothingResidual(
|
||||
const double & weight,
|
||||
const Eigen::Matrix<T, 2, 1> & pt,
|
||||
const Eigen::Matrix<T, 2, 1> & pt_next,
|
||||
const Eigen::Matrix<T, 2, 1> & pt_prev,
|
||||
T & r) const
|
||||
{
|
||||
Eigen::Matrix<T, 2, 1> d_next = pt_next - pt;
|
||||
Eigen::Matrix<T, 2, 1> d_prev = pt - pt_prev;
|
||||
Eigen::Matrix<T, 2, 1> d_diff = next_to_last_length_ratio_ * d_next - d_prev;
|
||||
r += (T)weight * d_diff.dot(d_diff); // objective function value
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cost function term for maximum curved paths
|
||||
* @param weight Weight to apply to function
|
||||
* @param pt Point Xi for evaluation
|
||||
* @param pt_next Point Xi+1 for calculating Xi's cost
|
||||
* @param pt_prev Point Xi-1 for calculating Xi's cost
|
||||
* @param curvature_params A struct to cache computations for the jacobian to use
|
||||
* @param r Residual (cost) of term
|
||||
*/
|
||||
template<typename T>
|
||||
inline void addCurvatureResidual(
|
||||
const double & weight,
|
||||
const Eigen::Matrix<T, 2, 1> & pt,
|
||||
const Eigen::Matrix<T, 2, 1> & pt_next,
|
||||
const Eigen::Matrix<T, 2, 1> & pt_prev,
|
||||
T & r) const
|
||||
{
|
||||
Eigen::Matrix<T, 2, 1> center = arcCenter(
|
||||
pt_prev, pt, pt_next,
|
||||
next_to_last_length_ratio_ < 0);
|
||||
if (CERES_ISINF(center[0])) {
|
||||
return;
|
||||
}
|
||||
T turning_rad = (pt - center).norm();
|
||||
T ki_minus_kmax = (T)1.0 / turning_rad - params_.max_curvature;
|
||||
|
||||
if (ki_minus_kmax <= (T)EPSILON) {
|
||||
return;
|
||||
}
|
||||
|
||||
r += (T)weight * ki_minus_kmax * ki_minus_kmax; // objective function value
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cost function derivative term for steering away changes in pose
|
||||
* @param weight Weight to apply to function
|
||||
* @param xi Point Xi for evaluation
|
||||
* @param xi_original original point Xi for evaluation
|
||||
* @param r Residual (cost) of term
|
||||
*/
|
||||
template<typename T>
|
||||
inline void addDistanceResidual(
|
||||
const double & weight,
|
||||
const Eigen::Matrix<T, 2, 1> & xi,
|
||||
const Eigen::Matrix<T, 2, 1> & xi_original,
|
||||
T & r) const
|
||||
{
|
||||
r += (T)weight * (xi - xi_original).squaredNorm(); // objective function value
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cost function term for steering away from costs
|
||||
* @param weight Weight to apply to function
|
||||
* @param value Point Xi's cost'
|
||||
* @param params computed values to reduce overhead
|
||||
* @param r Residual (cost) of term
|
||||
*/
|
||||
template<typename T>
|
||||
inline void addCostResidual(
|
||||
const double & weight,
|
||||
const Eigen::Matrix<T, 2, 1> & pt,
|
||||
const Eigen::Matrix<T, 2, 1> & pt_next,
|
||||
const Eigen::Matrix<T, 2, 1> & pt_prev,
|
||||
T & r) const
|
||||
{
|
||||
if (params_.cost_check_points.empty()) {
|
||||
Eigen::Matrix<T, 2, 1> interp_pos =
|
||||
(pt - costmap_origin_.template cast<T>()) / (T)costmap_resolution_;
|
||||
T value;
|
||||
costmap_interpolator_->Evaluate(interp_pos[1] - (T)0.5, interp_pos[0] - (T)0.5, &value);
|
||||
r += (T)weight * value * value; // objective function value
|
||||
} else {
|
||||
Eigen::Matrix<T, 2, 1> dir = tangentDir(
|
||||
pt_prev, pt, pt_next,
|
||||
next_to_last_length_ratio_ < 0);
|
||||
dir.normalize();
|
||||
if (((pt_next - pt).dot(dir) < (T)0) != reversing_) {
|
||||
dir = -dir;
|
||||
}
|
||||
Eigen::Matrix<T, 3, 3> transform;
|
||||
transform << dir[0], -dir[1], pt[0],
|
||||
dir[1], dir[0], pt[1],
|
||||
(T)0, (T)0, (T)1;
|
||||
for (size_t i = 0; i < params_.cost_check_points.size(); i += 3) {
|
||||
Eigen::Matrix<T, 3, 1> ccpt((T)params_.cost_check_points[i],
|
||||
(T)params_.cost_check_points[i + 1], (T)1);
|
||||
auto ccpt_world = (transform * ccpt).template block<2, 1>(0, 0);
|
||||
Eigen::Matrix<T, 2,
|
||||
1> interp_pos = (ccpt_world - costmap_origin_.template cast<T>()) /
|
||||
(T)costmap_resolution_;
|
||||
T value;
|
||||
costmap_interpolator_->Evaluate(interp_pos[1] - (T)0.5, interp_pos[0] - (T)0.5, &value);
|
||||
|
||||
r += (T)weight * (T)params_.cost_check_points[i + 2] * value * value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Eigen::Vector2d original_pos_;
|
||||
double next_to_last_length_ratio_;
|
||||
bool reversing_;
|
||||
SmootherParams params_;
|
||||
double costmap_weight_;
|
||||
Eigen::Vector2d costmap_origin_;
|
||||
double costmap_resolution_;
|
||||
std::shared_ptr<ceres::BiCubicInterpolator<ceres::Grid2D<u_char>>> costmap_interpolator_;
|
||||
};
|
||||
|
||||
} // namespace nav2_constrained_smoother
|
||||
|
||||
#endif // NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_COST_FUNCTION_HPP_
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2021 RoboTech Vision
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef NAV2_CONSTRAINED_SMOOTHER__UTILS_HPP_
|
||||
#define NAV2_CONSTRAINED_SMOOTHER__UTILS_HPP_
|
||||
|
||||
#include <limits>
|
||||
#include "Eigen/Core"
|
||||
|
||||
#define EPSILON 0.0001
|
||||
|
||||
/**
|
||||
* Compatibility with different ceres::isinf() and ceres::IsInfinite() API
|
||||
* used in Ceres Solver 2.1.0+ and 2.0.0- versions respectively
|
||||
*/
|
||||
#if defined(USE_OLD_CERES_API)
|
||||
#define CERES_ISINF(x) ceres::IsInfinite(x)
|
||||
#else
|
||||
#define CERES_ISINF(x) ceres::isinf(x)
|
||||
#endif
|
||||
|
||||
namespace nav2_constrained_smoother
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Center of an arc between three points
|
||||
* @param pt_prev Starting point of the arc
|
||||
* @param pt Mid point of the arc
|
||||
* @param pt_next Last point of the arc
|
||||
* @param is_cusp True if pt is a cusp point
|
||||
* @result position of the center or Vector2(inf, inf) for straight lines and 180 deg turns
|
||||
*/
|
||||
template<typename T>
|
||||
inline Eigen::Matrix<T, 2, 1> arcCenter(
|
||||
Eigen::Matrix<T, 2, 1> pt_prev,
|
||||
Eigen::Matrix<T, 2, 1> pt,
|
||||
Eigen::Matrix<T, 2, 1> pt_next,
|
||||
bool is_cusp)
|
||||
{
|
||||
Eigen::Matrix<T, 2, 1> d1 = pt - pt_prev;
|
||||
Eigen::Matrix<T, 2, 1> d2 = pt_next - pt;
|
||||
|
||||
if (is_cusp) {
|
||||
d2 = -d2;
|
||||
pt_next = pt + d2;
|
||||
}
|
||||
|
||||
T det = d1[0] * d2[1] - d1[1] * d2[0];
|
||||
if (ceres::abs(det) < (T)1e-4) { // straight line
|
||||
return Eigen::Matrix<T, 2, 1>(
|
||||
(T)std::numeric_limits<double>::infinity(), (T)std::numeric_limits<double>::infinity());
|
||||
}
|
||||
|
||||
// circle center is at the intersection of mirror axes of the segments:
|
||||
// http://paulbourke.net/geometry/circlesphere/
|
||||
// line intersection:
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Intersection%20of%20two%20lines
|
||||
Eigen::Matrix<T, 2, 1> mid1 = (pt_prev + pt) / (T)2;
|
||||
Eigen::Matrix<T, 2, 1> mid2 = (pt + pt_next) / (T)2;
|
||||
Eigen::Matrix<T, 2, 1> n1(-d1[1], d1[0]);
|
||||
Eigen::Matrix<T, 2, 1> n2(-d2[1], d2[0]);
|
||||
T det1 = (mid1[0] + n1[0]) * mid1[1] - (mid1[1] + n1[1]) * mid1[0];
|
||||
T det2 = (mid2[0] + n2[0]) * mid2[1] - (mid2[1] + n2[1]) * mid2[0];
|
||||
Eigen::Matrix<T, 2, 1> center((det1 * n2[0] - det2 * n1[0]) / det,
|
||||
(det1 * n2[1] - det2 * n1[1]) / det);
|
||||
return center;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direction of a line which contains pt and is tangential to arc
|
||||
* between pt_prev, pt, pt_next
|
||||
* @param pt_prev Starting point of the arc
|
||||
* @param pt Mid point of the arc, lying on the tangential line
|
||||
* @param pt_next Last point of the arc
|
||||
* @param is_cusp True if pt is a cusp point
|
||||
* @result Tangential line direction.
|
||||
* Note: the sign of tangentDir is undefined here, should be assigned in post-process
|
||||
* depending on movement direction. Also, for speed reasons, direction vector is not normalized.
|
||||
*/
|
||||
template<typename T>
|
||||
inline Eigen::Matrix<T, 2, 1> tangentDir(
|
||||
Eigen::Matrix<T, 2, 1> pt_prev,
|
||||
Eigen::Matrix<T, 2, 1> pt,
|
||||
Eigen::Matrix<T, 2, 1> pt_next,
|
||||
bool is_cusp)
|
||||
{
|
||||
Eigen::Matrix<T, 2, 1> center = arcCenter(pt_prev, pt, pt_next, is_cusp);
|
||||
if (CERES_ISINF(center[0])) { // straight line
|
||||
Eigen::Matrix<T, 2, 1> d1 = pt - pt_prev;
|
||||
Eigen::Matrix<T, 2, 1> d2 = pt_next - pt;
|
||||
|
||||
if (is_cusp) {
|
||||
d2 = -d2;
|
||||
pt_next = pt + d2;
|
||||
}
|
||||
|
||||
Eigen::Matrix<T, 2, 1> result(pt_next[0] - pt_prev[0], pt_next[1] - pt_prev[1]);
|
||||
if (result[0] == 0.0 && result[1] == 0.0) { // a very rare edge situation
|
||||
return Eigen::Matrix<T, 2, 1>(d1[1], -d1[0]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// tangent is prependicular to (pt - center)
|
||||
// Note: not determining + or - direction here, this should be handled at the caller side
|
||||
return Eigen::Matrix<T, 2, 1>(center[1] - pt[1], pt[0] - center[0]);
|
||||
}
|
||||
|
||||
} // namespace nav2_constrained_smoother
|
||||
|
||||
#endif // NAV2_CONSTRAINED_SMOOTHER__UTILS_HPP_
|
||||
@@ -0,0 +1,7 @@
|
||||
<class_libraries>
|
||||
<library path="nav2_constrained_smoother">
|
||||
<class name="nav2_constrained_smoother/ConstrainedSmoother" type="nav2_constrained_smoother::ConstrainedSmoother" base_class_type="nav2_core::Smoother">
|
||||
<description>Increases smoothness and distance from obstacles of a path using Ceres solver optimization</description>
|
||||
</class>
|
||||
</library>
|
||||
</class_libraries>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="3">
|
||||
<name>nav2_constrained_smoother</name>
|
||||
<version>1.1.18</version>
|
||||
<description>Ceres constrained smoother</description>
|
||||
<maintainer email="vargovcik@robotechvision.com">Matej Vargovcik</maintainer>
|
||||
<maintainer email="stevenmacenski@gmail.com">Steve Macenski</maintainer>
|
||||
<license>Apache-2.0</license>
|
||||
|
||||
<buildtool_depend>ament_cmake</buildtool_depend>
|
||||
<depend>nav2_common</depend>
|
||||
<depend>angles</depend>
|
||||
<depend>rclcpp</depend>
|
||||
<depend>nav2_util</depend>
|
||||
<depend>nav2_msgs</depend>
|
||||
<depend>nav2_costmap_2d</depend>
|
||||
<depend>nav2_core</depend>
|
||||
<depend>pluginlib</depend>
|
||||
<depend>libceres-dev</depend>
|
||||
|
||||
<test_depend>ament_lint_common</test_depend>
|
||||
<test_depend>ament_lint_auto</test_depend>
|
||||
<test_depend>ament_cmake_gtest</test_depend>
|
||||
<test_depend>ament_cmake_pytest</test_depend>
|
||||
|
||||
<export>
|
||||
<build_type>ament_cmake</build_type>
|
||||
<nav2_core plugin="${prefix}/nav2_constrained_smoother.xml" />
|
||||
</export>
|
||||
</package>
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright (c) 2021 RoboTech Vision
|
||||
// Copyright (c) 2020 Shrijit Singh
|
||||
// Copyright (c) 2020 Samsung Research America
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "nav2_constrained_smoother/constrained_smoother.hpp"
|
||||
#include "nav2_core/exceptions.hpp"
|
||||
#include "nav2_util/node_utils.hpp"
|
||||
#include "nav2_util/geometry_utils.hpp"
|
||||
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
|
||||
|
||||
#include "pluginlib/class_loader.hpp"
|
||||
#include "pluginlib/class_list_macros.hpp"
|
||||
|
||||
#include "tf2/utils.h"
|
||||
|
||||
using nav2_util::declare_parameter_if_not_declared;
|
||||
using nav2_util::geometry_utils::euclidean_distance;
|
||||
using namespace nav2_costmap_2d; // NOLINT
|
||||
|
||||
namespace nav2_constrained_smoother
|
||||
{
|
||||
|
||||
void ConstrainedSmoother::configure(
|
||||
const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
|
||||
std::string name, std::shared_ptr<tf2_ros::Buffer> tf,
|
||||
std::shared_ptr<nav2_costmap_2d::CostmapSubscriber> costmap_sub,
|
||||
std::shared_ptr<nav2_costmap_2d::FootprintSubscriber>)
|
||||
{
|
||||
auto node = parent.lock();
|
||||
if (!node) {
|
||||
throw std::runtime_error("Unable to lock node!");
|
||||
}
|
||||
|
||||
costmap_sub_ = costmap_sub;
|
||||
tf_ = tf;
|
||||
plugin_name_ = name;
|
||||
logger_ = node->get_logger();
|
||||
|
||||
smoother_ = std::make_unique<nav2_constrained_smoother::Smoother>();
|
||||
optimizer_params_.get(node.get(), name);
|
||||
smoother_params_.get(node.get(), name);
|
||||
smoother_->initialize(optimizer_params_);
|
||||
}
|
||||
|
||||
void ConstrainedSmoother::cleanup()
|
||||
{
|
||||
RCLCPP_INFO(
|
||||
logger_,
|
||||
"Cleaning up smoother: %s of type"
|
||||
" nav2_constrained_smoother::ConstrainedSmoother",
|
||||
plugin_name_.c_str());
|
||||
}
|
||||
|
||||
void ConstrainedSmoother::activate()
|
||||
{
|
||||
RCLCPP_INFO(
|
||||
logger_,
|
||||
"Activating smoother: %s of type "
|
||||
"nav2_constrained_smoother::ConstrainedSmoother",
|
||||
plugin_name_.c_str());
|
||||
}
|
||||
|
||||
void ConstrainedSmoother::deactivate()
|
||||
{
|
||||
RCLCPP_INFO(
|
||||
logger_,
|
||||
"Deactivating smoother: %s of type "
|
||||
"nav2_constrained_smoother::ConstrainedSmoother",
|
||||
plugin_name_.c_str());
|
||||
}
|
||||
|
||||
bool ConstrainedSmoother::smooth(nav_msgs::msg::Path & path, const rclcpp::Duration & max_time)
|
||||
{
|
||||
if (path.poses.size() < 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// populate smoother input with (x, y, forward/reverse dir)
|
||||
std::vector<Eigen::Vector3d> path_world;
|
||||
path_world.reserve(path.poses.size());
|
||||
// smoother keeps record of start/end orientations so that it
|
||||
// can use them in the final path, preventing degradation of these (often important) values
|
||||
Eigen::Vector2d start_dir;
|
||||
Eigen::Vector2d end_dir;
|
||||
for (size_t i = 0; i < path.poses.size(); i++) {
|
||||
auto & pose = path.poses[i].pose;
|
||||
double angle = tf2::getYaw(pose.orientation);
|
||||
Eigen::Vector2d orientation(cos(angle), sin(angle));
|
||||
if (i == path.poses.size() - 1) {
|
||||
// Note: `reversing` indicates the direction of the segment after the point and
|
||||
// there is no segment after the last point. Most probably the value is irrelevant, but
|
||||
// copying it from the last but one point, just to make it defined...
|
||||
path_world.emplace_back(pose.position.x, pose.position.y, path_world.back()[2]);
|
||||
end_dir = orientation;
|
||||
} else {
|
||||
auto & pos_next = path.poses[i + 1].pose.position;
|
||||
Eigen::Vector2d mvmt(pos_next.x - pose.position.x, pos_next.y - pose.position.y);
|
||||
// robot is considered reversing when angle between its orientation and movement direction
|
||||
// is more than 90 degrees (i.e. dot product is less than 0)
|
||||
bool reversing = smoother_params_.reversing_enabled && orientation.dot(mvmt) < 0;
|
||||
// we transform boolean value of "reversing" into sign of movement direction (+1 or -1)
|
||||
// to simplify further computations
|
||||
path_world.emplace_back(pose.position.x, pose.position.y, reversing ? -1 : 1);
|
||||
if (i == 0) {
|
||||
start_dir = orientation;
|
||||
} else if (i == 1 && !smoother_params_.keep_start_orientation) {
|
||||
// overwrite start forward/reverse when orientation was set to be ignored
|
||||
// note: start_dir is overwritten inside Smoother::upsampleAndPopulate() method
|
||||
path_world[0][2] = path_world.back()[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
smoother_params_.max_time = max_time.seconds();
|
||||
|
||||
// Smooth plan
|
||||
auto costmap = costmap_sub_->getCostmap();
|
||||
if (!smoother_->smooth(path_world, start_dir, end_dir, costmap.get(), smoother_params_)) {
|
||||
RCLCPP_WARN(
|
||||
logger_,
|
||||
"%s: failed to smooth plan, Ceres could not find a usable solution to optimize.",
|
||||
plugin_name_.c_str());
|
||||
throw new nav2_core::PlannerException(
|
||||
"Failed to smooth plan, Ceres could not find a usable solution.");
|
||||
}
|
||||
|
||||
// populate final path
|
||||
geometry_msgs::msg::PoseStamped pose;
|
||||
pose.header = path.poses.front().header;
|
||||
path.poses.clear();
|
||||
path.poses.reserve(path_world.size());
|
||||
for (auto & pw : path_world) {
|
||||
pose.pose.position.x = pw[0];
|
||||
pose.pose.position.y = pw[1];
|
||||
pose.pose.orientation.z = sin(pw[2] / 2);
|
||||
pose.pose.orientation.w = cos(pw[2] / 2);
|
||||
|
||||
path.poses.push_back(pose);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace nav2_constrained_smoother
|
||||
|
||||
// Register this smoother as a nav2_core plugin
|
||||
PLUGINLIB_EXPORT_CLASS(
|
||||
nav2_constrained_smoother::ConstrainedSmoother,
|
||||
nav2_core::Smoother)
|
||||
@@ -0,0 +1,20 @@
|
||||
ament_add_gtest(test_constrained_smoother
|
||||
test_constrained_smoother.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test_constrained_smoother
|
||||
${library_name}
|
||||
)
|
||||
ament_target_dependencies(test_constrained_smoother
|
||||
${dependencies}
|
||||
)
|
||||
|
||||
ament_add_gtest(test_smoother_cost_function
|
||||
test_smoother_cost_function.cpp
|
||||
)
|
||||
target_link_libraries(test_smoother_cost_function
|
||||
${library_name}
|
||||
)
|
||||
ament_target_dependencies(test_smoother_cost_function
|
||||
${dependencies}
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2021 RoboTech Vision
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License. Reserved.
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "rclcpp/rclcpp.hpp"
|
||||
#include "nav2_constrained_smoother/smoother_cost_function.hpp"
|
||||
|
||||
class TestableSmootherCostFunction : nav2_constrained_smoother::SmootherCostFunction
|
||||
{
|
||||
public:
|
||||
TestableSmootherCostFunction(
|
||||
const Eigen::Vector2d & original_pos,
|
||||
double next_to_last_length_ratio,
|
||||
bool reversing,
|
||||
const nav2_costmap_2d::Costmap2D * costmap,
|
||||
const std::shared_ptr<ceres::BiCubicInterpolator<ceres::Grid2D<u_char>>> & costmap_interpolator,
|
||||
const nav2_constrained_smoother::SmootherParams & params,
|
||||
double costmap_weight)
|
||||
: SmootherCostFunction(
|
||||
original_pos, next_to_last_length_ratio, reversing,
|
||||
costmap, costmap_interpolator,
|
||||
params, costmap_weight)
|
||||
{
|
||||
}
|
||||
|
||||
inline double getCurvatureResidual(
|
||||
const double & weight,
|
||||
const Eigen::Vector2d & pt,
|
||||
const Eigen::Vector2d & pt_next,
|
||||
const Eigen::Vector2d & pt_prev) const
|
||||
{
|
||||
double r = 0.0;
|
||||
addCurvatureResidual<double>(weight, pt, pt_next, pt_prev, r);
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
class Test : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(Test, testingCurvatureResidual)
|
||||
{
|
||||
nav2_costmap_2d::Costmap2D costmap;
|
||||
TestableSmootherCostFunction fn(
|
||||
Eigen::Vector2d(1.0, 0.0), 1.0, false,
|
||||
&costmap, std::shared_ptr<ceres::BiCubicInterpolator<ceres::Grid2D<u_char>>>(),
|
||||
nav2_constrained_smoother::SmootherParams(), 0.0
|
||||
);
|
||||
|
||||
// test for edge values
|
||||
Eigen::Vector2d pt(1.0, 0.0);
|
||||
Eigen::Vector2d pt_other(0.0, 0.0);
|
||||
EXPECT_EQ(fn.getCurvatureResidual(0.0, pt, pt_other, pt_other), 0.0);
|
||||
|
||||
nav2_constrained_smoother::SmootherParams params_no_min_turning_radius;
|
||||
params_no_min_turning_radius.max_curvature = 1.0f / 0.0;
|
||||
TestableSmootherCostFunction fn_no_min_turning_radius(
|
||||
Eigen::Vector2d(1.0, 0.0), 1.0, false,
|
||||
&costmap, std::shared_ptr<ceres::BiCubicInterpolator<ceres::Grid2D<u_char>>>(),
|
||||
params_no_min_turning_radius, 0.0
|
||||
);
|
||||
EXPECT_EQ(fn_no_min_turning_radius.getCurvatureResidual(1.0, pt, pt_other, pt_other), 0.0);
|
||||
}
|
||||
|
||||
TEST_F(Test, testingUtils)
|
||||
{
|
||||
Eigen::Vector2d pt(1.0, 0.0);
|
||||
Eigen::Vector2d pt_prev(0.0, 0.0);
|
||||
Eigen::Vector2d pt_next(0.0, 0.0);
|
||||
|
||||
// test for intermediate values
|
||||
auto center = nav2_constrained_smoother::arcCenter(pt_prev, pt, pt_next, false);
|
||||
// although in this situation the center would be at (0.5, 0.0),
|
||||
// cases where pt_prev == pt_next are very rare and thus unhandled
|
||||
// during the smoothing points will be separated (and thus made valid) by smoothness cost anyways
|
||||
EXPECT_EQ(center[0], std::numeric_limits<double>::infinity());
|
||||
EXPECT_EQ(center[1], std::numeric_limits<double>::infinity());
|
||||
|
||||
auto tangent =
|
||||
nav2_constrained_smoother::tangentDir(pt_prev, pt, pt_next, false).normalized();
|
||||
EXPECT_NEAR(tangent[0], 0, 1e-10);
|
||||
EXPECT_NEAR(std::abs(tangent[1]), 1, 1e-10);
|
||||
|
||||
// no rotation when mid point is a cusp
|
||||
tangent = nav2_constrained_smoother::tangentDir(pt_prev, pt, pt_next, true).normalized();
|
||||
EXPECT_NEAR(std::abs(tangent[0]), 1, 1e-10);
|
||||
EXPECT_NEAR(tangent[1], 0, 1e-10);
|
||||
|
||||
pt_prev[0] = -1.0;
|
||||
// rotation is mathematically invalid, picking direction of a shorter segment
|
||||
tangent = nav2_constrained_smoother::tangentDir(pt_prev, pt, pt_next, true).normalized();
|
||||
EXPECT_NEAR(std::abs(tangent[0]), 1, 1e-10);
|
||||
EXPECT_NEAR(tangent[1], 0, 1e-10);
|
||||
|
||||
pt_prev[0] = 0.0;
|
||||
pt_next[0] = -1.0;
|
||||
// rotation is mathematically invalid, picking direction of a shorter segment
|
||||
tangent = nav2_constrained_smoother::tangentDir(pt_prev, pt, pt_next, true).normalized();
|
||||
EXPECT_NEAR(std::abs(tangent[0]), 1, 1e-10);
|
||||
EXPECT_NEAR(tangent[1], 0, 1e-10);
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
// initialize ROS
|
||||
rclcpp::init(argc, argv);
|
||||
|
||||
bool all_successful = RUN_ALL_TESTS();
|
||||
|
||||
// shutdown ROS
|
||||
rclcpp::shutdown();
|
||||
|
||||
return all_successful;
|
||||
}
|
||||
Reference in New Issue
Block a user