Files
2025-05-27 19:03:40 +08:00

281 lines
12 KiB
C++

// Copyright (c) 2022, 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.
#include <math.h>
#include <memory>
#include <string>
#include <vector>
#include <chrono>
#include <limits>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_msgs/msg/costmap.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_smoother/simple_smoother.hpp"
#include "ament_index_cpp/get_package_share_directory.hpp"
using namespace smoother_utils; // NOLINT
using namespace nav2_smoother; // NOLINT
using namespace std::chrono_literals; // NOLINT
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
class SmootherWrapper : public nav2_smoother::SimpleSmoother
{
public:
SmootherWrapper()
: nav2_smoother::SimpleSmoother()
{
}
std::vector<PathSegment> findDirectionalPathSegmentsWrapper(nav_msgs::msg::Path path)
{
return findDirectionalPathSegments(path);
}
void setMaxItsToInvalid()
{
max_its_ = 0;
}
};
TEST(SmootherTest, test_simple_smoother)
{
rclcpp_lifecycle::LifecycleNode::SharedPtr node =
std::make_shared<rclcpp_lifecycle::LifecycleNode>("SmacSmootherTest");
std::shared_ptr<nav2_msgs::msg::Costmap> costmap_msg =
std::make_shared<nav2_msgs::msg::Costmap>();
costmap_msg->header.stamp = node->now();
costmap_msg->header.frame_id = "map";
costmap_msg->data.resize(100 * 100);
costmap_msg->metadata.resolution = 0.05;
costmap_msg->metadata.size_x = 100;
costmap_msg->metadata.size_y = 100;
// island in the middle of lethal cost to cross
for (unsigned int i = 20; i <= 30; ++i) {
for (unsigned int j = 20; j <= 30; ++j) {
costmap_msg->data[j * 100 + i] = 254;
}
}
std::weak_ptr<rclcpp_lifecycle::LifecycleNode> parent = node;
std::shared_ptr<nav2_costmap_2d::CostmapSubscriber> dummy_costmap;
dummy_costmap = std::make_shared<nav2_costmap_2d::CostmapSubscriber>(parent, "dummy_topic");
dummy_costmap->costmapCallback(costmap_msg);
// Make smoother
std::shared_ptr<tf2_ros::Buffer> dummy_tf;
std::shared_ptr<nav2_costmap_2d::FootprintSubscriber> dummy_footprint;
auto smoother = std::make_unique<SmootherWrapper>();
smoother->configure(parent, "test", dummy_tf, dummy_costmap, dummy_footprint);
// Test that an irregular distributed path becomes more distributed
nav_msgs::msg::Path straight_irregular_path;
straight_irregular_path.header.frame_id = "map";
straight_irregular_path.header.stamp = node->now();
straight_irregular_path.poses.resize(11);
straight_irregular_path.poses[0].pose.position.x = 0.5;
straight_irregular_path.poses[0].pose.position.y = 0.0;
straight_irregular_path.poses[1].pose.position.x = 0.5;
straight_irregular_path.poses[1].pose.position.y = 0.1;
straight_irregular_path.poses[2].pose.position.x = 0.5;
straight_irregular_path.poses[2].pose.position.y = 0.2;
straight_irregular_path.poses[3].pose.position.x = 0.5;
straight_irregular_path.poses[3].pose.position.y = 0.35;
straight_irregular_path.poses[4].pose.position.x = 0.5;
straight_irregular_path.poses[4].pose.position.y = 0.4;
straight_irregular_path.poses[5].pose.position.x = 0.5;
straight_irregular_path.poses[5].pose.position.y = 0.56;
straight_irregular_path.poses[6].pose.position.x = 0.5;
straight_irregular_path.poses[6].pose.position.y = 0.9;
straight_irregular_path.poses[7].pose.position.x = 0.5;
straight_irregular_path.poses[7].pose.position.y = 0.95;
straight_irregular_path.poses[8].pose.position.x = 0.5;
straight_irregular_path.poses[8].pose.position.y = 1.3;
straight_irregular_path.poses[9].pose.position.x = 0.5;
straight_irregular_path.poses[9].pose.position.y = 2.0;
straight_irregular_path.poses[10].pose.position.x = 0.5;
straight_irregular_path.poses[10].pose.position.y = 2.5;
rclcpp::Duration no_time = rclcpp::Duration::from_seconds(0.0); // 0 seconds
rclcpp::Duration max_time = rclcpp::Duration::from_seconds(1); // 1 second
EXPECT_FALSE(smoother->smooth(straight_irregular_path, no_time));
EXPECT_TRUE(smoother->smooth(straight_irregular_path, max_time));
for (uint i = 0; i != straight_irregular_path.poses.size() - 1; i++) {
// Check distances are more evenly spaced out now
EXPECT_LT(
fabs(
straight_irregular_path.poses[i].pose.position.y -
straight_irregular_path.poses[i + 1].pose.position.y), 0.38);
}
// Test regular path, should see no effective change
nav_msgs::msg::Path straight_regular_path;
straight_regular_path.header = straight_irregular_path.header;
straight_regular_path.poses.resize(11);
straight_regular_path.poses[0].pose.position.x = 0.5;
straight_regular_path.poses[0].pose.position.y = 0.0;
straight_regular_path.poses[1].pose.position.x = 0.5;
straight_regular_path.poses[1].pose.position.y = 0.1;
straight_regular_path.poses[2].pose.position.x = 0.5;
straight_regular_path.poses[2].pose.position.y = 0.2;
straight_regular_path.poses[3].pose.position.x = 0.5;
straight_regular_path.poses[3].pose.position.y = 0.3;
straight_regular_path.poses[4].pose.position.x = 0.5;
straight_regular_path.poses[4].pose.position.y = 0.4;
straight_regular_path.poses[5].pose.position.x = 0.5;
straight_regular_path.poses[5].pose.position.y = 0.5;
straight_regular_path.poses[6].pose.position.x = 0.5;
straight_regular_path.poses[6].pose.position.y = 0.6;
straight_regular_path.poses[7].pose.position.x = 0.5;
straight_regular_path.poses[7].pose.position.y = 0.7;
straight_regular_path.poses[8].pose.position.x = 0.5;
straight_regular_path.poses[8].pose.position.y = 0.8;
straight_regular_path.poses[9].pose.position.x = 0.5;
straight_regular_path.poses[9].pose.position.y = 0.9;
straight_regular_path.poses[10].pose.position.x = 0.5;
straight_regular_path.poses[10].pose.position.y = 1.0;
EXPECT_TRUE(smoother->smooth(straight_regular_path, max_time));
for (uint i = 0; i != straight_regular_path.poses.size() - 1; i++) {
// Check distances are still very evenly spaced
EXPECT_NEAR(
fabs(
straight_regular_path.poses[i].pose.position.y -
straight_regular_path.poses[i + 1].pose.position.y), 0.1, 0.001);
}
// test shorter and curved if given a right angle
nav_msgs::msg::Path right_angle_path;
right_angle_path = straight_regular_path;
straight_regular_path.poses[6].pose.position.x = 0.6;
straight_regular_path.poses[6].pose.position.y = 0.5;
straight_regular_path.poses[7].pose.position.x = 0.7;
straight_regular_path.poses[7].pose.position.y = 0.5;
straight_regular_path.poses[8].pose.position.x = 0.8;
straight_regular_path.poses[8].pose.position.y = 0.5;
straight_regular_path.poses[9].pose.position.x = 0.9;
straight_regular_path.poses[9].pose.position.y = 0.5;
straight_regular_path.poses[10].pose.position.x = 0.95;
straight_regular_path.poses[10].pose.position.y = 0.5;
EXPECT_TRUE(smoother->smooth(straight_regular_path, max_time));
EXPECT_NEAR(straight_regular_path.poses[5].pose.position.x, 0.637, 0.01);
EXPECT_NEAR(straight_regular_path.poses[5].pose.position.y, 0.353, 0.01);
// Test that collisions are rejected
nav_msgs::msg::Path collision_path;
collision_path.poses.resize(11);
collision_path.poses[0].pose.position.x = 0.0;
collision_path.poses[0].pose.position.y = 0.0;
collision_path.poses[1].pose.position.x = 0.2;
collision_path.poses[1].pose.position.y = 0.2;
collision_path.poses[2].pose.position.x = 0.4;
collision_path.poses[2].pose.position.y = 0.4;
collision_path.poses[3].pose.position.x = 0.6;
collision_path.poses[3].pose.position.y = 0.6;
collision_path.poses[4].pose.position.x = 0.8;
collision_path.poses[4].pose.position.y = 0.8;
collision_path.poses[5].pose.position.x = 1.0;
collision_path.poses[5].pose.position.y = 1.0;
collision_path.poses[6].pose.position.x = 1.1;
collision_path.poses[6].pose.position.y = 1.1;
collision_path.poses[7].pose.position.x = 1.2;
collision_path.poses[7].pose.position.y = 1.2;
collision_path.poses[8].pose.position.x = 1.3;
collision_path.poses[8].pose.position.y = 1.3;
collision_path.poses[9].pose.position.x = 1.4;
collision_path.poses[9].pose.position.y = 1.4;
collision_path.poses[10].pose.position.x = 1.5;
collision_path.poses[10].pose.position.y = 1.5;
EXPECT_FALSE(smoother->smooth(collision_path, max_time));
// test cusp / reversing segments
nav_msgs::msg::Path reversing_path;
reversing_path.poses.resize(11);
reversing_path.poses[0].pose.position.x = 0.5;
reversing_path.poses[0].pose.position.y = 0.0;
reversing_path.poses[1].pose.position.x = 0.5;
reversing_path.poses[1].pose.position.y = 0.1;
reversing_path.poses[2].pose.position.x = 0.5;
reversing_path.poses[2].pose.position.y = 0.2;
reversing_path.poses[3].pose.position.x = 0.5;
reversing_path.poses[3].pose.position.y = 0.3;
reversing_path.poses[4].pose.position.x = 0.5;
reversing_path.poses[4].pose.position.y = 0.4;
reversing_path.poses[5].pose.position.x = 0.5;
reversing_path.poses[5].pose.position.y = 0.5;
reversing_path.poses[6].pose.position.x = 0.5;
reversing_path.poses[6].pose.position.y = 0.4;
reversing_path.poses[7].pose.position.x = 0.5;
reversing_path.poses[7].pose.position.y = 0.3;
reversing_path.poses[8].pose.position.x = 0.5;
reversing_path.poses[8].pose.position.y = 0.2;
reversing_path.poses[9].pose.position.x = 0.5;
reversing_path.poses[9].pose.position.y = 0.1;
reversing_path.poses[10].pose.position.x = 0.5;
reversing_path.poses[10].pose.position.y = 0.0;
EXPECT_TRUE(smoother->smooth(reversing_path, max_time));
// // test rotate in place
tf2::Quaternion quat1, quat2;
quat1.setRPY(0.0, 0.0, 0.0);
quat2.setRPY(0.0, 0.0, 1.0);
straight_irregular_path.poses[5].pose.position.x = 0.5;
straight_irregular_path.poses[5].pose.position.y = 0.5;
straight_irregular_path.poses[5].pose.orientation = tf2::toMsg(quat1);
straight_irregular_path.poses[6].pose.position.x = 0.5;
straight_irregular_path.poses[6].pose.position.y = 0.5;
straight_irregular_path.poses[6].pose.orientation = tf2::toMsg(quat2);
EXPECT_TRUE(smoother->smooth(straight_irregular_path, max_time));
// test max iterations
smoother->setMaxItsToInvalid();
nav_msgs::msg::Path max_its_path;
max_its_path.poses.resize(11);
max_its_path.poses[0].pose.position.x = 0.5;
max_its_path.poses[0].pose.position.y = 0.0;
max_its_path.poses[1].pose.position.x = 0.5;
max_its_path.poses[1].pose.position.y = 0.1;
max_its_path.poses[2].pose.position.x = 0.5;
max_its_path.poses[2].pose.position.y = 0.2;
max_its_path.poses[3].pose.position.x = 0.5;
max_its_path.poses[3].pose.position.y = 0.3;
max_its_path.poses[4].pose.position.x = 0.5;
max_its_path.poses[4].pose.position.y = 0.4;
max_its_path.poses[5].pose.position.x = 0.5;
max_its_path.poses[5].pose.position.y = 0.5;
max_its_path.poses[6].pose.position.x = 0.5;
max_its_path.poses[6].pose.position.y = 0.6;
max_its_path.poses[7].pose.position.x = 0.5;
max_its_path.poses[7].pose.position.y = 0.7;
max_its_path.poses[8].pose.position.x = 0.5;
max_its_path.poses[8].pose.position.y = 0.8;
max_its_path.poses[9].pose.position.x = 0.5;
max_its_path.poses[9].pose.position.y = 0.9;
max_its_path.poses[10].pose.position.x = 0.5;
max_its_path.poses[10].pose.position.y = 1.0;
EXPECT_FALSE(smoother->smooth(max_its_path, max_time));
}