python-motion-planning 2.0.dev1__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_motion_planning/__init__.py +1 -1
- python_motion_planning/common/env/map/base_map.py +2 -8
- python_motion_planning/common/env/map/grid.py +456 -198
- python_motion_planning/common/utils/__init__.py +2 -1
- python_motion_planning/common/utils/child_tree.py +22 -0
- python_motion_planning/common/utils/geometry.py +18 -29
- python_motion_planning/common/visualizer/__init__.py +3 -1
- python_motion_planning/common/visualizer/base_visualizer.py +165 -0
- python_motion_planning/common/visualizer/{visualizer.py → visualizer_2d.py} +97 -220
- python_motion_planning/common/visualizer/visualizer_3d.py +242 -0
- python_motion_planning/controller/base_controller.py +37 -4
- python_motion_planning/controller/path_tracker/__init__.py +2 -1
- python_motion_planning/controller/path_tracker/apf.py +22 -23
- python_motion_planning/controller/path_tracker/dwa.py +14 -17
- python_motion_planning/controller/path_tracker/path_tracker.py +4 -1
- python_motion_planning/controller/path_tracker/pid.py +7 -1
- python_motion_planning/controller/path_tracker/pure_pursuit.py +7 -1
- python_motion_planning/controller/path_tracker/rpp.py +111 -0
- python_motion_planning/path_planner/__init__.py +2 -1
- python_motion_planning/path_planner/base_path_planner.py +45 -11
- python_motion_planning/path_planner/graph_search/__init__.py +4 -1
- python_motion_planning/path_planner/graph_search/a_star.py +12 -14
- python_motion_planning/path_planner/graph_search/dijkstra.py +15 -15
- python_motion_planning/path_planner/graph_search/gbfs.py +100 -0
- python_motion_planning/path_planner/graph_search/jps.py +199 -0
- python_motion_planning/path_planner/graph_search/lazy_theta_star.py +113 -0
- python_motion_planning/path_planner/graph_search/theta_star.py +17 -19
- python_motion_planning/path_planner/hybrid_search/__init__.py +1 -0
- python_motion_planning/path_planner/hybrid_search/voronoi_planner.py +204 -0
- python_motion_planning/path_planner/sample_search/__init__.py +2 -1
- python_motion_planning/path_planner/sample_search/rrt.py +73 -31
- python_motion_planning/path_planner/sample_search/rrt_connect.py +237 -0
- python_motion_planning/path_planner/sample_search/rrt_star.py +220 -150
- python_motion_planning/traj_optimizer/__init__.py +2 -0
- python_motion_planning/traj_optimizer/base_curve_generator.py +53 -0
- python_motion_planning/traj_optimizer/curve_generator/__init__.py +2 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/__init__.py +2 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/bspline.py +256 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/cubic_spline.py +115 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/__init__.py +4 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/bezier.py +121 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/dubins.py +355 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/polynomial.py +197 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/reeds_shepp.py +606 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/METADATA +71 -29
- python_motion_planning-2.0.1.dist-info/RECORD +64 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/WHEEL +1 -1
- python_motion_planning/common/env/robot/tmp.py +0 -404
- python_motion_planning/curve_generator/__init__.py +0 -9
- python_motion_planning/curve_generator/bezier_curve.py +0 -131
- python_motion_planning/curve_generator/bspline_curve.py +0 -271
- python_motion_planning/curve_generator/cubic_spline.py +0 -128
- python_motion_planning/curve_generator/curve.py +0 -64
- python_motion_planning/curve_generator/dubins_curve.py +0 -348
- python_motion_planning/curve_generator/fem_pos_smooth.py +0 -114
- python_motion_planning/curve_generator/polynomial_curve.py +0 -226
- python_motion_planning/curve_generator/reeds_shepp.py +0 -736
- python_motion_planning-2.0.dev1.dist-info/RECORD +0 -53
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: bspline_curve.py
|
|
3
|
+
@author: Yang Haodong, Wu Maojia
|
|
4
|
+
@update: 2026.4.12
|
|
5
|
+
"""
|
|
6
|
+
from typing import List, Tuple, Dict, Any
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from python_motion_planning.traj_optimizer.base_curve_generator import BaseCurveGenerator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BSpline(BaseCurveGenerator):
|
|
15
|
+
"""
|
|
16
|
+
Class for B-Spline curve generator.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
*args: see the parent class.
|
|
20
|
+
k: Degree of the B-Spline curve.
|
|
21
|
+
param_mode: Parameter selection mode, one of {"centripetal", "chord_length", "uniform_spaced"}.
|
|
22
|
+
spline_mode: B-Spline construction mode, one of {"interpolation", "approximation"}.
|
|
23
|
+
*kwargs: see the parent class.
|
|
24
|
+
|
|
25
|
+
References:
|
|
26
|
+
[1] https://en.wikipedia.org/wiki/B-spline
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
>>> generator = BSpline(step=0.1, k=3)
|
|
30
|
+
>>> points = [(0.0, 0.0), (10.0, 10.0), (20.0, 5.0)]
|
|
31
|
+
>>> path, curve_info = generator.generate(points)
|
|
32
|
+
>>> print(curve_info['success'])
|
|
33
|
+
True
|
|
34
|
+
"""
|
|
35
|
+
def __init__(self, *args,
|
|
36
|
+
k: int = 3,
|
|
37
|
+
param_mode: str = "centripetal",
|
|
38
|
+
spline_mode: str = "interpolation",
|
|
39
|
+
**kwargs) -> None:
|
|
40
|
+
super().__init__(*args, **kwargs)
|
|
41
|
+
self.k = k
|
|
42
|
+
|
|
43
|
+
if param_mode not in ("centripetal", "chord_length", "uniform_spaced"):
|
|
44
|
+
raise ValueError("Parameter selection mode error!")
|
|
45
|
+
self.param_mode = param_mode
|
|
46
|
+
|
|
47
|
+
if spline_mode not in ("interpolation", "approximation"):
|
|
48
|
+
raise ValueError("Spline mode selection error!")
|
|
49
|
+
self.spline_mode = spline_mode
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
return "B-Spline Curve"
|
|
53
|
+
|
|
54
|
+
def generate(self, points: List[Tuple[float, ...]]) -> Tuple[List[Tuple[float, float]], Dict[str, Any]]:
|
|
55
|
+
"""
|
|
56
|
+
Generate a B-Spline curve through a list of 2D points.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
points: A list of 2D points (x, y) in world frame. If the points contain
|
|
60
|
+
additional entries (e.g. yaw), only the first two values are used.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
path: A list of (x, y) waypoints of the generated curve in world frame.
|
|
64
|
+
curve_info: A dictionary containing the curve information (success, length).
|
|
65
|
+
"""
|
|
66
|
+
if len(points) < 2:
|
|
67
|
+
return [], {"success": False, "length": 0.0}
|
|
68
|
+
|
|
69
|
+
points_2d = [(float(p[0]), float(p[1])) for p in points]
|
|
70
|
+
|
|
71
|
+
t = np.linspace(0, 1, int(1 / self.step))
|
|
72
|
+
params = self._param_selection(points_2d)
|
|
73
|
+
knot = self._knot_generation(params, len(points_2d))
|
|
74
|
+
|
|
75
|
+
if self.spline_mode == "interpolation":
|
|
76
|
+
control_pts = self._interpolation(points_2d, params, knot)
|
|
77
|
+
else:
|
|
78
|
+
control_pts = self._approximation(points_2d, params, knot)
|
|
79
|
+
h = len(control_pts)
|
|
80
|
+
new_points = [(control_pts[i][0], control_pts[i][1]) for i in range(h)]
|
|
81
|
+
params = self._param_selection(new_points)
|
|
82
|
+
knot = self._knot_generation(params, h)
|
|
83
|
+
|
|
84
|
+
curve = self._generate_curve(t, self.k, knot, control_pts)
|
|
85
|
+
path = [(float(pt[0]), float(pt[1])) for pt in curve]
|
|
86
|
+
|
|
87
|
+
return path, {"success": True, "length": self.length(path)}
|
|
88
|
+
|
|
89
|
+
def _base_function(self, i: int, k: int, t: float, knot: List[float]) -> float:
|
|
90
|
+
"""
|
|
91
|
+
Calculate the base function using the Cox-de Boor recursion.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
i: The index of the base function.
|
|
95
|
+
k: The degree of the curve.
|
|
96
|
+
t: Parameter value.
|
|
97
|
+
knot: The knot vector.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Nik_t: The value of the base function Nik(t).
|
|
101
|
+
"""
|
|
102
|
+
Nik_t = 0.0
|
|
103
|
+
if k == 0:
|
|
104
|
+
Nik_t = 1.0 if knot[i] <= t < knot[i + 1] else 0.0
|
|
105
|
+
else:
|
|
106
|
+
length1 = knot[i + k] - knot[i]
|
|
107
|
+
length2 = knot[i + k + 1] - knot[i + 1]
|
|
108
|
+
if not length1 and not length2:
|
|
109
|
+
Nik_t = 0.0
|
|
110
|
+
elif not length1:
|
|
111
|
+
Nik_t = (knot[i + k + 1] - t) / length2 * self._base_function(i + 1, k - 1, t, knot)
|
|
112
|
+
elif not length2:
|
|
113
|
+
Nik_t = (t - knot[i]) / length1 * self._base_function(i, k - 1, t, knot)
|
|
114
|
+
else:
|
|
115
|
+
Nik_t = ((t - knot[i]) / length1 * self._base_function(i, k - 1, t, knot)
|
|
116
|
+
+ (knot[i + k + 1] - t) / length2 * self._base_function(i + 1, k - 1, t, knot))
|
|
117
|
+
return Nik_t
|
|
118
|
+
|
|
119
|
+
def _param_selection(self, points: List[Tuple[float, float]]) -> List[float]:
|
|
120
|
+
"""
|
|
121
|
+
Calculate the parameters of the given points using the selected method.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
points: A list of 2D points.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
parameters: The parameters of the given points.
|
|
128
|
+
"""
|
|
129
|
+
n = len(points)
|
|
130
|
+
x_list = [pt[0] for pt in points]
|
|
131
|
+
y_list = [pt[1] for pt in points]
|
|
132
|
+
dx, dy = np.diff(x_list), np.diff(y_list)
|
|
133
|
+
|
|
134
|
+
if self.param_mode == "uniform_spaced":
|
|
135
|
+
return np.linspace(0, 1, n).tolist()
|
|
136
|
+
|
|
137
|
+
if self.param_mode == "chord_length":
|
|
138
|
+
parameters = np.zeros(n)
|
|
139
|
+
s = np.cumsum([math.hypot(idx, idy) for (idx, idy) in zip(dx, dy)])
|
|
140
|
+
for i in range(1, n):
|
|
141
|
+
parameters[i] = s[i - 1] / s[-1]
|
|
142
|
+
return parameters.tolist()
|
|
143
|
+
|
|
144
|
+
# centripetal
|
|
145
|
+
alpha = 0.5
|
|
146
|
+
s = np.cumsum([math.pow(math.hypot(idx, idy), alpha) for (idx, idy) in zip(dx, dy)])
|
|
147
|
+
parameters = np.zeros(n)
|
|
148
|
+
for i in range(1, n):
|
|
149
|
+
parameters[i] = s[i - 1] / s[-1]
|
|
150
|
+
return parameters.tolist()
|
|
151
|
+
|
|
152
|
+
def _knot_generation(self, param: List[float], n: int) -> List[float]:
|
|
153
|
+
"""
|
|
154
|
+
Generate the knot vector.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
param: The parameters of the given points.
|
|
158
|
+
n: The number of data points.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
knot: The knot vector.
|
|
162
|
+
"""
|
|
163
|
+
m = n + self.k + 1
|
|
164
|
+
knot = np.zeros(m)
|
|
165
|
+
for i in range(n, m):
|
|
166
|
+
knot[i] = 1
|
|
167
|
+
for i in range(self.k + 1, n):
|
|
168
|
+
for j in range(i - self.k, i):
|
|
169
|
+
knot[i] = knot[i] + param[j]
|
|
170
|
+
knot[i] = knot[i] / self.k
|
|
171
|
+
return knot.tolist()
|
|
172
|
+
|
|
173
|
+
def _interpolation(self, points: List[Tuple[float, float]], param: List[float],
|
|
174
|
+
knot: List[float]) -> np.ndarray:
|
|
175
|
+
"""
|
|
176
|
+
Build a B-Spline curve of degree k defined by N control points that passes
|
|
177
|
+
through all N data points.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
points: A list of 2D points.
|
|
181
|
+
param: The parameters of the given points.
|
|
182
|
+
knot: The knot vector.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
control_points: The control points of the resulting B-Spline.
|
|
186
|
+
"""
|
|
187
|
+
n = len(points)
|
|
188
|
+
N = np.zeros((n, n))
|
|
189
|
+
|
|
190
|
+
for i in range(n):
|
|
191
|
+
for j in range(n):
|
|
192
|
+
N[i][j] = self._base_function(j, self.k, param[i], knot)
|
|
193
|
+
N[n - 1][n - 1] = 1
|
|
194
|
+
N_inv = np.linalg.inv(N)
|
|
195
|
+
|
|
196
|
+
D = np.array(points)
|
|
197
|
+
return N_inv @ D
|
|
198
|
+
|
|
199
|
+
def _approximation(self, points: List[Tuple[float, float]], param: List[float],
|
|
200
|
+
knot: List[float]) -> np.ndarray:
|
|
201
|
+
"""
|
|
202
|
+
Build a B-Spline curve of degree k defined by H control points (H < N) that
|
|
203
|
+
passes through the first and last data points and approximates the rest in
|
|
204
|
+
the least-square sense.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
points: A list of 2D points.
|
|
208
|
+
param: The parameters of the given points.
|
|
209
|
+
knot: The knot vector.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
control_points: The control points of the resulting B-Spline.
|
|
213
|
+
"""
|
|
214
|
+
n = len(points)
|
|
215
|
+
D = np.array(points)
|
|
216
|
+
|
|
217
|
+
h = n - 1
|
|
218
|
+
|
|
219
|
+
N = np.zeros((n, h))
|
|
220
|
+
for i in range(n):
|
|
221
|
+
for j in range(h):
|
|
222
|
+
N[i][j] = self._base_function(j, self.k, param[i], knot)
|
|
223
|
+
N_ = N[1: n - 1, 1: h - 1]
|
|
224
|
+
|
|
225
|
+
qk = np.zeros((n - 2, 2))
|
|
226
|
+
for i in range(1, n - 1):
|
|
227
|
+
qk[i - 1] = D[i, :] - N[i][0] * D[0, :] - N[i][h - 1] * D[-1, :]
|
|
228
|
+
Q = N_.T @ qk
|
|
229
|
+
|
|
230
|
+
P = np.linalg.inv(N_.T @ N_) @ Q
|
|
231
|
+
P = np.insert(P, 0, D[0, :], axis=0)
|
|
232
|
+
P = np.insert(P, len(P), D[-1, :], axis=0)
|
|
233
|
+
return P
|
|
234
|
+
|
|
235
|
+
def _generate_curve(self, t: np.ndarray, k: int, knot: List[float],
|
|
236
|
+
control_pts: np.ndarray) -> np.ndarray:
|
|
237
|
+
"""
|
|
238
|
+
Sample points on the B-Spline curve.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
t: The parameter values to sample at.
|
|
242
|
+
k: The degree of the B-Spline curve.
|
|
243
|
+
knot: The knot vector.
|
|
244
|
+
control_pts: The control points.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
curve: Sampled points along the B-Spline curve.
|
|
248
|
+
"""
|
|
249
|
+
N = np.zeros((len(t), len(control_pts)))
|
|
250
|
+
|
|
251
|
+
for i in range(len(t)):
|
|
252
|
+
for j in range(len(control_pts)):
|
|
253
|
+
N[i][j] = self._base_function(j, k, t[i], knot)
|
|
254
|
+
N[len(t) - 1][len(control_pts) - 1] = 1
|
|
255
|
+
|
|
256
|
+
return N @ control_pts
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: cubic_spline.py
|
|
3
|
+
@author: Yang Haodong, Wu Maojia
|
|
4
|
+
@update: 2026.4.12
|
|
5
|
+
"""
|
|
6
|
+
from typing import List, Tuple, Dict, Any
|
|
7
|
+
import math
|
|
8
|
+
import bisect
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from python_motion_planning.traj_optimizer.base_curve_generator import BaseCurveGenerator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CubicSpline(BaseCurveGenerator):
|
|
16
|
+
"""
|
|
17
|
+
Class for cubic spline curve generator.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
*args: see the parent class.
|
|
21
|
+
*kwargs: see the parent class.
|
|
22
|
+
|
|
23
|
+
References:
|
|
24
|
+
[1] https://en.wikipedia.org/wiki/Spline_(mathematics)#Algorithm_for_computing_natural_cubic_splines
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> generator = CubicSpline(step=0.1)
|
|
28
|
+
>>> points = [(0.0, 0.0), (10.0, 10.0), (20.0, 5.0)]
|
|
29
|
+
>>> path, curve_info = generator.generate(points)
|
|
30
|
+
>>> print(curve_info['success'])
|
|
31
|
+
True
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
34
|
+
super().__init__(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
return "Cubic Spline"
|
|
38
|
+
|
|
39
|
+
def generate(self, points: List[Tuple[float, ...]]) -> Tuple[List[Tuple[float, float]], Dict[str, Any]]:
|
|
40
|
+
"""
|
|
41
|
+
Generate a cubic spline curve through a list of 2D points.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
points: A list of 2D points (x, y) in world frame. If the points contain
|
|
45
|
+
additional entries (e.g. yaw), only the first two values are used.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
path: A list of (x, y) waypoints of the generated curve in world frame.
|
|
49
|
+
curve_info: A dictionary containing the curve information (success, length).
|
|
50
|
+
"""
|
|
51
|
+
if len(points) < 2:
|
|
52
|
+
return [], {"success": False, "length": 0.0}
|
|
53
|
+
|
|
54
|
+
x_list = [float(p[0]) for p in points]
|
|
55
|
+
y_list = [float(p[1]) for p in points]
|
|
56
|
+
|
|
57
|
+
dx, dy = np.diff(x_list), np.diff(y_list)
|
|
58
|
+
ds = [math.hypot(idx, idy) for (idx, idy) in zip(dx, dy)]
|
|
59
|
+
s = [0.0]
|
|
60
|
+
s.extend(np.cumsum(ds))
|
|
61
|
+
t = np.arange(0, s[-1], self.step)
|
|
62
|
+
|
|
63
|
+
path_x, _ = self._spline(s, x_list, t)
|
|
64
|
+
path_y, _ = self._spline(s, y_list, t)
|
|
65
|
+
|
|
66
|
+
path = [(float(ix), float(iy)) for ix, iy in zip(path_x, path_y)]
|
|
67
|
+
return path, {"success": True, "length": self.length(path)}
|
|
68
|
+
|
|
69
|
+
def _spline(self, x_list: List[float], y_list: List[float],
|
|
70
|
+
t: np.ndarray) -> Tuple[List[float], List[float]]:
|
|
71
|
+
"""
|
|
72
|
+
Build and evaluate a 1D natural cubic spline y = f(x).
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
x_list: Monotonically increasing x-coordinates of the control points.
|
|
76
|
+
y_list: y-coordinates of the control points.
|
|
77
|
+
t: Values of x at which to evaluate the spline.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
p: Values of the spline evaluated at t.
|
|
81
|
+
dp: Values of the spline derivative evaluated at t.
|
|
82
|
+
"""
|
|
83
|
+
a, b, c, d = y_list, [], [], []
|
|
84
|
+
h = np.diff(x_list)
|
|
85
|
+
num = len(x_list)
|
|
86
|
+
|
|
87
|
+
A = np.zeros((num, num))
|
|
88
|
+
for i in range(1, num - 1):
|
|
89
|
+
A[i, i - 1] = h[i - 1]
|
|
90
|
+
A[i, i] = 2.0 * (h[i - 1] + h[i])
|
|
91
|
+
A[i, i + 1] = h[i]
|
|
92
|
+
A[0, 0] = 1.0
|
|
93
|
+
A[num - 1, num - 1] = 1.0
|
|
94
|
+
|
|
95
|
+
B = np.zeros(num)
|
|
96
|
+
for i in range(1, num - 1):
|
|
97
|
+
B[i] = (3.0 * (a[i + 1] - a[i]) / h[i]
|
|
98
|
+
- 3.0 * (a[i] - a[i - 1]) / h[i - 1])
|
|
99
|
+
|
|
100
|
+
c = np.linalg.solve(A, B)
|
|
101
|
+
for i in range(num - 1):
|
|
102
|
+
d.append((c[i + 1] - c[i]) / (3.0 * h[i]))
|
|
103
|
+
b.append((a[i + 1] - a[i]) / h[i] - h[i] * (c[i + 1] + 2.0 * c[i]) / 3.0)
|
|
104
|
+
|
|
105
|
+
p, dp = [], []
|
|
106
|
+
for it in t:
|
|
107
|
+
if it < x_list[0] or it > x_list[-1]:
|
|
108
|
+
continue
|
|
109
|
+
i = bisect.bisect(x_list, it) - 1
|
|
110
|
+
i = min(max(i, 0), num - 2)
|
|
111
|
+
dx = it - x_list[i]
|
|
112
|
+
p.append(a[i] + b[i] * dx + c[i] * dx ** 2 + d[i] * dx ** 3)
|
|
113
|
+
dp.append(b[i] + 2.0 * c[i] * dx + 3.0 * d[i] * dx ** 2)
|
|
114
|
+
|
|
115
|
+
return p, dp
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: bezier_curve.py
|
|
3
|
+
@author: Yang Haodong, Wu Maojia
|
|
4
|
+
@update: 2026.4.12
|
|
5
|
+
"""
|
|
6
|
+
from typing import List, Tuple, Dict, Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy.special import comb
|
|
10
|
+
|
|
11
|
+
from python_motion_planning.traj_optimizer.base_curve_generator import BaseCurveGenerator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Bezier(BaseCurveGenerator):
|
|
15
|
+
"""
|
|
16
|
+
Class for Bezier curve generator.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
*args: see the parent class.
|
|
20
|
+
offset: The offset of control points (larger value yields sharper curvature).
|
|
21
|
+
*kwargs: see the parent class.
|
|
22
|
+
|
|
23
|
+
References:
|
|
24
|
+
[1] https://en.wikipedia.org/wiki/B%C3%A9zier_curve
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> import math
|
|
28
|
+
>>> generator = Bezier(step=0.1, offset=3.0)
|
|
29
|
+
>>> points = [(0.0, 0.0, 0.0), (10.0, 10.0, -math.pi/2), (20.0, 5.0, math.pi/3)]
|
|
30
|
+
>>> path, curve_info = generator.generate(points)
|
|
31
|
+
>>> print(curve_info['success'])
|
|
32
|
+
True
|
|
33
|
+
"""
|
|
34
|
+
def __init__(self, *args,
|
|
35
|
+
offset: float = 3.0,
|
|
36
|
+
**kwargs) -> None:
|
|
37
|
+
super().__init__(*args, **kwargs)
|
|
38
|
+
self.offset = offset
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
return "Bezier Curve"
|
|
42
|
+
|
|
43
|
+
def generate(self, points: List[Tuple[float, float, float]]) -> Tuple[List[Tuple[float, float]], Dict[str, Any]]:
|
|
44
|
+
"""
|
|
45
|
+
Generate a concatenated Bezier curve through a list of poses.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
points: A list of poses (x, y, yaw) in world frame.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
path: A list of (x, y) waypoints of the generated curve in world frame.
|
|
52
|
+
curve_info: A dictionary containing the curve information (success, length).
|
|
53
|
+
"""
|
|
54
|
+
if len(points) < 2:
|
|
55
|
+
return [], {"success": False, "length": 0.0}
|
|
56
|
+
|
|
57
|
+
path: List[Tuple[float, float]] = []
|
|
58
|
+
for i in range(len(points) - 1):
|
|
59
|
+
segment, _ = self._generate_segment(points[i], points[i + 1])
|
|
60
|
+
path.extend([(float(pt[0]), float(pt[1])) for pt in segment])
|
|
61
|
+
|
|
62
|
+
return path, {"success": True, "length": self.length(path)}
|
|
63
|
+
|
|
64
|
+
def _generate_segment(self, start_pose: Tuple[float, float, float],
|
|
65
|
+
goal_pose: Tuple[float, float, float]
|
|
66
|
+
) -> Tuple[List[np.ndarray], List[Tuple[float, float]]]:
|
|
67
|
+
"""
|
|
68
|
+
Generate a single Bezier curve segment between two poses.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
start_pose: Initial pose (x, y, yaw).
|
|
72
|
+
goal_pose: Target pose (x, y, yaw).
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
segment: A list of points sampled from the Bezier curve.
|
|
76
|
+
control_points: The control points of the Bezier curve.
|
|
77
|
+
"""
|
|
78
|
+
sx, sy, _ = start_pose
|
|
79
|
+
gx, gy, _ = goal_pose
|
|
80
|
+
n_points = max(int(np.hypot(sx - gx, sy - gy) / self.step), 2)
|
|
81
|
+
control_points = self._get_control_points(start_pose, goal_pose)
|
|
82
|
+
|
|
83
|
+
segment = [self._bezier(t, control_points) for t in np.linspace(0, 1, n_points)]
|
|
84
|
+
return segment, control_points
|
|
85
|
+
|
|
86
|
+
def _bezier(self, t: float, control_points: List[Tuple[float, float]]) -> np.ndarray:
|
|
87
|
+
"""
|
|
88
|
+
Calculate the Bezier curve point.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
t: Scale factor in [0, 1].
|
|
92
|
+
control_points: Control points of the Bezier curve.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
point: Point on the Bezier curve at the given t.
|
|
96
|
+
"""
|
|
97
|
+
n = len(control_points) - 1
|
|
98
|
+
control_points = np.array(control_points)
|
|
99
|
+
return np.sum([comb(n, i) * t ** i * (1 - t) ** (n - i) * control_points[i]
|
|
100
|
+
for i in range(n + 1)], axis=0)
|
|
101
|
+
|
|
102
|
+
def _get_control_points(self, start_pose: Tuple[float, float, float],
|
|
103
|
+
goal_pose: Tuple[float, float, float]) -> List[Tuple[float, float]]:
|
|
104
|
+
"""
|
|
105
|
+
Calculate the control points heuristically from start and goal poses.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
start_pose: Initial pose (x, y, yaw).
|
|
109
|
+
goal_pose: Target pose (x, y, yaw).
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
control_points: Control points of the Bezier curve.
|
|
113
|
+
"""
|
|
114
|
+
sx, sy, syaw = start_pose
|
|
115
|
+
gx, gy, gyaw = goal_pose
|
|
116
|
+
|
|
117
|
+
dist = np.hypot(sx - gx, sy - gy) / self.offset
|
|
118
|
+
return [(sx, sy),
|
|
119
|
+
(sx + dist * np.cos(syaw), sy + dist * np.sin(syaw)),
|
|
120
|
+
(gx - dist * np.cos(gyaw), gy - dist * np.sin(gyaw)),
|
|
121
|
+
(gx, gy)]
|