@@ -41,7 +41,7 @@ AnalyticExpansion<NodeT>::AnalyticExpansion(
4141
4242template <typename NodeT>
4343void AnalyticExpansion<NodeT>::setCollisionChecker(
44- GridCollisionChecker * collision_checker)
44+ GridCollisionChecker * & collision_checker)
4545{
4646 _collision_checker = collision_checker;
4747}
@@ -80,7 +80,8 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
8080 if (analytic_iterations <= 0 ) {
8181 // Reset the counter and try the analytic path expansion
8282 analytic_iterations = desired_iterations;
83- AnalyticExpansionNodes analytic_nodes = getAnalyticPath (current_node, goal_node, getter);
83+ AnalyticExpansionNodes analytic_nodes =
84+ getAnalyticPath (current_node, goal_node, getter, current_node->motion_table .state_space );
8485 if (!analytic_nodes.empty ()) {
8586 // If we have a valid path, attempt to refine it
8687 NodePtr node = current_node;
@@ -94,7 +95,8 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
9495 test_node->parent ->parent ->parent ->parent ->parent )
9596 {
9697 test_node = test_node->parent ->parent ->parent ->parent ->parent ;
97- refined_analytic_nodes = getAnalyticPath (test_node, goal_node, getter);
98+ refined_analytic_nodes =
99+ getAnalyticPath (test_node, goal_node, getter, test_node->motion_table .state_space );
98100 if (refined_analytic_nodes.empty ()) {
99101 break ;
100102 }
@@ -105,6 +107,50 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
105107 }
106108 }
107109
110+ // The analytic expansion can short-cut near obstacles when closer to a goal
111+ // So, we can attempt to refine it more by increasing the possible radius
112+ // higher than the minimum turning radius and use the best solution based on
113+ // a scoring function similar to that used in traveral cost estimation.
114+ auto scoringFn = [&](const AnalyticExpansionNodes & expansion) {
115+ if (expansion.size () < 2 ) {
116+ return std::numeric_limits<float >::max ();
117+ }
118+
119+ float score = 0.0 ;
120+ float normalized_cost = 0.0 ;
121+ // Analytic expansions are consistently spaced
122+ const float distance = hypotf (
123+ expansion[1 ].proposed_coords .x - expansion[0 ].proposed_coords .x ,
124+ expansion[1 ].proposed_coords .y - expansion[0 ].proposed_coords .y );
125+ const float & weight = expansion[0 ].node ->motion_table .cost_penalty ;
126+ for (auto iter = expansion.begin (); iter != expansion.end (); ++iter) {
127+ normalized_cost = iter->node ->getCost () / 252 .0f ;
128+ // Search's Traversal Cost Function
129+ score += distance * (1.0 + weight * normalized_cost);
130+ }
131+ return score;
132+ };
133+
134+ float best_score = scoringFn (analytic_nodes);
135+ float score = std::numeric_limits<float >::max ();
136+ float min_turn_rad = node->motion_table .min_turning_radius ;
137+ const float max_min_turn_rad = 4.0 * min_turn_rad; // Up to 4x the turning radius
138+ while (min_turn_rad < max_min_turn_rad) {
139+ min_turn_rad += 0.5 ; // In Grid Coords, 1/2 cell steps
140+ ompl::base::StateSpacePtr state_space;
141+ if (node->motion_table .motion_model == MotionModel::DUBIN) {
142+ state_space = std::make_shared<ompl::base::DubinsStateSpace>(min_turn_rad);
143+ } else {
144+ state_space = std::make_shared<ompl::base::ReedsSheppStateSpace>(min_turn_rad);
145+ }
146+ refined_analytic_nodes = getAnalyticPath (node, goal_node, getter, state_space);
147+ score = scoringFn (refined_analytic_nodes);
148+ if (score <= best_score) {
149+ analytic_nodes = refined_analytic_nodes;
150+ best_score = score;
151+ }
152+ }
153+
108154 return setAnalyticPath (node, goal_node, analytic_nodes);
109155 }
110156 }
@@ -120,18 +166,18 @@ template<typename NodeT>
120166typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<NodeT>::getAnalyticPath(
121167 const NodePtr & node,
122168 const NodePtr & goal,
123- const NodeGetter & node_getter)
169+ const NodeGetter & node_getter,
170+ const ompl::base::StateSpacePtr & state_space)
124171{
125- static ompl::base::ScopedState<> from (node->motion_table .state_space ), to (
126- node->motion_table .state_space ), s (node->motion_table .state_space );
172+ static ompl::base::ScopedState<> from (state_space), to (state_space), s (state_space);
127173 from[0 ] = node->pose .x ;
128174 from[1 ] = node->pose .y ;
129175 from[2 ] = node->motion_table .getAngleFromBin (node->pose .theta );
130176 to[0 ] = goal->pose .x ;
131177 to[1 ] = goal->pose .y ;
132178 to[2 ] = node->motion_table .getAngleFromBin (goal->pose .theta );
133179
134- float d = node-> motion_table . state_space ->distance (from (), to ());
180+ float d = state_space->distance (from (), to ());
135181
136182 // If the length is too far, exit. This prevents unsafe shortcutting of paths
137183 // into higher cost areas far out from the goal itself, let search to the work of getting
@@ -142,8 +188,8 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
142188 }
143189
144190 // A move of sqrt(2) is guaranteed to be in a new cell
145- static const float sqrt_2 = std::sqrt (2 .);
146- unsigned int num_intervals = std::floor (d / sqrt_2);
191+ static const float sqrt_2 = std::sqrt (2 .0f );
192+ unsigned int num_intervals = static_cast < unsigned int >( std::floor (d / sqrt_2) );
147193
148194 AnalyticExpansionNodes possible_nodes;
149195 // When "from" and "to" are zero or one cell away,
@@ -159,10 +205,12 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
159205 float angle = 0.0 ;
160206 Coordinates proposed_coordinates;
161207 bool failure = false ;
208+ std::vector<float > node_costs;
209+ node_costs.reserve (num_intervals);
162210
163211 // Check intermediary poses (non-goal, non-start)
164212 for (float i = 1 ; i < num_intervals; i++) {
165- node-> motion_table . state_space ->interpolate (from (), to (), i / num_intervals, s ());
213+ state_space->interpolate (from (), to (), i / num_intervals, s ());
166214 reals = s.reals ();
167215 // Make sure in range [0, 2PI)
168216 theta = (reals[2 ] < 0.0 ) ? (reals[2 ] + 2.0 * M_PI) : reals[2 ];
@@ -182,6 +230,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
182230 if (next->isNodeValid (_traverse_unknown, _collision_checker) && next != prev) {
183231 // Save the node, and its previous coordinates in case we need to abort
184232 possible_nodes.emplace_back (next, initial_node_coords, proposed_coordinates);
233+ node_costs.emplace_back (next->getCost ());
185234 prev = next;
186235 } else {
187236 // Abort
@@ -196,6 +245,38 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
196245 }
197246 }
198247
248+ if (!failure) {
249+ // We found 'a' valid expansion. Now to tell if its a quality option...
250+ const float max_cost = _search_info.analytic_expansion_max_cost ;
251+ if (*std::max_element (node_costs.begin (), node_costs.end ()) > max_cost) {
252+ // If any element is above the comfortable cost limit, check edge cases:
253+ // (1) Check if goal is in greater than max_cost space requiring
254+ // entering it, but only entering it on final approach, not in-and-out
255+ // (2) Checks if goal is in normal space, but enters costed space unnecessarily
256+ // mid-way through, skirting obstacle or in non-globally confined space
257+ bool cost_exit_high_cost_region = false ;
258+ for (auto iter = node_costs.rbegin (); iter != node_costs.rend (); ++iter) {
259+ const float & curr_cost = *iter;
260+ if (curr_cost <= max_cost) {
261+ cost_exit_high_cost_region = true ;
262+ } else if (curr_cost > max_cost && cost_exit_high_cost_region) {
263+ failure = true ;
264+ break ;
265+ }
266+ }
267+
268+ // (3) Handle exception: there may be no other option close to goal
269+ // if max cost is set too low (optional)
270+ if (failure) {
271+ if (d < 2 .0f * M_PI * goal->motion_table .min_turning_radius &&
272+ _search_info.analytic_expansion_max_cost_override )
273+ {
274+ failure = false ;
275+ }
276+ }
277+ }
278+ }
279+
199280 // Reset to initial poses to not impact future searches
200281 for (const auto & node_pose : possible_nodes) {
201282 const auto & n = node_pose.node ;
@@ -256,7 +337,8 @@ typename AnalyticExpansion<Node2D>::AnalyticExpansionNodes AnalyticExpansion<Nod
256337getAnalyticPath (
257338 const NodePtr & node,
258339 const NodePtr & goal,
259- const NodeGetter & node_getter)
340+ const NodeGetter & node_getter,
341+ const ompl::base::StateSpacePtr & state_space)
260342{
261343 return AnalyticExpansionNodes ();
262344}
0 commit comments