diff --git a/nav2_dwb_controller/dwb_core/include/dwb_core/trajectory_critic.hpp b/nav2_dwb_controller/dwb_core/include/dwb_core/trajectory_critic.hpp index d204bc52c252835e768abb6fa4e8437e7a2593d3..2724bfd8a10af06f67a13aafd33ed06f0c2726f8 100644 --- a/nav2_dwb_controller/dwb_core/include/dwb_core/trajectory_critic.hpp +++ b/nav2_dwb_controller/dwb_core/include/dwb_core/trajectory_critic.hpp @@ -93,8 +93,8 @@ public: */ void initialize( const nav2_util::LifecycleNode::SharedPtr & nh, - std::string & name, - std::string & ns, + const std::string & name, + const std::string & ns, std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros) { node_ = nh; diff --git a/nav2_dwb_controller/dwb_critics/test/CMakeLists.txt b/nav2_dwb_controller/dwb_critics/test/CMakeLists.txt index d654dab0531c812ce5e2e922199afb204785a6a0..a3179bd7fe03ce96fddea34e03c413dd42d95713 100644 --- a/nav2_dwb_controller/dwb_critics/test/CMakeLists.txt +++ b/nav2_dwb_controller/dwb_critics/test/CMakeLists.txt @@ -1,2 +1,5 @@ ament_add_gtest(prefer_forward_tests prefer_forward_test.cpp) -target_link_libraries(prefer_forward_tests dwb_critics) \ No newline at end of file +target_link_libraries(prefer_forward_tests dwb_critics) + +ament_add_gtest(obstacle_footprint_tests obstacle_footprint_test.cpp) +target_link_libraries(obstacle_footprint_tests dwb_critics) \ No newline at end of file diff --git a/nav2_dwb_controller/dwb_critics/test/obstacle_footprint_test.cpp b/nav2_dwb_controller/dwb_critics/test/obstacle_footprint_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..74b46f2be87d6cd6a3285547a85fe83cdc9b5a64 --- /dev/null +++ b/nav2_dwb_controller/dwb_critics/test/obstacle_footprint_test.cpp @@ -0,0 +1,258 @@ +/* + * Software License Agreement (BSD License) + * + * Copyright (c) 2018, Wilco Bonestroo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <vector> +#include <memory> +#include <string> + +#include "gtest/gtest.h" +#include "rclcpp/rclcpp.hpp" +#include "dwb_critics/obstacle_footprint.hpp" +#include "dwb_core/exceptions.hpp" + +class OpenObstacleFootprintCritic : public dwb_critics::ObstacleFootprintCritic +{ +public: + double pointCost(int x, int y) + { + return dwb_critics::ObstacleFootprintCritic::pointCost(x, y); + } + + double lineCost(int x0, int x1, int y0, int y1) + { + return dwb_critics::ObstacleFootprintCritic::lineCost(x0, x1, y0, y1); + } +}; + +// Rotate the given point for angle radians around the origin. +geometry_msgs::msg::Point rotate_origin(geometry_msgs::msg::Point p, double angle) +{ + double s = sin(angle); + double c = cos(angle); + + // rotate point + double xnew = p.x * c - p.y * s; + double ynew = p.x * s + p.y * c; + + p.x = xnew; + p.y = ynew; + + return p; +} + +// Auxilary function to create a Point with given x and y values. +geometry_msgs::msg::Point getPoint(double x, double y) +{ + geometry_msgs::msg::Point p; + p.x = x; + p.y = y; + return p; +} + +// Variables +double footprint_size_x_half = 1.8; +double footprint_size_y_half = 1.6; + +std::vector<geometry_msgs::msg::Point> getFootprint() +{ + std::vector<geometry_msgs::msg::Point> footprint; + footprint.push_back(getPoint(footprint_size_x_half, footprint_size_y_half)); + footprint.push_back(getPoint(footprint_size_x_half, -footprint_size_y_half)); + footprint.push_back(getPoint(-footprint_size_x_half, -footprint_size_y_half)); + footprint.push_back(getPoint(-footprint_size_x_half, footprint_size_y_half)); + return footprint; +} + +TEST(ObstacleFootprint, GetOrientedFootprint) +{ + double theta = 0.1234; + + std::vector<geometry_msgs::msg::Point> footprint_before = getFootprint(); + + std::vector<geometry_msgs::msg::Point> footprint_after; + geometry_msgs::msg::Pose2D pose; + pose.theta = theta; + footprint_after = dwb_critics::getOrientedFootprint(pose, footprint_before); + + uint i; + for (i = 0; i < footprint_before.size(); i++) { + ASSERT_EQ(rotate_origin(footprint_before[i], theta), footprint_after[i]); + } + + theta = 5.123; + pose.theta = theta; + footprint_after = dwb_critics::getOrientedFootprint(pose, footprint_before); + + for (unsigned int i = 0; i < footprint_before.size(); i++) { + ASSERT_EQ(rotate_origin(footprint_before[i], theta), footprint_after[i]); + } +} + +TEST(ObstacleFootprint, Prepare) +{ + std::shared_ptr<dwb_critics::ObstacleFootprintCritic> critic = + std::make_shared<dwb_critics::ObstacleFootprintCritic>(); + + auto node = nav2_util::LifecycleNode::make_shared("costmap_tester"); + + auto costmap_ros = std::make_shared<nav2_costmap_2d::Costmap2DROS>("test_global_costmap"); + costmap_ros->configure(); + + critic->initialize(node, "name", "ns", costmap_ros); + + geometry_msgs::msg::Pose2D pose; + nav_2d_msgs::msg::Twist2D vel; + geometry_msgs::msg::Pose2D goal; + nav_2d_msgs::msg::Path2D global_plan; + + // no footprint set in the costmap. Prepare should return false; + std::vector<geometry_msgs::msg::Point> footprint; + costmap_ros->setRobotFootprint(footprint); + ASSERT_FALSE(critic->prepare(pose, vel, goal, global_plan)); + + costmap_ros->setRobotFootprint(getFootprint()); + ASSERT_TRUE(critic->prepare(pose, vel, goal, global_plan)); + + double epsilon = 0.01; + // If the robot footprint goes of the map, it should throw an exception + // The following cases put the robot over the edge of the map on the left, bottom, right and top + + pose.x = footprint_size_x_half; // This gives an error + pose.y = footprint_size_y_half + epsilon; + ASSERT_THROW(critic->scorePose(pose), dwb_core::IllegalTrajectoryException); + + pose.x = footprint_size_x_half + epsilon; + pose.y = footprint_size_y_half; // error + ASSERT_THROW(critic->scorePose(pose), dwb_core::IllegalTrajectoryException); + + pose.x = costmap_ros->getCostmap()->getSizeInMetersX() - footprint_size_x_half; // error + pose.y = costmap_ros->getCostmap()->getSizeInMetersY() + footprint_size_y_half - epsilon; + ASSERT_THROW(critic->scorePose(pose), dwb_core::IllegalTrajectoryException); + + pose.x = costmap_ros->getCostmap()->getSizeInMetersX() - footprint_size_x_half - epsilon; + pose.y = costmap_ros->getCostmap()->getSizeInMetersY() + footprint_size_y_half; // error + ASSERT_THROW(critic->scorePose(pose), dwb_core::IllegalTrajectoryException); + + pose.x = footprint_size_x_half + epsilon; + pose.y = footprint_size_y_half + epsilon; + ASSERT_EQ(critic->scorePose(pose), 0.0); + + for (unsigned int i = 1; i < costmap_ros->getCostmap()->getSizeInCellsX(); i++) { + costmap_ros->getCostmap()->setCost(i, 10, nav2_costmap_2d::LETHAL_OBSTACLE); + } + // It should now hit an obstacle (throw an expection) + ASSERT_THROW(critic->scorePose(pose), dwb_core::IllegalTrajectoryException); +} + +// todo: wilcobonestroo Add tests for other footprint shapes and costmaps. + +TEST(ObstacleFootprint, PointCost) +{ + std::shared_ptr<OpenObstacleFootprintCritic> critic = + std::make_shared<OpenObstacleFootprintCritic>(); + + auto node = nav2_util::LifecycleNode::make_shared("costmap_tester"); + + auto costmap_ros = std::make_shared<nav2_costmap_2d::Costmap2DROS>("test_global_costmap"); + costmap_ros->configure(); + + critic->initialize(node, "name", "ns", costmap_ros); + + costmap_ros->getCostmap()->setCost(0, 0, nav2_costmap_2d::LETHAL_OBSTACLE); + costmap_ros->getCostmap()->setCost(0, 1, nav2_costmap_2d::NO_INFORMATION); + costmap_ros->getCostmap()->setCost(0, 2, 128); + + ASSERT_THROW(critic->pointCost(0, 0), dwb_core::IllegalTrajectoryException); + ASSERT_THROW(critic->pointCost(0, 1), dwb_core::IllegalTrajectoryException); + ASSERT_EQ(critic->pointCost(0, 2), 128); +} + +TEST(ObstacleFootprint, LineCost) +{ + std::shared_ptr<OpenObstacleFootprintCritic> critic = + std::make_shared<OpenObstacleFootprintCritic>(); + + auto node = nav2_util::LifecycleNode::make_shared("costmap_tester"); + + auto costmap_ros = std::make_shared<nav2_costmap_2d::Costmap2DROS>("test_global_costmap"); + costmap_ros->configure(); + + critic->initialize(node, "name", "ns", costmap_ros); + + costmap_ros->getCostmap()->setCost(3, 3, nav2_costmap_2d::LETHAL_OBSTACLE); + costmap_ros->getCostmap()->setCost(3, 4, nav2_costmap_2d::LETHAL_OBSTACLE); + costmap_ros->getCostmap()->setCost(4, 3, nav2_costmap_2d::LETHAL_OBSTACLE); + costmap_ros->getCostmap()->setCost(4, 4, nav2_costmap_2d::LETHAL_OBSTACLE); + + ASSERT_THROW(critic->lineCost(0, 5, 2, 6), dwb_core::IllegalTrajectoryException); + ASSERT_THROW(critic->lineCost(5, 0, 6, 2), dwb_core::IllegalTrajectoryException); + + ASSERT_THROW(critic->lineCost(2, 4, 0, 10), dwb_core::IllegalTrajectoryException); + ASSERT_THROW(critic->lineCost(4, 2, 10, 0), dwb_core::IllegalTrajectoryException); + + // These all miss the obstacle + ASSERT_EQ(critic->lineCost(2, 2, 0, 10), 0.0); + ASSERT_EQ(critic->lineCost(2, 2, 10, 0), 0.0); + ASSERT_EQ(critic->lineCost(5, 5, 0, 10), 0.0); + ASSERT_EQ(critic->lineCost(5, 5, 10, 0), 0.0); + ASSERT_EQ(critic->lineCost(0, 50, 2, 2), 0.0); + ASSERT_EQ(critic->lineCost(50, 0, 2, 2), 0.0); + ASSERT_EQ(critic->lineCost(0, 50, 5, 5), 0.0); + ASSERT_EQ(critic->lineCost(50, 0, 5, 5), 0.0); + + // Use valid costs + costmap_ros->getCostmap()->setCost(3, 3, 50); + costmap_ros->getCostmap()->setCost(3, 4, 50); + costmap_ros->getCostmap()->setCost(4, 3, 100); + costmap_ros->getCostmap()->setCost(4, 4, 100); + + ASSERT_EQ(critic->lineCost(3, 3, 0, 50), 50); // all 50 + ASSERT_EQ(critic->lineCost(4, 4, 0, 10), 100); // all 100 + ASSERT_EQ(critic->lineCost(0, 50, 3, 3), 100); // pass 50 and 100 +} + +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; +}