394 const std::optional<RecoveryInfo>& recovery)
396 PipelineTimings timings;
398 if (not planner.
plan.has_value())
400 ARMARX_INFO <<
"Invalid spfa result, could not calculate path!";
404 const Eigen::Vector2f goalPos =
conv::to2D(goal.translation());
407 Eigen::Vector2f effectiveStartPos;
408 if (recovery.has_value())
410 effectiveStartPos = recovery->recoveryPosition;
420 ScopedStageTimer timer(timings.constructPath);
427 ARMARX_INFO <<
"Could not plan collision-free path from"
428 << (recovery ? recovery->recoveryPosition
430 <<
" to " <<
"(" << goal.translation().x() <<
"," << goal.translation().y()
437 if (recovery.has_value())
440 conv::to2D(recovery->originalStartPose.translation()));
441 ARMARX_INFO <<
"Prepended original start position. Path now has " <<
plan.path.size()
447 ARMARX_DEBUG <<
"The plan consists of the following positions:";
448 for (
const auto& position :
plan.path)
456 std::vector<core::Position> wpts;
458 if (plan3d.size() >= 2)
460 for (
size_t i = 1; i < (plan3d.size() - 1); i++)
462 wpts.push_back(plan3d.at(i));
478 positionsForInitialVelocities.reserve(wpts.size() + 2);
483 ? recovery->originalStartPose.translation()
484 : planner.
start.translation();
486 positionsForInitialVelocities.emplace_back(startPosition);
487 positionsForInitialVelocities.insert(
488 positionsForInitialVelocities.end(), wpts.begin(), wpts.end());
489 positionsForInitialVelocities.emplace_back(goal.translation());
491 const std::vector<float> velocitiesRespectingObstacles =
499 recovery.has_value() ? recovery->originalStartPose : planner.
start;
503 ScopedStageTimer timer(timings.buildTrajectory);
505 trajectoryStart, wpts, goal, velocitiesRespectingObstacles);
510 std::optional<core::GlobalTrajectory> resampledTrajectory;
513 ScopedStageTimer timer(timings.resample);
516 const float resampleDistance =
517 params_.resampleDistance > 0.F ? params_.resampleDistance
519 resampledTrajectory =
trajectory.resample(resampleDistance);
520 ARMARX_DEBUG <<
"Terminal velocity: " << resampledTrajectory->points().back().velocity;
528 ARMARX_VERBOSE <<
"Resampled trajectory contains " << resampledTrajectory->points().size()
531 resampledTrajectory->setMaxVelocity(generalConfig_.maxVel.linear);
532 ARMARX_DEBUG <<
"Terminal velocity: " << resampledTrajectory->points().back().velocity;
535 if (resampledTrajectory->points().size() == 2)
537 ARMARX_VERBOSE <<
"Only start and goal provided. Not optimizing orientation";
541 .helperTrajectory = std::nullopt,
542 .timings = withGridSearch(timings, planner),
546 bool positionSmoothingApplied =
false;
552 if (params_.enablePositionSmoothing)
554 ScopedStageTimer timer(timings.smoothPositions);
556 const auto smoothingParams =
559 resampledTrajectory.value(), costmap_.value(), smoothingParams);
560 const auto smoothingResult = smoother.
optimize();
563 float inputSharpestTurn = 0.F;
565 const auto& p = resampledTrajectory->points();
566 for (std::size_t i = 1; i + 1 < p.size(); i++)
568 const Eigen::Vector2f in =
569 (p[i].waypoint.pose.translation() - p[i - 1].waypoint.pose.translation())
571 const Eigen::Vector2f out =
572 (p[i + 1].waypoint.pose.translation() - p[i].waypoint.pose.translation())
575 if (in.norm() < 1e-3F or out.norm() < 1e-3F)
581 std::clamp(in.normalized().dot(out.normalized()), -1.F, 1.F);
582 inputSharpestTurn = std::max(
583 inputSharpestTurn, std::acos(cosine) * 180.F /
static_cast<float>(
M_PI));
590 std::size_t collisions = 0;
591 std::size_t endpointCollisions = 0;
592 float worstCollisionClearance = 0.F;
594 if (smoothingResult.trajectory.has_value() and not smoothingResult.isCollisionFree)
597 costmap_.value(), smoothingParams.min_clearance);
599 const auto found = checker.
violations(smoothingResult.trajectory.value());
600 const std::size_t last = smoothingResult.trajectory->points().size() - 1;
602 collisions = found.size();
603 worstCollisionClearance = std::numeric_limits<float>::max();
605 for (
const auto& violation : found)
607 worstCollisionClearance =
608 std::min(worstCollisionClearance, violation.clearance);
612 const bool atStart = violation.index == 0;
614 violation.index == last or (violation.onSegment and violation.index + 1 == last);
616 if (atStart or atGoal)
618 endpointCollisions++;
624 worstCollisionClearance = 0.F;
628 smoothingOutcome = {.ran =
true,
629 .collisionFree = smoothingResult.isCollisionFree,
630 .geometryValid = smoothingResult.isGeometryValid,
631 .repairedWaypoints = smoothingResult.repairedWaypoints.size(),
632 .inputSharpestTurnDeg = inputSharpestTurn,
633 .collisions = collisions,
634 .endpointCollisions = endpointCollisions,
635 .worstCollisionClearance = worstCollisionClearance,
636 .inputMinClearance = smoothingResult.inputMinClearance,
637 .outputMinClearance = smoothingResult.outputMinClearance,
638 .judgedAgainstInput = smoothingResult.judgedAgainstInput};
640 if (smoothingResult.trajectory && smoothingResult.isCollisionFree &&
641 smoothingResult.isGeometryValid)
643 resampledTrajectory = smoothingResult.trajectory.value();
644 positionSmoothingApplied =
true;
646 if (smoothingResult.repairedWaypoints.empty())
648 ARMARX_INFO <<
"SPFA position smoothing succeeded.";
652 ARMARX_INFO <<
"SPFA position smoothing succeeded after removing "
653 << smoothingResult.repairedWaypoints.size()
654 <<
" folded waypoint(s).";
662 << (smoothingResult.isCollisionFree
663 ?
"the smoothed path is not geometrically valid (it "
664 "reverses direction)"
665 :
"the smoothed path is not collision-free")
666 <<
"; using original resampled SPFA path.";
677 std::vector<std::size_t> removed;
678 const std::vector<std::size_t> violations =
680 resampledTrajectory.value());
682 if (not violations.empty())
692 costmap_.value(), smoothingParams.min_clearance);
695 checker.
check(repaired) and
700 ARMARX_WARNING <<
"[nav-guard] fallback-repaired: the unsmoothed path "
701 "reverses direction at "
702 << violations.size() <<
" waypoint(s); removed "
703 << removed.size() <<
" of them, "
704 << resampledTrajectory->points().size() <<
" -> "
705 << repaired.
points().size()
706 <<
" points, still collision-free.";
708 resampledTrajectory = repaired;
714 ARMARX_ERROR <<
"[nav-guard] fallback-folded: the unsmoothed path "
715 "reverses direction at "
717 <<
" waypoint(s) and removing them would "
718 << (checker.
check(repaired) ?
"leave it folded"
719 :
"cut a corner into an obstacle")
720 <<
". Driving it as it stands; expect the trajectory "
721 "reparametrization to stall near waypoint "
722 << violations.front() <<
".";
729 ARMARX_INFO <<
"SPFA position smoothing is disabled.";
735 ScopedStageTimer timer(timings.recomputeVelocities);
738 smoothedPositions.reserve(resampledTrajectory->points().size());
739 for (
const auto& point : resampledTrajectory->points())
741 smoothedPositions.emplace_back(point.waypoint.pose.translation());
744 const std::vector<float> recomputedVelocities =
velocityLimit().
at(smoothedPositions);
746 ARMARX_CHECK(recomputedVelocities.size() == resampledTrajectory->points().size());
748 auto& mutablePoints = resampledTrajectory->mutablePoints();
749 for (
size_t i = 0; i < mutablePoints.size(); ++i)
751 mutablePoints[i].velocity = recomputedVelocities[i];
756 size_t subTrajectoryStartIndex = 0;
757 std::optional<core::GlobalTrajectory> trajectoryToOptimize;
758 float recoveryDistance = 0.0f;
760 ScopedStageTimer timer(timings.recoveryHandling);
761 const auto computeRecoveryDistance = [&]() ->
float
763 if (!recovery.has_value())
765 return (recovery->recoveryPosition -
766 recovery->originalStartPose.translation().head<2>())
769 recoveryDistance = computeRecoveryDistance();
772 if (recoveryDistance > 0)
774 auto& pts = resampledTrajectory->mutablePoints();
775 const auto startOrientation = pts.front().waypoint.pose.linear();
778 for (
size_t i = 0; i < pts.size(); i++)
781 (pts[i].waypoint.pose.translation() - pts.front().waypoint.pose.translation())
783 if (dist <= recoveryDistance)
785 pts[i].waypoint.pose.linear() = startOrientation;
790 subTrajectoryStartIndex = i;
791 if (subTrajectoryStartIndex < pts.size())
793 pts[subTrajectoryStartIndex].waypoint.pose.linear() = startOrientation;
798 ARMARX_INFO <<
"Recovery distance: " << recoveryDistance <<
" mm, fixed "
799 << subTrajectoryStartIndex
800 <<
" collision segment points, sub-trajectory starts at index "
801 << subTrajectoryStartIndex;
805 if (subTrajectoryStartIndex > 0)
807 trajectoryToOptimize = resampledTrajectory->getSubTrajectory(
808 subTrajectoryStartIndex, resampledTrajectory->points().size());
812 trajectoryToOptimize = resampledTrajectory;
818 ScopedStageTimer timer(timings.loadOrientationConfig);
820 "config/global_planning/OrientationOptimizer.json");
824 <<
"OrientationOptimizer config file does not exist: " << filename;
826 ARMARX_INFO <<
"Loading config from file `" << filename <<
"`.";
827 std::ifstream ifs{filename};
829 nlohmann::json jsonConfig;
837 armarx::navigation::global_planning::arondto::OrientationOptimizerParams dto;
840 dto.read(reader, jsonConfig);
846 auto result = [&]() {
847 ScopedStageTimer timer(timings.optimizeOrientation);
861 ScopedStageTimer timer(timings.stitchTrajectory);
862 if (recoveryDistance > 0 && subTrajectoryStartIndex > 0)
865 auto& resampledPts = resampledTrajectory->mutablePoints();
866 const auto& optimizedPts = result.trajectory->points();
869 if (subTrajectoryStartIndex + optimizedPts.size() == resampledPts.size())
872 for (
size_t i = 0; i < optimizedPts.size(); i++)
874 resampledPts[subTrajectoryStartIndex + i].waypoint.pose.linear() =
875 optimizedPts[i].waypoint.pose.linear();
876 resampledPts[subTrajectoryStartIndex + i].velocity = optimizedPts[i].velocity;
880 finalTrajectory = resampledTrajectory.value();
882 ARMARX_INFO <<
"Combined fixed collision segment (" << subTrajectoryStartIndex
883 <<
" points) with optimized trajectory (" << optimizedPts.size()
888 ARMARX_WARNING <<
"Trajectory size mismatch in collision recovery stitching. "
889 <<
"Using optimized trajectory only.";
903 if (params_.enableFinalVelocityClamp)
905 ScopedStageTimer timer(timings.finalVelocityClamp);
910 const float permissible =
911 limit.at(Eigen::Vector2f{point.waypoint.pose.translation().head<2>()});
913 point.velocity = std::min(permissible, point.velocity);
918 ARMARX_INFO <<
"Final obstacle-aware velocity clamp is disabled.";
926 .helperTrajectory = std::nullopt,
927 .timings = withGridSearch(timings, planner),
929 .positionSmoothingApplied = positionSmoothingApplied,
930 .smoothingOutcome = smoothingOutcome};