SPFA.cpp
Go to the documentation of this file.
1#include "SPFA.h"
2
3#include <algorithm>
4#include <chrono>
5#include <cmath>
6#include <cstddef>
7#include <iomanip>
8#include <optional>
9#include <sstream>
10#include <tuple>
11#include <vector>
12
13#include <Eigen/Geometry>
14
15#include <IceUtil/Time.h>
16
17#include <range/v3/range/conversion.hpp>
18#include <range/v3/view/transform.hpp>
19#include <range/v3/view/zip.hpp>
20
21#include <VirtualRobot/Robot.h> // IWYU pragma: keep
22#include <VirtualRobot/math/Helpers.h>
23
31
34
44#include <armarx/navigation/global_planning/aron/SPFAParams.aron.generated.h>
48
49namespace
50{
51 struct PipelineTimings
52 {
53 std::chrono::microseconds constructPath{0};
54 std::chrono::microseconds buildTrajectory{0};
55 std::chrono::microseconds resample{0};
56 std::chrono::microseconds smoothPositions{0};
57 std::chrono::microseconds recomputeVelocities{0};
58 std::chrono::microseconds recoveryHandling{0};
59 std::chrono::microseconds loadOrientationConfig{0};
60 std::chrono::microseconds optimizeOrientation{0};
61 std::chrono::microseconds stitchTrajectory{0};
62 std::chrono::microseconds finalVelocityClamp{0};
63 };
64
65 class ScopedStageTimer
66 {
67 public:
68 explicit ScopedStageTimer(std::chrono::microseconds& out) :
69 out_(out), start_(std::chrono::steady_clock::now())
70 {
71 }
72
73 ~ScopedStageTimer()
74 {
75 out_ = std::chrono::duration_cast<std::chrono::microseconds>(
76 std::chrono::steady_clock::now() - start_);
77 }
78
79 private:
80 std::chrono::microseconds& out_;
81 std::chrono::steady_clock::time_point start_;
82 };
83
84 std::map<std::string, float> toMap(const PipelineTimings& timings)
85 {
86 const auto seconds = [](std::chrono::microseconds us) -> float
87 { return static_cast<float>(us.count()) / 1e6F; };
88
89 return {{"construct_path", seconds(timings.constructPath)},
90 {"build_trajectory", seconds(timings.buildTrajectory)},
91 {"resample", seconds(timings.resample)},
92 {"smooth_positions", seconds(timings.smoothPositions)},
93 {"recompute_velocities", seconds(timings.recomputeVelocities)},
94 {"recovery_handling", seconds(timings.recoveryHandling)},
95 {"load_orientation_config", seconds(timings.loadOrientationConfig)},
96 {"optimize_orientation", seconds(timings.optimizeOrientation)},
97 {"stitch_trajectory", seconds(timings.stitchTrajectory)},
98 {"final_velocity_clamp", seconds(timings.finalVelocityClamp)}};
99 }
100
101 /// The grid search runs before `calculatePath`, so its duration is carried on the
102 /// `PlanningResult` rather than in `PipelineTimings`.
103 std::map<std::string, float>
104 withGridSearch(const PipelineTimings& timings,
106 {
107 std::map<std::string, float> map = toMap(timings);
108 map["grid_search"] = planner.gridSearchDuration;
109
110 return map;
111 }
112
113 std::string formatTimings(const PipelineTimings& timings)
114 {
115 const auto ms = [](std::chrono::microseconds us) -> std::string
116 {
117 std::ostringstream oss;
118 oss << std::fixed << std::setprecision(3) << (us.count() / 1000.0);
119 return oss.str();
120 };
121
122 std::ostringstream oss;
123 oss << "[SPFA timing] "
124 << "build=" << ms(timings.buildTrajectory) << "ms "
125 << "resample=" << ms(timings.resample) << "ms "
126 << "smooth=" << ms(timings.smoothPositions) << "ms "
127 << "velocity=" << ms(timings.recomputeVelocities) << "ms "
128 << "recovery=" << ms(timings.recoveryHandling) << "ms "
129 << "orientationConfig=" << ms(timings.loadOrientationConfig) << "ms "
130 << "orientationOptimize=" << ms(timings.optimizeOrientation) << "ms "
131 << "stitch=" << ms(timings.stitchTrajectory) << "ms "
132 << "clamp=" << ms(timings.finalVelocityClamp) << "ms";
133 return oss.str();
134 }
135} // namespace
136
138{
139
140 // SPFAParams
141
144 {
145 return Algorithms::SPFA;
146 }
147
150 {
151 arondto::SPFAParams dto;
152
153 aron_conv::toAron(dto, *this);
154
155 return dto.toAron();
156 }
157
160 {
162
163 // ARMARX_DEBUG << dict->getAllKeysAsString();
164
165 arondto::SPFAParams dto;
166 dto.fromAron(dict);
167
168 SPFAParams bo;
169 aron_conv::fromAron(dto, bo);
170
171 return bo;
172 }
173
174 // SPFA
175
176 SPFA::SPFA(const Params& params,
178 const core::Scene& ctx) :
180 impl_(params,
182 ctx.staticScene.has_value() ? ctx.staticScene->distanceToObstaclesCostmap
183 : std::nullopt)
184 {
185 }
186
187 std::optional<GlobalPlannerResult>
189 {
190 const core::Pose start(scene.robot->getGlobalPose());
191 return plan(start, goal);
192 }
193
194 std::optional<GlobalPlannerResult>
195 SPFA::plan(const core::Pose& start, const core::Pose& goal)
196 {
197 if (scene.staticScene.has_value())
198 {
199 impl_.updateCostmap(scene.staticScene->distanceToObstaclesCostmap);
200 }
201 return impl_.plan(start, goal);
202 }
203
206 {
207 return impl_.executePlanner(start);
208 }
209
210 std::optional<GlobalPlannerResult>
211 SPFA::calculatePath(const PlanningResult& planner, const core::Pose& goal)
212 {
213 return impl_.calculatePath(planner, goal);
214 }
215
216 // SPFAImpl
217
219 const core::GeneralConfig& generalParams,
220 const std::optional<navigation::algorithms::Costmap>& costmap) :
221 params_(params), generalConfig_(generalParams), costmap_(costmap)
222 {
223 }
224
225 void
226 SPFAImpl::updateCostmap(const std::optional<navigation::algorithms::Costmap>& costmap)
227 {
228 if (costmap.has_value())
229 {
230 costmap_.emplace(costmap.value());
231 }
232 else
233 {
234 costmap_.reset();
235 }
236 }
237
240 {
241 ARMARX_CHECK(costmap_.has_value());
242
244 costmap_.value(),
246 .maxVelocity = generalConfig_.maxVel.linear,
247 .obstacleMaxDistance = params_.algo.obstacleMaxDistance,
248 .obstacleDistanceWeight = params_.algo.obstacleDistanceWeight,
249 .obstacleCostExponent = params_.algo.obstacleCostExponent}};
250 }
251
252 std::optional<GlobalPlannerResult>
253 SPFAImpl::plan(const core::Pose& start, const core::Pose& goal)
254 {
255 const Eigen::Vector2f startPos2D = conv::to2D(start.translation());
256
257 // Check if start is in collision
258 if (costmap_->isInCollision(startPos2D))
259 {
260 ARMARX_WARNING << "Start position " << startPos2D << " is in collision. "
261 << "Searching for recovery position within "
262 << generalConfig_.inCollisionDistanceThresholdForRecovery << " mm...";
263
264 const auto recoveryVertex = costmap_->findClosestCollisionFreeVertex(
265 startPos2D, generalConfig_.inCollisionDistanceThresholdForRecovery);
266
267 if (!recoveryVertex)
268 {
269 ARMARX_ERROR << "No valid recovery position found within threshold of "
270 << generalConfig_.inCollisionDistanceThresholdForRecovery << " mm";
271 return std::nullopt;
272 }
273
274 const Eigen::Vector2f recoveryPos = recoveryVertex->position;
275
276 ARMARX_WARNING << "Found recovery position at " << recoveryPos
277 << " (distance: " << (recoveryPos - startPos2D).norm() << " mm). "
278 << "Prepending original start position to trajectory.";
279
280 const auto planningResult = executePlannerFromPosition(recoveryPos);
281
282 if (!planningResult.plan.has_value())
283 {
284 return std::nullopt;
285 }
286
287 const RecoveryInfo recoveryInfo{start, recoveryPos};
288
289 auto result = calculatePath(planningResult, goal, recoveryInfo);
290
291 if (!result && generalConfig_.navigateCloseAsPossible)
292 {
293 const Eigen::Vector2f goalPos = conv::to2D(goal.translation());
295 planningResult.plan.value(), goalPos, costmap_.value());
296 if (closest)
297 {
298 ARMARX_WARNING << "Goal " << goalPos << " is not reachable. "
299 << "Navigating to closest reachable position at "
300 << closest->position
301 << " (distance to goal: " << closest->euclideanDistanceToGoal
302 << " mm)";
303 core::Pose alternativeGoal = goal;
304 alternativeGoal.translation().head<2>() = closest->position;
305 result = calculatePath(planningResult, alternativeGoal, recoveryInfo);
306 }
307 }
308
309 return result;
310 }
311
312 // Normal case: start is valid
313 const auto planningResult = executePlanner(start);
314 auto result = calculatePath(planningResult, goal);
315
316 if (!result && generalConfig_.navigateCloseAsPossible && planningResult.plan.has_value())
317 {
318 const Eigen::Vector2f goalPos = conv::to2D(goal.translation());
320 planningResult.plan.value(), goalPos, costmap_.value());
321 if (closest)
322 {
323 ARMARX_WARNING << "Goal " << goalPos << " is not reachable. "
324 << "Navigating to closest reachable position at "
325 << closest->position
326 << " (distance to goal: " << closest->euclideanDistanceToGoal
327 << " mm)";
328 core::Pose alternativeGoal = goal;
329 alternativeGoal.translation().head<2>() = closest->position;
330 result = calculatePath(planningResult, alternativeGoal);
331 }
332 }
333
334 return result;
335 }
336
339 {
341
342 const Eigen::Vector2f startPosition = conv::to2D(start.translation());
343
344 // FIXME check if costmap is available
345 ARMARX_CHECK(costmap_.has_value());
346
348
349 PlanningResult result{
350 .start = start, // Preserve full pose including orientation
351 .algorithm =
352 algorithms::spfa::ShortestPathFasterAlgorithm(costmap_.value(), spfaParams),
353 .plan = std::nullopt,
354 };
355
356 const auto timeStart = IceUtil::Time::now();
357
359 try
360 {
361 result.plan = result.algorithm.spfa(startPosition, false);
362 }
363 catch (...)
364 {
365 ARMARX_INFO << "Could not execute spfa from " << "(" << startPosition.x() << ","
366 << startPosition.y() << ")" << " due to exception "
368 }
369
370 const auto timeEnd = IceUtil::Time::now();
371
372 result.gridSearchDuration =
373 static_cast<float>((timeEnd - timeStart).toMicroSeconds()) / 1e6F;
374
375 ARMARX_VERBOSE << "SPFA execution time: " << (timeEnd - timeStart).toMilliSeconds()
376 << " ms";
377
378 return result;
379 }
380
382 SPFAImpl::executePlannerFromPosition(const Eigen::Vector2f& startPosition)
383 {
384 // Create a pose with identity orientation for backward compatibility
385 const core::Pose startPose(Eigen::Translation3f(conv::to3D(startPosition)));
386 return executePlanner(startPose);
387 }
388
389 std::optional<GlobalPlannerResult>
391 const core::Pose& goal,
392 const std::optional<RecoveryInfo>& recovery)
393 {
394 PipelineTimings timings;
395
396 if (not planner.plan.has_value())
397 {
398 ARMARX_INFO << "Invalid spfa result, could not calculate path!";
399 return std::nullopt;
400 }
401
402 const Eigen::Vector2f goalPos = conv::to2D(goal.translation());
403
404 // Determine effective start position
405 Eigen::Vector2f effectiveStartPos;
406 if (recovery.has_value())
407 {
408 effectiveStartPos = recovery->recoveryPosition;
409 }
410 else
411 {
412 effectiveStartPos = conv::to2D(planner.start.translation());
413 }
414
416 try
417 {
418 ScopedStageTimer timer(timings.constructPath);
419 plan =
420 planner.algorithm.constructPath(effectiveStartPos, planner.plan->parents, goalPos);
422 }
423 catch (...)
424 {
425 ARMARX_INFO << "Could not plan collision-free path from"
426 << (recovery ? recovery->recoveryPosition
427 : conv::to2D(planner.start.translation()))
428 << " to " << "(" << goal.translation().x() << "," << goal.translation().y()
429 << ")" << " due to exception " << GetHandledExceptionString();
430
431 return std::nullopt;
432 }
433
434 // Prepend original start position if recovery was used
435 if (recovery.has_value())
436 {
437 plan.path.insert(plan.path.begin(),
438 conv::to2D(recovery->originalStartPose.translation()));
439 ARMARX_INFO << "Prepended original start position. Path now has " << plan.path.size()
440 << " points.";
441 }
442
443 ARMARX_VERBOSE << "Path contains " << plan.path.size() << " points";
444
445 ARMARX_DEBUG << "The plan consists of the following positions:";
446 for (const auto& position : plan.path)
447 {
448 ARMARX_DEBUG << position;
449 }
450
452 const auto plan3d = conv::to3D(plan.path);
453
454 std::vector<core::Position> wpts;
455 // when the position is already reached (but not the orientation) the planned path is empty
456 if (plan3d.size() >= 2)
457 {
458 for (size_t i = 1; i < (plan3d.size() - 1); i++)
459 {
460 wpts.push_back(plan3d.at(i));
461 }
462 }
463
464 // ARMARX_TRACE;
465 // auto smoothPlan = postProcessPath(plan.path);
466 // ARMARX_IMPORTANT << "Smooth path contains " << smoothPlan.size() << " points";
467
468 // ARMARX_TRACE;
469 // // we need to strip the first and the last points from the plan as they encode the start and goal position
470 // smoothPlan.erase(smoothPlan.begin());
471 // smoothPlan.pop_back();
472
473 // Compute velocities for all positions
474 // For recovery case: includes collision start (which is planner.start) and recovery position (which is wpts[0])
475 core::Positions positionsForInitialVelocities;
476 positionsForInitialVelocities.reserve(wpts.size() + 2);
477
478 // For recovery case: use the collision start pose from recoveryInfo
479 // For normal case: use planner.start
480 const core::Position startPosition = recovery.has_value()
481 ? recovery->originalStartPose.translation()
482 : planner.start.translation();
483
484 positionsForInitialVelocities.emplace_back(startPosition);
485 positionsForInitialVelocities.insert(
486 positionsForInitialVelocities.end(), wpts.begin(), wpts.end());
487 positionsForInitialVelocities.emplace_back(goal.translation());
488
489 const std::vector<float> velocitiesRespectingObstacles =
490 velocityLimit().at(positionsForInitialVelocities);
491
493
494 // Build trajectory normally (same for both recovery and non-recovery cases)
495 // Use the collision start pose for recovery case, planner.start for normal case
496 const core::Pose& trajectoryStart =
497 recovery.has_value() ? recovery->originalStartPose : planner.start;
498
500 {
501 ScopedStageTimer timer(timings.buildTrajectory);
503 trajectoryStart, wpts, goal, velocitiesRespectingObstacles);
504 }
505
506 // TODO(fabian.reister): resampling of trajectory
507
508 std::optional<core::GlobalTrajectory> resampledTrajectory;
509
510 {
511 ScopedStageTimer timer(timings.resample);
512 try
513 {
514 resampledTrajectory = trajectory.resample(200);
515 ARMARX_DEBUG << "Terminal velocity: " << resampledTrajectory->points().back().velocity;
516 }
517 catch (...)
518 {
519 ARMARX_INFO << "Caught exception during resampling: " << GetHandledExceptionString();
520 resampledTrajectory = trajectory;
521 }
522
523 ARMARX_VERBOSE << "Resampled trajectory contains " << resampledTrajectory->points().size()
524 << " points";
525
526 resampledTrajectory->setMaxVelocity(generalConfig_.maxVel.linear);
527 ARMARX_DEBUG << "Terminal velocity: " << resampledTrajectory->points().back().velocity;
528 }
529
530 if (resampledTrajectory->points().size() == 2)
531 {
532 ARMARX_VERBOSE << "Only start and goal provided. Not optimizing orientation";
534 ARMARX_INFO << formatTimings(timings);
535 return GlobalPlannerResult{.trajectory = resampledTrajectory.value(),
536 .helperTrajectory = std::nullopt,
537 .timings = withGridSearch(timings, planner),
538 .gridPath = plan3d};
539 }
540
541 bool positionSmoothingApplied = false;
542
543 // Smooth the resampled trajectory using the 2-D distance-to-obstacle costmap.
544 // Theta is not optimized here; orientations are computed afterwards by the
545 // OrientationOptimizer.
546 if (params_.enablePositionSmoothing)
547 {
548 ScopedStageTimer timer(timings.smoothPositions);
550 const auto smoothingParams =
553 resampledTrajectory.value(), costmap_.value(), smoothingParams);
554 const auto smoothingResult = smoother.optimize();
555
556 if (smoothingResult.trajectory && smoothingResult.isCollisionFree &&
557 smoothingResult.isGeometryValid)
558 {
559 resampledTrajectory = smoothingResult.trajectory.value();
560 positionSmoothingApplied = true;
561
562 if (smoothingResult.repairedWaypoints.empty())
563 {
564 ARMARX_INFO << "SPFA position smoothing succeeded.";
565 }
566 else
567 {
568 ARMARX_INFO << "SPFA position smoothing succeeded after removing "
569 << smoothingResult.repairedWaypoints.size()
570 << " folded waypoint(s).";
571 }
572 }
573 else
574 {
575 // Says which check failed: a collision-free path can still be folded, and the two
576 // need different fixes.
577 ARMARX_WARNING << "[nav-guard] smoothing-rejected: "
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.";
583 }
584 }
585 else
586 {
587 ARMARX_INFO << "SPFA position smoothing is disabled.";
588 }
589
590 // Recompute velocities based on the (possibly smoothed) positions so that obstacle
591 // proximity is reflected correctly after the path has been deformed.
592 {
593 ScopedStageTimer timer(timings.recomputeVelocities);
595 core::Positions smoothedPositions;
596 smoothedPositions.reserve(resampledTrajectory->points().size());
597 for (const auto& point : resampledTrajectory->points())
598 {
599 smoothedPositions.emplace_back(point.waypoint.pose.translation());
600 }
601
602 const std::vector<float> recomputedVelocities = velocityLimit().at(smoothedPositions);
603
604 ARMARX_CHECK(recomputedVelocities.size() == resampledTrajectory->points().size());
605
606 auto& mutablePoints = resampledTrajectory->mutablePoints();
607 for (size_t i = 0; i < mutablePoints.size(); ++i)
608 {
609 mutablePoints[i].velocity = recomputedVelocities[i];
610 }
611 }
612
613 // Compute recovery distance and find sub-trajectory start index
614 size_t subTrajectoryStartIndex = 0;
615 std::optional<core::GlobalTrajectory> trajectoryToOptimize;
616 float recoveryDistance = 0.0f;
617 {
618 ScopedStageTimer timer(timings.recoveryHandling);
619 const auto computeRecoveryDistance = [&]() -> float
620 {
621 if (!recovery.has_value())
622 return 0.0f;
623 return (recovery->recoveryPosition -
624 recovery->originalStartPose.translation().head<2>())
625 .norm();
626 };
627 recoveryDistance = computeRecoveryDistance();
628
629 // Find first point outside recovery distance (for sub-trajectory extraction)
630 if (recoveryDistance > 0)
631 {
632 auto& pts = resampledTrajectory->mutablePoints();
633 const auto startOrientation = pts.front().waypoint.pose.linear();
634
635 // Fix orientations for all points within recovery distance (collision segment)
636 for (size_t i = 0; i < pts.size(); i++)
637 {
638 const float dist =
639 (pts[i].waypoint.pose.translation() - pts.front().waypoint.pose.translation())
640 .norm();
641 if (dist <= recoveryDistance)
642 {
643 pts[i].waypoint.pose.linear() = startOrientation;
644 }
645 else
646 {
647 // Found first point outside collision - fix it for smooth transition
648 subTrajectoryStartIndex = i;
649 if (subTrajectoryStartIndex < pts.size())
650 {
651 pts[subTrajectoryStartIndex].waypoint.pose.linear() = startOrientation;
652 }
653 break;
654 }
655 }
656 ARMARX_INFO << "Recovery distance: " << recoveryDistance << " mm, fixed "
657 << subTrajectoryStartIndex
658 << " collision segment points, sub-trajectory starts at index "
659 << subTrajectoryStartIndex;
660 }
661
662 // Extract sub-trajectory for optimization (full trajectory if no recovery)
663 if (subTrajectoryStartIndex > 0)
664 {
665 trajectoryToOptimize = resampledTrajectory->getSubTrajectory(
666 subTrajectoryStartIndex, resampledTrajectory->points().size());
667 }
668 else
669 {
670 trajectoryToOptimize = resampledTrajectory;
671 }
672 }
673
675 {
676 ScopedStageTimer timer(timings.loadOrientationConfig);
677 const armarx::PackagePath pp("armarx_navigation",
678 "config/global_planning/OrientationOptimizer.json");
679 const std::string filename = pp.toSystemPath();
680
681 ARMARX_CHECK(std::filesystem::exists(filename))
682 << "OrientationOptimizer config file does not exist: " << filename;
683
684 ARMARX_INFO << "Loading config from file `" << filename << "`.";
685 std::ifstream ifs{filename};
686
687 nlohmann::json jsonConfig;
688 ifs >> jsonConfig;
689
690 ARMARX_VERBOSE << "Initializing config";
691
692
694
695 armarx::navigation::global_planning::arondto::OrientationOptimizerParams dto;
696
697 ARMARX_VERBOSE << "reading file.";
698 dto.read(reader, jsonConfig);
699
700 aron_conv::fromAron(dto, params_.optimizerParams);
701 }
702
703 OrientationOptimizer optimizer(trajectoryToOptimize.value(), params_.optimizerParams);
704 auto result = [&]() {
705 ScopedStageTimer timer(timings.optimizeOrientation);
706 return optimizer.optimize();
707 }();
708
709 if (not result)
710 {
711 ARMARX_ERROR << "Optimizer failure";
712 return std::nullopt;
713 }
714
715 // Combine collision segment (fixed) with optimized sub-trajectory
716 core::GlobalTrajectory finalTrajectory = result.trajectory.value();
717
718 {
719 ScopedStageTimer timer(timings.stitchTrajectory);
720 if (recoveryDistance > 0 && subTrajectoryStartIndex > 0)
721 {
722 // Stitch: fixed collision segment + optimized post-collision segment
723 auto& resampledPts = resampledTrajectory->mutablePoints();
724 const auto& optimizedPts = result.trajectory->points();
725
726 // Verify sizes match
727 if (subTrajectoryStartIndex + optimizedPts.size() == resampledPts.size())
728 {
729 // Copy optimized orientations and velocities to the resampled trajectory
730 for (size_t i = 0; i < optimizedPts.size(); i++)
731 {
732 resampledPts[subTrajectoryStartIndex + i].waypoint.pose.linear() =
733 optimizedPts[i].waypoint.pose.linear();
734 resampledPts[subTrajectoryStartIndex + i].velocity = optimizedPts[i].velocity;
735 }
736
737 // Use the resampled trajectory (with fixed collision segment + optimized post-collision)
738 finalTrajectory = resampledTrajectory.value();
739
740 ARMARX_INFO << "Combined fixed collision segment (" << subTrajectoryStartIndex
741 << " points) with optimized trajectory (" << optimizedPts.size()
742 << " points)";
743 }
744 else
745 {
746 ARMARX_WARNING << "Trajectory size mismatch in collision recovery stitching. "
747 << "Using optimized trajectory only.";
748 }
749 }
750 }
751
752 // TODO circular path smoothing should be done now
753
754 // algorithm::CircularPathSmoothing smoothing;
755 // auto smoothTrajectory = smoothing.smooth(result.trajectory.value());
756 // smoothTrajectory.setMaxVelocity(params.linearVelocity);
757
758 // Safety net: the orientation optimization and the recovery stitching above may change
759 // positions or velocities, so re-apply the same limit that produced them. This only ever
760 // reduces a velocity.
761 if (params_.enableFinalVelocityClamp)
762 {
763 ScopedStageTimer timer(timings.finalVelocityClamp);
764 const auto limit = velocityLimit();
765
766 for (auto& point : finalTrajectory.mutablePoints())
767 {
768 const float permissible =
769 limit.at(Eigen::Vector2f{point.waypoint.pose.translation().head<2>()});
770
771 point.velocity = std::min(permissible, point.velocity);
772 }
773 }
774 else
775 {
776 ARMARX_INFO << "Final obstacle-aware velocity clamp is disabled.";
777 }
778
779
781 ARMARX_DEBUG << "Terminal velocity: " << finalTrajectory.points().back().velocity;
782 ARMARX_INFO << formatTimings(timings);
783 return GlobalPlannerResult{.trajectory = finalTrajectory,
784 .helperTrajectory = std::nullopt,
785 .timings = withGridSearch(timings, planner),
786 .gridPath = plan3d,
787 .positionSmoothingApplied = positionSmoothingApplied};
788 }
789
790 std::vector<Eigen::Vector2f>
791 SPFAImpl::postProcessPath(const std::vector<Eigen::Vector2f>& path)
792 {
793 /// chain approximation
795 path, algorithm::ChainApproximation::Params{.distanceTh = 200.F});
796 approx.approximate();
797 const auto p = approx.approximatedChain();
798
799 // visualizePath(p, "approximated", simox::Color::green());
800
801 // algo::CircularPathSmoothing smoothing;
802 // const auto points = smoothing.smooth(p);
803
804 return p;
805 }
806
807} // namespace armarx::navigation::global_planning
static std::filesystem::path toSystemPath(const data::PackagePath &pp)
The maximum permissible linear velocity as a function of the distance to the closest obstacle.
float at(const Eigen::Vector2f &position) const
The limit at a position in the costmap's global frame.
PlanningResult constructPath(const Eigen::Vector2f &start, const std::vector< std::vector< Eigen::Vector2i > > &spfaParents, const Eigen::Vector2f &goal) const
PlanningResult plan(const Eigen::Vector2f &start, const Eigen::Vector2f &goal, bool checkStartForCollision=true) const
const std::vector< GlobalTrajectoryPoint > & points() const
std::vector< GlobalTrajectoryPoint > & mutablePoints()
static GlobalTrajectory FromPath(const Path &path, float velocity)
Note: the velocity will not be set!
GlobalPlanner(const core::GeneralConfig &generalConfig, const core::Scene &scene)
::armarx::navigation::global_planning::PlanningResult PlanningResult
Definition SPFA.h:109
void updateCostmap(const std::optional< navigation::algorithms::Costmap > &costmap)
Definition SPFA.cpp:226
std::optional< GlobalPlannerResult > plan(const core::Pose &start, const core::Pose &goal)
Definition SPFA.cpp:253
SPFAImpl(const Params &params, const core::GeneralConfig &generalParams, const std::optional< navigation::algorithms::Costmap > &costmap)
Definition SPFA.cpp:218
PlanningResult executePlannerFromPosition(const Eigen::Vector2f &startPosition)
Definition SPFA.cpp:382
PlanningResult executePlanner(const core::Pose &start)
Definition SPFA.cpp:338
std::vector< Eigen::Vector2f > postProcessPath(const std::vector< Eigen::Vector2f > &path)
Definition SPFA.cpp:791
std::optional< GlobalPlannerResult > calculatePath(const PlanningResult &planner, const core::Pose &goal, const std::optional< RecoveryInfo > &recovery=std::nullopt)
Definition SPFA.cpp:390
algorithms::ObstacleAwareVelocityLimit velocityLimit() const
The obstacle-aware velocity limit this planner applies, for the current costmap.
Definition SPFA.cpp:239
::armarx::navigation::global_planning::PlanningResult PlanningResult
Definition SPFA.h:148
std::optional< GlobalPlannerResult > calculatePath(const PlanningResult &planner, const core::Pose &goal)
Definition SPFA.cpp:211
std::optional< GlobalPlannerResult > plan(const core::Pose &goal) override
Definition SPFA.cpp:188
PlanningResult executePlanner(const core::Pose &start)
Definition SPFA.cpp:205
SPFA(const Params &params, const core::GeneralConfig &generalParams, const core::Scene &ctx)
Definition SPFA.cpp:176
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
std::shared_ptr< Dict > DictPtr
Definition Dict.h:42
SmoothingParams loadSmoothingParams(const std::string &filePath)
Definition io.cpp:29
std::optional< ShortestPathFasterAlgorithm::ClosestReachableResult > findClosestReachablePosition(const ShortestPathFasterAlgorithm::Result &spfaResult, const Eigen::Vector2f &goal, const Costmap &costmap)
std::vector< Eigen::Vector2f > to2D(const std::vector< Eigen::Vector3f > &v)
Definition eigen.cpp:29
std::vector< Eigen::Vector3f > to3D(const std::vector< Eigen::Vector2f > &v)
Definition eigen.cpp:14
std::vector< Position > Positions
Definition basic_types.h:37
Eigen::Isometry3f Pose
Definition basic_types.h:31
Eigen::Vector3f Position
Definition basic_types.h:36
void toAron(arondto::GlobalPlannerParams &dto, const GlobalPlannerParams &bo)
void fromAron(const arondto::GlobalPlannerParams &dto, GlobalPlannerParams &bo)
This file is part of ArmarX.
Definition fwd.h:30
std::string GetHandledExceptionString()
double norm(const Point &a)
Definition point.hpp:102
float gridSearchDuration
Duration [s] of the SPFA grid search itself, which runs before calculatePath.
Definition SPFA.h:87
algorithms::spfa::ShortestPathFasterAlgorithm algorithm
Definition SPFA.h:83
std::optional< algorithms::spfa::ShortestPathFasterAlgorithm::Result > plan
Definition SPFA.h:84
Information about collision recovery.
Definition SPFA.h:97
aron::data::DictPtr toAron() const override
Definition SPFA.cpp:149
static SPFAParams FromAron(const aron::data::DictPtr &dict)
Definition SPFA.cpp:159
Algorithms algorithm() const override
Definition SPFA.cpp:143
#define ARMARX_TRACE
Definition trace.h:75