From 8be4e356de9febd6c7c9140f271eae4156bd56e8 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 9 Aug 2022 17:06:59 +0900 Subject: [PATCH] feat(motion_utils): add new nearest index function (#1396) * feat(motion_utils): add new nearest index function Signed-off-by: Takayuki Murooka * add tests Signed-off-by: Takayuki Murooka * fix rebase Signed-off-by: Takayuki Murooka * add README Signed-off-by: Takayuki Murooka * minor update Signed-off-by: Takayuki Murooka * fix ci error Signed-off-by: Takayuki Murooka * fix for traffic nearest index Signed-off-by: Takayuki Murooka * fix for markdownlint error Signed-off-by: Takayuki Murooka --- common/motion_utils/README.md | 100 ++++ .../trajectory/path_with_lane_id.hpp | 99 ++++ .../motion_utils/trajectory/trajectory.hpp | 121 ++++ .../motion_utils/media/ego_nearest_search.svg | 3 + .../src/trajectory/test_path_with_lane_id.cpp | 163 ++++++ .../test/src/trajectory/test_trajectory.cpp | 526 ++++++++++++++++++ 6 files changed, 1012 insertions(+) create mode 100644 common/motion_utils/README.md create mode 100644 common/motion_utils/include/motion_utils/trajectory/path_with_lane_id.hpp create mode 100644 common/motion_utils/media/ego_nearest_search.svg create mode 100644 common/motion_utils/test/src/trajectory/test_path_with_lane_id.cpp diff --git a/common/motion_utils/README.md b/common/motion_utils/README.md new file mode 100644 index 0000000000000..154da4bdf4387 --- /dev/null +++ b/common/motion_utils/README.md @@ -0,0 +1,100 @@ +# Motion Utils package + +## Nearest index search + +In this section, the nearest index and nearest segment index search is explained. + +We have the same functions for the nearest index search and nearest segment index search. +Taking for the example the nearest index search, we have two types of functions. + +The first function finds the nearest index with distance and yaw thresholds. + +```cpp +template +size_t findFirstNearestIndexWithSoftConstraints( + const T & points, const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()); +``` + +This function finds the first local solution within thresholds. +The reason to find the first local one is to deal with some edge cases explained in the next subsection. + +There are default parameters for thresholds arguments so that you can decide which thresholds to pass to the function. + +1. When both the distance and yaw thresholds are given. + - First, try to find the nearest index with both the distance and yaw thresholds. + - If not found, try to find again with only the distance threshold. + - If not found, find without any thresholds. +2. When only distance are given. + - First, try to find the nearest index the distance threshold. + - If not found, find without any thresholds. +3. When no thresholds are given. + - Find the nearest index. + +The second function finds the nearest index in the lane whose id is `lane_id`. + +```cpp +size_t findNearestIndexFromLaneId( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const geometry_msgs::msg::Point & pos, const int64_t lane_id); +``` + +### Application to various object + +Many node packages often calculate the nearest index of objects. +We will explain the recommended method to calculate it. + +#### Nearest index for the ego + +Assuming that the path length before the ego is short enough, we expect to find the correct nearest index in the following edge cases by `findFirstNearestIndexWithSoftConstraints` with both distance and yaw thresholds. +Blue circles describes the distance threshold from the base link position and two blue lines describe the yaw threshold against the base link orientation. +Among points in these cases, the correct nearest point which is red can be found. + +![ego_nearest_search](./media/ego_nearest_search.svg) + +Therefore, the implementation is as follows. + +```cpp +const size_t ego_nearest_idx = findFirstNearestIndexWithSoftConstraints(points, ego_pose, ego_nearest_dist_threshold, ego_nearest_yaw_threshold); +const size_t ego_nearest_seg_idx = findFirstNearestIndexWithSoftConstraints(points, ego_pose, ego_nearest_dist_threshold, ego_nearest_yaw_threshold); +``` + +#### Nearest index for dynamic objects + +For the ego nearest index, the orientation is considered in addition to the position since the ego is supposed to follow the points. +However, for the dynamic objects (e.g., predicted object), sometimes its orientation may be different from the points order, e.g. the dynamic object driving backward although the ego is driving forward. + +Therefore, the yaw threshold should not be considered for the dynamic object. +The implementation is as follows. + +```cpp +const size_t dynamic_obj_nearest_idx = findFirstNearestIndexWithSoftConstraints(points, dynamic_obj_pose, dynamic_obj_nearest_dist_threshold); +const size_t dynamic_obj_nearest_seg_idx = findFirstNearestIndexWithSoftConstraints(points, dynamic_obj_pose, dynamic_obj_nearest_dist_threshold); +``` + +#### Nearest index for traffic objects + +In lanelet maps, traffic objects belong to the specific lane. +With this specific lane's id, the correct nearest index can be found. + +The implementation is as follows. + +```cpp +// first extract `lane_id` which the traffic object belong to. +const size_t traffic_obj_nearest_idx = findNearestIndexFromLaneId(path_with_lane_id, traffic_obj_pos, lane_id); +const size_t traffic_obj_nearest_seg_idx = findNearestSegmentIndexFromLaneId(path_with_lane_id, traffic_obj_pos, lane_id); +``` + +## Path/Trajectory length calculation between designated points + +Based on the discussion so far, the nearest index search algorithm is different depending on the object type. +Therefore, we recommended using the wrapper utility functions which require the nearest index search (e.g., calculating the path length) with each nearest index search. + +For example, when we want to calculate the path length between the ego and the dynamic object, the implementation is as follows. + +```cpp +const size_t ego_nearest_idx = findFirstNearestSegmentIndex(points, ego_pose, ego_nearest_dist_threshold, ego_nearest_yaw_threshold); +const size_t dyn_obj_nearest_seg_idx = findFirstNearestSegmentIndex(points, dyn_obj_pose, dyn_obj_nearest_dist_threshold); +const double length_from_ego_to_obj = calcSignedArcLength(points, ego_pose, ego_nearest_seg_idx, dyn_obj_pose, dyn_obj_nearest_seg_idx); +``` diff --git a/common/motion_utils/include/motion_utils/trajectory/path_with_lane_id.hpp b/common/motion_utils/include/motion_utils/trajectory/path_with_lane_id.hpp new file mode 100644 index 0000000000000..a10db47a377a2 --- /dev/null +++ b/common/motion_utils/include/motion_utils/trajectory/path_with_lane_id.hpp @@ -0,0 +1,99 @@ +// Copyright 2022 TIER IV, Inc. +// +// 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 MOTION_UTILS__TRAJECTORY__PATH_WITH_LANE_ID_HPP_ +#define MOTION_UTILS__TRAJECTORY__PATH_WITH_LANE_ID_HPP_ + +#include "motion_utils/trajectory/trajectory.hpp" +#include "tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp" + +#include + +#include +#include + +namespace motion_utils +{ +inline boost::optional> getPathIndexRangeWithLaneId( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int64_t lane_id) +{ + size_t start_idx = 0; // NOTE: to prevent from maybe-uninitialized error + size_t end_idx = 0; // NOTE: to prevent from maybe-uninitialized error + + bool found_first_idx = false; + for (size_t i = 0; i < path.points.size(); ++i) { + const auto & p = path.points.at(i); + for (const auto & id : p.lane_ids) { + if (id == lane_id) { + if (!found_first_idx) { + start_idx = i; + found_first_idx = true; + } + end_idx = i; + } + } + } + + if (found_first_idx) { + return std::make_pair(start_idx, end_idx); + } + + return {}; +} + +inline size_t findNearestIndexFromLaneId( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const geometry_msgs::msg::Point & pos, const int64_t lane_id) +{ + const auto opt_range = getPathIndexRangeWithLaneId(path, lane_id); + if (opt_range) { + const size_t start_idx = opt_range->first; + const size_t end_idx = opt_range->second; + + validateNonEmpty(path.points); + + const auto sub_points = std::vector{ + path.points.begin() + start_idx, path.points.begin() + end_idx + 1}; + validateNonEmpty(sub_points); + + return start_idx + findNearestIndex(sub_points, pos); + } + + return findNearestIndex(path.points, pos); +} + +inline size_t findNearestSegmentIndexFromLaneId( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const geometry_msgs::msg::Point & pos, const int64_t lane_id) +{ + const size_t nearest_idx = findNearestIndexFromLaneId(path, pos, lane_id); + + if (nearest_idx == 0) { + return 0; + } + if (nearest_idx == path.points.size() - 1) { + return path.points.size() - 2; + } + + const double signed_length = calcLongitudinalOffsetToSegment(path.points, nearest_idx, pos); + + if (signed_length <= 0) { + return nearest_idx - 1; + } + + return nearest_idx; +} +} // namespace motion_utils + +#endif // MOTION_UTILS__TRAJECTORY__PATH_WITH_LANE_ID_HPP_ diff --git a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp index 912502ed9e61e..ae12808b29465 100644 --- a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp +++ b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp @@ -1059,6 +1059,127 @@ inline boost::optional insertStopPoint( return stop_idx; } + +template +double calcSignedArcLength( + const T & points, const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx) +{ + validateNonEmpty(points); + + const double signed_length_on_traj = calcSignedArcLength(points, src_seg_idx, dst_seg_idx); + const double signed_length_src_offset = + calcLongitudinalOffsetToSegment(points, src_seg_idx, src_point); + const double signed_length_dst_offset = + calcLongitudinalOffsetToSegment(points, dst_seg_idx, dst_point); + + return signed_length_on_traj - signed_length_src_offset + signed_length_dst_offset; +} + +template +size_t findFirstNearestIndexWithSoftConstraints( + const T & points, const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()) +{ + validateNonEmpty(points); + + { // with dist and yaw thresholds + const double squared_dist_threshold = dist_threshold * dist_threshold; + double min_squared_dist = std::numeric_limits::max(); + size_t min_idx = 0; + bool is_within_constraints = false; + for (size_t i = 0; i < points.size(); ++i) { + const auto squared_dist = + tier4_autoware_utils::calcSquaredDistance2d(points.at(i), pose.position); + const auto yaw = + tier4_autoware_utils::calcYawDeviation(tier4_autoware_utils::getPose(points.at(i)), pose); + + if (squared_dist_threshold < squared_dist || yaw_threshold < std::abs(yaw)) { + if (is_within_constraints) { + break; + } else { + continue; + } + } + + if (min_squared_dist <= squared_dist) { + continue; + } + + min_squared_dist = squared_dist; + min_idx = i; + is_within_constraints = true; + } + + // nearest index is found + if (is_within_constraints) { + return min_idx; + } + } + + { // with dist threshold + const double squared_dist_threshold = dist_threshold * dist_threshold; + double min_squared_dist = std::numeric_limits::max(); + size_t min_idx = 0; + bool is_within_constraints = false; + for (size_t i = 0; i < points.size(); ++i) { + const auto squared_dist = + tier4_autoware_utils::calcSquaredDistance2d(points.at(i), pose.position); + + if (squared_dist_threshold < squared_dist) { + if (is_within_constraints) { + break; + } else { + continue; + } + } + + if (min_squared_dist <= squared_dist) { + continue; + } + + min_squared_dist = squared_dist; + min_idx = i; + is_within_constraints = true; + } + + // nearest index is found + if (is_within_constraints) { + return min_idx; + } + } + + // without any threshold + return findNearestIndex(points, pose.position); +} + +template +size_t findFirstNearestSegmentIndexWithSoftConstraints( + const T & points, const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()) +{ + // find first nearest index with soft constraints (not segment index) + const size_t nearest_idx = + findFirstNearestIndexWithSoftConstraints(points, pose, dist_threshold, yaw_threshold); + + // calculate segment index + if (nearest_idx == 0) { + return 0; + } + if (nearest_idx == points.size() - 1) { + return points.size() - 2; + } + + const double signed_length = calcLongitudinalOffsetToSegment(points, nearest_idx, pose.position); + + if (signed_length <= 0) { + return nearest_idx - 1; + } + + return nearest_idx; +} } // namespace motion_utils #endif // MOTION_UTILS__TRAJECTORY__TRAJECTORY_HPP_ diff --git a/common/motion_utils/media/ego_nearest_search.svg b/common/motion_utils/media/ego_nearest_search.svg new file mode 100644 index 0000000000000..164f420bf4853 --- /dev/null +++ b/common/motion_utils/media/ego_nearest_search.svg @@ -0,0 +1,3 @@ + + +
nearest
nearest
2 * yaw
threshold
2 * yaw...
nearest
nearest
nearest
nearest
nearest
nearest
distance
threshold
distance...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/common/motion_utils/test/src/trajectory/test_path_with_lane_id.cpp b/common/motion_utils/test/src/trajectory/test_path_with_lane_id.cpp new file mode 100644 index 0000000000000..1eadf7105d087 --- /dev/null +++ b/common/motion_utils/test/src/trajectory/test_path_with_lane_id.cpp @@ -0,0 +1,163 @@ +// Copyright 2022 TIER IV, Inc. +// +// 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 "motion_utils/trajectory/path_with_lane_id.hpp" +// #include "tier4_autoware_utils/geometry/geometry.hpp" +// #include "tier4_autoware_utils/math/unit_conversion.hpp" + +#include + +#include +#include + +namespace +{ +using autoware_auto_planning_msgs::msg::PathPointWithLaneId; +using autoware_auto_planning_msgs::msg::PathWithLaneId; +using tier4_autoware_utils::createPoint; + +geometry_msgs::msg::Pose createPose( + double x, double y, double z, double roll, double pitch, double yaw) +{ + geometry_msgs::msg::Pose p; + p.position = createPoint(x, y, z); + p.orientation = tier4_autoware_utils::createQuaternionFromRPY(roll, pitch, yaw); + return p; +} + +PathWithLaneId generateTestPathWithLaneId(const size_t num_points, const double point_interval) +{ + PathWithLaneId path; + for (size_t i = 0; i < num_points; ++i) { + const double x = i * point_interval; + const double y = 0.0; + + PathPointWithLaneId p; + p.point.pose = createPose(x, y, 0.0, 0.0, 0.0, 0.0); + p.lane_ids.push_back(i); + path.points.push_back(p); + } + + return path; +} +} // namespace + +TEST(path_with_lane_id, getPathIndexRangeWithLaneId) +{ + using autoware_auto_planning_msgs::msg::PathWithLaneId; + using motion_utils::getPathIndexRangeWithLaneId; + + // Usual cases + { + PathWithLaneId points; + points.points.resize(6); + points.points.at(0).lane_ids.push_back(3); + points.points.at(1).lane_ids.push_back(3); + points.points.at(2).lane_ids.push_back(1); + points.points.at(3).lane_ids.push_back(2); + points.points.at(4).lane_ids.push_back(2); + points.points.at(5).lane_ids.push_back(2); + + { + const auto res = getPathIndexRangeWithLaneId(points, 3); + EXPECT_EQ(res->first, 0U); + EXPECT_EQ(res->second, 1U); + } + { + const auto res = getPathIndexRangeWithLaneId(points, 1); + EXPECT_EQ(res->first, 2U); + EXPECT_EQ(res->second, 2U); + } + { + const auto res = getPathIndexRangeWithLaneId(points, 2); + EXPECT_EQ(res->first, 3U); + EXPECT_EQ(res->second, 5U); + } + { + const auto res = getPathIndexRangeWithLaneId(points, 4); + EXPECT_EQ(res, boost::none); + } + } + + // Empty points + { + PathWithLaneId points; + const auto res = getPathIndexRangeWithLaneId(points, 4); + EXPECT_EQ(res, boost::none); + } +} + +TEST(path_with_lane_id, findNearestIndexFromLaneId) +{ + using motion_utils::findNearestIndexFromLaneId; + using motion_utils::findNearestSegmentIndexFromLaneId; + + const auto path = generateTestPathWithLaneId(10, 1.0); + + // Normal cases + { + auto modified_path = path; + for (size_t i = 0; i < 10; ++i) { + modified_path.points.at(i).lane_ids = {100}; + } + EXPECT_EQ(findNearestIndexFromLaneId(modified_path, createPoint(2.4, 1.3, 0.0), 100), 2U); + EXPECT_EQ( + findNearestSegmentIndexFromLaneId(modified_path, createPoint(2.4, 1.3, 0.0), 100), 2U); + } + + { + auto modified_path = path; + for (size_t i = 3; i < 6; ++i) { + modified_path.points.at(i).lane_ids = {100}; + } + EXPECT_EQ(findNearestIndexFromLaneId(modified_path, createPoint(4.1, 0.3, 0.0), 100), 4U); + EXPECT_EQ( + findNearestSegmentIndexFromLaneId(modified_path, createPoint(4.1, 0.3, 0.0), 100), 4U); + } + + { + auto modified_path = path; + for (size_t i = 8; i < 9; ++i) { + modified_path.points.at(i).lane_ids = {100}; + } + EXPECT_EQ(findNearestIndexFromLaneId(modified_path, createPoint(8.5, -0.5, 0.0), 100), 8U); + EXPECT_EQ( + findNearestSegmentIndexFromLaneId(modified_path, createPoint(8.5, -0.5, 0.0), 100), 8U); + } + + // Nearest is not within range + { + auto modified_path = path; + for (size_t i = 3; i < 9; ++i) { + modified_path.points.at(i).lane_ids = {100}; + } + EXPECT_EQ(findNearestIndexFromLaneId(modified_path, createPoint(2.4, 1.3, 0.0), 100), 3U); + EXPECT_EQ( + findNearestSegmentIndexFromLaneId(modified_path, createPoint(2.4, 1.3, 0.0), 100), 2U); + } + + // Path does not contain lane_id. + { + EXPECT_EQ(findNearestIndexFromLaneId(path, createPoint(2.4, 1.3, 0.0), 100), 2U); + EXPECT_EQ(findNearestSegmentIndexFromLaneId(path, createPoint(2.4, 1.3, 0.0), 100), 2U); + } + + // Empty points + EXPECT_THROW( + findNearestIndexFromLaneId(PathWithLaneId{}, createPoint(2.4, 1.3, 0.0), 100), + std::invalid_argument); + EXPECT_THROW( + findNearestSegmentIndexFromLaneId(PathWithLaneId{}, createPoint(2.4, 1.3, 0.0), 100), + std::invalid_argument); +} diff --git a/common/motion_utils/test/src/trajectory/test_trajectory.cpp b/common/motion_utils/test/src/trajectory/test_trajectory.cpp index 8e3809f739936..068fbe3e42de1 100644 --- a/common/motion_utils/test/src/trajectory/test_trajectory.cpp +++ b/common/motion_utils/test/src/trajectory/test_trajectory.cpp @@ -3489,3 +3489,529 @@ TEST(trajectory, insertStopPoint_from_a_pose) EXPECT_EQ(insertStopPoint(src_pose, 10.0, traj_out.points, max_dist, deg2rad(45)), boost::none); } } + +TEST(trajectory, findFirstNearestIndexWithSoftConstraints) +{ + using motion_utils::findFirstNearestIndexWithSoftConstraints; + using motion_utils::findFirstNearestSegmentIndexWithSoftConstraints; + using tier4_autoware_utils::pi; + + const auto traj = generateTestTrajectory(10, 1.0); + + // Non overlapped points + { + // 1. Dist and yaw thresholds are given + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 2.0, 0.4), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 2.0, 0.4), + 2U); + + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(4.1, 0.3, 0.0, 0.0, 0.0, -0.8), 0.5, 1.0), + 4U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(4.1, 0.3, 0.0, 0.0, 0.0, -0.8), 0.5, 1.0), + 4U); + + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(8.5, -0.5, 0.0, 0.0, 0.0, 0.0), 1.0, 0.1), + 8U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(8.5, -0.5, 0.0, 0.0, 0.0, 0.0), 1.0, 0.1), + 8U); + + // Dist is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 1.0, 0.4), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 1.0, 0.4), + 2U); + + // Yaw is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 2.0, 0.2), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 2.0, 0.2), + 2U); + + // Dist and yaw is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 1.0, 0.2), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 1.0, 0.2), + 2U); + + // Empty points + EXPECT_THROW( + findFirstNearestIndexWithSoftConstraints( + Trajectory{}.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 1.0, 0.2), + std::invalid_argument); + EXPECT_THROW( + findFirstNearestSegmentIndexWithSoftConstraints( + Trajectory{}.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 1.0, 0.2), + std::invalid_argument); + + // 2. Dist threshold is given + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 2.0), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 2.0), + 2U); + + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(4.1, 0.3, 0.0, 0.0, 0.0, -0.8), 0.5), + 4U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(4.1, 0.3, 0.0, 0.0, 0.0, -0.8), 0.5), + 4U); + + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(8.5, -0.5, 0.0, 0.0, 0.0, 0.0), 1.0), + 8U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(8.5, -0.5, 0.0, 0.0, 0.0, 0.0), 1.0), + 8U); + + // Dist is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 1.0), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3), 1.0), + 2U); + + // 3. No threshold is given + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3)), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(2.4, 1.3, 0.0, 0.0, 0.0, 0.3)), + 2U); + + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(4.1, 0.3, 0.0, 0.0, 0.0, -0.8)), + 4U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(4.1, 0.3, 0.0, 0.0, 0.0, -0.8)), + 4U); + + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + traj.points, createPose(8.5, -0.5, 0.0, 0.0, 0.0, 0.0)), + 8U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + traj.points, createPose(8.5, -0.5, 0.0, 0.0, 0.0, 0.0)), + 8U); + } + + // Vertically crossing points + { + // ___ + // | | + // S__|__| + // | + // | + // G + std::vector poses; + poses.push_back(createPose(-2.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(-1.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(1.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(2.0, 0.0, 0.0, 0.0, 0.0, pi / 2.0)); + poses.push_back(createPose(2.0, 1.0, 0.0, 0.0, 0.0, pi / 2.0)); + poses.push_back(createPose(2.0, 2.0, 0.0, 0.0, 0.0, pi)); + poses.push_back(createPose(1.0, 2.0, 0.0, 0.0, 0.0, pi)); + poses.push_back(createPose(0.0, 2.0, 0.0, 0.0, 0.0, -pi / 2.0)); + poses.push_back(createPose(0.0, 1.0, 0.0, 0.0, 0.0, -pi / 2.0)); + poses.push_back(createPose(0.0, 0.0, 0.0, 0.0, 0.0, -pi / 2.0)); + poses.push_back(createPose(0.0, -1.0, 0.0, 0.0, 0.0, -pi / 2.0)); + poses.push_back(createPose(0.0, -2.0, 0.0, 0.0, 0.0, -pi / 2.0)); + + // 1. Dist and yaw thresholds are given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 1.0, 0.4), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 1.0, 0.4), + 2U); + + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, -pi / 2.0), 1.0, 0.4), + 10U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, -pi / 2.0), 1.0, 0.4), + 9U); + + // Several nearest index within threshold + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 10.0, pi * 2.0), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 10.0, pi * 2.0), + 2U); + + // Dist is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 0.0, 0.4), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 0.0, 0.4), + 2U); + + // Yaw is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.3), 1.0, 0.0), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.3), 1.0, 0.0), + 2U); + + // Dist and yaw is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.3), 0.0, 0.0), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.3), 0.0, 0.0), + 2U); + } + + // 2. Dist threshold is given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 1.0), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 1.0), + 2U); + + // Several nearest index within threshold + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 10.0), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 10.0), + 2U); + + // Dist is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 0.0), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0), 0.0), + 2U); + } + + // 3. No threshold is given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints(poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0)), + 2U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(0.3, 0.3, 0.0, 0.0, 0.0, 0.0)), + 2U); + } + } + + { + // Points has a loop structure with the opposite direction (= u-turn) + // __ + // S/G ___|_| + + std::vector poses; + poses.push_back(createPose(-3.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(-2.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(-1.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(1.0, 0.0, 0.0, 0.0, 0.0, pi / 2.0)); + poses.push_back(createPose(1.0, 1.0, 0.0, 0.0, 0.0, pi)); + poses.push_back(createPose(0.0, 1.0, 0.0, 0.0, 0.0, -pi / 2.0)); + poses.push_back(createPose(0.0, 0.0, 0.0, 0.0, 0.0, pi)); + poses.push_back(createPose(-1.0, 0.0, 0.0, 0.0, 0.0, pi)); + poses.push_back(createPose(-2.0, 0.0, 0.0, 0.0, 0.0, pi)); + poses.push_back(createPose(-3.0, 0.0, 0.0, 0.0, 0.0, pi)); + + // 1. Dist and yaw thresholds are given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0, 0.4), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0, 0.4), + 0U); + + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi), 1.0, 0.4), + 9U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi), 1.0, 0.4), + 9U); + + // Several nearest index within threshold + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi), 10.0, pi * 2.0), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi), 10.0, pi * 2.0), + 0U); + + // Dist is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi), 0.0, 0.4), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi), 0.0, 0.4), + 0U); + + // Yaw is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi * 0.9), 1.0, 0.0), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi * 0.9), 1.0, 0.0), + 0U); + + // Dist and yaw is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi * 0.9), 0.0, 0.0), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, pi * 0.9), 0.0, 0.0), + 0U); + } + + // 2. Dist threshold is given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0), + 0U); + + // Several nearest index within threshold + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0), 10.0), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0), 10.0), + 0U); + + // Dist is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0), 0.0), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0), 0.0), + 0U); + } + + // 3. No threshold is given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints(poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0)), + 1U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(-2.1, 0.1, 0.0, 0.0, 0.0, 0.0)), + 0U); + } + } + + { // Points has a loop structure with the same direction + // ___ + // | | + // S__|__|__G + std::vector poses; + poses.push_back(createPose(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(1.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(2.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(3.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(4.0, 0.0, 0.0, 0.0, 0.0, pi / 2.0)); + poses.push_back(createPose(4.0, 1.0, 0.0, 0.0, 0.0, pi / 2.0)); + poses.push_back(createPose(4.0, 2.0, 0.0, 0.0, 0.0, pi)); + poses.push_back(createPose(3.0, 2.0, 0.0, 0.0, 0.0, pi)); + poses.push_back(createPose(2.0, 2.0, 0.0, 0.0, 0.0, -pi / 2.0)); + poses.push_back(createPose(2.0, 1.0, 0.0, 0.0, 0.0, -pi / 2.0)); + poses.push_back(createPose(2.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(3.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(4.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(5.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + poses.push_back(createPose(6.0, 0.0, 0.0, 0.0, 0.0, 0.0)); + + // 1. Dist and yaw thresholds are given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0, 0.4), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0, 0.4), + 3U); + + // Several nearest index within threshold + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 10.0, pi * 2.0), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 10.0, pi * 2.0), + 3U); + + // Dist is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 0.0, 0.4), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 0.0, 0.4), + 3U); + + // Yaw is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0, 0.0), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0, 0.0), + 3U); + + // Dist and yaw is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 0.0, 0.0), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 0.0, 0.0), + 3U); + } + + // 2. Dist threshold is given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 1.0), + 3U); + + // Several nearest index within threshold + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 10.0), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 10.0), + 3U); + + // Dist is out of range + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 0.0), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0), 0.0), + 3U); + } + + // 3. No threshold is given + { + // Normal cases + EXPECT_EQ( + findFirstNearestIndexWithSoftConstraints(poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0)), + 3U); + EXPECT_EQ( + findFirstNearestSegmentIndexWithSoftConstraints( + poses, createPose(3.1, 0.1, 0.0, 0.0, 0.0, 0.0)), + 3U); + } + } +}