simple_planner v1.4.0
Loading...
Searching...
No Matches
object_geometry.hpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
4#pragma once
5
6#include <algorithm>
7#include <array>
8#include <cmath>
9#include <cstdint>
10#include <vector>
11
12#include <Eigen/Dense>
13
14#include <geometry_msgs/msg/pose.hpp>
15#include <std_msgs/msg/header.hpp>
16
17#include <perception_msgs/msg/object_state.hpp>
18#include <perception_msgs_utils/object_access.hpp>
19
20#include <rclcpp/time.hpp>
21
22// Pure, node-independent geometry used by the object-handling logic (see object_handling.cpp).
23// Everything here is free of ROS-node state and therefore unit-testable in isolation.
24
25namespace simple_planner {
26
27// 2D oriented bounding box used for object/ego conflict checks.
29 Eigen::Vector2d center = Eigen::Vector2d::Zero();
30 Eigen::Vector2d axis_x = Eigen::Vector2d::UnitX();
31 Eigen::Vector2d axis_y = Eigen::Vector2d::UnitY();
32 double half_length = 0.0;
33 double half_width = 0.0;
34};
35
36// Oriented bounding box with the time (relative to the planning stamp) it refers to.
37struct TimedBox2D {
39 double t = 0.0;
40 double yaw = 0.0;
41 double length = 0.0;
42 double width = 0.0;
43};
44
45// An object reduced to a sequence of timed boxes (single box if static).
47 uint64_t id = 0;
48 bool is_static = false;
49 std::vector<TimedBox2D> samples;
50};
51
52// A detected conflict between the ego trajectory and an object box.
58
65inline double wrap_angle_rad(double angle_rad) {
66 double capped_angle_rad = angle_rad;
67 while (capped_angle_rad > M_PI) capped_angle_rad -= 2 * M_PI;
68 while (capped_angle_rad < -M_PI) capped_angle_rad += 2 * M_PI;
69 return capped_angle_rad;
70}
71
79inline Eigen::Vector2d rotate(const Eigen::Vector2d& vec, double yaw) {
80 const double c = std::cos(yaw);
81 const double s = std::sin(yaw);
82 return Eigen::Vector2d(c * vec.x() - s * vec.y(), s * vec.x() + c * vec.y());
83}
84
93inline double getStateRelativeTime(const perception_msgs::msg::ObjectState& state,
94 const std_msgs::msg::Header& fallback_header,
95 const rclcpp::Time& planning_stamp) {
96 const bool has_state_stamp = state.header.stamp.sec != 0 || state.header.stamp.nanosec != 0;
97 const auto& source_header = has_state_stamp ? state.header : fallback_header;
98 return (rclcpp::Time(source_header.stamp) - planning_stamp).seconds();
99}
100
110inline OrientedBox2D buildOrientedBox(const Eigen::Vector2d& center, double yaw, double length, double width) {
111 OrientedBox2D box;
112 box.center = center;
113 box.axis_x = rotate(Eigen::Vector2d::UnitX(), yaw);
114 box.axis_y = rotate(Eigen::Vector2d::UnitY(), yaw);
115 box.half_length = std::max(length, 0.0) / 2.0;
116 box.half_width = std::max(width, 0.0) / 2.0;
117 return box;
118}
119
129 double longitudinal_safety_distance,
130 double lateral_safety_distance) {
131 OrientedBox2D expanded_box = box;
132 const double longitudinal_margin = std::max(longitudinal_safety_distance, -2.0 * box.half_length);
133 const double lateral_margin = std::max(lateral_safety_distance, -box.half_width);
134 expanded_box.center += 0.5 * longitudinal_margin * box.axis_x;
135 expanded_box.half_length += 0.5 * longitudinal_margin;
136 expanded_box.half_width += lateral_margin;
137 return expanded_box;
138}
139
146inline std::array<Eigen::Vector2d, 4> getBoxCorners(const OrientedBox2D& box) {
147 return {box.center + box.half_length * box.axis_x + box.half_width * box.axis_y,
148 box.center + box.half_length * box.axis_x - box.half_width * box.axis_y,
149 box.center - box.half_length * box.axis_x - box.half_width * box.axis_y,
150 box.center - box.half_length * box.axis_x + box.half_width * box.axis_y};
151}
152
159inline geometry_msgs::msg::Pose toPose(const OrientedBox2D& box) {
160 geometry_msgs::msg::Pose pose;
161 pose.position.x = box.center.x();
162 pose.position.y = box.center.y();
163 pose.position.z = 0.0;
164 const double yaw = std::atan2(box.axis_x.y(), box.axis_x.x());
165 pose.orientation.z = std::sin(yaw / 2.0);
166 pose.orientation.w = std::cos(yaw / 2.0);
167 return pose;
168}
169
178inline TimedBox2D interpolateTimedBox(const TimedBox2D& lhs, const TimedBox2D& rhs, double t) {
179 const double duration = rhs.t - lhs.t;
180 const double alpha = std::abs(duration) > 1e-6 ? std::clamp((t - lhs.t) / duration, 0.0, 1.0) : 0.0;
181 TimedBox2D sample;
182 sample.t = t;
183 sample.yaw = wrap_angle_rad(lhs.yaw + alpha * wrap_angle_rad(rhs.yaw - lhs.yaw));
184 sample.length = lhs.length + alpha * (rhs.length - lhs.length);
185 sample.width = lhs.width + alpha * (rhs.width - lhs.width);
186 const Eigen::Vector2d center = lhs.box.center + alpha * (rhs.box.center - lhs.box.center);
187 sample.box = buildOrientedBox(center, sample.yaw, sample.length, sample.width);
188 return sample;
189}
190
199inline bool overlaps(const OrientedBox2D& first_box, const OrientedBox2D& second_box) {
200 const Eigen::Vector2d center_delta = second_box.center - first_box.center;
201 const std::array<Eigen::Vector2d, 4> axes = {first_box.axis_x, first_box.axis_y, second_box.axis_x, second_box.axis_y};
202 for (const auto& axis : axes) {
203 const double first_extent = first_box.half_length * std::abs(axis.dot(first_box.axis_x)) +
204 first_box.half_width * std::abs(axis.dot(first_box.axis_y));
205 const double second_extent = second_box.half_length * std::abs(axis.dot(second_box.axis_x)) +
206 second_box.half_width * std::abs(axis.dot(second_box.axis_y));
207 if (std::abs(axis.dot(center_delta)) > first_extent + second_extent + 1e-6) {
208 return false;
209 }
210 }
211 return true;
212}
213
224inline TimedBox2D buildObjectSample(const perception_msgs::msg::ObjectState& state,
225 const std_msgs::msg::Header& fallback_header,
226 const rclcpp::Time& stamp,
227 double length,
228 double width) {
229 const auto center_msg = perception_msgs::object_access::getCenterPosition(state);
230 TimedBox2D sample;
231 sample.yaw = perception_msgs::object_access::getYaw(state);
232 sample.length = length;
233 sample.width = width;
234 sample.box = buildOrientedBox(Eigen::Vector2d(center_msg.x, center_msg.y), sample.yaw, length, width);
235 sample.t = getStateRelativeTime(state, fallback_header, stamp);
236 return sample;
237}
238
239} // namespace simple_planner
Namespace for simple_planner package.
OrientedBox2D buildOrientedBox(const Eigen::Vector2d &center, double yaw, double length, double width)
Builds an oriented 2D bounding box from center pose and dimensions.
TimedBox2D interpolateTimedBox(const TimedBox2D &lhs, const TimedBox2D &rhs, double t)
Interpolates a timed box sample at the requested relative time.
Eigen::Vector2d rotate(const Eigen::Vector2d &vec, double yaw)
Rotates a 2D vector by the given yaw angle.
double wrap_angle_rad(double angle_rad)
Wraps an angle to the range [-pi, pi].
bool overlaps(const OrientedBox2D &first_box, const OrientedBox2D &second_box)
Checks whether two oriented boxes overlap.
TimedBox2D buildObjectSample(const perception_msgs::msg::ObjectState &state, const std_msgs::msg::Header &fallback_header, const rclcpp::Time &stamp, double length, double width)
Builds a timed bounding-box sample for a single object state.
OrientedBox2D expandBoxWithSafetyMargins(const OrientedBox2D &box, double longitudinal_safety_distance, double lateral_safety_distance)
Expands a box by the given safety margins.
double getStateRelativeTime(const perception_msgs::msg::ObjectState &state, const std_msgs::msg::Header &fallback_header, const rclcpp::Time &planning_stamp)
Computes an object state's time relative to the planning stamp.
geometry_msgs::msg::Pose toPose(const OrientedBox2D &box)
Converts an oriented box center and heading to a ROS pose.
std::array< Eigen::Vector2d, 4 > getBoxCorners(const OrientedBox2D &box)
Returns the corners of an oriented box.
std::vector< TimedBox2D > samples