Newer
Older
import numpy as np
import matplotlib.pyplot as plt
import math
from scipy.ndimage import gaussian_filter1d
def bezier(t, points):
"""Calculate Bezier curve point for parameter t and given control points."""
n = len(points) - 1
return sum(
(math.comb(n, i) * (1 - t) ** (n - i) * t ** i * points[i] for i in range(n + 1)),
np.zeros(2)
)
def smooth_path(points, N=100, alpha=0.25, sigma=1.0):
smooth_points = []
control_points = []
i = 0
while i < len(points) - 1:
if i < len(points) - 3 and is_bend(points[i:i+4]):
# Double bend (cubic bezier with two softened control points)
p0, p1, p2, p3 = np.array(points[i]), np.array(points[i+1]), np.array(points[i+2]), np.array(points[i+3])
cp1 = soften_control_point(p1, alpha, p0, p2) # Soften the first control point
cp2 = soften_control_point(p2, alpha, p1, p3) # Soften the second control point
control_points.extend([cp1, cp2]) # Collect control points for visualization
for t in np.linspace(0, 1, 20):
smooth_points.append(bezier(t, [p0, cp1, cp2, p3]))
i += 3
elif i < len(points) - 2 and is_bend(points[i:i+3]):
# Single bend (quadratic bezier with one softened control point)
p0, p1, p2 = np.array(points[i]), np.array(points[i+1]), np.array(points[i+2])
cp = soften_control_point(p1, alpha, p0, p2) # Use refined softening
control_points.append(cp) # Collect control point for visualization
for t in np.linspace(0, 1, 20):
smooth_points.append(bezier(t, [p0, cp, p2]))
i += 2
else:
# No bend, interpolate straight line
rachelmoan
committed
if points[i][0] == points[i+1][0] and points[i][1] == points[i+1][1]:
for t in range(20):
smooth_points.append(points[i])
i += 1
continue
for t in np.linspace(0, 1, 20):
smooth_points.append((1 - t) * points[i] + t * points[i + 1])
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
i += 1
# Ensure start and end points are included
smooth_points = [points[0]] + smooth_points + [points[-1]]
# Apply Gaussian smoothing to soften remaining sharp transitions
smooth_points = np.array(smooth_points)
smooth_points[:, 0] = gaussian_filter1d(smooth_points[:, 0], sigma=sigma)
smooth_points[:, 1] = gaussian_filter1d(smooth_points[:, 1], sigma=sigma)
# Downsample to N points, preserving start and end points
indices = np.linspace(0, len(smooth_points) - 1, N).astype(int)
downsampled_points = smooth_points[indices]
downsampled_points[0], downsampled_points[-1] = points[0], points[-1]
return downsampled_points, np.array(control_points)
def soften_control_point(middle_point, alpha, prev_point, next_point):
"""Move middle point along the bisector away from the 90-degree angle."""
middle_point = np.array(middle_point, dtype=np.float64)
prev_point = np.array(prev_point, dtype=np.float64)
next_point = np.array(next_point, dtype=np.float64)
# Vectors from middle point to adjacent points
vec1 = prev_point - middle_point
vec2 = next_point - middle_point
# Normalize the vectors
vec1 /= np.linalg.norm(vec1)
vec2 /= np.linalg.norm(vec2)
# Calculate the bisector direction
bisector = vec1 + vec2
bisector /= np.linalg.norm(bisector) # Normalize bisector
# Move middle point along the bisector direction
adjusted_point = middle_point + alpha * bisector
return adjusted_point
def is_bend(segment):
"""Check if three or four points form a 90-degree bend."""
if len(segment) == 3:
return np.cross(segment[1] - segment[0], segment[2] - segment[1]) != 0
elif len(segment) == 4:
return (np.cross(segment[1] - segment[0], segment[2] - segment[1]) != 0 and
np.cross(segment[2] - segment[1], segment[3] - segment[2]) != 0)
return False
def calculate_headings(path_points):
"""
Calculate headings for each segment in the path, allowing for reverse movement.
Parameters:
path_points (np.ndarray): Array of (x, y) points representing the smoothed path.
Returns:
headings (list): List of headings (in radians) for each segment in the path.
"""
headings = []
prev_heading = None
for i in range(len(path_points) - 1):
# Calculate forward and reverse headings for each segment
p1, p2 = path_points[i], path_points[i + 1]
forward_heading = np.arctan2(p2[1] - p1[1], p2[0] - p1[0])
rachelmoan
committed
# print(f"p1 = {p1}, p2 = {p2}")
if prev_heading is None:
chosen_heading = forward_heading
elif p2[0] == p1[0] and p2[1] == p1[1]:
# print("same")
chosen_heading = prev_heading
rachelmoan
committed
# Choose direction based on previous heading to minimize angle change
rachelmoan
committed
else:
reverse_heading = (forward_heading + np.pi) % (2 * np.pi)
forward_diff = np.abs((forward_heading - prev_heading + np.pi) % (2 * np.pi) - np.pi)
reverse_diff = np.abs((reverse_heading - prev_heading + np.pi) % (2 * np.pi) - np.pi)
chosen_heading = forward_heading if forward_diff <= reverse_diff else reverse_heading
rachelmoan
committed
# plot the two points and the heading
# import matplotlib.pyplot as plt
# plt.plot([p1[0], p2[0]], [p1[1], p2[1]], 'ro-') # Plot the two points
rachelmoan
committed
# # dx = 0.1 * np.cos(forward_heading)
# # dy = 0.1 * np.sin(forward_heading)
# # plt.arrow(p1[0], p1[1], dx, dy, head_width=0.01, head_length=0.1, fc='blue', ec='blue')
# dx = 0.1 * np.cos(chosen_heading)
# dy = 0.1 * np.sin(chosen_heading)
# plt.arrow(p1[0], p1[1], dx, dy, head_width=0.01, head_length=0.1, fc='green', ec='green')
# plt.show()
headings.append(chosen_heading)
prev_heading = chosen_heading
return headings
# Example points and visualization
points = np.array([
[0, 0], [0, 1], [1, 1], [2, 1], [2, 2], [2, 3], [2, 2], [2, 1], [2,0]
])
# points = np.array([
# [0, 0], [0, 1], [1, 1], [1,0], [0,0]
# ])
rachelmoan
committed
# points = np.array([
# [0, 0], [0, 1], [1, 1], [2, 1], [2, 2], [2, 3], [3, 3], [3, 2], [2,2], [2,3]
# ])
points = np.array([
[3,3], [3,5], [1,5]])
# Generate smooth curve and get control points
rachelmoan
committed
N = 40
smooth_points, control_points = smooth_path(points, N, alpha=-.5, sigma=0.8)
print(f"smooth_points = {smooth_points}")
# Example usage with a smooth path
# path_points, control_points = smooth_path(points, N=100, alpha=0.2, sigma=1.5)
headings = calculate_headings(smooth_points)
# Displaying the headings
for i, heading in enumerate(headings):
print(f"Segment {i}: Heading = {np.degrees(heading):.2f} degrees")
# Plotting
plt.figure(figsize=(8, 8))
plt.plot(smooth_points[:, 0], smooth_points[:, 1], 'b-', label="Bezier Smooth Path")
plt.scatter(points[:, 0], points[:, 1], color="purple", marker="x", s=100, label="Control Points")
# Add circles and headings
for i, (x, y) in enumerate(smooth_points):
plt.plot(x, y, 'ro') # Circle at each point
if i < len(headings):
heading = headings[i]
dx = 0.1 * np.cos(heading)
dy = 0.1 * np.sin(heading)
plt.arrow(x, y, dx, dy, head_width=0.05, head_length=0.1, fc='green', ec='green')
plt.xlabel("X")
plt.ylabel("Y")
rachelmoan
committed
plt.title("Smoothed Path")
plt.legend()
plt.grid(True)
plt.show()