1use super::functions::{solve_3x_systems, solve_linear_system_row};
6
7#[derive(Debug, Clone)]
11pub struct NurbsCurve {
12 pub basis: BsplineBasis,
14 pub control_points: Vec<[f64; 3]>,
16 pub weights: Vec<f64>,
18}
19impl NurbsCurve {
20 pub fn new(
22 degree: usize,
23 knot_vector: KnotVector,
24 control_points: Vec<[f64; 3]>,
25 weights: Vec<f64>,
26 ) -> Self {
27 let n_ctrl = control_points.len();
28 assert_eq!(
29 weights.len(),
30 n_ctrl,
31 "weights and control points must have the same length"
32 );
33 let basis = BsplineBasis::new(degree, knot_vector, n_ctrl);
34 Self {
35 basis,
36 control_points,
37 weights,
38 }
39 }
40 pub fn circle_arc(center: [f64; 3], radius: f64, start_angle: f64, end_angle: f64) -> Self {
45 let sweep = end_angle - start_angle;
46 let n_arcs = if sweep.abs() <= std::f64::consts::FRAC_PI_2 {
47 1
48 } else if sweep.abs() <= std::f64::consts::PI {
49 2
50 } else if sweep.abs() <= 3.0 * std::f64::consts::FRAC_PI_2 {
51 3
52 } else {
53 4
54 };
55 let n_pts = 2 * n_arcs + 1;
56 let mut ctrl_pts = Vec::with_capacity(n_pts);
57 let mut weights = Vec::with_capacity(n_pts);
58 let d_theta = sweep / n_arcs as f64;
59 let w1 = (d_theta / 2.0).cos();
60 let mut angle = start_angle;
61 ctrl_pts.push([
62 center[0] + radius * angle.cos(),
63 center[1] + radius * angle.sin(),
64 center[2],
65 ]);
66 weights.push(1.0);
67 for _i in 0..n_arcs {
68 let a_mid = angle + d_theta * 0.5;
69 let a_end = angle + d_theta;
70 ctrl_pts.push([
71 center[0] + radius / w1 * a_mid.cos(),
72 center[1] + radius / w1 * a_mid.sin(),
73 center[2],
74 ]);
75 weights.push(w1);
76 ctrl_pts.push([
77 center[0] + radius * a_end.cos(),
78 center[1] + radius * a_end.sin(),
79 center[2],
80 ]);
81 weights.push(1.0);
82 angle = a_end;
83 }
84 let mut knots = vec![0.0_f64; 3];
85 let knot_step = 1.0 / n_arcs as f64;
86 for i in 1..n_arcs {
87 knots.push(i as f64 * knot_step);
88 knots.push(i as f64 * knot_step);
89 }
90 knots.extend(std::iter::repeat_n(1.0_f64, 3));
91 let kv = KnotVector::new(knots);
92 Self::new(2, kv, ctrl_pts, weights)
93 }
94 pub fn eval(&self, t: f64) -> [f64; 3] {
96 let (span, nonzero) = self.basis.eval_nonzero(t);
97 let p = self.basis.degree;
98 let n_ctrl = self.control_points.len();
99 let mut num = [0.0_f64; 3];
100 let mut denom = 0.0_f64;
101 for (k, &n_val) in nonzero[..=p].iter().enumerate() {
102 let idx = span + k - p;
103 if idx >= n_ctrl {
104 continue;
105 }
106 let wn = self.weights[idx] * n_val;
107 denom += wn;
108 let cp = self.control_points[idx];
109 for d in 0..3 {
110 num[d] += wn * cp[d];
111 }
112 }
113 if denom.abs() < 1e-30 {
114 return self.control_points[0];
115 }
116 [num[0] / denom, num[1] / denom, num[2] / denom]
117 }
118 pub fn eval_deriv(&self, t: f64) -> [f64; 3] {
122 let h = 1e-7;
123 let t0 = (t - h).max(self.basis.knot_vector.knots[0]);
124 let t1 = (t + h).min(*self.basis.knot_vector.knots.last().unwrap_or(&1.0));
125 let p0 = self.eval(t0);
126 let p1 = self.eval(t1);
127 let dt = t1 - t0;
128 [
129 (p1[0] - p0[0]) / dt,
130 (p1[1] - p0[1]) / dt,
131 (p1[2] - p0[2]) / dt,
132 ]
133 }
134 pub fn update_control_point(&mut self, i: usize, point: [f64; 3], weight: f64) {
136 self.control_points[i] = point;
137 self.weights[i] = weight;
138 }
139 pub fn arc_length(&self, t_start: f64, t_end: f64, n_intervals: usize) -> f64 {
141 let h = (t_end - t_start) / n_intervals as f64;
142 let gp = [-0.906180, -0.538469, 0.0, 0.538469, 0.906180];
143 let gw = [0.236927, 0.478629, 0.568889, 0.478629, 0.236927];
144 let mut length = 0.0;
145 for i in 0..n_intervals {
146 let a = t_start + i as f64 * h;
147 let b = a + h;
148 let mid = (a + b) * 0.5;
149 let half = (b - a) * 0.5;
150 for (&xi, &wi) in gp.iter().zip(gw.iter()) {
151 let t = mid + half * xi;
152 let dt = self.eval_deriv(t);
153 let speed = (dt[0] * dt[0] + dt[1] * dt[1] + dt[2] * dt[2]).sqrt();
154 length += wi * half * speed;
155 }
156 }
157 length
158 }
159 pub fn curvature(&self, t: f64) -> f64 {
161 let h = 1e-5;
162 let t_min = self.basis.knot_vector.knots[0];
163 let t_max = *self.basis.knot_vector.knots.last().unwrap_or(&1.0);
164 let t0 = (t - h).max(t_min);
165 let t1 = (t + h).min(t_max);
166 let dt = t1 - t0;
167 if dt < 1e-14 {
168 return 0.0;
169 }
170 let d1 = self.eval_deriv(t);
171 let p0 = self.eval(t0);
172 let p1 = self.eval(t);
173 let p2 = self.eval(t1);
174 let d2 = [
175 (p2[0] - 2.0 * p1[0] + p0[0]) / (h * h),
176 (p2[1] - 2.0 * p1[1] + p0[1]) / (h * h),
177 (p2[2] - 2.0 * p1[2] + p0[2]) / (h * h),
178 ];
179 let cross = [
180 d1[1] * d2[2] - d1[2] * d2[1],
181 d1[2] * d2[0] - d1[0] * d2[2],
182 d1[0] * d2[1] - d1[1] * d2[0],
183 ];
184 let cross_mag = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
185 let d1_mag = (d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]).sqrt();
186 if d1_mag < 1e-14 {
187 0.0
188 } else {
189 cross_mag / d1_mag.powi(3)
190 }
191 }
192 pub fn sample(&self, n_points: usize) -> Vec<[f64; 3]> {
194 let (t0, t1) = self.basis.knot_vector.domain();
195 (0..n_points)
196 .map(|i| {
197 let t = t0 + (t1 - t0) * i as f64 / (n_points - 1).max(1) as f64;
198 self.eval(t)
199 })
200 .collect()
201 }
202 pub fn b_spline_basis(i: usize, p: usize, t: f64, knots: &[f64]) -> f64 {
204 if p == 0 {
205 let at_end = (t - knots[knots.len() - 1]).abs() < 1e-14
206 && (knots[i + 1] - knots[knots.len() - 1]).abs() < 1e-14;
207 return if (knots[i] <= t && t < knots[i + 1]) || at_end {
208 1.0
209 } else {
210 0.0
211 };
212 }
213 let left_denom = knots[i + p] - knots[i];
214 let left = if left_denom.abs() > 1e-14 {
215 (t - knots[i]) / left_denom * Self::b_spline_basis(i, p - 1, t, knots)
216 } else {
217 0.0
218 };
219 let right_denom = knots[i + p + 1] - knots[i + 1];
220 let right = if right_denom.abs() > 1e-14 {
221 (knots[i + p + 1] - t) / right_denom * Self::b_spline_basis(i + 1, p - 1, t, knots)
222 } else {
223 0.0
224 };
225 left + right
226 }
227 pub fn from_points_and_weights(
231 points: Vec<[f64; 3]>,
232 weights: Vec<f64>,
233 degree: usize,
234 ) -> Self {
235 let n = points.len();
236 let m = n + degree + 1;
237 let mut knots = vec![0.0_f64; degree + 1];
238 let interior = m - 2 * (degree + 1);
239 for i in 1..=interior {
240 knots.push(i as f64 / (interior + 1) as f64);
241 }
242 knots.extend(vec![1.0_f64; degree + 1]);
243 let kv = KnotVector::new(knots);
244 Self::new(degree, kv, points, weights)
245 }
246 pub fn circle(radius: f64) -> Self {
248 let w = std::f64::consts::FRAC_1_SQRT_2;
249 let r = radius;
250 let ctrl = vec![
251 [r, 0.0, 0.0],
252 [r, r, 0.0],
253 [0.0, r, 0.0],
254 [-r, r, 0.0],
255 [-r, 0.0, 0.0],
256 [-r, -r, 0.0],
257 [0.0, -r, 0.0],
258 [r, -r, 0.0],
259 [r, 0.0, 0.0],
260 ];
261 let weights = vec![1.0, w, 1.0, w, 1.0, w, 1.0, w, 1.0];
262 let knots = KnotVector::new(vec![
263 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0,
264 ]);
265 Self::new(2, knots, ctrl, weights)
266 }
267 pub fn compute_arc_length_reparametrize(
270 &self,
271 n_intervals: usize,
272 n_output: usize,
273 ) -> Vec<[f64; 3]> {
274 let (arc_lengths, params) = self.arc_length_table(n_intervals);
275 let total_len = *arc_lengths.last().unwrap_or(&0.0);
276 if total_len < 1e-14 || n_output == 0 {
277 return Vec::new();
278 }
279 let mut result = Vec::with_capacity(n_output);
280 for k in 0..n_output {
281 let target = total_len * k as f64 / (n_output - 1).max(1) as f64;
282 let idx = arc_lengths
283 .partition_point(|&s| s < target)
284 .min(arc_lengths.len() - 1);
285 let t = if idx == 0 {
286 params[0]
287 } else if (arc_lengths[idx] - arc_lengths[idx - 1]).abs() < 1e-14 {
288 params[idx]
289 } else {
290 let frac =
291 (target - arc_lengths[idx - 1]) / (arc_lengths[idx] - arc_lengths[idx - 1]);
292 params[idx - 1] + frac * (params[idx] - params[idx - 1])
293 };
294 result.push(self.eval(t));
295 }
296 result
297 }
298 pub fn arc_length_table(&self, n: usize) -> (Vec<f64>, Vec<f64>) {
302 let (t0, t1) = self.basis.knot_vector.domain();
303 let mut arc_lengths = Vec::with_capacity(n + 1);
304 let mut params = Vec::with_capacity(n + 1);
305 let mut cumulative = 0.0_f64;
306 let mut prev_pt = self.eval(t0);
307 arc_lengths.push(0.0);
308 params.push(t0);
309 for i in 1..=n {
310 let t = t0 + (t1 - t0) * i as f64 / n as f64;
311 let pt = self.eval(t);
312 let dx = pt[0] - prev_pt[0];
313 let dy = pt[1] - prev_pt[1];
314 let dz = pt[2] - prev_pt[2];
315 cumulative += (dx * dx + dy * dy + dz * dz).sqrt();
316 arc_lengths.push(cumulative);
317 params.push(t);
318 prev_pt = pt;
319 }
320 (arc_lengths, params)
321 }
322}
323#[derive(Debug, Clone)]
331pub struct BsplineBasis {
332 pub degree: usize,
334 pub knot_vector: KnotVector,
336 pub n_ctrl: usize,
338}
339impl BsplineBasis {
340 pub fn new(degree: usize, knot_vector: KnotVector, n_ctrl: usize) -> Self {
342 Self {
343 degree,
344 knot_vector,
345 n_ctrl,
346 }
347 }
348 pub fn clamped(degree: usize, n_ctrl: usize) -> Self {
350 let kv = KnotVector::clamped_uniform(n_ctrl, degree);
351 Self {
352 degree,
353 knot_vector: kv,
354 n_ctrl,
355 }
356 }
357 pub fn eval_nonzero(&self, t: f64) -> (usize, Vec<f64>) {
361 let p = self.degree;
362 let knots = &self.knot_vector.knots;
363 let span = self.knot_vector.find_span(t, p, self.n_ctrl);
364 let mut n = vec![0.0_f64; p + 1];
365 let mut left = vec![0.0_f64; p + 1];
366 let mut right = vec![0.0_f64; p + 1];
367 n[0] = 1.0;
368 for j in 1..=p {
369 left[j] = t - knots[span + 1 - j];
370 right[j] = knots[span + j] - t;
371 let mut saved = 0.0;
372 for r in 0..j {
373 let temp = n[r] / (right[r + 1] + left[j - r]);
374 n[r] = saved + right[r + 1] * temp;
375 saved = left[j - r] * temp;
376 }
377 n[j] = saved;
378 }
379 (span, n)
380 }
381 pub fn eval_all(&self, t: f64) -> Vec<f64> {
385 let (span, nonzero) = self.eval_nonzero(t);
386 let p = self.degree;
387 let mut result = vec![0.0_f64; self.n_ctrl];
388 for k in 0..=p {
389 if span + k >= p && span + k - p < self.n_ctrl {
390 result[span + k - p] = nonzero[k];
391 }
392 }
393 result
394 }
395 pub fn eval(&self, i: usize, t: f64) -> f64 {
397 let all = self.eval_all(t);
398 if i < all.len() { all[i] } else { 0.0 }
399 }
400 pub fn eval_nonzero_derivs(&self, t: f64, deriv: usize) -> (usize, Vec<Vec<f64>>) {
405 let p = self.degree;
406 let knots = &self.knot_vector.knots;
407 let span = self.knot_vector.find_span(t, p, self.n_ctrl);
408 let d = deriv.min(p);
409 let mut ndu = vec![vec![0.0_f64; p + 1]; p + 1];
410 let mut left = vec![0.0_f64; p + 1];
411 let mut right = vec![0.0_f64; p + 1];
412 ndu[0][0] = 1.0;
413 for j in 1..=p {
414 left[j] = t - knots[span + 1 - j];
415 right[j] = knots[span + j] - t;
416 let mut saved = 0.0;
417 for r in 0..j {
418 ndu[j][r] = right[r + 1] + left[j - r];
419 let temp = ndu[r][j - 1] / ndu[j][r];
420 ndu[r][j] = saved + right[r + 1] * temp;
421 saved = left[j - r] * temp;
422 }
423 ndu[j][j] = saved;
424 }
425 let mut ders = vec![vec![0.0_f64; p + 1]; d + 1];
426 for j in 0..=p {
427 ders[0][j] = ndu[j][p];
428 }
429 let mut a = vec![vec![0.0_f64; p + 1]; 2];
430 for r in 0..=p {
431 let mut s1 = 0_usize;
432 let mut s2 = 1_usize;
433 a[0][0] = 1.0;
434 for k in 1..=d {
435 let mut nd = 0.0;
436 let rk = r as isize - k as isize;
437 let pk = p as isize - k as isize;
438 if r >= k {
439 a[s2][0] = a[s1][0] / ndu[pk as usize + 1][rk as usize];
440 nd = a[s2][0] * ndu[rk as usize][pk as usize];
441 }
442 let j1 = if rk >= -1 {
443 1_usize
444 } else {
445 (-(rk + 1)) as usize
446 };
447 let j2 = if (r as isize - 1) <= pk { k - 1 } else { p - r };
448 for j in j1..=j2 {
449 let idx2 = rk + j as isize;
450 if idx2 < 0 {
451 continue;
452 }
453 let idx2 = idx2 as usize;
454 a[s2][j] = (a[s1][j] - a[s1][j.saturating_sub(1)]) / ndu[pk as usize + 1][idx2];
455 nd += a[s2][j] * ndu[idx2][pk as usize];
456 }
457 if r <= (p - k) {
458 a[s2][k] = -a[s1][k - 1] / ndu[pk as usize + 1][r];
459 nd += a[s2][k] * ndu[r][pk as usize];
460 }
461 ders[k][r] = nd;
462 std::mem::swap(&mut s1, &mut s2);
463 }
464 }
465 let mut rfact = p as f64;
466 for (k, ders_row) in ders[1..=d].iter_mut().enumerate().map(|(i, v)| (i + 1, v)) {
467 for val in ders_row[..=p].iter_mut() {
468 *val *= rfact;
469 }
470 if k < d {
471 rfact *= (p - k) as f64;
472 }
473 }
474 (span, ders)
475 }
476 pub fn eval_deriv(&self, i: usize, t: f64, k: usize) -> f64 {
478 let p = self.degree;
479 let (span, ders) = self.eval_nonzero_derivs(t, k);
480 let local_idx = i as isize - span as isize;
481 if local_idx < 0 || local_idx > p as isize {
482 0.0
483 } else {
484 ders[k][local_idx as usize]
485 }
486 }
487 pub fn greville_abscissae(&self) -> Vec<f64> {
491 let p = self.degree;
492 let knots = &self.knot_vector.knots;
493 (0..self.n_ctrl)
494 .map(|i| {
495 if p == 0 {
496 knots[i]
497 } else {
498 knots[i + 1..i + p + 1].iter().sum::<f64>() / p as f64
499 }
500 })
501 .collect()
502 }
503}
504#[derive(Debug, Clone)]
512pub struct BsplineFitting {
513 pub degree: usize,
515 pub n_ctrl: usize,
517 pub curve: Option<BsplineCurve>,
519}
520impl BsplineFitting {
521 pub fn new(degree: usize, n_ctrl: usize) -> Self {
523 Self {
524 degree,
525 n_ctrl,
526 curve: None,
527 }
528 }
529 pub fn chord_length_parameterization(points: &[[f64; 3]]) -> Vec<f64> {
533 let n = points.len();
534 if n == 0 {
535 return vec![];
536 }
537 if n == 1 {
538 return vec![0.0];
539 }
540 let mut d = vec![0.0_f64; n];
541 let mut total = 0.0_f64;
542 for i in 1..n {
543 let dx = points[i][0] - points[i - 1][0];
544 let dy = points[i][1] - points[i - 1][1];
545 let dz = points[i][2] - points[i - 1][2];
546 d[i] = (dx * dx + dy * dy + dz * dz).sqrt();
547 total += d[i];
548 }
549 if total < 1e-14 {
550 return (0..n).map(|i| i as f64 / (n - 1) as f64).collect();
551 }
552 let mut params = vec![0.0_f64; n];
553 for i in 1..n - 1 {
554 params[i] = params[i - 1] + d[i] / total;
555 }
556 params[n - 1] = 1.0;
557 params
558 }
559 pub fn centripetal_parameterization(points: &[[f64; 3]]) -> Vec<f64> {
561 let n = points.len();
562 if n == 0 {
563 return vec![];
564 }
565 if n == 1 {
566 return vec![0.0];
567 }
568 let mut d = vec![0.0_f64; n];
569 let mut total = 0.0_f64;
570 for i in 1..n {
571 let dx = points[i][0] - points[i - 1][0];
572 let dy = points[i][1] - points[i - 1][1];
573 let dz = points[i][2] - points[i - 1][2];
574 d[i] = (dx * dx + dy * dy + dz * dz).sqrt().sqrt();
575 total += d[i];
576 }
577 if total < 1e-14 {
578 return (0..n).map(|i| i as f64 / (n - 1) as f64).collect();
579 }
580 let mut params = vec![0.0_f64; n];
581 for i in 1..n - 1 {
582 params[i] = params[i - 1] + d[i] / total;
583 }
584 params[n - 1] = 1.0;
585 params
586 }
587 pub fn select_knots_by_averaging(params: &[f64], n_ctrl: usize, degree: usize) -> KnotVector {
591 let n = params.len();
592 let p = degree;
593 let m = n_ctrl + p;
594 let mut knots = vec![0.0_f64; m + 1];
595 for val in knots[..=p].iter_mut() {
596 *val = 0.0;
597 }
598 for val in knots[(m - p)..=m].iter_mut() {
599 *val = 1.0;
600 }
601 let n_interior = n_ctrl - p - 1;
602 if n_interior > 0 {
603 let d = n as f64 / n_ctrl as f64;
604 for j in 1..=n_interior {
605 let i_float = j as f64 * d;
606 let i_floor = i_float as usize;
607 let alpha = i_float - i_floor as f64;
608 let t_avg = if i_floor + 1 < n {
609 params[i_floor] * (1.0 - alpha) + params[i_floor + 1] * alpha
610 } else {
611 params[n - 1]
612 };
613 knots[p + j] = t_avg.clamp(0.0, 1.0);
614 }
615 }
616 for i in 1..knots.len() {
617 if knots[i] < knots[i - 1] {
618 knots[i] = knots[i - 1];
619 }
620 }
621 KnotVector::new(knots)
622 }
623 pub fn fit(&mut self, points: &[[f64; 3]]) {
628 let n_pts = points.len();
629 let n_ctrl = self.n_ctrl;
630 let p = self.degree;
631 if n_pts < 2 || n_ctrl < p + 1 {
632 self.curve = Some(BsplineCurve::clamped(
633 1,
634 vec![points[0], *points.last().unwrap_or(&points[0])],
635 ));
636 return;
637 }
638 let params = Self::chord_length_parameterization(points);
639 let kv = Self::select_knots_by_averaging(¶ms, n_ctrl, p);
640 let basis = BsplineBasis::new(p, kv.clone(), n_ctrl);
641 if n_pts == n_ctrl {
642 let mut a = vec![vec![0.0_f64; n_ctrl]; n_ctrl];
643 for (row, &t) in params.iter().enumerate() {
644 let row_vals = basis.eval_all(t);
645 a[row][..n_ctrl].copy_from_slice(&row_vals[..n_ctrl]);
646 }
647 let ctrl_pts: Vec<[f64; 3]> = (0..n_ctrl)
648 .map(|i| solve_linear_system_row(&a, points, i, n_ctrl))
649 .collect();
650 self.curve = Some(BsplineCurve::new(p, kv, ctrl_pts));
651 } else {
652 let mut n_mat = vec![vec![0.0_f64; n_ctrl]; n_pts];
653 for (row, &t) in params.iter().enumerate() {
654 let row_vals = basis.eval_all(t);
655 n_mat[row][..n_ctrl].copy_from_slice(&row_vals[..n_ctrl]);
656 }
657 let mut ata = vec![vec![0.0_f64; n_ctrl]; n_ctrl];
658 let mut atd = vec![[0.0_f64; 3]; n_ctrl];
659 for row in 0..n_pts {
660 for col_j in 0..n_ctrl {
661 for col_k in 0..n_ctrl {
662 ata[col_j][col_k] += n_mat[row][col_j] * n_mat[row][col_k];
663 }
664 for d in 0..3 {
665 atd[col_j][d] += n_mat[row][col_j] * points[row][d];
666 }
667 }
668 }
669 let ctrl_pts: Vec<[f64; 3]> = solve_3x_systems(&ata, &atd, n_ctrl);
670 self.curve = Some(BsplineCurve::new(p, kv, ctrl_pts));
671 }
672 }
673 pub fn residual(&self, points: &[[f64; 3]]) -> f64 {
675 let curve = match &self.curve {
676 Some(c) => c,
677 None => return f64::INFINITY,
678 };
679 let params = Self::chord_length_parameterization(points);
680 params
681 .iter()
682 .zip(points.iter())
683 .map(|(&t, &p)| {
684 let c = curve.eval(t);
685 let dx = c[0] - p[0];
686 let dy = c[1] - p[1];
687 let dz = c[2] - p[2];
688 dx * dx + dy * dy + dz * dz
689 })
690 .sum()
691 }
692}
693#[derive(Debug, Clone)]
699pub struct BsplineCurve {
700 pub basis: BsplineBasis,
702 pub control_points: Vec<[f64; 3]>,
704}
705impl BsplineCurve {
706 pub fn new(degree: usize, knot_vector: KnotVector, control_points: Vec<[f64; 3]>) -> Self {
708 let n_ctrl = control_points.len();
709 let basis = BsplineBasis::new(degree, knot_vector, n_ctrl);
710 Self {
711 basis,
712 control_points,
713 }
714 }
715 pub fn clamped(degree: usize, control_points: Vec<[f64; 3]>) -> Self {
717 let n_ctrl = control_points.len();
718 let basis = BsplineBasis::clamped(degree, n_ctrl);
719 Self {
720 basis,
721 control_points,
722 }
723 }
724 pub fn eval(&self, t: f64) -> [f64; 3] {
726 let (span, nonzero) = self.basis.eval_nonzero(t);
727 let p = self.basis.degree;
728 let mut point = [0.0_f64; 3];
729 for (k, &n_val) in nonzero[..=p].iter().enumerate() {
730 let idx = span + k - p;
731 if idx < self.control_points.len() {
732 let cp = self.control_points[idx];
733 for (pt, &cp_d) in point.iter_mut().zip(cp.iter()) {
734 *pt += n_val * cp_d;
735 }
736 }
737 }
738 point
739 }
740 pub fn eval_deriv(&self, t: f64, k: usize) -> [f64; 3] {
744 let p = self.basis.degree;
745 let (span, ders) = self.basis.eval_nonzero_derivs(t, k);
746 let n_ctrl = self.control_points.len();
747 let mut result = [0.0_f64; 3];
748 for (j, &d_val) in ders[k][..=p].iter().enumerate() {
749 let idx = span + j - p;
750 if idx < n_ctrl {
751 let cp = self.control_points[idx];
752 for (res_d, &cp_d) in result.iter_mut().zip(cp.iter()) {
753 *res_d += d_val * cp_d;
754 }
755 }
756 }
757 result
758 }
759 pub fn arc_length(&self, t_start: f64, t_end: f64, n_intervals: usize) -> f64 {
761 let h = (t_end - t_start) / n_intervals as f64;
762 let gp = [-0.906180, -0.538469, 0.0, 0.538469, 0.906180];
763 let gw = [0.236927, 0.478629, 0.568889, 0.478629, 0.236927];
764 let mut length = 0.0;
765 for i in 0..n_intervals {
766 let a = t_start + i as f64 * h;
767 let b = a + h;
768 let mid = (a + b) * 0.5;
769 let half = (b - a) * 0.5;
770 for (&xi, &wi) in gp.iter().zip(gw.iter()) {
771 let t = mid + half * xi;
772 let dt = self.eval_deriv(t, 1);
773 let speed = (dt[0] * dt[0] + dt[1] * dt[1] + dt[2] * dt[2]).sqrt();
774 length += wi * half * speed;
775 }
776 }
777 length
778 }
779 pub fn tangent(&self, t: f64) -> [f64; 3] {
781 let d1 = self.eval_deriv(t, 1);
782 let mag = (d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]).sqrt();
783 if mag < 1e-14 {
784 [0.0, 0.0, 0.0]
785 } else {
786 [d1[0] / mag, d1[1] / mag, d1[2] / mag]
787 }
788 }
789 pub fn curvature(&self, t: f64) -> f64 {
791 let d1 = self.eval_deriv(t, 1);
792 let d2 = self.eval_deriv(t, 2);
793 let cross = [
794 d1[1] * d2[2] - d1[2] * d2[1],
795 d1[2] * d2[0] - d1[0] * d2[2],
796 d1[0] * d2[1] - d1[1] * d2[0],
797 ];
798 let cross_mag = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
799 let d1_mag = (d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]).sqrt();
800 if d1_mag < 1e-14 {
801 0.0
802 } else {
803 cross_mag / d1_mag.powi(3)
804 }
805 }
806 pub fn torsion(&self, t: f64) -> f64 {
808 let d1 = self.eval_deriv(t, 1);
809 let d2 = self.eval_deriv(t, 2);
810 let d3 = self.eval_deriv(t, 3);
811 let cross = [
812 d1[1] * d2[2] - d1[2] * d2[1],
813 d1[2] * d2[0] - d1[0] * d2[2],
814 d1[0] * d2[1] - d1[1] * d2[0],
815 ];
816 let cross_mag2 = cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2];
817 if cross_mag2 < 1e-28 {
818 return 0.0;
819 }
820 let dot_d3 = cross[0] * d3[0] + cross[1] * d3[1] + cross[2] * d3[2];
821 dot_d3 / cross_mag2
822 }
823 pub fn principal_normal(&self, t: f64) -> [f64; 3] {
825 let kappa = self.curvature(t);
826 if kappa < 1e-14 {
827 return [0.0, 0.0, 0.0];
828 }
829 let d1 = self.eval_deriv(t, 1);
830 let d2 = self.eval_deriv(t, 2);
831 let d1_mag = (d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]).sqrt();
832 let d1_mag3 = d1_mag.powi(3);
833 let d1_dot_d2 = d1[0] * d2[0] + d1[1] * d2[1] + d1[2] * d2[2];
834 let d1_mag2 = d1_mag * d1_mag;
835 let mut normal = [0.0_f64; 3];
836 for i in 0..3 {
837 normal[i] = (d2[i] * d1_mag2 - d1[i] * d1_dot_d2) / (kappa * d1_mag3);
838 }
839 normal
840 }
841 pub fn sample(&self, n_points: usize) -> Vec<[f64; 3]> {
845 let (t0, t1) = self.basis.knot_vector.domain();
846 (0..n_points)
847 .map(|i| {
848 let t = t0 + (t1 - t0) * i as f64 / (n_points - 1).max(1) as f64;
849 self.eval(t)
850 })
851 .collect()
852 }
853 pub fn bounding_box(&self, n_samples: usize) -> ([f64; 3], [f64; 3]) {
857 let pts = self.sample(n_samples);
858 let mut lo = pts[0];
859 let mut hi = pts[0];
860 for p in &pts {
861 for d in 0..3 {
862 lo[d] = lo[d].min(p[d]);
863 hi[d] = hi[d].max(p[d]);
864 }
865 }
866 (lo, hi)
867 }
868}
869#[derive(Debug, Clone)]
875pub struct KnotVector {
876 pub knots: Vec<f64>,
878}
879impl KnotVector {
880 pub fn new(knots: Vec<f64>) -> Self {
886 for i in 1..knots.len() {
887 assert!(
888 knots[i] >= knots[i - 1],
889 "knot vector must be non-decreasing"
890 );
891 }
892 Self { knots }
893 }
894 pub fn clamped_uniform(n_ctrl: usize, degree: usize) -> Self {
899 let n = n_ctrl - 1;
900 let p = degree;
901 let m = n + p + 1;
902 let mut knots = vec![0.0_f64; m + 1];
903 for val in knots[..=p].iter_mut() {
904 *val = 0.0;
905 }
906 for val in knots[(m - p)..=m].iter_mut() {
907 *val = 1.0;
908 }
909 let n_interior = m - 2 * p - 1;
910 for j in 1..=n_interior {
911 knots[p + j] = j as f64 / (n_interior + 1) as f64;
912 }
913 Self { knots }
914 }
915 pub fn find_span(&self, t: f64, degree: usize, n_ctrl: usize) -> usize {
920 let n = n_ctrl - 1;
921 let p = degree;
922 let knots = &self.knots;
923 if t >= knots[n + 1] {
924 let mut i = n;
925 while i > p && (knots[i] - knots[i + 1]).abs() < 1e-14 {
926 i -= 1;
927 }
928 return i;
929 }
930 if t <= knots[p] {
931 return p;
932 }
933 let mut lo = p;
934 let mut hi = n + 1;
935 let mut mid = (lo + hi) / 2;
936 while t < knots[mid] || t >= knots[mid + 1] {
937 if t < knots[mid] {
938 hi = mid;
939 } else {
940 lo = mid;
941 }
942 mid = (lo + hi) / 2;
943 }
944 mid
945 }
946 pub fn len(&self) -> usize {
948 self.knots.len()
949 }
950 pub fn is_empty(&self) -> bool {
952 self.knots.is_empty()
953 }
954 pub fn domain(&self) -> (f64, f64) {
956 (
957 *self.knots.first().unwrap_or(&0.0),
958 *self.knots.last().unwrap_or(&1.0),
959 )
960 }
961}