49 const std::optional<ConflictSample>& conflict) {
59 visualization_msgs::msg::MarkerArray marker_array;
60 auto make_box_marker = [&](
const std::string& ns,
int marker_id,
const geometry_msgs::msg::Pose& pose,
double length,
61 double width,
double z,
float r,
float g,
float b,
float a) {
62 visualization_msgs::msg::Marker marker;
63 marker.header = target_header;
65 marker.id = marker_id;
66 marker.type = visualization_msgs::msg::Marker::CUBE;
67 marker.action = visualization_msgs::msg::Marker::ADD;
69 marker.pose.position.z = z;
70 marker.scale.x = length;
71 marker.scale.y = width;
72 marker.scale.z = 0.08;
73 marker.lifetime.sec = 0;
74 marker.lifetime.nanosec = 500000000;
79 marker_array.markers.push_back(marker);
82 const geometry_msgs::msg::Pose ego_pose =
toPose(conflict->ego_box);
85 const double ego_length = 2.0 * conflict->ego_box.half_length;
86 const double ego_width = 2.0 * conflict->ego_box.half_width;
87 make_box_marker(
"object_interaction_ego_safety_box", 0,
toPose(ego_safety_box), 2.0 * ego_safety_box.
half_length,
88 2.0 * ego_safety_box.
half_width, 0.12, 1.0F, 0.55F, 0.0F, 0.28F);
89 make_box_marker(
"object_interaction_ego_box", 1, ego_pose, ego_length, ego_width, 0.18, 1.0F, 0.0F, 0.0F, 0.55F);
90 make_box_marker(
"object_interaction_object_box", 2,
toPose(conflict->object_box), 2.0 * conflict->object_box.half_length,
91 2.0 * conflict->object_box.half_width, 0.24, 0.0F, 0.45F, 1.0F, 0.55F);
97 const rclcpp::Time& stamp)
const {
98 std::vector<ObjectTrajectory> object_trajectories;
99 for (
const auto&
object : tf_object_list.objects) {
100 double object_width = 0.0;
101 double object_length = 0.0;
103 object_width = perception_msgs::object_access::getWidth(
object);
104 object_length = perception_msgs::object_access::getLength(
object);
105 }
catch (
const std::exception&) {
112 auto add_static_object = [&]() {
114 trajectory.
id =
object.id;
116 trajectory.
samples.push_back(
buildObjectSample(
object.state, tf_object_list.header, stamp, object_length, object_width));
117 object_trajectories.push_back(trajectory);
121 std::vector<const perception_msgs::msg::ObjectStatePrediction*> selected_predictions;
122 for (
const auto& prediction :
object.state_predictions) {
123 if (prediction.probability >=
min_prediction_prob_) selected_predictions.push_back(&prediction);
125 if (selected_predictions.empty() && !
object.state_predictions.empty()) {
126 selected_predictions.push_back(
127 &*std::max_element(
object.state_predictions.begin(),
object.state_predictions.end(),
128 [](
const auto& lhs,
const auto& rhs) { return lhs.probability < rhs.probability; }));
131 if (selected_predictions.empty()) {
136 for (
const auto* prediction : selected_predictions) {
137 if (prediction ==
nullptr || prediction->states.empty()) {
142 trajectory.
id =
object.id;
144 trajectory.
samples.reserve(prediction->states.size());
145 for (
const auto& state : prediction->states) {
148 object_trajectories.push_back(trajectory);
151 return object_trajectories;
155 const std::vector<ObjectTrajectory>& object_trajectories)
const {
156 if (ego_path.empty())
return std::nullopt;
158 const auto& ego_offset_msg =
ego_data_.state.reference_point.translation_to_geometric_center;
159 const Eigen::Vector2d ego_center_offset(ego_offset_msg.x, ego_offset_msg.y);
162 auto ego_box_at = [&](
double ego_t) {
163 const size_t idx = std::min(
static_cast<size_t>(std::floor(std::max(ego_t, 0.0) /
dt_)), ego_path.size() - 1);
164 const size_t next_idx = std::min(idx + 1, ego_path.size() - 1);
165 const double segment_start_t =
dt_ *
static_cast<double>(idx);
166 const double alpha = next_idx > idx ? std::clamp((ego_t - segment_start_t) /
dt_, 0.0, 1.0) : 0.0;
167 const Eigen::Vector2d position = ego_path[idx].position + alpha * (ego_path[next_idx].position - ego_path[idx].position);
168 Eigen::Vector2d heading = ego_path[next_idx].position - ego_path[idx].position;
169 if (heading.squaredNorm() < 1e-9 && idx > 0) heading = ego_path[idx].position - ego_path[idx - 1].position;
170 const double yaw = heading.squaredNorm() > 1e-9 ?
wrap_angle_rad(std::atan2(heading.y(), heading.x())) : 0.0;
175 const size_t last_segment_idx = ego_path.size() > 1 ? ego_path.size() - 2 : 0;
176 for (
size_t i = 0; i <= last_segment_idx; ++i) {
177 const double segment_start_t =
dt_ *
static_cast<double>(i);
180 const int steps = std::max(1,
static_cast<int>(std::ceil((segment_end_t - segment_start_t) / check_dt)));
182 for (
int step = 0; step <= steps; ++step) {
184 segment_start_t + (segment_end_t - segment_start_t) *
static_cast<double>(step) /
static_cast<double>(steps);
189 for (
const auto& object_trajectory : object_trajectories) {
190 if (object_trajectory.samples.empty())
continue;
192 if (object_trajectory.is_static) {
193 const auto& object_sample = object_trajectory.samples.front();
194 if (
overlaps(ego_safety_box, object_sample.box)) {
195 return ConflictSample{object_trajectory.id, ego_box, object_sample.box};
200 for (
size_t sample_idx = 0; sample_idx < object_trajectory.samples.size(); ++sample_idx) {
201 const auto& object_sample = object_trajectory.samples[sample_idx];
203 sample_idx + 1 < object_trajectory.samples.size() ? &object_trajectory.samples[sample_idx + 1] :
nullptr;
205 TimedBox2D timed_object_sample = object_sample;
208 if (ego_t >= object_sample.t && ego_t <= next_sample->t) {
210 }
else if (std::abs(ego_t - next_sample->
t) < std::abs(ego_t - object_sample.t)) {
211 timed_object_sample = *next_sample;
217 if (
overlaps(ego_safety_box, timed_object_sample.
box)) {
228 const std::vector<SimplePathPoint>& base_path_points,
241 const rclcpp::Time stamp(target_header.stamp);
244 "Object list is older than " + std::to_string(
object_timeout_) +
" seconds. Ignoring objects for this planning cycle.";
246 RCLCPP_DEBUG(this->get_logger(),
"%s", msg.c_str());
255 perception_msgs::msg::ObjectList tf_object_list =
object_list_;
258 tf_object_list =
tf2_buffer_->transform(
object_list_, target_header.frame_id, tf2_ros::fromMsg(target_header.stamp),
260 }
catch (tf2::TransformException& ex) {
262 "Object transformation is not available: " + std::string(ex.what()) +
". Ignoring objects for this planning cycle.";
264 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
271 tf_object_list.objects.erase(
272 std::remove_if(tf_object_list.objects.begin(), tf_object_list.objects.end(),
273 [](
const auto&
object) { return perception_msgs::object_access::getCenterPosition(object.state).x < 0.0; }),
274 tf_object_list.objects.end());
277 if (object_trajectories.empty()) {
282 double initial_speed_cap = 0.0;
283 for (
const auto& point : route_plan.
path.
points) initial_speed_cap = std::max(initial_speed_cap, point.v);
284 if (
v_ref_ >= 0.0) initial_speed_cap = std::max(initial_speed_cap,
v_ref_);
288 const rclcpp::Time iteration_begin = rclcpp::Clock(RCL_SYSTEM_TIME).now();
289 const double remembered_speed_cap = std::clamp(
last_object_speed_cap_.value_or(initial_speed_cap), 0.0, initial_speed_cap);
290 double search_start_speed_cap = remembered_speed_cap;
291 bool attempted_release =
false;
295 attempted_release = search_start_speed_cap > remembered_speed_cap + 1e-6;
299 double speed_cap = search_start_speed_cap;
300 size_t iteration_count = 0;
301 std::optional<ConflictSample> last_conflict;
302 while (speed_cap > 0.0) {
304 std::vector<SimplePathPoint> candidate_path =
306 const std::optional<ConflictSample> conflict =
firstConflict(candidate_path, object_trajectories);
307 if (conflict.has_value()) {
308 last_conflict = conflict;
313 const rclcpp::Time iteration_end = rclcpp::Clock(RCL_SYSTEM_TIME).now();
315 speed_cap < initial_speed_cap ? last_conflict : std::optional<ConflictSample>{});
316 RCLCPP_DEBUG(this->get_logger(),
"Object velocity iteration took %f ms (%zu iterations)",
317 (iteration_end - iteration_begin).seconds() * 1e3, iteration_count);
322 if (speed_cap >= initial_speed_cap - 1e-6 || speed_cap + 1e-6 < search_start_speed_cap || attempted_release) {
330 if (last_conflict.has_value()) {
335 const std::string msg =
"Object avoidance speed cap " + std::to_string(speed_cap) +
337 " m/s. Publishing standstill.";
339 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
344 if (speed_cap < initial_speed_cap) {
346 if (last_conflict.has_value()) {
349 RCLCPP_DEBUG(this->get_logger(),
"Reduced reference speed cap to %f m/s to avoid object conflict", speed_cap);
356 const rclcpp::Time iteration_end = rclcpp::Clock(RCL_SYSTEM_TIME).now();
357 RCLCPP_DEBUG(this->get_logger(),
"Object velocity iteration took %f ms (%zu iterations)",
358 (iteration_end - iteration_begin).seconds() * 1e3, iteration_count);
360 const std::optional<ConflictSample> standstill_conflict =
firstConflict(route_plan.
path.
points, object_trajectories);
361 if (standstill_conflict.has_value()) {
364 health_.
key_value_pairs.insert_or_assign(
"ObjectConflictId", std::to_string(standstill_conflict->object_id));
365 last_conflict = standstill_conflict;
367 const std::string msg =
"Reduced reference speed cap to 0.0 m/s; object " + std::to_string(standstill_conflict->object_id) +
368 " still conflicts at standstill. Publishing standstill.";
370 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
374 if (last_conflict.has_value()) {
378 const std::string msg =
"Reduced reference speed cap to 0.0 m/s to avoid object conflict. Publishing standstill.";
380 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());