-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpath_approximator.py
176 lines (118 loc) · 4.87 KB
/
path_approximator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import numpy as np
BEZIER_TOLERANCE = 0.025
CATMULL_DETAIL = 50
CIRCULAR_ARC_TOLERANCE = 0.01
length_squared = lambda x: np.inner(x, x)
def approximate_bezier(control_points):
output = []
count = len(control_points)
if count == 0:
return output
subdivision_buffer1 = np.empty([count, 2])
subdivision_buffer2 = np.empty([count * 2 - 1, 2])
to_flatten = []
free_buffers = []
to_flatten.append(control_points.copy())
left_child = subdivision_buffer2
while len(to_flatten) > 0:
parent = to_flatten.pop()
if bezier_is_flat_enough(parent):
bezierApproximate(parent, output, subdivision_buffer1, subdivision_buffer2, count)
free_buffers.append(parent)
continue
right_child = free_buffers.pop() if len(free_buffers) > 0 else np.empty([count, 2])
bezier_subdivide(parent, left_child, right_child, subdivision_buffer1, count)
for i in range(count):
parent[i] = left_child[i]
to_flatten.append(right_child)
to_flatten.append(parent)
output.append(control_points[-1].copy())
return np.vstack(output)
def approximate_catmull(control_points):
result = []
for i in range(len(control_points) - 1):
v1 = control_points[i - 1] if i > 0 else control_points[i]
v2 = control_points[i]
v3 = control_points[i + 1] if i < len(control_points) - 1 else v2 + v2 - v1
v4 = control_points[i + 2] if i < len(control_points) - 2 else v3 + v3 - v2
for c in range(CATMULL_DETAIL):
result.append(catmull_find_point(v1, v2, v3, v4, c / CATMULL_DETAIL))
result.append(catmull_find_point(v1, v2, v3, v4, (c + 1) / CATMULL_DETAIL))
return result
def approximate_circular_arc(control_points):
a = control_points[0]
b = control_points[1]
c = control_points[2]
aSq = length_squared(b - c)
bSq = length_squared(a - c)
cSq = length_squared(a - b)
if np.isclose(aSq, 0) or np.isclose(bSq, 0) or np.isclose(cSq, 0):
return []
s = aSq * (bSq + cSq - aSq)
t = bSq * (aSq + cSq - bSq)
u = cSq * (aSq + bSq - cSq)
sum = s + t + u
if np.isclose(sum, 0):
return []
centre = (s * a + t * b + u * c) / sum
dA = a - centre
dC = c - centre
r = np.linalg.norm(dA)
theta_start = np.arctan2(dA[1], dA[0])
theta_end = np.arctan2(dC[1], dC[0])
while theta_end < theta_start:
theta_end += 2 * np.pi
direction = 1
theta_range = theta_range = theta_end - theta_start
ortho_ato_c = c - a
ortho_ato_c = np.array([ortho_ato_c[1], -ortho_ato_c[0]])
if np.dot(ortho_ato_c, b - a) < 0:
direction = -direction
theta_range = 2 * np.pi - theta_range
amount_points = 2 if 2 * r <= CIRCULAR_ARC_TOLERANCE else int(max(2, np.ceil(theta_range / (2 * np.arccos(1 - CIRCULAR_ARC_TOLERANCE / r)))))
output = []
for i in range(amount_points):
fract = i / (amount_points - 1)
theta = theta_start + direction * fract * theta_range
o = np.array([np.cos(theta), np.sin(theta)]) * r
output.append(centre + o)
return output
def approximate_linear(control_points):
result = []
for c in control_points:
result.append(c.copy())
return result
def bezier_is_flat_enough(control_points):
for i in range(1, len(control_points) - 1):
p = control_points[i - 1] - 2 * control_points[i] + control_points[i + 1]
if length_squared(p) > BEZIER_TOLERANCE * BEZIER_TOLERANCE * 4:
return False
return True
def bezier_subdivide(control_points, l, r, subdivision_buffer, count):
midpoints = subdivision_buffer
for i in range(count):
midpoints[i] = control_points[i]
for i in range(count):
l[i] = midpoints[0].copy()
r[count - i - 1] = midpoints[count - i - 1]
for j in range(count - i - 1):
midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2
def bezierApproximate(control_points, output, subdivision_buffer1, subdivision_buffer2, count):
l = subdivision_buffer2
r = subdivision_buffer1
bezier_subdivide(control_points, l, r, subdivision_buffer1, count)
for i in range(count - 1):
l[count + i] = r[i + 1]
output.append(control_points[0].copy())
for i in range(1, count - 1):
index = 2 * i
p = 0.25 * (l[index - 1] + 2 * l[index] + l[index + 1])
output.append(p.copy())
def catmull_find_point(vec1, vec2, vec3, vec4, t):
t2 = t * t
t3 = t * t2
result = np.array([
0.5 * (2 * vec2[0] + (-vec1[0] + vec3[0]) * t + (2 * vec1[0] - 5 * vec2[0] + 4 * vec3[0] - vec4[0]) * t2 + (-vec1[0] + 3 * vec2[0] - 3 * vec3[0] + vec4[0]) * t3),
0.5 * (2 * vec2[1] + (-vec1[1] + vec3[1]) * t + (2 * vec1[1] - 5 * vec2[1] + 4 * vec3[1] - vec4[1]) * t2 + (-vec1[1] + 3 * vec2[1] - 3 * vec3[1] + vec4[1]) * t3)
])
return result