Skip to content

Commit c52dae7

Browse files
author
cobot
committed
improving linear eval and omni obstac avoid logic
1 parent fbd7556 commit c52dae7

File tree

8 files changed

+151
-122
lines changed

8 files changed

+151
-122
lines changed

config/navigation.lua

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ NavigationParameters = {
2323
base_link_offset_y = 0;
2424
max_free_path_length = 6.0;
2525
max_clearance = 1.0;
26-
local_half_fov = deg2rad(90);
27-
center_threshold = deg2rad(10);
26+
lidar_fov_half_angle = deg2rad(60);
2827
can_traverse_stairs = false;
2928
target_dist_tolerance = 0.1;
3029
nudge_dist_tolerance = 0.3;

src/navigation/linear_evaluator.cc

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ using std::vector;
4848
using namespace geometry;
4949
using namespace math_util;
5050

51-
// Cost function weights for path selection
52-
DEFINE_double(clearance_weight, -0.5, "Weight for obstacle clearance (negative = prefer higher)");
53-
DEFINE_double(freepath_weight, -1, "Weight for rollout length (negative = prefer longer)");
54-
DEFINE_double(subopt_tolerance, 1.5, "Max path length multiplier for clearance tradeoff");
51+
// Cost function weights for safety optimization in pass 2 (negative = prefer higher values)
52+
// Pass 1 handles goal-reaching (shortest total distance), pass 2 handles safety among near-optimal paths.
53+
DEFINE_double(clearance_weight, -0.5, "Weight for lateral clearance");
54+
DEFINE_double(freepath_weight, -1.0, "Weight for free path length");
55+
DEFINE_double(subopt_tolerance, 1.5, "Max total distance multiplier for constrained optimization");
5556

5657
namespace motion_primitives {
5758

@@ -60,18 +61,20 @@ shared_ptr<PathRolloutBase> LinearEvaluator::FindBest(const vector<shared_ptr<Pa
6061

6162
// Check line-of-sight (LOS) from each path's endpoint to the goal
6263
const size_t N = paths.size();
63-
vector<float> los_clearance(N, 0.0f); // clearance along LOS line to goal
6464
vector<float> remaining_dist(N, FLT_MAX); // distance from endpoint to goal
6565
bool any_path_has_los = false;
6666

6767
#pragma omp parallel
6868
{
6969
bool local_has_los = false;
70-
#pragma omp for schedule(runtime)
70+
#pragma omp for schedule(runtime)
7171
for (int i = 0; i < static_cast<int>(N); ++i) {
7272
const auto endpoint = paths[i]->EndPoint().translation;
73-
los_clearance[i] = LOSClearanceToLine(Line2f(endpoint, local_target), *point_cloud);
74-
if (los_clearance[i] > 0.0f) {
73+
const float los_clearance =
74+
LOSClearanceToLine(Line2f(endpoint, local_target), *point_cloud);
75+
// Require clearance >= half the robot's smallest dimension for meaningful LOS
76+
const float min_los_clearance = 0.5f * std::min(nav_params.robot_width, nav_params.robot_length);
77+
if (los_clearance > min_los_clearance) {
7578
remaining_dist[i] = (endpoint - local_target).norm();
7679
local_has_los = true;
7780
}
@@ -83,33 +86,37 @@ shared_ptr<PathRolloutBase> LinearEvaluator::FindBest(const vector<shared_ptr<Pa
8386
}
8487

8588
// Pass 1: Find shortest total path to goal
86-
// With LOS: total = rollout + remaining distance. Without LOS: total = rollout only.
89+
// Requires LOS: total = rollout + remaining distance. If no LOS exists, return no path.
90+
if (!any_path_has_los) {
91+
printf("No valid path found\n");
92+
return nullptr;
93+
}
8794
shared_ptr<PathRolloutBase> best = nullptr;
8895
float best_total_dist = FLT_MAX;
8996
for (size_t i = 0; i < paths.size(); ++i) {
9097
if (paths[i]->Length() <= 0.0f) continue;
91-
const float total_dist =
92-
any_path_has_los ? (paths[i]->Length() + remaining_dist[i]) : paths[i]->Length();
98+
if (remaining_dist[i] == FLT_MAX) continue;
99+
const float total_dist = paths[i]->Length() + remaining_dist[i];
93100
if (total_dist < best_total_dist) {
94101
best_total_dist = total_dist;
95102
best = paths[i];
96103
}
97104
}
98-
99105
if (best == nullptr) {
100106
printf("No valid path found\n");
101107
return nullptr;
102108
}
103109

104-
// Pass 2: Among paths within subopt tolerance of shortest, find best clearance/length tradeoff
110+
// Pass 2: Among LOS paths within subopt tolerance of shortest, find best clearance/FPL tradeoff
111+
// ?? TODO: should this be FPL() or Length() ?
105112
const float max_allowed_dist = FLAGS_subopt_tolerance * best_total_dist;
106-
float best_cost = FLAGS_freepath_weight * best->Length() + FLAGS_clearance_weight * best->Clearance();
113+
float best_cost = FLAGS_freepath_weight * best->FPL() + FLAGS_clearance_weight * best->Clearance();
107114
for (size_t i = 0; i < paths.size(); ++i) {
108115
if (paths[i]->Length() <= 0.0f) continue;
109-
const float total_dist =
110-
any_path_has_los ? (paths[i]->Length() + remaining_dist[i]) : paths[i]->Length();
116+
if (remaining_dist[i] == FLT_MAX) continue;
117+
const float total_dist = paths[i]->Length() + remaining_dist[i];
111118
if (total_dist > max_allowed_dist) continue;
112-
const float cost = FLAGS_freepath_weight * paths[i]->Length() + FLAGS_clearance_weight * paths[i]->Clearance();
119+
const float cost = FLAGS_freepath_weight * paths[i]->FPL() + FLAGS_clearance_weight * paths[i]->Clearance();
113120
if (cost < best_cost) {
114121
best = paths[i];
115122
best_cost = cost;

src/navigation/motion_primitives.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ struct PathEvaluatorBase {
125125
point_cloud = &new_point_cloud; // zero-copy view
126126
}
127127

128+
void SetNavParams(const navigation::NavigationParameters& new_params) { nav_params = new_params; }
129+
128130
// Return the best path rollout from the provided set of paths.
129131
virtual std::shared_ptr<PathRolloutBase> FindBest(const std::vector<std::shared_ptr<PathRolloutBase>>& paths) = 0;
130132

@@ -140,6 +142,8 @@ struct PathEvaluatorBase {
140142
Eigen::Vector2f local_target;
141143
// Non-owning view of obstacle point cloud.
142144
const std::vector<Eigen::Vector2f>* point_cloud = nullptr;
145+
// Navigation parameters.
146+
navigation::NavigationParameters nav_params;
143147
};
144148

145149
float Run1DTimeOptimalControl(const navigation::MotionLimits& limits, const float x_init, const float v_init,

src/navigation/navigation.cc

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ void Navigation::Initialize(const NavigationParameters& params, const string& ma
242242
PathEvaluatorBase* evaluator = nullptr;
243243
if (params_.evaluator_type == "linear") {
244244
evaluator = (PathEvaluatorBase*)new LinearEvaluator();
245+
evaluator->SetNavParams(params);
245246
} else {
246247
printf("Unknown evaluator type %s\n", params_.evaluator_type.c_str());
247248
exit(1);
@@ -915,20 +916,21 @@ bool Navigation::Run(const double& time, Vector2f& cmd_vel, float& cmd_angle_vel
915916
// "Nudge" window: when close to goal, prefer continuing OA over FOV-based turning
916917
const float goal_dist2 = (nav_goal_loc_ - robot_loc_fp_).squaredNorm(); // MAP-frame distance^2
917918
const bool near_goal_nudge = (goal_dist2 <= Sq(params_.nudge_dist_tolerance));
918-
const bool fov_ok = (fabs(theta) <= params_.local_half_fov);
919+
// Hysteresis-based lidar FOV check to prevent oscillation:
920+
// - To START obstacle avoidance: target must be within conservative FOV (±0.8 * lidar_fov_half_angle)
921+
// - To CONTINUE obstacle avoidance: target can be anywhere in full FOV (±lidar_fov_half_angle)
919922

920-
// Hysteresis-based FOV check to prevent oscillation:
921-
// - To START obstacle avoidance: target must be well-centered (±center_threshold)
922-
// - To CONTINUE obstacle avoidance: target can be anywhere in FOV (±local_half_fov)
923+
const bool fov_ok_for_continue = (fabs(theta) <= params_.lidar_fov_half_angle);
924+
const bool fov_ok_for_start = (fabs(theta) <= 0.8f * params_.lidar_fov_half_angle);
923925

924926
// Check nudge condition first
925927
if (near_goal_nudge) {
926-
fprintf(stderr, "DEBUG: Not in FOV but goal nudge is active\n");
928+
fprintf(stderr, "DEBUG: Not in lidar FOV but goal nudge is active\n");
927929
RunObstacleAvoidance(cmd_vel, cmd_angle_vel);
928930
} else {
929931
if (in_obstacle_avoidance_mode_) {
930-
// Already doing obstacle avoidance: keep going unless target leaves FOV
931-
if (!fov_ok) {
932+
// Already doing obstacle avoidance: keep going unless target leaves full FOV
933+
if (!fov_ok_for_continue) {
932934
// Target left FOV: switch back to turning
933935
in_obstacle_avoidance_mode_ = false;
934936
TurnInPlace(cmd_vel, cmd_angle_vel);
@@ -939,12 +941,12 @@ bool Navigation::Run(const double& time, Vector2f& cmd_vel, float& cmd_angle_vel
939941
} else {
940942
// Currently turning: only start obstacle avoidance when target is well-centered
941943
yaw_align_sp_init_ = false;
942-
if (fabs(theta) <= params_.center_threshold) {
943-
// Target is centered: start obstacle avoidance
944+
if (fov_ok_for_start) {
945+
// Target is well-centered in FOV: start obstacle avoidance
944946
in_obstacle_avoidance_mode_ = true;
945947
RunObstacleAvoidance(cmd_vel, cmd_angle_vel);
946948
} else {
947-
// Target not centered: keep turning
949+
// Target not well-centered: keep turning
948950
TurnInPlace(cmd_vel, cmd_angle_vel);
949951
}
950952
}

src/navigation/navigation_main.cc

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ CONFIG_FLOAT(base_link_offset_x, "NavigationParameters.base_link_offset_x");
109109
CONFIG_FLOAT(base_link_offset_y, "NavigationParameters.base_link_offset_y");
110110
CONFIG_FLOAT(max_free_path_length, "NavigationParameters.max_free_path_length");
111111
CONFIG_FLOAT(max_clearance, "NavigationParameters.max_clearance");
112-
CONFIG_FLOAT(local_half_fov, "NavigationParameters.local_half_fov");
113-
CONFIG_FLOAT(center_threshold, "NavigationParameters.center_threshold");
112+
CONFIG_FLOAT(lidar_fov_half_angle, "NavigationParameters.lidar_fov_half_angle");
114113
CONFIG_BOOL(can_traverse_stairs, "NavigationParameters.can_traverse_stairs");
115114
CONFIG_FLOAT(target_dist_tolerance, "NavigationParameters.target_dist_tolerance");
116115
CONFIG_FLOAT(nudge_dist_tolerance, "NavigationParameters.nudge_dist_tolerance");
@@ -596,9 +595,9 @@ class NavigationNode : public rclcpp::Node, public std::enable_shared_from_this<
596595
const uint32_t nudge_color = within_nudge ? 0xFF0000 : 0xE0E0E0; // red if within, light gray otherwise
597596
visualization::DrawArc(goal_in_local, nudge_dist_tolerance, -M_PI, M_PI, nudge_color, local_viz_msg_);
598597

599-
// Draw FOV cone boundaries (dark yellow)
598+
// Draw lidar FOV cone boundaries (dark yellow)
600599
const float fov_length = 2.0f; // Length of FOV lines in meters
601-
const float fov_half_angle = CONFIG_local_half_fov;
600+
const float fov_half_angle = CONFIG_lidar_fov_half_angle;
602601
Eigen::Vector2f fov_left(fov_length * cos(fov_half_angle), fov_length * sin(fov_half_angle));
603602
Eigen::Vector2f fov_right(fov_length * cos(-fov_half_angle), fov_length * sin(-fov_half_angle));
604603
visualization::DrawLine(Eigen::Vector2f(0, 0), fov_left, 0xFFCC00, local_viz_msg_);
@@ -712,23 +711,24 @@ class NavigationNode : public rclcpp::Node, public std::enable_shared_from_this<
712711
std::vector<std::shared_ptr<motion_primitives::PathRolloutBase>> path_rollouts = navigation_.sampled_paths_;
713712
std::shared_ptr<motion_primitives::PathRolloutBase> best_option = navigation_.best_option_;
714713

715-
// Draw path options that participate in optimization (Length > 0) in blue
714+
// Draw path options that participate in optimization (Length > 0) in light blue
715+
constexpr uint32_t kCandidatePathColor = 0x80A0FF; // Light blue for non-winning paths
716716
for (const auto& rollout : path_rollouts) {
717717
if (rollout->Length() <= 0.0f) continue; // Skip zero-length paths (not in optimization)
718718

719719
// Handle constant curvature arc paths
720720
const auto* arc = dynamic_cast<const motion_primitives::ConstantCurvatureArcPath*>(rollout.get());
721721
if (arc) {
722-
// Draw arc path (blue: 0x0000FF)
723-
visualization::DrawPathOption(arc->curvature, arc->Length(), arc->Clearance(), 0x0000FF, false,
722+
// Draw arc path
723+
visualization::DrawPathOption(arc->curvature, arc->Length(), arc->Clearance(), kCandidatePathColor, false,
724724
local_viz_msg_);
725725
}
726726
// Handle omnidirectional straight-line paths
727727
const auto* omni = dynamic_cast<const motion_primitives::OmnidirectionalMovePath*>(rollout.get());
728728
if (omni) {
729-
// Draw straight line from origin to endpoint (blue: 0x0000FF)
729+
// Draw straight line from origin to endpoint
730730
Eigen::Vector2f endpoint = omni->EndPoint().translation;
731-
visualization::DrawLine(Eigen::Vector2f(0, 0), endpoint, 0x0000FF, local_viz_msg_);
731+
visualization::DrawLine(Eigen::Vector2f(0, 0), endpoint, kCandidatePathColor, local_viz_msg_);
732732
}
733733
}
734734

@@ -745,17 +745,24 @@ class NavigationNode : public rclcpp::Node, public std::enable_shared_from_this<
745745
const auto* best_omni = dynamic_cast<const motion_primitives::OmnidirectionalMovePath*>(best_option.get());
746746
if (best_omni) {
747747
// Draw selected straight path (red: 0xFF0000)
748-
Eigen::Vector2f endpoint = best_omni->EndPoint().translation;
748+
const Eigen::Vector2f endpoint = best_omni->EndPoint().translation;
749749
visualization::DrawLine(Eigen::Vector2f(0, 0), endpoint, 0xFF0000, local_viz_msg_);
750750

751-
// Draw clearance boundaries showing minimum distance to obstacles (red: 0xFF0000)
751+
// Draw clearance corridor: robot body extent + clearance on each side
752+
// Path centerline is at base_link origin; support() gives distance from base_link to robot edge
753+
const Eigen::Vector2f dir_lateral(-best_omni->direction.y(), best_omni->direction.x());
754+
const motion_primitives::OffsetRect robot_body = {
755+
Eigen::Vector2f(navigation_.params_.base_link_offset_x, navigation_.params_.base_link_offset_y),
756+
0.5f * navigation_.params_.robot_length,
757+
0.5f * navigation_.params_.robot_width
758+
};
752759
const float clearance = best_omni->Clearance();
753-
// Calculate perpendicular vector for clearance boundaries
754-
Eigen::Vector2f perp(-best_omni->direction.y(), best_omni->direction.x());
755-
Eigen::Vector2f clearance_offset = clearance * perp;
756-
// Draw parallel lines showing clearance boundaries
757-
visualization::DrawLine(clearance_offset, endpoint + clearance_offset, 0xFF0000, local_viz_msg_);
758-
visualization::DrawLine(-clearance_offset, endpoint - clearance_offset, 0xFF0000, local_viz_msg_);
760+
// Corridor edge = body extent (from base_link) + clearance
761+
const Eigen::Vector2f offset_left = (clearance + robot_body.support(dir_lateral)) * dir_lateral;
762+
const Eigen::Vector2f offset_right = (clearance + robot_body.support(-dir_lateral)) * (-dir_lateral);
763+
constexpr uint32_t kCorridorColor = 0xFF8080; // Light red
764+
visualization::DrawLine(offset_left, endpoint + offset_left, kCorridorColor, local_viz_msg_);
765+
visualization::DrawLine(offset_right, endpoint + offset_right, kCorridorColor, local_viz_msg_);
759766
}
760767
}
761768
}
@@ -776,8 +783,7 @@ class NavigationNode : public rclcpp::Node, public std::enable_shared_from_this<
776783
params->base_link_offset_y = CONFIG_base_link_offset_y;
777784
params->max_free_path_length = CONFIG_max_free_path_length;
778785
params->max_clearance = CONFIG_max_clearance;
779-
params->local_half_fov = CONFIG_local_half_fov;
780-
params->center_threshold = CONFIG_center_threshold;
786+
params->lidar_fov_half_angle = CONFIG_lidar_fov_half_angle;
781787
params->can_traverse_stairs = CONFIG_can_traverse_stairs;
782788
params->target_dist_tolerance = CONFIG_target_dist_tolerance;
783789
params->nudge_dist_tolerance = CONFIG_nudge_dist_tolerance;

src/navigation/navigation_parameters.h

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,9 @@ struct NavigationParameters {
6868
float base_link_offset_y;
6969
float max_free_path_length;
7070
float max_clearance;
71-
// Half-angle of the local field of view cone (radians).
72-
// Full FOV cone is ±local_half_fov. Used to determine when obstacle avoidance can continue.
73-
float local_half_fov;
74-
// Angle threshold (radians) for starting obstacle avoidance in hysteresis logic.
75-
// Target must be within ±center_threshold to start obstacle avoidance.
76-
float center_threshold;
71+
// Half-angle of the lidar field of view cone (radians).
72+
// Full FOV cone is ±lidar_fov_half_angle. Used to determine when obstacle avoidance can run safely.
73+
float lidar_fov_half_angle;
7774

7875
bool can_traverse_stairs;
7976

@@ -130,8 +127,7 @@ struct NavigationParameters {
130127
base_link_offset_y(0),
131128
max_free_path_length(10.0),
132129
max_clearance(1.0),
133-
local_half_fov(1.57),
134-
center_threshold(0.174),
130+
lidar_fov_half_angle(1.57),
135131
can_traverse_stairs(false),
136132
target_dist_tolerance(0.1),
137133
nudge_dist_tolerance(0.3),

0 commit comments

Comments
 (0)