Spaces:
Configuration error
Configuration error
add trajectory generation w. cubic splines
Browse files
interactive_3d_plot.html
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
main.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from flight_environment import FlightEnvironment
|
| 2 |
from planners.rrt_planner import rrt_planner
|
|
|
|
| 3 |
from plot_plotly import plot_cylinders
|
| 4 |
|
| 5 |
env = FlightEnvironment(50)
|
|
@@ -20,9 +21,11 @@ path = rrt_planner(env, start, goal, step_size=1)
|
|
| 20 |
|
| 21 |
# --------------------------------------------------------------------------------------------------- #
|
| 22 |
|
| 23 |
-
|
| 24 |
plot_cylinders(env, path)
|
| 25 |
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# --------------------------------------------------------------------------------------------------- #
|
| 28 |
# Call your trajectory planning algorithm here. The algorithm should
|
|
@@ -37,13 +40,12 @@ plot_cylinders(env, path)
|
|
| 37 |
# points on the same figure to clearly show how the continuous trajectory
|
| 38 |
# follows these path points.
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
|
| 43 |
# --------------------------------------------------------------------------------------------------- #
|
| 44 |
|
| 45 |
|
| 46 |
-
|
| 47 |
# You must manage this entire project using Git.
|
| 48 |
# When submitting your assignment, upload the project to a code-hosting platform
|
| 49 |
# such as GitHub or GitLab. The repository must be accessible and directly cloneable.
|
|
|
|
| 1 |
from flight_environment import FlightEnvironment
|
| 2 |
from planners.rrt_planner import rrt_planner
|
| 3 |
+
from planners.trajectory_generator import plan_trajectory_through_path, plot_trajectory
|
| 4 |
from plot_plotly import plot_cylinders
|
| 5 |
|
| 6 |
env = FlightEnvironment(50)
|
|
|
|
| 21 |
|
| 22 |
# --------------------------------------------------------------------------------------------------- #
|
| 23 |
|
| 24 |
+
# Matplotlib (laggy)
|
| 25 |
plot_cylinders(env, path)
|
| 26 |
|
| 27 |
+
# Plotly
|
| 28 |
+
# env.plot_cylinders(path)
|
| 29 |
|
| 30 |
# --------------------------------------------------------------------------------------------------- #
|
| 31 |
# Call your trajectory planning algorithm here. The algorithm should
|
|
|
|
| 40 |
# points on the same figure to clearly show how the continuous trajectory
|
| 41 |
# follows these path points.
|
| 42 |
|
| 43 |
+
t_traj, traj = plan_trajectory_through_path(path, desired_speed=1.0, samples_per_meter=8)
|
| 44 |
+
plot_trajectory(t_traj, traj, path)
|
| 45 |
|
| 46 |
# --------------------------------------------------------------------------------------------------- #
|
| 47 |
|
| 48 |
|
|
|
|
| 49 |
# You must manage this entire project using Git.
|
| 50 |
# When submitting your assignment, upload the project to a code-hosting platform
|
| 51 |
# such as GitHub or GitLab. The repository must be accessible and directly cloneable.
|
planners/__pycache__/rrt_planner.cpython-313.pyc
CHANGED
|
Binary files a/planners/__pycache__/rrt_planner.cpython-313.pyc and b/planners/__pycache__/rrt_planner.cpython-313.pyc differ
|
|
|
planners/__pycache__/trajectory_generator.cpython-313.pyc
ADDED
|
Binary file (8.61 kB). View file
|
|
|
planners/path_planner.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
import numpy as np
|
| 2 |
"""
|
| 3 |
In this file, you should implement your own path planning class or function.
|
| 4 |
Within your implementation, you may call `env.is_collide()` and `env.is_outside()`
|
|
@@ -10,100 +9,4 @@ any existing path planning algorithms from others is strictly
|
|
| 10 |
prohibited. Please avoid using external packages beyond common Python libraries
|
| 11 |
such as `numpy`, `math`, or `scipy`. If you must use additional packages, you
|
| 12 |
must clearly explain the reason in your report.
|
| 13 |
-
"""
|
| 14 |
-
|
| 15 |
-
def is_segment_collision_free(env, p1, p2, step=0.1):
|
| 16 |
-
"""Return True if straight segment p1->p2 is free.
|
| 17 |
-
- env: FlightEnvironment instance
|
| 18 |
-
- p1,p2: iterable (x,y,z)
|
| 19 |
-
- step: distance between samples along the segment (meters)
|
| 20 |
-
"""
|
| 21 |
-
dist = np.linalg.norm(p2 - p1)
|
| 22 |
-
if dist == 0:
|
| 23 |
-
return not env.is_collide(tuple(p1)) and not env.is_collide(tuple(p1))
|
| 24 |
-
n = max(1, int(np.ceil(dist / step)))
|
| 25 |
-
for i in range(n + 1):
|
| 26 |
-
t = i / n
|
| 27 |
-
p = p1 * (1 - t) + p2 * t
|
| 28 |
-
if env.is_outside(tuple(p)) or env.is_collide(tuple(p)):
|
| 29 |
-
return False
|
| 30 |
-
return True
|
| 31 |
-
|
| 32 |
-
def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance=0.8, goal_sample_rate=0.1):
|
| 33 |
-
"""
|
| 34 |
-
Basic RRT planner in continuous 3D space with cylinder obstacles provided by env.
|
| 35 |
-
Returns a path as an (N x 3) numpy array from start to goal (inclusive).
|
| 36 |
-
"""
|
| 37 |
-
start = np.array(start, dtype=float)
|
| 38 |
-
goal = np.array(goal, dtype=float)
|
| 39 |
-
|
| 40 |
-
# quick checks
|
| 41 |
-
if env.is_outside(tuple(start)) or env.is_collide(tuple(start)):
|
| 42 |
-
raise RuntimeError("Start is invalid (outside or in collision).")
|
| 43 |
-
if env.is_outside(tuple(goal)) or env.is_collide(tuple(goal)):
|
| 44 |
-
raise RuntimeError("Goal is invalid (outside or in collision).")
|
| 45 |
-
|
| 46 |
-
# recorded statistics stored here
|
| 47 |
-
stats = dict(collisions=0)
|
| 48 |
-
|
| 49 |
-
@dataclass
|
| 50 |
-
class Node:
|
| 51 |
-
point: np.ndarray
|
| 52 |
-
parent: np.ndarray = None
|
| 53 |
-
|
| 54 |
-
# environment bounds
|
| 55 |
-
Xmax, Ymax, Zmax = env.space_size
|
| 56 |
-
|
| 57 |
-
nodes = [Node(start)]
|
| 58 |
-
|
| 59 |
-
for it in range(max_iters):
|
| 60 |
-
# sample point
|
| 61 |
-
if np.random.rand() < goal_sample_rate:
|
| 62 |
-
sample = goal.copy()
|
| 63 |
-
else:
|
| 64 |
-
sample = np.array([np.random.uniform(0, Xmax),
|
| 65 |
-
np.random.uniform(0, Ymax),
|
| 66 |
-
np.random.uniform(0, Zmax)])
|
| 67 |
-
# find nearest node
|
| 68 |
-
dists = [np.linalg.norm(node.point - sample) for node in nodes]
|
| 69 |
-
nearest_idx = int(np.argmin(dists))
|
| 70 |
-
nearest = nodes[nearest_idx]
|
| 71 |
-
|
| 72 |
-
direction = sample - nearest.point
|
| 73 |
-
norm = np.linalg.norm(direction)
|
| 74 |
-
if norm == 0:
|
| 75 |
-
print('norm0')
|
| 76 |
-
continue
|
| 77 |
-
direction = direction / norm
|
| 78 |
-
|
| 79 |
-
new_point = nearest.point + direction * min(step_size, norm)
|
| 80 |
-
|
| 81 |
-
# ensure within bounds
|
| 82 |
-
if env.is_outside(tuple(new_point)):
|
| 83 |
-
print('outside')
|
| 84 |
-
continue
|
| 85 |
-
|
| 86 |
-
# check collision along segment
|
| 87 |
-
if not is_segment_collision_free(env, nearest.point, new_point, step=step_size / 5.0):
|
| 88 |
-
stats["collisions"]+=1
|
| 89 |
-
continue
|
| 90 |
-
|
| 91 |
-
new_node = Node(new_point, nearest)
|
| 92 |
-
nodes.append(new_node)
|
| 93 |
-
|
| 94 |
-
# check if we reached goal
|
| 95 |
-
if np.linalg.norm(new_point - goal) <= goal_tolerance:
|
| 96 |
-
# try connect directly to goal
|
| 97 |
-
if is_segment_collision_free(env, new_point, goal, step=step_size / 5.0):
|
| 98 |
-
goal_node = Node(goal, new_node)
|
| 99 |
-
nodes.append(goal_node)
|
| 100 |
-
# reconstruct path
|
| 101 |
-
path_nodes = []
|
| 102 |
-
cur = goal_node
|
| 103 |
-
while cur is not None:
|
| 104 |
-
path_nodes.append(cur.point)
|
| 105 |
-
cur = cur.parent
|
| 106 |
-
path = np.array(path_nodes[::-1])
|
| 107 |
-
return path
|
| 108 |
-
|
| 109 |
-
raise RuntimeError(f"RRT failed to find a path within the given iterations. {stats=}")
|
|
|
|
|
|
|
| 1 |
"""
|
| 2 |
In this file, you should implement your own path planning class or function.
|
| 3 |
Within your implementation, you may call `env.is_collide()` and `env.is_outside()`
|
|
|
|
| 9 |
prohibited. Please avoid using external packages beyond common Python libraries
|
| 10 |
such as `numpy`, `math`, or `scipy`. If you must use additional packages, you
|
| 11 |
must clearly explain the reason in your report.
|
| 12 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
planners/rrt_planner.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from dataclasses import dataclass
|
| 2 |
import numpy as np
|
|
|
|
| 3 |
|
| 4 |
def is_segment_collision_free(env, p1, p2, step=0.1):
|
| 5 |
"""Return True if straight segment p1->p2 is free.
|
|
@@ -20,7 +21,7 @@ def is_segment_collision_free(env, p1, p2, step=0.1):
|
|
| 20 |
|
| 21 |
def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance=0.8, goal_sample_rate=0.1):
|
| 22 |
"""
|
| 23 |
-
Basic RRT planner in continuous 3D space
|
| 24 |
Returns a path as an (N x 3) numpy array from start to goal (inclusive).
|
| 25 |
"""
|
| 26 |
start = np.array(start, dtype=float)
|
|
@@ -32,8 +33,9 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
|
|
| 32 |
if env.is_outside(tuple(goal)) or env.is_collide(tuple(goal)):
|
| 33 |
raise RuntimeError("Goal is invalid (outside or in collision).")
|
| 34 |
|
| 35 |
-
# recorded statistics stored here
|
| 36 |
stats = dict(collisions=0)
|
|
|
|
| 37 |
|
| 38 |
@dataclass
|
| 39 |
class Node:
|
|
@@ -46,7 +48,7 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
|
|
| 46 |
nodes = [Node(start)]
|
| 47 |
|
| 48 |
for it in range(max_iters):
|
| 49 |
-
# sample point
|
| 50 |
if np.random.rand() < goal_sample_rate:
|
| 51 |
sample = goal.copy()
|
| 52 |
else:
|
|
@@ -61,7 +63,6 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
|
|
| 61 |
direction = sample - nearest.point
|
| 62 |
norm = np.linalg.norm(direction)
|
| 63 |
if norm == 0:
|
| 64 |
-
print('norm0')
|
| 65 |
continue
|
| 66 |
direction = direction / norm
|
| 67 |
|
|
@@ -69,7 +70,6 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
|
|
| 69 |
|
| 70 |
# ensure within bounds
|
| 71 |
if env.is_outside(tuple(new_point)):
|
| 72 |
-
print('outside')
|
| 73 |
continue
|
| 74 |
|
| 75 |
# check collision along segment
|
|
@@ -93,6 +93,7 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
|
|
| 93 |
path_nodes.append(cur.point)
|
| 94 |
cur = cur.parent
|
| 95 |
path = np.array(path_nodes[::-1])
|
|
|
|
| 96 |
return path
|
| 97 |
|
| 98 |
raise RuntimeError(f"RRT failed to find a path within the given iterations. {stats=}")
|
|
|
|
| 1 |
from dataclasses import dataclass
|
| 2 |
import numpy as np
|
| 3 |
+
import time
|
| 4 |
|
| 5 |
def is_segment_collision_free(env, p1, p2, step=0.1):
|
| 6 |
"""Return True if straight segment p1->p2 is free.
|
|
|
|
| 21 |
|
| 22 |
def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance=0.8, goal_sample_rate=0.1):
|
| 23 |
"""
|
| 24 |
+
Basic RRT planner in continuous 3D space.
|
| 25 |
Returns a path as an (N x 3) numpy array from start to goal (inclusive).
|
| 26 |
"""
|
| 27 |
start = np.array(start, dtype=float)
|
|
|
|
| 33 |
if env.is_outside(tuple(goal)) or env.is_collide(tuple(goal)):
|
| 34 |
raise RuntimeError("Goal is invalid (outside or in collision).")
|
| 35 |
|
| 36 |
+
# recorded statistics stored here, can be inspected later
|
| 37 |
stats = dict(collisions=0)
|
| 38 |
+
tstart = time.time()
|
| 39 |
|
| 40 |
@dataclass
|
| 41 |
class Node:
|
|
|
|
| 48 |
nodes = [Node(start)]
|
| 49 |
|
| 50 |
for it in range(max_iters):
|
| 51 |
+
# sample random point sometimes, otherwise take goal (->bias)
|
| 52 |
if np.random.rand() < goal_sample_rate:
|
| 53 |
sample = goal.copy()
|
| 54 |
else:
|
|
|
|
| 63 |
direction = sample - nearest.point
|
| 64 |
norm = np.linalg.norm(direction)
|
| 65 |
if norm == 0:
|
|
|
|
| 66 |
continue
|
| 67 |
direction = direction / norm
|
| 68 |
|
|
|
|
| 70 |
|
| 71 |
# ensure within bounds
|
| 72 |
if env.is_outside(tuple(new_point)):
|
|
|
|
| 73 |
continue
|
| 74 |
|
| 75 |
# check collision along segment
|
|
|
|
| 93 |
path_nodes.append(cur.point)
|
| 94 |
cur = cur.parent
|
| 95 |
path = np.array(path_nodes[::-1])
|
| 96 |
+
print("reached goal", len(nodes), "nodes", len(path), "waypoints", f'{time.time()-tstart:.2f}', "seconds")
|
| 97 |
return path
|
| 98 |
|
| 99 |
raise RuntimeError(f"RRT failed to find a path within the given iterations. {stats=}")
|
planners/trajectory_generator.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
In this file, you should implement your trajectory generation class or function.
|
| 3 |
+
Your method must generate a smooth 3-axis trajectory (x(t), y(t), z(t)) that
|
| 4 |
+
passes through all the previously computed path points. A positional deviation
|
| 5 |
+
up to 0.1 m from each path point is allowed.
|
| 6 |
+
|
| 7 |
+
You should output the generated trajectory and visualize it. The figure must
|
| 8 |
+
contain three subplots showing x, y, and z, respectively, with time t (in seconds)
|
| 9 |
+
as the horizontal axis. Additionally, you must plot the original discrete path
|
| 10 |
+
points on the same figure for comparison.
|
| 11 |
+
|
| 12 |
+
You are expected to write the implementation yourself. Do NOT copy or reuse any
|
| 13 |
+
existing trajectory generation code from others. Avoid using external packages
|
| 14 |
+
beyond general scientific libraries such as numpy, math, or scipy. If you decide
|
| 15 |
+
to use additional packages, you must clearly explain the reason in your report.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
import matplotlib.pyplot as plt
|
| 20 |
+
|
| 21 |
+
# --------------------------------------------------------------------------------------------------- #
|
| 22 |
+
# Trajectory planning: Natural cubic spline through the discrete path points
|
| 23 |
+
# The trajectory is continuous and smooth and passes through all path points.
|
| 24 |
+
# We parameterize the spline by cumulative chord length (time ~ distance / speed).
|
| 25 |
+
# Then we plot x(t), y(t), z(t) with the discrete path points overlayed.
|
| 26 |
+
# --------------------------------------------------------------------------------------------------- #
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def natural_cubic_spline_coeffs(t, y):
|
| 30 |
+
"""
|
| 31 |
+
Compute natural cubic spline second derivatives (m values) for points (t, y).
|
| 32 |
+
Returns array m of length n (m[0] = m[-1] = 0).
|
| 33 |
+
"""
|
| 34 |
+
n = len(t)
|
| 35 |
+
if n < 2:
|
| 36 |
+
raise ValueError("Need at least two points for spline.")
|
| 37 |
+
h = np.diff(t)
|
| 38 |
+
# handle degenerate h
|
| 39 |
+
if np.any(h <= 0):
|
| 40 |
+
raise ValueError("t must be strictly increasing.", t)
|
| 41 |
+
# set up tridiagonal system for m[1..n-2]
|
| 42 |
+
A = np.zeros((n, n))
|
| 43 |
+
rhs = np.zeros(n)
|
| 44 |
+
A[0, 0] = 1.0
|
| 45 |
+
A[-1, -1] = 1.0
|
| 46 |
+
for i in range(1, n - 1):
|
| 47 |
+
A[i, i - 1] = h[i - 1]
|
| 48 |
+
A[i, i] = 2 * (h[i - 1] + h[i])
|
| 49 |
+
A[i, i + 1] = h[i]
|
| 50 |
+
rhs[i] = 6 * ((y[i + 1] - y[i]) / h[i] - (y[i] - y[i - 1]) / h[i - 1])
|
| 51 |
+
m = np.linalg.solve(A, rhs)
|
| 52 |
+
return m
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def build_spline_segments(t, y):
|
| 56 |
+
"""
|
| 57 |
+
Given t and y (arrays of length n), compute spline coefficients for each segment.
|
| 58 |
+
Returns list of dicts with keys a,b,c,d and interval [t_i, t_{i+1}).
|
| 59 |
+
Spline form on segment i: S_i(s) = a + b*s + c*s^2 + d*s^3, where s = (t - t_i)
|
| 60 |
+
"""
|
| 61 |
+
n = len(t)
|
| 62 |
+
h = np.diff(t)
|
| 63 |
+
m = natural_cubic_spline_coeffs(t, y)
|
| 64 |
+
segments = []
|
| 65 |
+
for i in range(n - 1):
|
| 66 |
+
a = y[i]
|
| 67 |
+
c = m[i] / 2.0
|
| 68 |
+
d = (m[i + 1] - m[i]) / (6.0 * h[i])
|
| 69 |
+
b = (y[i + 1] - y[i]) / h[i] - (h[i] * (2 * m[i] + m[i + 1])) / 6.0
|
| 70 |
+
segments.append({'t0': t[i], 't1': t[i + 1], 'a': a, 'b': b, 'c': c, 'd': d, 'h': h[i]})
|
| 71 |
+
return segments
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def evaluate_spline(segments, t_query):
|
| 75 |
+
"""
|
| 76 |
+
Evaluate spline (list of segments) at times t_query (array).
|
| 77 |
+
"""
|
| 78 |
+
t_query = np.array(t_query)
|
| 79 |
+
y_out = np.zeros_like(t_query, dtype=float)
|
| 80 |
+
for i, ti in enumerate(t_query):
|
| 81 |
+
# find segment
|
| 82 |
+
# segments are ordered, so we can do a simple search
|
| 83 |
+
if ti <= segments[0]['t0']:
|
| 84 |
+
seg = segments[0]
|
| 85 |
+
elif ti >= segments[-1]['t1']:
|
| 86 |
+
seg = segments[-1]
|
| 87 |
+
else:
|
| 88 |
+
# binary search could be used; linear search is fine for modest sizes
|
| 89 |
+
for seg in segments:
|
| 90 |
+
if seg['t0'] <= ti <= seg['t1']:
|
| 91 |
+
break
|
| 92 |
+
s = ti - seg['t0']
|
| 93 |
+
y_out[i] = seg['a'] + seg['b'] * s + seg['c'] * s**2 + seg['d'] * s**3
|
| 94 |
+
return y_out
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def plan_trajectory_through_path(path, desired_speed=1.0, samples_per_meter=10):
|
| 98 |
+
"""
|
| 99 |
+
Given path (N x 3), compute a smooth trajectory (x(t), y(t), z(t)).
|
| 100 |
+
desired_speed sets time scaling (seconds per meter ~ 1/desired_speed).
|
| 101 |
+
samples_per_meter controls temporal resolution.
|
| 102 |
+
Returns time array t_out and trajectory array traj_out (len(t_out) x 3)
|
| 103 |
+
"""
|
| 104 |
+
path = np.array(path, dtype=float)
|
| 105 |
+
n = path.shape[0]
|
| 106 |
+
# parameterize by cumulative chord length
|
| 107 |
+
dists = np.linalg.norm(np.diff(path, axis=0), axis=1)
|
| 108 |
+
chord = np.insert(np.cumsum(dists), 0, 0.0)
|
| 109 |
+
total_length = chord[-1]
|
| 110 |
+
if total_length == 0:
|
| 111 |
+
t = np.array([0.0])
|
| 112 |
+
traj = path.copy()
|
| 113 |
+
return t, traj
|
| 114 |
+
|
| 115 |
+
# time vector proportional to distance, with constant desired_speed
|
| 116 |
+
T_total = total_length / desired_speed
|
| 117 |
+
t_pts = chord / desired_speed # times at which we must pass through path points
|
| 118 |
+
|
| 119 |
+
# build spline for each axis
|
| 120 |
+
seg_x = build_spline_segments(t_pts, path[:, 0])
|
| 121 |
+
seg_y = build_spline_segments(t_pts, path[:, 1])
|
| 122 |
+
seg_z = build_spline_segments(t_pts, path[:, 2])
|
| 123 |
+
|
| 124 |
+
# sample times
|
| 125 |
+
n_samples = max(200, int(np.ceil(total_length * samples_per_meter)))
|
| 126 |
+
t_out = np.linspace(0.0, t_pts[-1], n_samples)
|
| 127 |
+
|
| 128 |
+
x_out = evaluate_spline(seg_x, t_out)
|
| 129 |
+
y_out = evaluate_spline(seg_y, t_out)
|
| 130 |
+
z_out = evaluate_spline(seg_z, t_out)
|
| 131 |
+
|
| 132 |
+
traj_out = np.vstack((x_out, y_out, z_out)).T
|
| 133 |
+
return t_out, traj_out
|
| 134 |
+
|
| 135 |
+
def plot_trajectory(t_traj, traj, path):
|
| 136 |
+
# Plot x(t), y(t), z(t) in separate subplots and overlay discrete path points at their times
|
| 137 |
+
fig = plt.figure(figsize=(8, 8))
|
| 138 |
+
axs = []
|
| 139 |
+
axs.append(plt.subplot2grid((7, 1), (0, 0), rowspan=3, colspan=1))
|
| 140 |
+
axs.append(plt.subplot2grid((7, 1), (3, 0), rowspan=3, colspan=1))
|
| 141 |
+
axs.append(plt.subplot2grid((7, 1), (6, 0), rowspan=1, colspan=1))
|
| 142 |
+
axs[0].plot(t_traj, traj[:, 0], label='x(t)')
|
| 143 |
+
axs[1].plot(t_traj, traj[:, 1], label='y(t)', color='orange')
|
| 144 |
+
axs[2].plot(t_traj, traj[:, 2], label='z(t)', color='green')
|
| 145 |
+
|
| 146 |
+
# overlay discrete path points
|
| 147 |
+
# compute discrete path times (same parameterization used for spline)
|
| 148 |
+
path = np.array(path)
|
| 149 |
+
dists = np.linalg.norm(np.diff(path, axis=0), axis=1)
|
| 150 |
+
chord = np.insert(np.cumsum(dists), 0, 0.0)
|
| 151 |
+
path_times = chord / 1.0 # desired_speed = 1.0 used above
|
| 152 |
+
|
| 153 |
+
axs[0].scatter(path_times, path[:, 0], color='k', zorder=10)
|
| 154 |
+
axs[1].scatter(path_times, path[:, 1], color='k', zorder=10)
|
| 155 |
+
axs[2].scatter(path_times, path[:, 2], color='k', zorder=10)
|
| 156 |
+
|
| 157 |
+
axs[2].set_xlabel('time (s)')
|
| 158 |
+
axs[0].set_ylabel('x (m)')
|
| 159 |
+
axs[1].set_ylabel('y (m)')
|
| 160 |
+
axs[2].set_ylabel('z (m)')
|
| 161 |
+
|
| 162 |
+
axs[0].grid(True)
|
| 163 |
+
axs[1].grid(True)
|
| 164 |
+
axs[2].grid(True)
|
| 165 |
+
|
| 166 |
+
axs[0].set_title('Trajectory')
|
| 167 |
+
|
| 168 |
+
plt.tight_layout()
|
| 169 |
+
plt.show()
|
trajectory_generator.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
In this file, you should implement your trajectory generation class or function.
|
| 3 |
-
Your method must generate a smooth 3-axis trajectory (x(t), y(t), z(t)) that
|
| 4 |
-
passes through all the previously computed path points. A positional deviation
|
| 5 |
-
up to 0.1 m from each path point is allowed.
|
| 6 |
-
|
| 7 |
-
You should output the generated trajectory and visualize it. The figure must
|
| 8 |
-
contain three subplots showing x, y, and z, respectively, with time t (in seconds)
|
| 9 |
-
as the horizontal axis. Additionally, you must plot the original discrete path
|
| 10 |
-
points on the same figure for comparison.
|
| 11 |
-
|
| 12 |
-
You are expected to write the implementation yourself. Do NOT copy or reuse any
|
| 13 |
-
existing trajectory generation code from others. Avoid using external packages
|
| 14 |
-
beyond general scientific libraries such as numpy, math, or scipy. If you decide
|
| 15 |
-
to use additional packages, you must clearly explain the reason in your report.
|
| 16 |
-
"""
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|