-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add AsymmetricInflationLayer costmap plugin for path-aware lane-keeping #6118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mbloechli
wants to merge
21
commits into
ros-navigation:main
Choose a base branch
from
mbloechli:feat/asymmetric_inflation_layer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
b7ca00a
ported over to rolling
mbloechli da93f02
improved efficiency of getObstacleSide
mbloechli 40eaf9e
update bounds iteratively
mbloechli d174002
renamed test file, added correct license headers
mbloechli abb0dfe
fix linting errors
mbloechli f1b686d
please pre-commit
mbloechli 1657018
revert unintended changes to nav2_bringup
mbloechli 3e7efbf
added benchmarking for incremental updates
mbloechli 8b8533c
updated license headers with my full name and authors who wrote the l…
mbloechli 9476e3e
unnest if(use_asymmetry) statement
mbloechli dffb144
fixed units bug that cancelled each other out in getEffectiveDistance…
mbloechli c81c8b2
updated parameters to be the same as in the docs.nav2.org page about …
mbloechli 53d150d
moved the hash lookup from computeObstacleSide() to updateCosts() for…
mbloechli 25733f7
please linter
mbloechli 70087de
fix: corrected a multiplication error, that created an incorrectly sc…
mbloechli 364b2d3
fix: deleted a copy-paste error, that didn't influence the behaviour
mbloechli f2269f4
refactored layer to inherit from inflation_layer, made it useable as …
mbloechli ae90007
updated layer description in costmap_plugins.xml
mbloechli 42f1848
removed dependency on legacyInflationLayer, only inflate disfavored s…
mbloechli a95d933
replaced BFS expansion with DT from inflation layer
mbloechli c3e500d
linting
mbloechli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
250 changes: 250 additions & 0 deletions
250
nav2_costmap_2d/include/nav2_costmap_2d/asymmetric_inflation_layer.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,250 @@ | ||
| // Copyright (c) 2026, Marc Blöchlinger | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #ifndef NAV2_COSTMAP_2D__ASYMMETRIC_INFLATION_LAYER_HPP_ | ||
|
SteveMacenski marked this conversation as resolved.
|
||
| #define NAV2_COSTMAP_2D__ASYMMETRIC_INFLATION_LAYER_HPP_ | ||
|
|
||
| #include <algorithm> | ||
| #include <vector> | ||
| #include <mutex> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <utility> | ||
| #include <unordered_map> | ||
| #include <cstdint> | ||
|
|
||
| #include "rclcpp/rclcpp.hpp" | ||
| #include "nav2_costmap_2d/inflation_layer.hpp" | ||
| #include "nav_msgs/msg/path.hpp" | ||
|
|
||
| namespace nav2_costmap_2d | ||
| { | ||
|
|
||
| /** | ||
| * @class AsymmetricInflationLayer | ||
| * @brief Costmap layer that inflates obstacles asymmetrically relative to the | ||
| * global path, biasing the navigable corridor toward one side. | ||
| * | ||
| * Inherits the distance-transform based InflationLayer, which writes the | ||
| * symmetric baseline first. This layer then classifies each lethal obstacle as | ||
| * left (+1), right (-1), or neutral (0) relative to the path and runs a second | ||
| * distance transform seeded from only the disfavored-side boundary cells. | ||
| * Falls back to the inherited symmetric inflation when no path is available, | ||
| * the goal is nearby, or the per-side scaling factors are equal. | ||
| */ | ||
| class AsymmetricInflationLayer : public nav2_costmap_2d::InflationLayer | ||
| { | ||
| public: | ||
| AsymmetricInflationLayer(); | ||
| ~AsymmetricInflationLayer() override = default; | ||
|
|
||
| /** | ||
| * @brief Initialization process of layer on startup | ||
| */ | ||
| void onInitialize() override; | ||
|
|
||
| /** | ||
| * @brief Activate the layer; registers the parameter validation/update callbacks. | ||
| */ | ||
| void activate() override; | ||
|
|
||
| /** | ||
| * @brief Deactivate the layer; removes the parameter validation/update callbacks. | ||
| */ | ||
| void deactivate() override; | ||
|
|
||
| /** | ||
| * @brief Update the bounds of the master costmap by this layer's update dimensions. | ||
| * | ||
| * Stores the robot position for goal-proximity checks in updateCosts(), then | ||
| * delegates the actual inflation bound expansion to InflationLayer. | ||
| * @param robot_x X pose of robot | ||
| * @param robot_y Y pose of robot | ||
| * @param robot_yaw Robot orientation | ||
| * @param min_x X min map coord of the window to update | ||
| * @param min_y Y min map coord of the window to update | ||
| * @param max_x X max map coord of the window to update | ||
| * @param max_y Y max map coord of the window to update | ||
| */ | ||
| void updateBounds( | ||
| double robot_x, double robot_y, double robot_yaw, double * min_x, | ||
| double * min_y, double * max_x, double * max_y) override; | ||
|
|
||
| /** | ||
| * @brief Update the costs in the master costmap. | ||
| * | ||
| * First writes the inherited symmetric inflation baseline. Runs a second | ||
| * distance-transform pass seeded from disfavored-side boundary cells when a | ||
| * valid path is available and the two per-side decay rates differ. | ||
| * Otherwise leaves the symmetric baseline unchanged. | ||
| * @param master_grid The master costmap grid to update | ||
| * @param min_i X min cell index of the window to update | ||
| * @param min_j Y min cell index of the window to update | ||
| * @param max_i X max cell index of the window to update | ||
| * @param max_j Y max cell index of the window to update | ||
| */ | ||
| void updateCosts( | ||
| nav2_costmap_2d::Costmap2D & master_grid, | ||
| int min_i, int min_j, int max_i, int max_j) override; | ||
|
|
||
| /** | ||
| * @brief Match the size of the master costmap | ||
| */ | ||
| void matchSize() override; | ||
|
|
||
| protected: | ||
| /** | ||
| * @brief Process updates on footprint changes to the inflation layer | ||
| */ | ||
| void onFootprintChanged() override; | ||
|
|
||
| /** | ||
| * @brief Callback for incoming global path messages | ||
| */ | ||
| void globalPathCallback(const nav_msgs::msg::Path::SharedPtr msg); | ||
|
|
||
| /** | ||
| * @brief Extract the global path points that fall inside the local costmap window. | ||
| * | ||
| * Transforms the path into the costmap frame via TF2. Returns an empty | ||
| * vector when the robot is within goal_distance_threshold_ of the goal, | ||
| * which disables asymmetry near the goal to prevent docking oscillations. | ||
| * @param master_grid Reference costmap used to filter points to the current window. | ||
| * @return Path points in costmap-frame world coordinates; empty disables asymmetry. | ||
| */ | ||
| std::vector<std::pair<double, double>> extractLocalPath( | ||
| nav2_costmap_2d::Costmap2D & master_grid); | ||
|
|
||
| /** | ||
| * @brief Classify an obstacle cell as left (+1), right (-1), or neutral (0) | ||
| * relative to the closest path segment. | ||
| * | ||
| * For each candidate segment, performs an AABB rejection pass followed by an | ||
| * exact perpendicular-distance and cross-product computation to determine side. | ||
| * Obstacles whose nearest perpendicular distance exceeds the inflation radius | ||
| * are returned as 0 (neutral -> symmetric inflation). | ||
| * | ||
| * @param cx World x-coordinate of the obstacle cell (metres). | ||
| * @param cy World y-coordinate of the obstacle cell (metres). | ||
| * @param candidates Segment indices whose padded AABB may contain (cx, cy). | ||
| * @param local_path_pts Path points in costmap-frame order; consecutive pairs form segments. | ||
| * @return +1 (left of path), -1 (right of path), or 0 (neutral / beyond inflation radius). | ||
| */ | ||
| int8_t computeObstacleSide( | ||
| double cx, double cy, | ||
| const std::vector<size_t> & candidates, | ||
| const std::vector<std::pair<double, double>> & local_path_pts); | ||
|
|
||
| /** | ||
| * @brief Build a spatial hash mapping 2D bucket keys to path segment indices. | ||
| * | ||
| * Uses inflation_radius_ as the bucket size so each bucket spans exactly one | ||
| * inflation radius. Each segment is inserted into every bucket whose padded | ||
| * AABB it overlaps, enabling O(1) nearest-segment queries during the disfavored-cell seeding phase. | ||
| * @param local_path_pts Path waypoints in costmap-frame world coordinates. | ||
| * @return Hash map from packed (bucket_x, bucket_y) key to segment index list. | ||
| */ | ||
| std::unordered_map<uint64_t, std::vector<size_t>> | ||
| buildPathSpatialHash( | ||
| const std::vector<std::pair<double, double>> & local_path_pts); | ||
|
|
||
| /** | ||
| * @brief Build a distance map seeded from disfavored-side obstacle boundary cells. | ||
| * | ||
| * Initialises a MatrixXfRM (roi_height × roi_width) to DT_INF, then sets | ||
| * disfavored boundary cells to 0.0f. The caller passes the result to | ||
| * DistanceTransform::distanceTransform2D() then applyInflation(). | ||
| * @param master_grid Costmap used for coordinate and cost lookups. | ||
| * @param roi_min_i Left edge of the padded ROI (cells). | ||
| * @param roi_min_j Bottom edge of the padded ROI (cells). | ||
| * @param roi_width Width of the padded ROI (cells). | ||
| * @param roi_height Height of the padded ROI (cells). | ||
| * @param spatial_hash Segment lookup structure from buildPathSpatialHash(). | ||
| * @param local_path_pts Path waypoints in costmap-frame world coordinates. | ||
| * @return Distance map with 0.0f at disfavored seeds and DT_INF elsewhere. | ||
| */ | ||
| MatrixXfRM seedDistanceMap( | ||
| nav2_costmap_2d::Costmap2D & master_grid, | ||
| int roi_min_i, int roi_min_j, int roi_width, int roi_height, | ||
| const std::unordered_map<uint64_t, std::vector<size_t>> & spatial_hash, | ||
| const std::vector<std::pair<double, double>> & local_path_pts); | ||
|
|
||
| /** | ||
| * @brief Apply disfavored-side costs from a distance map with max(old, new) semantics. | ||
| * | ||
| * Mirrors InflationLayer::applyInflation() but uses cost_lut_disfavored_ | ||
| * (built with c_side) instead of the parent's cost_lut_ (built with c_max). | ||
| * Costs are applied only within the originally requested update window. | ||
| * @param master_array Raw costmap data pointer. | ||
| * @param distance_map Result of distanceTransform2D() on the disfavored seed map. | ||
| * @param min_i Left edge of the write window. | ||
| * @param min_j Bottom edge of the write window. | ||
| * @param max_i Right edge of the write window. | ||
| * @param max_j Top edge of the write window. | ||
| * @param roi_min_i Left edge of the padded ROI used to build distance_map. | ||
| * @param roi_min_j Bottom edge of the padded ROI used to build distance_map. | ||
| * @param size_x Full costmap width (cells), for index arithmetic. | ||
| */ | ||
| void applyInflation( | ||
| unsigned char * master_array, | ||
| const MatrixXfRM & distance_map, | ||
| int min_i, int min_j, int max_i, int max_j, | ||
| int roi_min_i, int roi_min_j, | ||
| unsigned int size_x); | ||
|
|
||
| /** | ||
| * @brief Pre-compute cost_lut_disfavored_ using c_side (the smaller per-side scaling factor) | ||
| */ | ||
| void computeAsymmetricCaches(); | ||
|
|
||
| /** | ||
| * @brief Validate parameter updates (pre-set callback). Returns success/failure | ||
| * without mutating any state. | ||
| */ | ||
| rcl_interfaces::msg::SetParametersResult validateParameterUpdatesCallback( | ||
| const std::vector<rclcpp::Parameter> & parameters); | ||
|
|
||
| /** | ||
| * @brief Apply parameter updates (post-set callback) after they have been validated. | ||
| * Recomputes caches when geometry parameters change. | ||
| */ | ||
| void updateParametersCallback( | ||
| const std::vector<rclcpp::Parameter> & parameters); | ||
|
|
||
| // --- Parameters --- | ||
| /// Exponential decay rate for cells on the LEFT side of the path | ||
| double cost_scaling_factor_left_; | ||
| /// Exponential decay rate for cells on the RIGHT side of the path | ||
| double cost_scaling_factor_right_; | ||
| /// Distance to goal where asymmetry disables to prevent docking oscillations | ||
| double goal_distance_threshold_; | ||
| std::string plan_topic_; | ||
|
|
||
| // --- State --- | ||
| double current_robot_x_{0.0}; | ||
| double current_robot_y_{0.0}; | ||
|
|
||
| // --- Asymmetric Lookup Table --- | ||
| /// Cost LUT for the disfavored side, built with c_side (the smaller per-side scaling factor) | ||
| std::vector<unsigned char> cost_lut_disfavored_; | ||
|
|
||
| // --- Path subscription --- | ||
| nav2::Subscription<nav_msgs::msg::Path>::SharedPtr path_sub_; | ||
| nav_msgs::msg::Path::SharedPtr latest_global_path_; | ||
| std::mutex path_mutex_; | ||
| }; | ||
|
|
||
| } // namespace nav2_costmap_2d | ||
|
|
||
| #endif // NAV2_COSTMAP_2D__ASYMMETRIC_INFLATION_LAYER_HPP_ | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.