coordinate-system 7.0.0__cp313-cp313-win_amd64.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.
- coordinate_system/__init__.py +324 -0
- coordinate_system/complex_geometric_physics.py +519 -0
- coordinate_system/coordinate_system.cp313-win_amd64.pyd +0 -0
- coordinate_system/curve_interpolation.py +507 -0
- coordinate_system/differential_geometry.py +986 -0
- coordinate_system/spectral_geometry.py +1602 -0
- coordinate_system/u3_frame.py +885 -0
- coordinate_system/visualization.py +1069 -0
- coordinate_system-7.0.0.dist-info/LICENSE +9 -0
- coordinate_system-7.0.0.dist-info/METADATA +312 -0
- coordinate_system-7.0.0.dist-info/RECORD +13 -0
- coordinate_system-7.0.0.dist-info/WHEEL +5 -0
- coordinate_system-7.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Frame Field Curve Interpolation System
|
|
3
|
+
=======================================
|
|
4
|
+
|
|
5
|
+
Frame field spline interpolation based on C++ implementation,
|
|
6
|
+
providing geometrically continuous curve reconstruction equivalent to NURBS.
|
|
7
|
+
|
|
8
|
+
Main Features:
|
|
9
|
+
- Generate Frenet frames from discrete points
|
|
10
|
+
- Frame field interpolation with multiple parameterization methods
|
|
11
|
+
- C2-continuous high-order interpolation
|
|
12
|
+
- B-spline and frame field hybrid interpolation
|
|
13
|
+
- Curvature distribution analysis
|
|
14
|
+
|
|
15
|
+
Author: PanGuoJun
|
|
16
|
+
Date: 2025-12-01
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
import numpy as np
|
|
21
|
+
from typing import List, Tuple, Optional
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from .coordinate_system import vec3, coord3, quat
|
|
25
|
+
except ImportError:
|
|
26
|
+
import coordinate_system
|
|
27
|
+
vec3 = coordinate_system.vec3
|
|
28
|
+
coord3 = coordinate_system.coord3
|
|
29
|
+
quat = coordinate_system.quat
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ========== Utility Functions ==========
|
|
33
|
+
|
|
34
|
+
def catmull_rom(p0: vec3, p1: vec3, p2: vec3, p3: vec3, t: float) -> vec3:
|
|
35
|
+
"""
|
|
36
|
+
Catmull-Rom spline interpolation for smooth position interpolation
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
p0, p1, p2, p3: Four control points
|
|
40
|
+
t: Parameter [0, 1] between p1 and p2
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Interpolated position
|
|
44
|
+
"""
|
|
45
|
+
t2 = t * t
|
|
46
|
+
t3 = t2 * t
|
|
47
|
+
|
|
48
|
+
# Catmull-Rom basis functions
|
|
49
|
+
result = (
|
|
50
|
+
p0 * (-0.5 * t3 + 1.0 * t2 - 0.5 * t) +
|
|
51
|
+
p1 * ( 1.5 * t3 - 2.5 * t2 + 1.0) +
|
|
52
|
+
p2 * (-1.5 * t3 + 2.0 * t2 + 0.5 * t) +
|
|
53
|
+
p3 * ( 0.5 * t3 - 0.5 * t2)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def squad_interp(q0: quat, q1: quat, q2: quat, q3: quat, t: float) -> quat:
|
|
60
|
+
"""
|
|
61
|
+
SQUAD (Spherical Quadrangle) quaternion interpolation for C2-continuous rotation
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
q0, q1, q2, q3: Four quaternions
|
|
65
|
+
t: Parameter [0, 1] between q1 and q2
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Interpolated quaternion
|
|
69
|
+
"""
|
|
70
|
+
# Compute intermediate control quaternions for smooth interpolation
|
|
71
|
+
def compute_intermediate(qa: quat, qb: quat, qc: quat) -> quat:
|
|
72
|
+
"""Compute intermediate control quaternion"""
|
|
73
|
+
# Ensure shortest path
|
|
74
|
+
if qa.dot(qb) < 0:
|
|
75
|
+
qb = quat(-qb.w, -qb.x, -qb.y, -qb.z)
|
|
76
|
+
if qb.dot(qc) < 0:
|
|
77
|
+
qc = quat(-qc.w, -qc.x, -qc.y, -qc.z)
|
|
78
|
+
|
|
79
|
+
# Log map
|
|
80
|
+
inv_qb = qb.inverse()
|
|
81
|
+
log_qa_qb = (inv_qb * qa).ln()
|
|
82
|
+
log_qc_qb = (inv_qb * qc).ln()
|
|
83
|
+
|
|
84
|
+
# Intermediate control point
|
|
85
|
+
intermediate = qb * (((log_qa_qb + log_qc_qb) * -0.25).exp())
|
|
86
|
+
return intermediate
|
|
87
|
+
|
|
88
|
+
# Compute control quaternions
|
|
89
|
+
try:
|
|
90
|
+
s1 = compute_intermediate(q0, q1, q2)
|
|
91
|
+
s2 = compute_intermediate(q1, q2, q3)
|
|
92
|
+
except:
|
|
93
|
+
# Fallback to simple slerp if SQUAD fails
|
|
94
|
+
return quat.slerp(q1, q2, t)
|
|
95
|
+
|
|
96
|
+
# SQUAD interpolation: slerp(slerp(q1, q2, t), slerp(s1, s2, t), 2t(1-t))
|
|
97
|
+
slerp1 = quat.slerp(q1, q2, t)
|
|
98
|
+
slerp2 = quat.slerp(s1, s2, t)
|
|
99
|
+
h = 2.0 * t * (1.0 - t)
|
|
100
|
+
|
|
101
|
+
return quat.slerp(slerp1, slerp2, h)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ========== Core Functions ==========
|
|
105
|
+
|
|
106
|
+
def generate_frenet_frames(points: List[vec3]) -> List[coord3]:
|
|
107
|
+
"""
|
|
108
|
+
Generate Frenet-like frames from discrete point sequence
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
points: Discrete point sequence
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Corresponding frame sequence
|
|
115
|
+
"""
|
|
116
|
+
frames = []
|
|
117
|
+
n = len(points)
|
|
118
|
+
|
|
119
|
+
if n < 3:
|
|
120
|
+
# Use simple frames when not enough points
|
|
121
|
+
for i in range(n):
|
|
122
|
+
if i == 0:
|
|
123
|
+
tangent = (points[1] - points[0]).normalized()
|
|
124
|
+
elif i == n-1:
|
|
125
|
+
tangent = (points[n-1] - points[n-2]).normalized()
|
|
126
|
+
else:
|
|
127
|
+
tangent = (points[i+1] - points[i-1]).normalized()
|
|
128
|
+
|
|
129
|
+
# Construct orthogonal frame
|
|
130
|
+
UZ = vec3(0, 0, 1)
|
|
131
|
+
UY = vec3(0, 1, 0)
|
|
132
|
+
|
|
133
|
+
if abs(1.0 - abs(tangent.dot(UZ))) > 0.1:
|
|
134
|
+
normal = tangent.cross(UZ).normalized()
|
|
135
|
+
else:
|
|
136
|
+
normal = tangent.cross(UY).normalized()
|
|
137
|
+
|
|
138
|
+
binormal = tangent.cross(normal).normalized()
|
|
139
|
+
|
|
140
|
+
frame = coord3.from_axes(tangent, normal, binormal)
|
|
141
|
+
frame.o = points[i]
|
|
142
|
+
frames.append(frame)
|
|
143
|
+
|
|
144
|
+
return frames
|
|
145
|
+
|
|
146
|
+
# Calculate Frenet frame for each point
|
|
147
|
+
for i in range(n):
|
|
148
|
+
if i == 0:
|
|
149
|
+
# Start point: forward difference
|
|
150
|
+
tangent = (points[1] - points[0]).normalized()
|
|
151
|
+
v1 = points[1] - points[0]
|
|
152
|
+
v2 = points[2] - points[1]
|
|
153
|
+
binormal = v1.cross(v2).normalized()
|
|
154
|
+
|
|
155
|
+
if binormal.length() < 1e-6:
|
|
156
|
+
UZ = vec3(0, 0, 1)
|
|
157
|
+
UY = vec3(0, 1, 0)
|
|
158
|
+
if abs(1.0 - abs(tangent.dot(UZ))) > 0.1:
|
|
159
|
+
binormal = tangent.cross(UZ).normalized()
|
|
160
|
+
else:
|
|
161
|
+
binormal = tangent.cross(UY).normalized()
|
|
162
|
+
|
|
163
|
+
elif i == n-1:
|
|
164
|
+
# End point: backward difference
|
|
165
|
+
tangent = (points[n-1] - points[n-2]).normalized()
|
|
166
|
+
v1 = points[n-2] - points[n-3]
|
|
167
|
+
v2 = points[n-1] - points[n-2]
|
|
168
|
+
binormal = v1.cross(v2).normalized()
|
|
169
|
+
|
|
170
|
+
if binormal.length() < 1e-6:
|
|
171
|
+
UZ = vec3(0, 0, 1)
|
|
172
|
+
UY = vec3(0, 1, 0)
|
|
173
|
+
if abs(1.0 - abs(tangent.dot(UZ))) > 0.1:
|
|
174
|
+
binormal = tangent.cross(UZ).normalized()
|
|
175
|
+
else:
|
|
176
|
+
binormal = tangent.cross(UY).normalized()
|
|
177
|
+
else:
|
|
178
|
+
# Interior point: central difference
|
|
179
|
+
tangent = (points[i+1] - points[i-1]).normalized()
|
|
180
|
+
v1 = points[i] - points[i-1]
|
|
181
|
+
v2 = points[i+1] - points[i]
|
|
182
|
+
binormal = v1.cross(v2).normalized()
|
|
183
|
+
|
|
184
|
+
if binormal.length() < 1e-6:
|
|
185
|
+
UZ = vec3(0, 0, 1)
|
|
186
|
+
UY = vec3(0, 1, 0)
|
|
187
|
+
if abs(1.0 - abs(tangent.dot(UZ))) > 0.1:
|
|
188
|
+
binormal = tangent.cross(UZ).normalized()
|
|
189
|
+
else:
|
|
190
|
+
binormal = tangent.cross(UY).normalized()
|
|
191
|
+
|
|
192
|
+
# Re-orthogonalize
|
|
193
|
+
normal = binormal.cross(tangent).normalized()
|
|
194
|
+
binormal = tangent.cross(normal).normalized()
|
|
195
|
+
|
|
196
|
+
frame = coord3.from_axes(tangent, normal, binormal)
|
|
197
|
+
frame.o = points[i]
|
|
198
|
+
frames.append(frame)
|
|
199
|
+
|
|
200
|
+
return frames
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def frame_field_spline(frames: List[coord3], t: float, curve_type: int = 1) -> coord3:
|
|
204
|
+
"""
|
|
205
|
+
Frame field spline interpolation
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
frames: Frame sequence
|
|
209
|
+
t: Global parameter [0,1]
|
|
210
|
+
curve_type: Curve type (0=uniform, 1=chord-length, 2=centripetal)
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Interpolated frame
|
|
214
|
+
"""
|
|
215
|
+
if not frames:
|
|
216
|
+
return coord3()
|
|
217
|
+
if len(frames) == 1:
|
|
218
|
+
return frames[0]
|
|
219
|
+
|
|
220
|
+
n = len(frames)
|
|
221
|
+
|
|
222
|
+
# Compute node vector
|
|
223
|
+
nodes = [0.0]
|
|
224
|
+
|
|
225
|
+
if curve_type == 0:
|
|
226
|
+
# Uniform parameterization
|
|
227
|
+
for i in range(1, n):
|
|
228
|
+
nodes.append(i / (n - 1))
|
|
229
|
+
else:
|
|
230
|
+
# Chord-length or centripetal parameterization
|
|
231
|
+
total_length = 0.0
|
|
232
|
+
segment_lengths = []
|
|
233
|
+
|
|
234
|
+
for i in range(n - 1):
|
|
235
|
+
dist = (frames[i+1].o - frames[i].o).length()
|
|
236
|
+
if curve_type == 2:
|
|
237
|
+
dist = math.sqrt(dist)
|
|
238
|
+
segment_lengths.append(dist)
|
|
239
|
+
total_length += dist
|
|
240
|
+
|
|
241
|
+
for i in range(1, n):
|
|
242
|
+
nodes.append(nodes[i-1] + segment_lengths[i-1] / total_length)
|
|
243
|
+
|
|
244
|
+
# Find the segment containing parameter t
|
|
245
|
+
segment_index = 0
|
|
246
|
+
for i in range(n - 1):
|
|
247
|
+
if nodes[i] <= t <= nodes[i+1]:
|
|
248
|
+
segment_index = i
|
|
249
|
+
break
|
|
250
|
+
if t >= nodes[n-1]:
|
|
251
|
+
segment_index = n - 2
|
|
252
|
+
|
|
253
|
+
# Local parameter
|
|
254
|
+
local_t = (t - nodes[segment_index]) / (nodes[segment_index+1] - nodes[segment_index])
|
|
255
|
+
local_t = max(0.0, min(1.0, local_t))
|
|
256
|
+
|
|
257
|
+
# SE(3) interpolation using slerp
|
|
258
|
+
return coord3.slerp(frames[segment_index], frames[segment_index+1], local_t)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def frame_field_spline_c2(frames: List[coord3], t: float) -> coord3:
|
|
262
|
+
"""
|
|
263
|
+
C2-continuous frame field spline with high-order continuity
|
|
264
|
+
Uses Catmull-Rom for position and SQUAD for rotation
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
frames: Frame sequence (requires at least 4 frames)
|
|
268
|
+
t: Global parameter [0,1]
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Interpolated frame with C2 continuity
|
|
272
|
+
"""
|
|
273
|
+
if len(frames) < 4:
|
|
274
|
+
# Fallback to C1 continuity when not enough frames
|
|
275
|
+
return frame_field_spline(frames, t, 1)
|
|
276
|
+
|
|
277
|
+
n = len(frames)
|
|
278
|
+
|
|
279
|
+
# Compute node vector using chord-length parameterization
|
|
280
|
+
nodes = [0.0]
|
|
281
|
+
total_length = 0.0
|
|
282
|
+
segment_lengths = []
|
|
283
|
+
|
|
284
|
+
for i in range(n - 1):
|
|
285
|
+
dist = (frames[i+1].o - frames[i].o).length()
|
|
286
|
+
segment_lengths.append(dist)
|
|
287
|
+
total_length += dist
|
|
288
|
+
|
|
289
|
+
for i in range(1, n):
|
|
290
|
+
nodes.append(nodes[i-1] + segment_lengths[i-1] / total_length)
|
|
291
|
+
|
|
292
|
+
# Find the segment containing parameter t
|
|
293
|
+
i = 0
|
|
294
|
+
for i in range(n - 1):
|
|
295
|
+
if t >= nodes[i] and t <= nodes[i+1]:
|
|
296
|
+
break
|
|
297
|
+
if t >= nodes[n-1]:
|
|
298
|
+
i = n - 2
|
|
299
|
+
|
|
300
|
+
# Local parameter
|
|
301
|
+
local_t = (t - nodes[i]) / (nodes[i+1] - nodes[i])
|
|
302
|
+
local_t = max(0.0, min(1.0, local_t))
|
|
303
|
+
|
|
304
|
+
# Get 4 control frames for interpolation
|
|
305
|
+
i0 = max(0, i - 1)
|
|
306
|
+
i1 = i
|
|
307
|
+
i2 = i + 1
|
|
308
|
+
i3 = min(n - 1, i + 2)
|
|
309
|
+
|
|
310
|
+
# Position using Catmull-Rom spline
|
|
311
|
+
pos = catmull_rom(frames[i0].o, frames[i1].o, frames[i2].o, frames[i3].o, local_t)
|
|
312
|
+
|
|
313
|
+
# Rotation using SQUAD interpolation
|
|
314
|
+
q0 = frames[i0].Q()
|
|
315
|
+
q1 = frames[i1].Q()
|
|
316
|
+
q2 = frames[i2].Q()
|
|
317
|
+
q3 = frames[i3].Q()
|
|
318
|
+
|
|
319
|
+
rot = squad_interp(q0, q1, q2, q3, local_t)
|
|
320
|
+
|
|
321
|
+
return coord3(pos, rot)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def reconstruct_curve_from_polygon(
|
|
325
|
+
polygon: List[vec3],
|
|
326
|
+
samples: int = 100,
|
|
327
|
+
curve_type: int = 1
|
|
328
|
+
) -> List[vec3]:
|
|
329
|
+
"""
|
|
330
|
+
Reconstruct curve from polygon vertices using frame field spline
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
polygon: Input polygon vertices
|
|
334
|
+
samples: Number of sample points
|
|
335
|
+
curve_type: Curve type (0=uniform, 1=chord-length, 2=centripetal, 3=C2-continuous)
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Reconstructed curve point sequence
|
|
339
|
+
"""
|
|
340
|
+
if len(polygon) < 2:
|
|
341
|
+
return []
|
|
342
|
+
|
|
343
|
+
# Generate frame field from polygon vertices
|
|
344
|
+
frames = generate_frenet_frames(polygon)
|
|
345
|
+
|
|
346
|
+
# Interpolate and sample
|
|
347
|
+
curve_points = []
|
|
348
|
+
for i in range(samples):
|
|
349
|
+
t = i / (samples - 1)
|
|
350
|
+
|
|
351
|
+
if curve_type == 3:
|
|
352
|
+
# C2-continuous interpolation
|
|
353
|
+
interpolated_frame = frame_field_spline_c2(frames, t)
|
|
354
|
+
else:
|
|
355
|
+
# Standard interpolation
|
|
356
|
+
interpolated_frame = frame_field_spline(frames, t, curve_type)
|
|
357
|
+
|
|
358
|
+
curve_points.append(interpolated_frame.o)
|
|
359
|
+
|
|
360
|
+
return curve_points
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def compute_curvature_profile(curve: List[vec3]) -> List[float]:
|
|
364
|
+
"""
|
|
365
|
+
Compute curvature distribution of reconstructed curve
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
curve: Curve point sequence
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Curvature value sequence
|
|
372
|
+
"""
|
|
373
|
+
if len(curve) < 3:
|
|
374
|
+
return []
|
|
375
|
+
|
|
376
|
+
curvatures = []
|
|
377
|
+
|
|
378
|
+
for i in range(1, len(curve) - 1):
|
|
379
|
+
v1 = curve[i] - curve[i-1]
|
|
380
|
+
v2 = curve[i+1] - curve[i]
|
|
381
|
+
|
|
382
|
+
# Calculate angle change
|
|
383
|
+
dot_product = v1.normalized().dot(v2.normalized())
|
|
384
|
+
dot_product = max(-1.0, min(1.0, dot_product))
|
|
385
|
+
delta_angle = math.acos(dot_product)
|
|
386
|
+
|
|
387
|
+
avg_length = (v1.length() + v2.length()) * 0.5
|
|
388
|
+
|
|
389
|
+
# Curvature = angle change rate / arc length
|
|
390
|
+
curvature = delta_angle / (avg_length + 1e-6)
|
|
391
|
+
curvatures.append(curvature)
|
|
392
|
+
|
|
393
|
+
# Use boundary values for endpoints
|
|
394
|
+
if curvatures:
|
|
395
|
+
curvatures.insert(0, curvatures[0])
|
|
396
|
+
curvatures.append(curvatures[-1])
|
|
397
|
+
|
|
398
|
+
return curvatures
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# ========== Main Class ==========
|
|
402
|
+
|
|
403
|
+
class InterpolatedCurve:
|
|
404
|
+
"""
|
|
405
|
+
Interpolated curve class - Encapsulates frame field interpolation functionality
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
def __init__(self, control_points: List[vec3], curve_type: int = 1, c2_continuity: bool = False):
|
|
409
|
+
"""
|
|
410
|
+
Initialize interpolated curve
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
control_points: Control point list
|
|
414
|
+
curve_type: Curve type (0=uniform, 1=chord-length, 2=centripetal)
|
|
415
|
+
c2_continuity: Enable C2-continuous interpolation (requires 4+ points)
|
|
416
|
+
"""
|
|
417
|
+
self.control_points = control_points
|
|
418
|
+
self.curve_type = curve_type
|
|
419
|
+
self.c2_continuity = c2_continuity
|
|
420
|
+
self.frames = generate_frenet_frames(control_points)
|
|
421
|
+
|
|
422
|
+
def evaluate(self, t: float) -> vec3:
|
|
423
|
+
"""
|
|
424
|
+
Evaluate position at parameter t
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
t: Parameter value [0, 1]
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Point on curve
|
|
431
|
+
"""
|
|
432
|
+
frame = self.evaluate_frame(t)
|
|
433
|
+
return frame.o
|
|
434
|
+
|
|
435
|
+
def evaluate_frame(self, t: float) -> coord3:
|
|
436
|
+
"""
|
|
437
|
+
Evaluate complete frame at parameter t
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
t: Parameter value [0, 1]
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
Frame on curve
|
|
444
|
+
"""
|
|
445
|
+
if self.c2_continuity and len(self.frames) >= 4:
|
|
446
|
+
return frame_field_spline_c2(self.frames, t)
|
|
447
|
+
else:
|
|
448
|
+
return frame_field_spline(self.frames, t, self.curve_type)
|
|
449
|
+
|
|
450
|
+
def sample(self, num_samples: int = 100) -> List[vec3]:
|
|
451
|
+
"""
|
|
452
|
+
Sample curve points
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
num_samples: Number of sample points
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
Sampled point list
|
|
459
|
+
"""
|
|
460
|
+
curve_points = []
|
|
461
|
+
for i in range(num_samples):
|
|
462
|
+
t = i / (num_samples - 1)
|
|
463
|
+
curve_points.append(self.evaluate(t))
|
|
464
|
+
return curve_points
|
|
465
|
+
|
|
466
|
+
def sample_frames(self, num_samples: int = 100) -> List[coord3]:
|
|
467
|
+
"""
|
|
468
|
+
Sample frames
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
num_samples: Number of sample points
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Frame list
|
|
475
|
+
"""
|
|
476
|
+
sampled_frames = []
|
|
477
|
+
for i in range(num_samples):
|
|
478
|
+
t = i / (num_samples - 1)
|
|
479
|
+
sampled_frames.append(self.evaluate_frame(t))
|
|
480
|
+
return sampled_frames
|
|
481
|
+
|
|
482
|
+
def get_curvature_profile(self, num_samples: int = 100) -> List[float]:
|
|
483
|
+
"""
|
|
484
|
+
Get curvature distribution
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
num_samples: Number of sample points
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
Curvature value list
|
|
491
|
+
"""
|
|
492
|
+
curve_points = self.sample(num_samples)
|
|
493
|
+
return compute_curvature_profile(curve_points)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
# ========== Export ==========
|
|
497
|
+
|
|
498
|
+
__all__ = [
|
|
499
|
+
'generate_frenet_frames',
|
|
500
|
+
'frame_field_spline',
|
|
501
|
+
'frame_field_spline_c2',
|
|
502
|
+
'reconstruct_curve_from_polygon',
|
|
503
|
+
'compute_curvature_profile',
|
|
504
|
+
'InterpolatedCurve',
|
|
505
|
+
'catmull_rom',
|
|
506
|
+
'squad_interp',
|
|
507
|
+
]
|