Skip to content

Commit 1fb484a

Browse files
committed
Add functions for path shortening
1 parent cfc7267 commit 1fb484a

2 files changed

Lines changed: 141 additions & 0 deletions

File tree

roboplan/include/roboplan/core/path_utils.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,34 @@ std::vector<Eigen::Matrix4d> computeFramePath(const Scene& scene, const Eigen::V
2828
bool hasCollisionsAlongPath(const Scene& scene, const Eigen::VectorXd& q_start,
2929
const Eigen::VectorXd& q_end, const double max_step_size);
3030

31+
/// @brief Attempts to shortcut the path with random sampling and checking connections.
32+
/// @details This implementation is based on section 3.5.3 of:
33+
/// https://motion.cs.illinois.edu/RoboticSystems/MotionPlanningHigherDimensions.html
34+
/// @param scene The scene for checking connectability between joint poses.
35+
/// @param path The JointPath to try to shorten.
36+
/// @param max_step_size Maximum step size to use in collision checking.
37+
/// @param max_iters Maximum number of iterations of random sampling (default 100).
38+
/// @return A shortcutted JointPath, if available.
39+
JointPath shortcutPath(const Scene& scene, const JointPath& path, double max_step_size,
40+
unsigned int max_iters = 100);
41+
42+
/// @brief Helper function to compute length-normalized scaling values along a JointPath.
43+
/// @param scene The scene to compute configuration distances.
44+
/// @param path The path to length-normalize.
45+
/// @return A vector of scaling values between 0.0 and 1.0 at each point in the path.
46+
std::vector<double> getNormalizedPathScaling(const Scene& scene, const JointPath& path);
47+
48+
/// @brief Helper function to get joint configurations from a path with normalized joint scalings.
49+
/// @param scene The scene to use for joint interpolation.
50+
/// @param path A JointPath of joint poses.
51+
/// @param path_scalings The corresponding path scalings (between 0 and 1) to the provided path.
52+
/// @param value A value between 0.0 and 1.0, at which to get the joint configuration along the
53+
/// path.
54+
/// @return a tuple containing the joint configuration at the specified scaling value along the
55+
/// path,
56+
/// as well as the index corresponding to the next point along the path.
57+
std::pair<Eigen::VectorXd, size_t>
58+
getConfigurationFromNormalizedPathScaling(const Scene& scene, const JointPath& path,
59+
const std::vector<double>& path_scalings, double value);
60+
3161
} // namespace roboplan

roboplan/src/core/path_utils.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,115 @@ bool hasCollisionsAlongPath(const Scene& scene, const Eigen::VectorXd& q_start,
4747
return false;
4848
}
4949

50+
JointPath shortcutPath(const Scene& scene, const JointPath& path, double max_step_size,
51+
unsigned int max_iters) {
52+
// Make a copy of the provided path.
53+
JointPath shortened_path = path;
54+
55+
// Cannot shorten paths if they don't have enough points.
56+
if (path.positions.size() < 3) {
57+
return shortened_path;
58+
}
59+
60+
std::random_device rd;
61+
std::mt19937 gen(rd());
62+
std::uniform_real_distribution<double> dis(0.0, 1.0);
63+
64+
for (unsigned int i = 0; i < max_iters; ++i) {
65+
66+
// The the path is at maximum shortcutted-ness
67+
if (shortened_path.positions.size() < 3) {
68+
return shortened_path;
69+
}
70+
71+
// Randomly sample two points along the scaled path
72+
const auto path_scalings = getNormalizedPathScaling(scene, shortened_path);
73+
double low_point = dis(gen);
74+
double high_point = dis(gen);
75+
if (low_point > high_point) {
76+
std::swap(low_point, high_point);
77+
}
78+
79+
const auto [q_low, idx_low] =
80+
getConfigurationFromNormalizedPathScaling(scene, shortened_path, path_scalings, low_point);
81+
const auto [q_high, idx_high] =
82+
getConfigurationFromNormalizedPathScaling(scene, shortened_path, path_scalings, high_point);
83+
84+
if (idx_low == idx_high) {
85+
continue;
86+
}
87+
88+
// Check if the sampled segment is collision free. If it is, shortcut the path.
89+
if (!hasCollisionsAlongPath(scene, q_low, q_high, max_step_size)) {
90+
std::vector<Eigen::VectorXd> new_positions;
91+
new_positions.reserve(idx_low + 2 + (shortened_path.positions.size() - idx_high));
92+
93+
for (size_t i = 0; i < idx_low; ++i) {
94+
new_positions.push_back(shortened_path.positions[i]);
95+
}
96+
97+
new_positions.push_back(q_low);
98+
new_positions.push_back(q_high);
99+
100+
for (size_t i = idx_high; i < shortened_path.positions.size(); ++i) {
101+
new_positions.push_back(shortened_path.positions[i]);
102+
}
103+
104+
shortened_path.positions = std::move(new_positions);
105+
}
106+
}
107+
108+
return shortened_path;
109+
}
110+
111+
std::vector<double> getNormalizedPathScaling(const Scene& scene, const JointPath& path) {
112+
if (path.positions.empty()) {
113+
return {};
114+
}
115+
if (path.positions.size() == 1) {
116+
return {1.0};
117+
}
118+
119+
std::vector<double> path_length_list;
120+
path_length_list.reserve(path.positions.size());
121+
122+
// Iteratively compute path lengths from start to finish
123+
double path_length = 0.0;
124+
path_length_list.push_back(path_length);
125+
for (size_t idx = 0; idx < path.positions.size() - 1; ++idx) {
126+
path_length += scene.configurationDistance(path.positions[idx], path.positions[idx + 1]);
127+
path_length_list.push_back(path_length);
128+
}
129+
130+
// Normalize
131+
for (auto& length : path_length_list) {
132+
length /= path_length;
133+
}
134+
135+
return path_length_list;
136+
}
137+
138+
std::pair<Eigen::VectorXd, size_t>
139+
getConfigurationFromNormalizedPathScaling(const Scene& scene, const JointPath& path,
140+
const std::vector<double>& path_scalings, double value) {
141+
142+
for (size_t idx = 0; idx < path_scalings.size(); ++idx) {
143+
// Find the smallest index that is less than value
144+
if (value > path_scalings[idx]) {
145+
continue;
146+
}
147+
148+
// Interpolate to the joint configuration
149+
const double delta_scale =
150+
(value - path_scalings[idx - 1]) / (path_scalings[idx] - path_scalings[idx - 1]);
151+
const Eigen::VectorXd q_interp =
152+
scene.interpolate(path.positions[idx - 1], path.positions[idx], delta_scale);
153+
154+
return {q_interp, idx};
155+
}
156+
157+
// This shouldn't be possible
158+
return {path.positions.back(), path.positions.size() - 1};
159+
}
160+
50161
} // namespace roboplan

0 commit comments

Comments
 (0)