255 const Eigen::Vector2f startPos2D =
conv::to2D(start.translation());
258 if (costmap_->isInCollision(startPos2D))
260 ARMARX_WARNING <<
"Start position " << startPos2D <<
" is in collision. "
261 <<
"Searching for recovery position within "
262 << generalConfig_.inCollisionDistanceThresholdForRecovery <<
" mm...";
264 const auto recoveryVertex = costmap_->findClosestCollisionFreeVertex(
265 startPos2D, generalConfig_.inCollisionDistanceThresholdForRecovery);
269 ARMARX_ERROR <<
"No valid recovery position found within threshold of "
270 << generalConfig_.inCollisionDistanceThresholdForRecovery <<
" mm";
274 const Eigen::Vector2f recoveryPos = recoveryVertex->position;
277 <<
" (distance: " << (recoveryPos - startPos2D).
norm() <<
" mm). "
278 <<
"Prepending original start position to trajectory.";
282 if (!planningResult.plan.has_value())
289 auto result =
calculatePath(planningResult, goal, recoveryInfo);
291 if (!result && generalConfig_.navigateCloseAsPossible)
293 const Eigen::Vector2f goalPos =
conv::to2D(goal.translation());
295 planningResult.plan.value(), goalPos, costmap_.value());
299 <<
"Navigating to closest reachable position at "
301 <<
" (distance to goal: " << closest->euclideanDistanceToGoal
304 alternativeGoal.translation().head<2>() = closest->position;
305 result =
calculatePath(planningResult, alternativeGoal, recoveryInfo);
316 if (!result && generalConfig_.navigateCloseAsPossible && planningResult.plan.has_value())
318 const Eigen::Vector2f goalPos =
conv::to2D(goal.translation());
320 planningResult.plan.value(), goalPos, costmap_.value());
324 <<
"Navigating to closest reachable position at "
326 <<
" (distance to goal: " << closest->euclideanDistanceToGoal
329 alternativeGoal.translation().head<2>() = closest->position;
392 const std::optional<RecoveryInfo>& recovery)
394 PipelineTimings timings;
396 if (not planner.
plan.has_value())
398 ARMARX_INFO <<
"Invalid spfa result, could not calculate path!";
402 const Eigen::Vector2f goalPos =
conv::to2D(goal.translation());
405 Eigen::Vector2f effectiveStartPos;
406 if (recovery.has_value())
408 effectiveStartPos = recovery->recoveryPosition;
418 ScopedStageTimer timer(timings.constructPath);
425 ARMARX_INFO <<
"Could not plan collision-free path from"
426 << (recovery ? recovery->recoveryPosition
428 <<
" to " <<
"(" << goal.translation().x() <<
"," << goal.translation().y()
435 if (recovery.has_value())
438 conv::to2D(recovery->originalStartPose.translation()));
439 ARMARX_INFO <<
"Prepended original start position. Path now has " <<
plan.path.size()
445 ARMARX_DEBUG <<
"The plan consists of the following positions:";
446 for (
const auto& position :
plan.path)
454 std::vector<core::Position> wpts;
456 if (plan3d.size() >= 2)
458 for (
size_t i = 1; i < (plan3d.size() - 1); i++)
460 wpts.push_back(plan3d.at(i));
476 positionsForInitialVelocities.reserve(wpts.size() + 2);
481 ? recovery->originalStartPose.translation()
482 : planner.
start.translation();
484 positionsForInitialVelocities.emplace_back(startPosition);
485 positionsForInitialVelocities.insert(
486 positionsForInitialVelocities.end(), wpts.begin(), wpts.end());
487 positionsForInitialVelocities.emplace_back(goal.translation());
489 const std::vector<float> velocitiesRespectingObstacles =
497 recovery.has_value() ? recovery->originalStartPose : planner.
start;
501 ScopedStageTimer timer(timings.buildTrajectory);
503 trajectoryStart, wpts, goal, velocitiesRespectingObstacles);
508 std::optional<core::GlobalTrajectory> resampledTrajectory;
511 ScopedStageTimer timer(timings.resample);
514 resampledTrajectory =
trajectory.resample(200);
515 ARMARX_DEBUG <<
"Terminal velocity: " << resampledTrajectory->points().back().velocity;
523 ARMARX_VERBOSE <<
"Resampled trajectory contains " << resampledTrajectory->points().size()
526 resampledTrajectory->setMaxVelocity(generalConfig_.maxVel.linear);
527 ARMARX_DEBUG <<
"Terminal velocity: " << resampledTrajectory->points().back().velocity;
530 if (resampledTrajectory->points().size() == 2)
532 ARMARX_VERBOSE <<
"Only start and goal provided. Not optimizing orientation";
536 .helperTrajectory = std::nullopt,
537 .timings = withGridSearch(timings, planner),
541 bool positionSmoothingApplied =
false;
546 if (params_.enablePositionSmoothing)
548 ScopedStageTimer timer(timings.smoothPositions);
550 const auto smoothingParams =
553 resampledTrajectory.value(), costmap_.value(), smoothingParams);
554 const auto smoothingResult = smoother.
optimize();
556 if (smoothingResult.trajectory && smoothingResult.isCollisionFree &&
557 smoothingResult.isGeometryValid)
559 resampledTrajectory = smoothingResult.trajectory.value();
560 positionSmoothingApplied =
true;
562 if (smoothingResult.repairedWaypoints.empty())
564 ARMARX_INFO <<
"SPFA position smoothing succeeded.";
568 ARMARX_INFO <<
"SPFA position smoothing succeeded after removing "
569 << smoothingResult.repairedWaypoints.size()
570 <<
" folded waypoint(s).";
578 << (smoothingResult.isCollisionFree
579 ?
"the smoothed path is not geometrically valid (it "
580 "reverses direction)"
581 :
"the smoothed path is not collision-free")
582 <<
"; using original resampled SPFA path.";
587 ARMARX_INFO <<
"SPFA position smoothing is disabled.";
593 ScopedStageTimer timer(timings.recomputeVelocities);
596 smoothedPositions.reserve(resampledTrajectory->points().size());
597 for (
const auto& point : resampledTrajectory->points())
599 smoothedPositions.emplace_back(point.waypoint.pose.translation());
602 const std::vector<float> recomputedVelocities =
velocityLimit().
at(smoothedPositions);
604 ARMARX_CHECK(recomputedVelocities.size() == resampledTrajectory->points().size());
606 auto& mutablePoints = resampledTrajectory->mutablePoints();
607 for (
size_t i = 0; i < mutablePoints.size(); ++i)
609 mutablePoints[i].velocity = recomputedVelocities[i];
614 size_t subTrajectoryStartIndex = 0;
615 std::optional<core::GlobalTrajectory> trajectoryToOptimize;
616 float recoveryDistance = 0.0f;
618 ScopedStageTimer timer(timings.recoveryHandling);
619 const auto computeRecoveryDistance = [&]() ->
float
621 if (!recovery.has_value())
623 return (recovery->recoveryPosition -
624 recovery->originalStartPose.translation().head<2>())
627 recoveryDistance = computeRecoveryDistance();
630 if (recoveryDistance > 0)
632 auto& pts = resampledTrajectory->mutablePoints();
633 const auto startOrientation = pts.front().waypoint.pose.linear();
636 for (
size_t i = 0; i < pts.size(); i++)
639 (pts[i].waypoint.pose.translation() - pts.front().waypoint.pose.translation())
641 if (dist <= recoveryDistance)
643 pts[i].waypoint.pose.linear() = startOrientation;
648 subTrajectoryStartIndex = i;
649 if (subTrajectoryStartIndex < pts.size())
651 pts[subTrajectoryStartIndex].waypoint.pose.linear() = startOrientation;
656 ARMARX_INFO <<
"Recovery distance: " << recoveryDistance <<
" mm, fixed "
657 << subTrajectoryStartIndex
658 <<
" collision segment points, sub-trajectory starts at index "
659 << subTrajectoryStartIndex;
663 if (subTrajectoryStartIndex > 0)
665 trajectoryToOptimize = resampledTrajectory->getSubTrajectory(
666 subTrajectoryStartIndex, resampledTrajectory->points().size());
670 trajectoryToOptimize = resampledTrajectory;
676 ScopedStageTimer timer(timings.loadOrientationConfig);
678 "config/global_planning/OrientationOptimizer.json");
682 <<
"OrientationOptimizer config file does not exist: " << filename;
684 ARMARX_INFO <<
"Loading config from file `" << filename <<
"`.";
685 std::ifstream ifs{filename};
687 nlohmann::json jsonConfig;
695 armarx::navigation::global_planning::arondto::OrientationOptimizerParams dto;
698 dto.read(reader, jsonConfig);
704 auto result = [&]() {
705 ScopedStageTimer timer(timings.optimizeOrientation);
719 ScopedStageTimer timer(timings.stitchTrajectory);
720 if (recoveryDistance > 0 && subTrajectoryStartIndex > 0)
723 auto& resampledPts = resampledTrajectory->mutablePoints();
724 const auto& optimizedPts = result.trajectory->points();
727 if (subTrajectoryStartIndex + optimizedPts.size() == resampledPts.size())
730 for (
size_t i = 0; i < optimizedPts.size(); i++)
732 resampledPts[subTrajectoryStartIndex + i].waypoint.pose.linear() =
733 optimizedPts[i].waypoint.pose.linear();
734 resampledPts[subTrajectoryStartIndex + i].velocity = optimizedPts[i].velocity;
738 finalTrajectory = resampledTrajectory.value();
740 ARMARX_INFO <<
"Combined fixed collision segment (" << subTrajectoryStartIndex
741 <<
" points) with optimized trajectory (" << optimizedPts.size()
746 ARMARX_WARNING <<
"Trajectory size mismatch in collision recovery stitching. "
747 <<
"Using optimized trajectory only.";
761 if (params_.enableFinalVelocityClamp)
763 ScopedStageTimer timer(timings.finalVelocityClamp);
768 const float permissible =
769 limit.at(Eigen::Vector2f{point.waypoint.pose.translation().head<2>()});
771 point.velocity = std::min(permissible, point.velocity);
776 ARMARX_INFO <<
"Final obstacle-aware velocity clamp is disabled.";
784 .helperTrajectory = std::nullopt,
785 .timings = withGridSearch(timings, planner),
787 .positionSmoothingApplied = positionSmoothingApplied};