1212// See the License for the specific language governing permissions and
1313// limitations under the License. Reserved.
1414
15- #include < ompl/base/ScopedState.h>
16- #include < ompl/base/spaces/DubinsStateSpace.h>
17- #include < ompl/base/spaces/ReedsSheppStateSpace.h>
18-
1915#include < algorithm>
2016#include < vector>
2117#include < memory>
@@ -64,7 +60,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
6460 NodeT::getCoords (
6561 current_node->getIndex (), _collision_checker->getCostmap ()->getSizeInCellsX (), _dim_3_size);
6662
67- AnalyticExpansionNodes current_best_analytic_nodes = {} ;
63+ AnalyticExpansionNodes current_best_analytic_nodes;
6864 NodePtr current_best_goal = nullptr ;
6965 NodePtr current_best_node = nullptr ;
7066 float current_best_score = std::numeric_limits<float >::max ();
@@ -96,7 +92,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
9692 getAnalyticPath (
9793 current_node, current_goal_node, getter,
9894 current_node->motion_table .state_space );
99- if (!analytic_nodes.empty ()) {
95+ if (!analytic_nodes.nodes . empty ()) {
10096 found_valid_expansion = true ;
10197 NodePtr node = current_node;
10298 float score = refineAnalyticPath (
@@ -118,7 +114,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
118114 getAnalyticPath (
119115 current_node, current_goal_node, getter,
120116 current_node->motion_table .state_space );
121- if (!analytic_nodes.empty ()) {
117+ if (!analytic_nodes.nodes . empty ()) {
122118 NodePtr node = current_node;
123119 float score = refineAnalyticPath (
124120 node, current_goal_node, getter, analytic_nodes);
@@ -134,7 +130,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
134130 }
135131 }
136132
137- if (!current_best_analytic_nodes.empty ()) {
133+ if (!current_best_analytic_nodes.nodes . empty ()) {
138134 return setAnalyticPath (
139135 current_best_node, current_best_goal,
140136 current_best_analytic_nodes);
@@ -146,6 +142,28 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
146142 return NodePtr (nullptr );
147143}
148144
145+ template <typename NodeT>
146+ int AnalyticExpansion<NodeT>::countDirectionChanges(
147+ const ompl::base::ReedsSheppStateSpace::ReedsSheppPath & path)
148+ {
149+ const double * lengths = path.length_ ;
150+ int changes = 0 ;
151+ int last_dir = 0 ;
152+ for (int i = 0 ; i < 5 ; ++i) {
153+ if (lengths[i] == 0.0 ) {
154+ continue ;
155+ }
156+
157+ int currentDirection = (lengths[i] > 0.0 ) ? 1 : -1 ;
158+ if (last_dir != 0 && currentDirection != last_dir) {
159+ ++changes;
160+ }
161+ last_dir = currentDirection;
162+ }
163+
164+ return changes;
165+ }
166+
149167template <typename NodeT>
150168typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<NodeT>::getAnalyticPath(
151169 const NodePtr & node,
@@ -163,6 +181,12 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
163181
164182 float d = state_space->distance (from (), to ());
165183
184+ auto rs_state_space = dynamic_cast <ompl::base::ReedsSheppStateSpace *>(state_space.get ());
185+ int direction_changes = 0 ;
186+ if (rs_state_space) {
187+ direction_changes = countDirectionChanges (rs_state_space->reedsShepp (from.get (), to.get ()));
188+ }
189+
166190 // A move of sqrt(2) is guaranteed to be in a new cell
167191 static const float sqrt_2 = sqrtf (2 .0f );
168192
@@ -179,7 +203,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
179203 AnalyticExpansionNodes possible_nodes;
180204 // When "from" and "to" are zero or one cell away,
181205 // num_intervals == 0
182- possible_nodes.reserve (num_intervals); // We won't store this node or the goal
206+ possible_nodes.nodes . reserve (num_intervals); // We won't store this node or the goal
183207 std::vector<double > reals;
184208 double theta;
185209
@@ -214,7 +238,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
214238 next->setPose (proposed_coordinates);
215239 if (next->isNodeValid (_traverse_unknown, _collision_checker) && next != prev) {
216240 // Save the node, and its previous coordinates in case we need to abort
217- possible_nodes.emplace_back (next, initial_node_coords, proposed_coordinates);
241+ possible_nodes.add (next, initial_node_coords, proposed_coordinates);
218242 node_costs.emplace_back (next->getCost ());
219243 prev = next;
220244 } else {
@@ -264,7 +288,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
264288 }
265289
266290 // Reset to initial poses to not impact future searches
267- for (const auto & node_pose : possible_nodes) {
291+ for (const auto & node_pose : possible_nodes. nodes ) {
268292 const auto & n = node_pose.node ;
269293 n->setPose (node_pose.initial_coords );
270294 }
@@ -273,6 +297,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
273297 return AnalyticExpansionNodes ();
274298 }
275299
300+ possible_nodes.setDirectionChanges (direction_changes);
276301 return possible_nodes;
277302}
278303
@@ -299,9 +324,13 @@ float AnalyticExpansion<NodeT>::refineAnalyticPath(
299324 getAnalyticPath (
300325 test_node, goal_node, getter,
301326 test_node->motion_table .state_space );
302- if (refined_analytic_nodes.empty ()) {
327+ if (refined_analytic_nodes.nodes . empty ()) {
303328 break ;
304329 }
330+ if (refined_analytic_nodes.direction_changes > analytic_nodes.direction_changes ) {
331+ // If the direction changes are worse, we don't want to use this path
332+ continue ;
333+ }
305334 analytic_nodes = refined_analytic_nodes;
306335 node = test_node;
307336 } else {
@@ -314,26 +343,27 @@ float AnalyticExpansion<NodeT>::refineAnalyticPath(
314343 // higher than the minimum turning radius and use the best solution based on
315344 // a scoring function similar to that used in traversal cost estimation.
316345 auto scoringFn = [&](const AnalyticExpansionNodes & expansion) {
317- if (expansion.size () < 2 ) {
346+ if (expansion.nodes . size () < 2 ) {
318347 return std::numeric_limits<float >::max ();
319348 }
320349
321350 float score = 0.0 ;
322351 float normalized_cost = 0.0 ;
323352 // Analytic expansions are consistently spaced
324353 const float distance = hypotf (
325- expansion[1 ].proposed_coords .x - expansion[0 ].proposed_coords .x ,
326- expansion[1 ].proposed_coords .y - expansion[0 ].proposed_coords .y );
327- const float & weight = expansion[0 ].node ->motion_table .cost_penalty ;
328- for (auto iter = expansion.begin (); iter != expansion.end (); ++iter) {
354+ expansion. nodes [1 ].proposed_coords .x - expansion. nodes [0 ].proposed_coords .x ,
355+ expansion. nodes [1 ].proposed_coords .y - expansion. nodes [0 ].proposed_coords .y );
356+ const float & weight = expansion. nodes [0 ].node ->motion_table .cost_penalty ;
357+ for (auto iter = expansion.nodes . begin (); iter != expansion. nodes .end (); ++iter) {
329358 normalized_cost = iter->node ->getCost () / 252 .0f ;
330- // Search's Traversal Cost Function
359+ // Search's Traversal Cost Function
331360 score += distance * (1.0 + weight * normalized_cost);
332361 }
333362 return score;
334363 };
335364
336- float best_score = scoringFn (analytic_nodes);
365+ float original_score = scoringFn (analytic_nodes);
366+ float best_score = original_score;
337367 float score = std::numeric_limits<float >::max ();
338368 float min_turn_rad = node->motion_table .min_turning_radius ;
339369 const float max_min_turn_rad = 4.0 * min_turn_rad; // Up to 4x the turning radius
@@ -347,7 +377,21 @@ float AnalyticExpansion<NodeT>::refineAnalyticPath(
347377 }
348378 refined_analytic_nodes = getAnalyticPath (node, goal_node, getter, state_space);
349379 score = scoringFn (refined_analytic_nodes);
350- if (score <= best_score) {
380+
381+ // Normal scoring: prioritize lower cost as long as not more directional changes
382+ if (score <= best_score &&
383+ refined_analytic_nodes.direction_changes <= analytic_nodes.direction_changes )
384+ {
385+ analytic_nodes = refined_analytic_nodes;
386+ best_score = score;
387+ continue ;
388+ }
389+
390+ // Special case: If we have a better score than original (only) and less directional changes
391+ // the path quality is still better than the original and is less operationally complex
392+ if (score <= original_score &&
393+ refined_analytic_nodes.direction_changes < analytic_nodes.direction_changes )
394+ {
351395 analytic_nodes = refined_analytic_nodes;
352396 best_score = score;
353397 }
@@ -365,7 +409,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::setAnalytic
365409 _detached_nodes.clear ();
366410 // Legitimate final path - set the parent relationships, states, and poses
367411 NodePtr prev = node;
368- for (const auto & node_pose : expanded_nodes) {
412+ for (const auto & node_pose : expanded_nodes. nodes ) {
369413 auto n = node_pose.node ;
370414 cleanNode (n);
371415 if (n->getIndex () != goal_node->getIndex ()) {
0 commit comments