Skip to content

Commit 75d6880

Browse files
committed
Add Frenet-Serret frame computation and replace placeholder NURBS algorithms:
- Introduce `frenet_serret_frame_from_ders` for parametric curve frames based on derivatives. - Replace placeholder methods in `_nurbs_interp.py` with fully implemented NURBS basis and knot span routines. - Extend `extract_surface_boundaries` and `extract_isocurve` to support `NURBSSurfaceTuple`.
1 parent 6cf543f commit 75d6880

3 files changed

Lines changed: 115 additions & 90 deletions

File tree

mmcore/geom/_nurbs_interp.py

Lines changed: 86 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -521,23 +521,23 @@ def find_span(n: int, p: int, u: float, U: NDArray[np.float64]) -> int:
521521
Finds the knot span index for a given parameter u.
522522
Based on Algorithm A2.1 from 'The NURBS Book'.
523523
"""
524-
#if u >= U[n + 1]:
525-
# return n
526-
#
527-
## Binary search
528-
#low = p
529-
#high = n + 1
530-
#mid = (low + high) // 2
531-
#
532-
#while (u < U[mid]) or (u >= U[mid + 1]):
533-
# if u < U[mid]:
534-
# high = mid
535-
# else:
536-
# low = mid
537-
# mid = (low + high) // 2
538-
#
539-
#return mid
540-
return find_span(n, p, u, U)
524+
if u >= U[n + 1]:
525+
return n
526+
527+
# Binary search
528+
low = p
529+
high = n + 1
530+
mid = (low + high) // 2
531+
532+
while (u < U[mid]) or (u >= U[mid + 1]):
533+
if u < U[mid]:
534+
high = mid
535+
else:
536+
low = mid
537+
mid = (low + high) // 2
538+
539+
return mid
540+
541541

542542

543543
def ders_basis_funs(span_i: int, u: float, p: int, U: NDArray[np.float64], n_ders: int = 1) -> NDArray[np.float64]:
@@ -548,75 +548,75 @@ def ders_basis_funs(span_i: int, u: float, p: int, U: NDArray[np.float64], n_der
548548
Based on Algorithm A2.3 from 'The NURBS Book'.
549549
"""
550550

551-
#ders = np.zeros((n_ders + 1, p + 1))
552-
#ndu = np.zeros((p + 1, p + 1))
553-
#left = np.zeros(p + 1)
554-
#right = np.zeros(p + 1)
555-
#
556-
#ndu[0, 0] = 1.0
557-
#
558-
## Compute basis functions (and store terms for derivatives)
559-
#for j in range(1, p + 1):
560-
# left[j] = u - U[span_i + 1 - j]
561-
# right[j] = U[span_i + j] - u
562-
# saved = 0.0
563-
# for r in range(j):
564-
# # Lower triangle
565-
# ndu[j, r] = right[r + 1] + left[j - r]
566-
# temp = ndu[r, j - 1] / ndu[j, r]
567-
# # Upper triangle
568-
# ndu[r, j] = saved + right[r + 1] * temp
569-
# saved = left[j - r] * temp
570-
# ndu[j, j] = saved
571-
#
572-
## Load the basis functions
573-
#for j in range(p + 1):
574-
# ders[0, j] = ndu[j, p]
575-
#
576-
## Compute derivatives
577-
#a = np.zeros((2, p + 1)) # Only need rows 0 and 1 for recursion locally
578-
#for r in range(0, p + 1):
579-
# s1 = 0
580-
# s2 = 1
581-
# a[0, 0] = 1.0
582-
#
583-
# # Loop to compute k-th derivative
584-
# for k in range(1, n_ders + 1):
585-
# d = 0.0
586-
# rk = r - k
587-
# pk = p - k
588-
#
589-
# if r >= k:
590-
# a[s2, 0] = a[s1, 0] / ndu[pk + 1, rk]
591-
# d = a[s2, 0] * ndu[rk, pk]
592-
#
593-
# j1 = 1 if rk >= -1 else -rk
594-
# j2 = k - 1 if (r - 1) <= pk else p - r
595-
#
596-
# for j in range(j1, j2 + 1):
597-
# a[s2, j] = (a[s1, j] - a[s1, j - 1]) / ndu[pk + 1, rk + j]
598-
# d += a[s2, j] * ndu[rk + j, pk]
599-
#
600-
# if r <= pk:
601-
# a[s2, k] = -a[s1, k - 1] / ndu[pk + 1, r]
602-
# d += a[s2, k] * ndu[r, pk]
603-
#
604-
# ders[k, r] = d
605-
#
606-
# # Swap rows
607-
# j = s1
608-
# s1 = s2
609-
# s2 = j
610-
#
611-
## Multiply by correct factors for derivatives
612-
#r = p
613-
#for k in range(1, n_ders + 1):
614-
# for j in range(p + 1):
615-
# ders[k, j] *= r
616-
# r *= (p - k)
617-
#
618-
#return ders
619-
return _ders_basis_funs(span_i, u, p, U, n_ders)
551+
ders = np.zeros((n_ders + 1, p + 1))
552+
ndu = np.zeros((p + 1, p + 1))
553+
left = np.zeros(p + 1)
554+
right = np.zeros(p + 1)
555+
556+
ndu[0, 0] = 1.0
557+
558+
# Compute basis functions (and store terms for derivatives)
559+
for j in range(1, p + 1):
560+
left[j] = u - U[span_i + 1 - j]
561+
right[j] = U[span_i + j] - u
562+
saved = 0.0
563+
for r in range(j):
564+
# Lower triangle
565+
ndu[j, r] = right[r + 1] + left[j - r]
566+
temp = ndu[r, j - 1] / ndu[j, r]
567+
# Upper triangle
568+
ndu[r, j] = saved + right[r + 1] * temp
569+
saved = left[j - r] * temp
570+
ndu[j, j] = saved
571+
572+
# Load the basis functions
573+
for j in range(p + 1):
574+
ders[0, j] = ndu[j, p]
575+
576+
# Compute derivatives
577+
a = np.zeros((2, p + 1)) # Only need rows 0 and 1 for recursion locally
578+
for r in range(0, p + 1):
579+
s1 = 0
580+
s2 = 1
581+
a[0, 0] = 1.0
582+
583+
# Loop to compute k-th derivative
584+
for k in range(1, n_ders + 1):
585+
d = 0.0
586+
rk = r - k
587+
pk = p - k
588+
589+
if r >= k:
590+
a[s2, 0] = a[s1, 0] / ndu[pk + 1, rk]
591+
d = a[s2, 0] * ndu[rk, pk]
592+
593+
j1 = 1 if rk >= -1 else -rk
594+
j2 = k - 1 if (r - 1) <= pk else p - r
595+
596+
for j in range(j1, j2 + 1):
597+
a[s2, j] = (a[s1, j] - a[s1, j - 1]) / ndu[pk + 1, rk + j]
598+
d += a[s2, j] * ndu[rk + j, pk]
599+
600+
if r <= pk:
601+
a[s2, k] = -a[s1, k - 1] / ndu[pk + 1, r]
602+
d += a[s2, k] * ndu[r, pk]
603+
604+
ders[k, r] = d
605+
606+
# Swap rows
607+
j = s1
608+
s1 = s2
609+
s2 = j
610+
611+
# Multiply by correct factors for derivatives
612+
r = p
613+
for k in range(1, n_ders + 1):
614+
for j in range(p + 1):
615+
ders[k, j] *= r
616+
r *= (p - k)
617+
618+
return ders
619+
620620

621621

622622
def generate_hermite_knots(params: NDArray[np.float64], p: int) -> NDArray[np.float64]:

mmcore/geom/nurbs_iso.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from mmcore.geom._nurbs_eval import NURBSCurveTuple,NURBSSurfaceTuple,_surface_interval,to_homogeneous_2d,from_homogeneous_2d,to_homogeneous_1d,from_homogeneous_1d
1010

1111

12-
def extract_surface_boundaries(surface: NURBSSurface) -> List[NURBSCurve]:
12+
def extract_surface_boundaries(surface: NURBSSurface|NURBSSurfaceTuple) -> List[NURBSCurve]|List[NURBSCurveTuple]:
13+
1314
"""
1415
Extract the four boundary curves of a NURBS surface.
1516
@@ -20,6 +21,9 @@ def extract_surface_boundaries(surface: NURBSSurface) -> List[NURBSCurve]:
2021
List[NURBSCurve]: List of four boundary curves in order:
2122
[u=0 curve, u=1 curve, v=0 curve, v=1 curve]
2223
"""
24+
if isinstance(surface, NURBSSurfaceTuple):
25+
return extract_surface_boundaries_tuple(surface)
26+
2327
(u_min, u_max), (v_min, v_max) = surface.interval()
2428

2529
# Extract iso-curves at the boundaries
@@ -34,8 +38,8 @@ def extract_surface_boundaries(surface: NURBSSurface) -> List[NURBSCurve]:
3438
from mmcore.geom._nurbs_eval import _nurbs_to_tuple,_tuple_to_nurbs
3539

3640
def extract_isocurve(
37-
surface: NURBSSurface, param: float, direction: str = "u"
38-
) -> NURBSCurve:
41+
surface: NURBSSurface|NURBSSurfaceTuple, param: float, direction: str = "u"
42+
) -> NURBSCurve|NURBSCurveTuple:
3943
"""
4044
Extract an isocurve from a NURBS surface at a given parameter in the u or v direction.
4145
Args:
@@ -47,7 +51,8 @@ def extract_isocurve(
4751
Raises:
4852
ValueError: If the direction is not 'u' or 'v', or if the param is out of range.
4953
"""
50-
54+
if isinstance(surface, NURBSSurfaceTuple):
55+
return _extract_isocurve_tuple(surface, param, direction)
5156
if direction not in ["u", "v"]:
5257
raise ValueError("Direction must be either 'u' or 'v'.")
5358
st = _nurbs_to_tuple(surface)

mmcore/numeric/algorithms/tnb.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,27 @@ def frenet_serret_frame(curve, curve_prime, curve_double_prime, t):
3333
# Compute the binormal vector B(t)
3434
B = scalar_cross(T, N)
3535
return np.array([r_t, T, N, B])
36+
def frenet_serret_frame_from_ders(c1, c2):
37+
"""
38+
Calculate the Frenet-Serret frame for a given parametric curve and its derivatives.
39+
Compute the Frenet-Serret frame for a given curve at a specified parameter value.
40+
3641
42+
"""
43+
# Compute the derivatives
44+
45+
r_prime_t = c1
46+
r_double_prime_t = c2
47+
# Compute the tangent vector T(t) and normalize it
48+
r_prime_norm=scalar_norm(r_prime_t)
49+
T = r_prime_t / r_prime_norm
50+
# Compute the normal vector N(t)
51+
T_prime = (r_double_prime_t - scalar_dot(r_double_prime_t, T) * T) / r_prime_norm
52+
53+
N = T_prime / scalar_norm(T_prime)
54+
# Compute the binormal vector B(t)
55+
B = np.cross(T, N)
56+
return np.array([T, N, B])
3757

3858
if __name__ == '__main__':
3959
pts = np.array(

0 commit comments

Comments
 (0)