1#[inline]
33fn vec3_sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
34 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
35}
36
37#[inline]
39fn vec3_scale(a: [f64; 3], s: f64) -> [f64; 3] {
40 [a[0] * s, a[1] * s, a[2] * s]
41}
42
43#[inline]
45fn vec3_dot(a: [f64; 3], b: [f64; 3]) -> f64 {
46 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
47}
48
49#[inline]
51fn vec3_cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
52 [
53 a[1] * b[2] - a[2] * b[1],
54 a[2] * b[0] - a[0] * b[2],
55 a[0] * b[1] - a[1] * b[0],
56 ]
57}
58
59#[inline]
61fn vec3_norm(a: [f64; 3]) -> f64 {
62 (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
63}
64
65#[inline]
67fn vec3_normalize(a: [f64; 3]) -> [f64; 3] {
68 let n = vec3_norm(a);
69 if n < 1.0e-15 {
70 [0.0; 3]
71 } else {
72 vec3_scale(a, 1.0 / n)
73 }
74}
75
76#[derive(Debug, Clone)]
84pub struct NurbsKnotVector {
85 pub knots: Vec<f64>,
87}
88
89impl NurbsKnotVector {
90 pub fn new(knots: Vec<f64>) -> Self {
96 for i in 1..knots.len() {
97 assert!(
98 knots[i] >= knots[i - 1],
99 "knot vector must be non-decreasing: knots[{}]={:.6} < knots[{}]={:.6}",
100 i,
101 knots[i],
102 i - 1,
103 knots[i - 1]
104 );
105 }
106 Self { knots }
107 }
108
109 pub fn clamped_uniform(n_ctrl: usize, degree: usize) -> Self {
113 let n = n_ctrl - 1;
114 let p = degree;
115 let m = n + p + 1;
116 let mut knots = vec![0.0_f64; m + 1];
117 for k in knots[0..=p].iter_mut() {
118 *k = 0.0;
119 }
120 for k in knots[(m - p)..=m].iter_mut() {
121 *k = 1.0;
122 }
123 let n_int = m as isize - 2 * p as isize - 1;
124 for j in 1..=(n_int.max(0) as usize) {
125 knots[p + j] = j as f64 / (n_int as f64 + 1.0);
126 }
127 Self { knots }
128 }
129
130 pub fn uniform(n_ctrl: usize, degree: usize) -> Self {
132 let m = n_ctrl + degree;
133 let knots: Vec<f64> = (0..=m).map(|i| i as f64 / m as f64).collect();
134 Self { knots }
135 }
136
137 pub fn len(&self) -> usize {
139 self.knots.len()
140 }
141
142 pub fn is_empty(&self) -> bool {
144 self.knots.is_empty()
145 }
146
147 pub fn find_span(&self, t: f64, degree: usize, n_ctrl: usize) -> usize {
152 let n = n_ctrl - 1;
153 let p = degree;
154 let m = n + p + 1;
155 if t >= self.knots[n + 1] {
157 return n;
158 }
159 if t <= self.knots[p] {
160 return p;
161 }
162 let mut lo = p;
164 let mut hi = m;
165 let mut mid = (lo + hi) / 2;
166 while t < self.knots[mid] || t >= self.knots[mid + 1] {
167 if t < self.knots[mid] {
168 hi = mid;
169 } else {
170 lo = mid;
171 }
172 mid = (lo + hi) / 2;
173 }
174 mid
175 }
176
177 pub fn domain_start(&self, degree: usize) -> f64 {
179 self.knots[degree]
180 }
181
182 pub fn domain_end(&self, _degree: usize, n_ctrl: usize) -> f64 {
184 self.knots[n_ctrl]
185 }
186
187 pub fn multiplicity(&self, t: f64) -> usize {
189 self.knots
190 .iter()
191 .filter(|&&k| (k - t).abs() < 1.0e-12)
192 .count()
193 }
194
195 pub fn insert_knot(&self, t_new: f64) -> Self {
197 let mut new_knots = self.knots.clone();
198 let pos = new_knots.partition_point(|&k| k <= t_new);
200 new_knots.insert(pos, t_new);
201 Self { knots: new_knots }
202 }
203}
204
205pub struct NurbsBasis;
213
214impl NurbsBasis {
215 pub fn evaluate(knots: &NurbsKnotVector, t: f64, degree: usize, n_ctrl: usize) -> Vec<f64> {
219 let p = degree;
220 let span = knots.find_span(t, p, n_ctrl);
221 let kv = &knots.knots;
222
223 let mut n = vec![0.0_f64; p + 1];
224 let mut left = vec![0.0_f64; p + 1];
225 let mut right = vec![0.0_f64; p + 1];
226
227 n[0] = 1.0;
228 for j in 1..=p {
229 left[j] = t - kv[span + 1 - j];
230 right[j] = kv[span + j] - t;
231 let mut saved = 0.0_f64;
232 for r in 0..j {
233 let denom = right[r + 1] + left[j - r];
234 let temp = if denom.abs() < 1.0e-15 {
235 0.0
236 } else {
237 n[r] / denom
238 };
239 n[r] = saved + right[r + 1] * temp;
240 saved = left[j - r] * temp;
241 }
242 n[j] = saved;
243 }
244 n
245 }
246
247 pub fn evaluate_deriv(
251 knots: &NurbsKnotVector,
252 t: f64,
253 degree: usize,
254 n_ctrl: usize,
255 ) -> (Vec<f64>, Vec<f64>) {
256 let ders = Self::evaluate_all_derivs(knots, t, degree, n_ctrl, 1);
257 (ders[0].clone(), ders[1].clone())
258 }
259
260 pub fn evaluate_all_derivs(
264 knots: &NurbsKnotVector,
265 t: f64,
266 degree: usize,
267 n_ctrl: usize,
268 n_deriv: usize,
269 ) -> Vec<Vec<f64>> {
270 let p = degree;
271 let span = knots.find_span(t, p, n_ctrl);
272 let kv = &knots.knots;
273 let d = n_deriv.min(p);
274
275 let mut ndu = vec![vec![0.0_f64; p + 1]; p + 1];
276 let mut left = vec![0.0_f64; p + 1];
277 let mut right = vec![0.0_f64; p + 1];
278 ndu[0][0] = 1.0;
279 for j in 1..=p {
280 left[j] = t - kv[span + 1 - j];
281 right[j] = kv[span + j] - t;
282 let mut saved = 0.0_f64;
283 for r in 0..j {
284 ndu[j][r] = right[r + 1] + left[j - r];
285 let temp = if ndu[j][r].abs() < 1.0e-15 {
286 0.0
287 } else {
288 ndu[r][j - 1] / ndu[j][r]
289 };
290 ndu[r][j] = saved + right[r + 1] * temp;
291 saved = left[j - r] * temp;
292 }
293 ndu[j][j] = saved;
294 }
295
296 let mut ders = vec![vec![0.0_f64; p + 1]; d + 1];
297 for j in 0..=p {
298 ders[0][j] = ndu[j][p];
299 }
300
301 let mut a = vec![vec![0.0_f64; p + 1]; 2];
302 for r in 0..=p {
303 let mut s1 = 0usize;
304 let mut s2 = 1usize;
305 a[s1][0] = 1.0;
306 for k in 1..=d {
307 let mut fac = 0.0_f64;
308 let rk = r as isize - k as isize;
309 let pk = p as isize - k as isize;
310 if r as isize >= k as isize {
311 a[s2][0] = a[s1][0]
312 / (ndu[(pk + 1) as usize][rk as usize]
313 .abs()
314 .max(1.0e-15)
315 .copysign(ndu[(pk + 1) as usize][rk as usize]));
316 fac = a[s2][0];
317 }
318 let j1 = if rk >= -1 { 1 } else { (-rk - 1) as usize };
319 let j2 = if (r as isize - 1) <= pk { k - 1 } else { p - r };
320 for j in j1..=j2 {
321 a[s2][j] = (a[s1][j] - a[s1][j - 1])
322 / (ndu[(pk + 1) as usize][(rk + j as isize) as usize]
323 .abs()
324 .max(1.0e-15)
325 .copysign(ndu[(pk + 1) as usize][(rk + j as isize) as usize]));
326 }
327 if (r as isize) <= pk {
328 a[s2][k] = -a[s1][k - 1]
329 / (ndu[(pk + 1) as usize][r]
330 .abs()
331 .max(1.0e-15)
332 .copysign(ndu[(pk + 1) as usize][r]));
333 fac += a[s2][k];
334 }
335 ders[k][r] = fac;
336 std::mem::swap(&mut s1, &mut s2);
337 }
338 }
339 ders
340 }
341}
342
343#[derive(Debug, Clone, Copy)]
349pub struct NurbsControlPoint {
350 pub point: [f64; 3],
352 pub weight: f64,
354}
355
356impl NurbsControlPoint {
357 pub fn new(x: f64, y: f64, z: f64, w: f64) -> Self {
359 Self {
360 point: [x, y, z],
361 weight: w,
362 }
363 }
364
365 pub fn unweighted(x: f64, y: f64, z: f64) -> Self {
367 Self::new(x, y, z, 1.0)
368 }
369
370 pub fn homogeneous(&self) -> [f64; 4] {
372 [
373 self.point[0] * self.weight,
374 self.point[1] * self.weight,
375 self.point[2] * self.weight,
376 self.weight,
377 ]
378 }
379}
380
381#[derive(Debug, Clone)]
387pub struct NurbsCurve {
388 pub knots: NurbsKnotVector,
390 pub control_points: Vec<NurbsControlPoint>,
392 pub degree: usize,
394}
395
396impl NurbsCurve {
397 pub fn new(
403 knots: NurbsKnotVector,
404 control_points: Vec<NurbsControlPoint>,
405 degree: usize,
406 ) -> Self {
407 let n = control_points.len();
408 let m = n + degree;
409 assert_eq!(
410 knots.len(),
411 m + 1,
412 "knot vector length must be n_ctrl + degree + 1 = {}, got {}",
413 m + 1,
414 knots.len()
415 );
416 Self {
417 knots,
418 control_points,
419 degree,
420 }
421 }
422
423 pub fn evaluate(&self, t: f64) -> [f64; 3] {
425 let n = self.control_points.len();
426 let p = self.degree;
427 let basis = NurbsBasis::evaluate(&self.knots, t, p, n);
428 let span = self.knots.find_span(t, p, n);
429
430 let mut w_sum = 0.0_f64;
431 let mut point = [0.0_f64; 3];
432 for (i, b_i) in basis.iter().enumerate() {
433 let idx = span - p + i;
434 if idx < n {
435 let cp = &self.control_points[idx];
436 let wn = b_i * cp.weight;
437 w_sum += wn;
438 for (pt, cp_pt) in point.iter_mut().zip(cp.point.iter()) {
439 *pt += wn * cp_pt;
440 }
441 }
442 }
443 if w_sum.abs() > 1.0e-15 {
444 for pt in point.iter_mut() {
445 *pt /= w_sum;
446 }
447 }
448 point
449 }
450
451 pub fn derivative(&self, t: f64) -> [f64; 3] {
453 let n = self.control_points.len();
454 let p = self.degree;
455 let (basis, dbasis) = NurbsBasis::evaluate_deriv(&self.knots, t, p, n);
456 let span = self.knots.find_span(t, p, n);
457
458 let mut w = 0.0_f64;
459 let mut dw = 0.0_f64;
460 let mut c = [0.0_f64; 3];
461 let mut dc = [0.0_f64; 3];
462 for i in 0..=p {
463 let idx = span - p + i;
464 if idx < n {
465 let cp = &self.control_points[idx];
466 let wi = cp.weight;
467 w += basis[i] * wi;
468 dw += dbasis[i] * wi;
469 for k in 0..3 {
470 c[k] += basis[i] * wi * cp.point[k];
471 dc[k] += dbasis[i] * wi * cp.point[k];
472 }
473 }
474 }
475 if w.abs() < 1.0e-15 {
477 return [0.0; 3];
478 }
479 let w2 = w * w;
480 let mut out = [0.0_f64; 3];
481 for k in 0..3 {
482 out[k] = (dc[k] * w - c[k] * dw) / w2;
483 }
484 out
485 }
486
487 pub fn tangent(&self, t: f64) -> [f64; 3] {
489 vec3_normalize(self.derivative(t))
490 }
491
492 pub fn curvature(&self, t: f64) -> f64 {
494 let dt = 1.0e-5;
495 let t_lo = (t - dt).max(self.knots.domain_start(self.degree));
496 let t_hi = (t + dt).min(
497 self.knots
498 .domain_end(self.degree, self.control_points.len()),
499 );
500 let d1 = self.derivative(t);
501 let p_lo = self.evaluate(t_lo);
502 let p_hi = self.evaluate(t_hi);
503 let p_mid = self.evaluate(t);
505 let d2 = [
506 (p_hi[0] - 2.0 * p_mid[0] + p_lo[0]) / (dt * dt),
507 (p_hi[1] - 2.0 * p_mid[1] + p_lo[1]) / (dt * dt),
508 (p_hi[2] - 2.0 * p_mid[2] + p_lo[2]) / (dt * dt),
509 ];
510 let cross = vec3_cross(d1, d2);
511 let num = vec3_norm(cross);
512 let denom = vec3_norm(d1).powi(3);
513 if denom < 1.0e-15 { 0.0 } else { num / denom }
514 }
515
516 pub fn arc_length(&self, n_pts: usize) -> f64 {
518 let t0 = self.knots.domain_start(self.degree);
519 let t1 = self
520 .knots
521 .domain_end(self.degree, self.control_points.len());
522 gauss_legendre_integral(t0, t1, n_pts, |t| vec3_norm(self.derivative(t)))
523 }
524
525 pub fn n_ctrl(&self) -> usize {
527 self.control_points.len()
528 }
529
530 pub fn circle(r: f64) -> Self {
534 let w = 0.5_f64.sqrt(); let cps = vec![
536 NurbsControlPoint::new(r, 0.0, 0.0, 1.0),
537 NurbsControlPoint::new(r, r, 0.0, w),
538 NurbsControlPoint::new(0.0, r, 0.0, 1.0),
539 NurbsControlPoint::new(-r, r, 0.0, w),
540 NurbsControlPoint::new(-r, 0.0, 0.0, 1.0),
541 NurbsControlPoint::new(-r, -r, 0.0, w),
542 NurbsControlPoint::new(0.0, -r, 0.0, 1.0),
543 NurbsControlPoint::new(r, -r, 0.0, w),
544 NurbsControlPoint::new(r, 0.0, 0.0, 1.0),
545 ];
546 let knots = NurbsKnotVector::new(vec![
547 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0,
548 ]);
549 Self {
550 knots,
551 control_points: cps,
552 degree: 2,
553 }
554 }
555}
556
557#[derive(Debug, Clone)]
566pub struct NurbsSurface {
567 pub knots_u: NurbsKnotVector,
569 pub knots_v: NurbsKnotVector,
571 pub control_net: Vec<NurbsControlPoint>,
573 pub n_u: usize,
575 pub n_v: usize,
577 pub degree_u: usize,
579 pub degree_v: usize,
581}
582
583impl NurbsSurface {
584 pub fn new(
586 knots_u: NurbsKnotVector,
587 knots_v: NurbsKnotVector,
588 control_net: Vec<NurbsControlPoint>,
589 n_u: usize,
590 n_v: usize,
591 degree_u: usize,
592 degree_v: usize,
593 ) -> Self {
594 assert_eq!(
595 control_net.len(),
596 n_u * n_v,
597 "control_net length must be n_u * n_v"
598 );
599 Self {
600 knots_u,
601 knots_v,
602 control_net,
603 n_u,
604 n_v,
605 degree_u,
606 degree_v,
607 }
608 }
609
610 pub fn evaluate(&self, u: f64, v: f64) -> [f64; 3] {
612 let nu = NurbsBasis::evaluate(&self.knots_u, u, self.degree_u, self.n_u);
613 let nv = NurbsBasis::evaluate(&self.knots_v, v, self.degree_v, self.n_v);
614 let span_u = self.knots_u.find_span(u, self.degree_u, self.n_u);
615 let span_v = self.knots_v.find_span(v, self.degree_v, self.n_v);
616
617 let mut w_sum = 0.0_f64;
618 let mut point = [0.0_f64; 3];
619 for (i, nu_i) in nu.iter().enumerate() {
620 let iu = span_u + i - self.degree_u;
621 if iu >= self.n_u {
622 continue;
623 }
624 for (j, nv_j) in nv.iter().enumerate() {
625 let jv = span_v + j - self.degree_v;
626 if jv >= self.n_v {
627 continue;
628 }
629 let cp = &self.control_net[iu * self.n_v + jv];
630 let wn = nu_i * nv_j * cp.weight;
631 w_sum += wn;
632 for (pt, cp_pt) in point.iter_mut().zip(cp.point.iter()) {
633 *pt += wn * cp_pt;
634 }
635 }
636 }
637 if w_sum.abs() > 1.0e-15 {
638 for pt in point.iter_mut() {
639 *pt /= w_sum;
640 }
641 }
642 point
643 }
644
645 pub fn du(&self, u: f64, v: f64) -> [f64; 3] {
647 let h = 1.0e-5;
648 let u0 = self.knots_u.domain_start(self.degree_u);
649 let u1 = self.knots_u.domain_end(self.degree_u, self.n_u);
650 let u_hi = (u + h).min(u1);
651 let u_lo = (u - h).max(u0);
652 let p_hi = self.evaluate(u_hi, v);
653 let p_lo = self.evaluate(u_lo, v);
654 let denom = u_hi - u_lo;
655 if denom < 1.0e-15 {
656 return [0.0; 3];
657 }
658 [
659 (p_hi[0] - p_lo[0]) / denom,
660 (p_hi[1] - p_lo[1]) / denom,
661 (p_hi[2] - p_lo[2]) / denom,
662 ]
663 }
664
665 pub fn dv(&self, u: f64, v: f64) -> [f64; 3] {
667 let h = 1.0e-5;
668 let v0 = self.knots_v.domain_start(self.degree_v);
669 let v1 = self.knots_v.domain_end(self.degree_v, self.n_v);
670 let v_hi = (v + h).min(v1);
671 let v_lo = (v - h).max(v0);
672 let p_hi = self.evaluate(u, v_hi);
673 let p_lo = self.evaluate(u, v_lo);
674 let denom = v_hi - v_lo;
675 if denom < 1.0e-15 {
676 return [0.0; 3];
677 }
678 [
679 (p_hi[0] - p_lo[0]) / denom,
680 (p_hi[1] - p_lo[1]) / denom,
681 (p_hi[2] - p_lo[2]) / denom,
682 ]
683 }
684
685 pub fn normal(&self, u: f64, v: f64) -> [f64; 3] {
687 let su = self.du(u, v);
688 let sv = self.dv(u, v);
689 vec3_normalize(vec3_cross(su, sv))
690 }
691
692 pub fn mean_curvature(&self, u: f64, v: f64) -> f64 {
694 let h: f64 = 1.0e-4;
695 let u0 = self.knots_u.domain_start(self.degree_u);
696 let u1 = self.knots_u.domain_end(self.degree_u, self.n_u);
697 let v0 = self.knots_v.domain_start(self.degree_v);
698 let v1 = self.knots_v.domain_end(self.degree_v, self.n_v);
699 let uh = h.min((u1 - u0) / 4.0);
700 let vh = h.min((v1 - v0) / 4.0);
701
702 let n_pu = self.normal((u + uh).min(u1), v);
703 let n_mu = self.normal((u - uh).max(u0), v);
704 let n_pv = self.normal(u, (v + vh).min(v1));
705 let n_mv = self.normal(u, (v - vh).max(v0));
706
707 let dn_du = vec3_scale(vec3_sub(n_pu, n_mu), 1.0 / (2.0 * uh));
708 let dn_dv = vec3_scale(vec3_sub(n_pv, n_mv), 1.0 / (2.0 * vh));
709
710 0.5 * (dn_du[0] + dn_dv[1]) }
712
713 pub fn gaussian_curvature(&self, u: f64, v: f64) -> f64 {
717 let h: f64 = 1.0e-4;
718 let u0 = self.knots_u.domain_start(self.degree_u);
719 let u1 = self.knots_u.domain_end(self.degree_u, self.n_u);
720 let v0 = self.knots_v.domain_start(self.degree_v);
721 let v1 = self.knots_v.domain_end(self.degree_v, self.n_v);
722 let uh = h.min((u1 - u0) / 4.0);
723 let vh = h.min((v1 - v0) / 4.0);
724
725 let r_u = self.du(u, v);
726 let r_v = self.dv(u, v);
727 let n = self.normal(u, v);
728 let e_ff = vec3_dot(r_u, r_u);
730 let f_ff = vec3_dot(r_u, r_v);
731 let g_ff = vec3_dot(r_v, r_v);
732 let det1 = e_ff * g_ff - f_ff * f_ff;
733 if det1.abs() < 1.0e-15 {
734 return 0.0;
735 }
736 let r_uu = {
738 let pu = (u + uh).min(u1);
739 let mu = (u - uh).max(u0);
740 let p = self.evaluate(pu, v);
741 let m = self.evaluate(mu, v);
742 let c = self.evaluate(u, v);
743 [
744 (p[0] - 2.0 * c[0] + m[0]) / (uh * uh),
745 (p[1] - 2.0 * c[1] + m[1]) / (uh * uh),
746 (p[2] - 2.0 * c[2] + m[2]) / (uh * uh),
747 ]
748 };
749 let r_vv = {
750 let pv = (v + vh).min(v1);
751 let mv = (v - vh).max(v0);
752 let p = self.evaluate(u, pv);
753 let m = self.evaluate(u, mv);
754 let c = self.evaluate(u, v);
755 [
756 (p[0] - 2.0 * c[0] + m[0]) / (vh * vh),
757 (p[1] - 2.0 * c[1] + m[1]) / (vh * vh),
758 (p[2] - 2.0 * c[2] + m[2]) / (vh * vh),
759 ]
760 };
761 let r_uv = {
762 let pp = self.evaluate((u + uh).min(u1), (v + vh).min(v1));
763 let pm = self.evaluate((u + uh).min(u1), (v - vh).max(v0));
764 let mp = self.evaluate((u - uh).max(u0), (v + vh).min(v1));
765 let mm = self.evaluate((u - uh).max(u0), (v - vh).max(v0));
766 [
767 (pp[0] - pm[0] - mp[0] + mm[0]) / (4.0 * uh * vh),
768 (pp[1] - pm[1] - mp[1] + mm[1]) / (4.0 * uh * vh),
769 (pp[2] - pm[2] - mp[2] + mm[2]) / (4.0 * uh * vh),
770 ]
771 };
772 let big_l = vec3_dot(r_uu, n);
773 let big_m = vec3_dot(r_uv, n);
774 let big_n = vec3_dot(r_vv, n);
775 (big_l * big_n - big_m * big_m) / det1
776 }
777
778 pub fn flat_plane(x0: f64, x1: f64, y0: f64, y1: f64) -> Self {
780 let cps = vec![
781 NurbsControlPoint::unweighted(x0, y0, 0.0),
782 NurbsControlPoint::unweighted(x1, y0, 0.0),
783 NurbsControlPoint::unweighted(x0, y1, 0.0),
784 NurbsControlPoint::unweighted(x1, y1, 0.0),
785 ];
786 let ku = NurbsKnotVector::new(vec![0.0, 0.0, 1.0, 1.0]);
787 let kv = NurbsKnotVector::new(vec![0.0, 0.0, 1.0, 1.0]);
788 Self::new(ku, kv, cps, 2, 2, 1, 1)
789 }
790}
791
792pub struct KnotInsertion;
801
802impl KnotInsertion {
803 pub fn insert_once(
807 control_points: &[NurbsControlPoint],
808 knots: &NurbsKnotVector,
809 degree: usize,
810 t_bar: f64,
811 _s: usize,
812 ) -> Vec<NurbsControlPoint> {
813 let n = control_points.len();
814 let p = degree;
815 let span = knots.find_span(t_bar, p, n);
816 let kv = &knots.knots;
817
818 let mut new_cps: Vec<NurbsControlPoint> = Vec::with_capacity(n + 1);
819 new_cps.extend_from_slice(&control_points[0..=(span - p)]);
821 for i in (span - p + 1)..=span {
823 let alpha = {
824 let denom = kv[i + p] - kv[i];
825 if denom.abs() < 1.0e-15 {
826 0.0
827 } else {
828 (t_bar - kv[i]) / denom
829 }
830 };
831 let prev = if i > 0 {
832 control_points[i - 1]
833 } else {
834 control_points[0]
835 };
836 let curr = control_points[i];
837 let w_new = (1.0 - alpha) * prev.weight + alpha * curr.weight;
838 let p_new = [
839 ((1.0 - alpha) * prev.weight * prev.point[0] + alpha * curr.weight * curr.point[0])
840 / w_new.max(1.0e-15),
841 ((1.0 - alpha) * prev.weight * prev.point[1] + alpha * curr.weight * curr.point[1])
842 / w_new.max(1.0e-15),
843 ((1.0 - alpha) * prev.weight * prev.point[2] + alpha * curr.weight * curr.point[2])
844 / w_new.max(1.0e-15),
845 ];
846 new_cps.push(NurbsControlPoint {
847 point: p_new,
848 weight: w_new,
849 });
850 }
851 new_cps.extend_from_slice(&control_points[span..n]);
853 new_cps
854 }
855}
856
857pub struct DegreeElevation;
865
866impl DegreeElevation {
867 pub fn elevate_bezier(pts: &[NurbsControlPoint]) -> Vec<NurbsControlPoint> {
871 let n = pts.len();
872 let p = n - 1;
873 let mut new_pts = Vec::with_capacity(n + 1);
874 new_pts.push(pts[0]);
876 for i in 1..=p {
877 let alpha = i as f64 / (p + 1) as f64;
878 let prev = pts[i - 1];
879 let curr = pts[i];
880 let w_new = (1.0 - alpha) * prev.weight + alpha * curr.weight;
881 let pn = [
882 ((1.0 - alpha) * prev.weight * prev.point[0] + alpha * curr.weight * curr.point[0])
883 / w_new.max(1e-15),
884 ((1.0 - alpha) * prev.weight * prev.point[1] + alpha * curr.weight * curr.point[1])
885 / w_new.max(1e-15),
886 ((1.0 - alpha) * prev.weight * prev.point[2] + alpha * curr.weight * curr.point[2])
887 / w_new.max(1e-15),
888 ];
889 new_pts.push(NurbsControlPoint {
890 point: pn,
891 weight: w_new,
892 });
893 }
894 new_pts.push(pts[p]);
895 new_pts
896 }
897
898 pub fn elevate_curve(curve: &NurbsCurve) -> NurbsCurve {
902 let beziers = BezierDecomposition::decompose(curve);
904 let elevated: Vec<Vec<NurbsControlPoint>> = beziers
905 .iter()
906 .map(|seg| DegreeElevation::elevate_bezier(seg))
907 .collect();
908 let new_degree = curve.degree + 1;
910 let mut all_pts: Vec<NurbsControlPoint> = Vec::new();
911 for (k, seg) in elevated.iter().enumerate() {
912 if k == 0 {
913 for pt in seg.iter() {
914 all_pts.push(*pt);
915 }
916 } else {
917 for pt in seg.iter().skip(1) {
918 all_pts.push(*pt);
919 }
920 }
921 }
922 let n_ctrl = all_pts.len();
923 let new_knots = NurbsKnotVector::clamped_uniform(n_ctrl, new_degree);
924 NurbsCurve::new(new_knots, all_pts, new_degree)
925 }
926}
927
928pub struct BezierDecomposition;
934
935impl BezierDecomposition {
936 pub fn decompose(curve: &NurbsCurve) -> Vec<Vec<NurbsControlPoint>> {
940 let p = curve.degree;
941 let n = curve.n_ctrl();
942 let kv = &curve.knots.knots;
943 let mut segments: Vec<Vec<NurbsControlPoint>> = Vec::new();
944
945 let t_start = curve.knots.domain_start(p);
947 let t_end = curve.knots.domain_end(p, n);
948 let n_pts = 16usize.max(n);
949 let dt = (t_end - t_start) / n_pts as f64;
950
951 let mut breaks: Vec<f64> = vec![t_start];
953 for &k in kv.iter() {
954 if k > *breaks.last().expect("collection should not be empty") + 1.0e-12
955 && k < t_end - 1.0e-12
956 {
957 breaks.push(k);
958 }
959 }
960 breaks.push(t_end);
961 let _ = dt;
962
963 for w in breaks.windows(2) {
964 let ta = w[0];
965 let tb = w[1];
966 let mut seg = Vec::with_capacity(p + 1);
967 for j in 0..=(p) {
968 let t_j = ta + (tb - ta) * (j as f64 / p as f64);
969 let pt = curve.evaluate(t_j);
970 seg.push(NurbsControlPoint::unweighted(pt[0], pt[1], pt[2]));
971 }
972 segments.push(seg);
973 }
974 segments
975 }
976}
977
978#[derive(Debug, Clone)]
986pub struct RuledSurface {
987 pub curve0: NurbsCurve,
989 pub curve1: NurbsCurve,
991}
992
993impl RuledSurface {
994 pub fn new(curve0: NurbsCurve, curve1: NurbsCurve) -> Self {
996 Self { curve0, curve1 }
997 }
998
999 pub fn evaluate(&self, u: f64, v: f64) -> [f64; 3] {
1001 let p0 = self.curve0.evaluate(u);
1002 let p1 = self.curve1.evaluate(u);
1003 [
1004 (1.0 - v) * p0[0] + v * p1[0],
1005 (1.0 - v) * p0[1] + v * p1[1],
1006 (1.0 - v) * p0[2] + v * p1[2],
1007 ]
1008 }
1009
1010 pub fn normal(&self, u: f64, v: f64) -> [f64; 3] {
1012 let h = 1.0e-5;
1013 let du = vec3_sub(self.evaluate(u + h, v), self.evaluate((u - h).max(0.0), v));
1014 let dv = vec3_sub(
1015 self.evaluate(u, (v + h).min(1.0)),
1016 self.evaluate(u, (v - h).max(0.0)),
1017 );
1018 vec3_normalize(vec3_cross(du, dv))
1019 }
1020}
1021
1022#[derive(Debug, Clone)]
1031pub struct SweptSurface {
1032 pub profile: NurbsCurve,
1034 pub path: NurbsCurve,
1036}
1037
1038impl SweptSurface {
1039 pub fn new(profile: NurbsCurve, path: NurbsCurve) -> Self {
1041 Self { profile, path }
1042 }
1043
1044 pub fn evaluate(&self, u_path: f64, v_profile: f64) -> [f64; 3] {
1048 let spine_pt = self.path.evaluate(u_path);
1049 let tangent = self.path.tangent(u_path);
1050 let ref_up = if tangent[2].abs() < 0.9 {
1052 [0.0, 0.0, 1.0]
1053 } else {
1054 [1.0, 0.0, 0.0]
1055 };
1056 let binormal = vec3_normalize(vec3_cross(tangent, ref_up));
1057 let normal = vec3_cross(binormal, tangent);
1058
1059 let p = self.profile.evaluate(v_profile);
1060 [
1062 spine_pt[0] + p[0] * binormal[0] + p[1] * normal[0] + p[2] * tangent[0],
1063 spine_pt[1] + p[0] * binormal[1] + p[1] * normal[1] + p[2] * tangent[1],
1064 spine_pt[2] + p[0] * binormal[2] + p[1] * normal[2] + p[2] * tangent[2],
1065 ]
1066 }
1067}
1068
1069#[derive(Debug, Clone)]
1075pub struct NurbsTrimCurve {
1076 pub control_points: Vec<NurbsControlPoint>,
1078 pub knots: NurbsKnotVector,
1080 pub degree: usize,
1082}
1083
1084impl NurbsTrimCurve {
1085 pub fn new(
1087 control_points: Vec<NurbsControlPoint>,
1088 knots: NurbsKnotVector,
1089 degree: usize,
1090 ) -> Self {
1091 Self {
1092 control_points,
1093 knots,
1094 degree,
1095 }
1096 }
1097
1098 pub fn evaluate(&self, t: f64) -> [f64; 2] {
1100 let n = self.control_points.len();
1101 let p = self.degree;
1102 let basis = NurbsBasis::evaluate(&self.knots, t, p, n);
1103 let span = self.knots.find_span(t, p, n);
1104 let mut w_sum = 0.0_f64;
1105 let mut uv = [0.0_f64; 2];
1106 for (i, b_i) in basis.iter().enumerate() {
1107 let idx = span - p + i;
1108 if idx < n {
1109 let cp = &self.control_points[idx];
1110 let wn = b_i * cp.weight;
1111 w_sum += wn;
1112 uv[0] += wn * cp.point[0];
1113 uv[1] += wn * cp.point[1];
1114 }
1115 }
1116 if w_sum.abs() > 1.0e-15 {
1117 uv[0] /= w_sum;
1118 uv[1] /= w_sum;
1119 }
1120 uv
1121 }
1122}
1123
1124#[derive(Debug, Clone)]
1126pub struct NurbsTrimming {
1127 pub surface: NurbsSurface,
1129 pub trim_curves: Vec<NurbsTrimCurve>,
1131}
1132
1133impl NurbsTrimming {
1134 pub fn new(surface: NurbsSurface) -> Self {
1136 Self {
1137 surface,
1138 trim_curves: Vec::new(),
1139 }
1140 }
1141
1142 pub fn add_trim_curve(&mut self, trim: NurbsTrimCurve) {
1144 self.trim_curves.push(trim);
1145 }
1146
1147 pub fn is_inside(&self, _u: f64, _v: f64) -> bool {
1151 if self.trim_curves.is_empty() {
1152 return true;
1153 }
1154 true
1156 }
1157
1158 pub fn evaluate(&self, u: f64, v: f64) -> Option<[f64; 3]> {
1160 if self.is_inside(u, v) {
1161 Some(self.surface.evaluate(u, v))
1162 } else {
1163 None
1164 }
1165 }
1166}
1167
1168#[derive(Debug, Clone)]
1177pub struct IgaElement {
1178 pub gauss_points: Vec<[f64; 3]>,
1180 pub basis_values: Vec<Vec<f64>>,
1182 pub phys_coords: Vec<[f64; 3]>,
1184 pub jacobians: Vec<f64>,
1186 pub element_id: usize,
1188}
1189
1190impl IgaElement {
1191 pub fn build(
1193 surface: &NurbsSurface,
1194 u_lo: f64,
1195 u_hi: f64,
1196 v_lo: f64,
1197 v_hi: f64,
1198 n_gauss: usize,
1199 element_id: usize,
1200 ) -> Self {
1201 let gauss_1d = gauss_points_1d(n_gauss);
1202 let mut gauss_points = Vec::new();
1203 let mut basis_values = Vec::new();
1204 let mut phys_coords = Vec::new();
1205 let mut jacobians = Vec::new();
1206
1207 for (u_xi, w_u) in &gauss_1d {
1208 for (v_xi, w_v) in &gauss_1d {
1209 let u = 0.5 * (u_hi - u_lo) * u_xi + 0.5 * (u_hi + u_lo);
1210 let v = 0.5 * (v_hi - v_lo) * v_xi + 0.5 * (v_hi + v_lo);
1211 let w = w_u * w_v * (u_hi - u_lo) * (v_hi - v_lo) * 0.25;
1212
1213 gauss_points.push([u, v, w]);
1214 let bv = NurbsBasis::evaluate(&surface.knots_u, u, surface.degree_u, surface.n_u);
1215 basis_values.push(bv);
1216
1217 let pt = surface.evaluate(u, v);
1218 phys_coords.push(pt);
1219
1220 let su = surface.du(u, v);
1221 let sv = surface.dv(u, v);
1222 let cross = vec3_cross(su, sv);
1223 jacobians.push(vec3_norm(cross));
1224 }
1225 }
1226
1227 Self {
1228 gauss_points,
1229 basis_values,
1230 phys_coords,
1231 jacobians,
1232 element_id,
1233 }
1234 }
1235
1236 pub fn n_gauss(&self) -> usize {
1238 self.gauss_points.len()
1239 }
1240
1241 pub fn integrate<F: Fn([f64; 3]) -> f64>(&self, f: F) -> f64 {
1243 self.gauss_points
1244 .iter()
1245 .zip(self.jacobians.iter())
1246 .zip(self.phys_coords.iter())
1247 .map(|((gp, &jac), &pt)| f(pt) * jac * gp[2])
1248 .sum()
1249 }
1250}
1251
1252pub struct SurfaceSurfaceIntersector;
1260
1261impl SurfaceSurfaceIntersector {
1262 pub fn intersect_newton(
1267 s1: &NurbsSurface,
1268 s2: &NurbsSurface,
1269 u1: f64,
1270 v1: f64,
1271 u2: f64,
1272 v2: f64,
1273 max_iter: usize,
1274 tol: f64,
1275 ) -> Option<(f64, f64, f64, f64, [f64; 3])> {
1276 let mut u1 = u1;
1277 let mut v1 = v1;
1278 let mut u2 = u2;
1279 let mut v2 = v2;
1280
1281 for _ in 0..max_iter {
1282 let p1 = s1.evaluate(u1, v1);
1283 let p2 = s2.evaluate(u2, v2);
1284 let diff = vec3_sub(p1, p2);
1285 if vec3_norm(diff) < tol {
1286 return Some((u1, v1, u2, v2, p1));
1287 }
1288 let su1 = s1.du(u1, v1);
1290 let sv1 = s1.dv(u1, v1);
1291 let su2 = s2.du(u2, v2);
1292 let sv2 = s2.dv(u2, v2);
1293
1294 let du1 = -vec3_dot(diff, su1) * 0.01;
1295 let dv1 = -vec3_dot(diff, sv1) * 0.01;
1296 let du2 = vec3_dot(diff, su2) * 0.01;
1297 let dv2 = vec3_dot(diff, sv2) * 0.01;
1298
1299 let u1_new = (u1 + du1).clamp(
1300 s1.knots_u.domain_start(s1.degree_u),
1301 s1.knots_u.domain_end(s1.degree_u, s1.n_u),
1302 );
1303 let v1_new = (v1 + dv1).clamp(
1304 s1.knots_v.domain_start(s1.degree_v),
1305 s1.knots_v.domain_end(s1.degree_v, s1.n_v),
1306 );
1307 let u2_new = (u2 + du2).clamp(
1308 s2.knots_u.domain_start(s2.degree_u),
1309 s2.knots_u.domain_end(s2.degree_u, s2.n_u),
1310 );
1311 let v2_new = (v2 + dv2).clamp(
1312 s2.knots_v.domain_start(s2.degree_v),
1313 s2.knots_v.domain_end(s2.degree_v, s2.n_v),
1314 );
1315
1316 if (u1_new - u1).abs() < 1e-14
1317 && (v1_new - v1).abs() < 1e-14
1318 && (u2_new - u2).abs() < 1e-14
1319 && (v2_new - v2).abs() < 1e-14
1320 {
1321 break;
1322 }
1323 u1 = u1_new;
1324 v1 = v1_new;
1325 u2 = u2_new;
1326 v2 = v2_new;
1327 }
1328 let p1 = s1.evaluate(u1, v1);
1329 let p2 = s2.evaluate(u2, v2);
1330 if vec3_norm(vec3_sub(p1, p2)) < tol * 10.0 {
1331 Some((u1, v1, u2, v2, p1))
1332 } else {
1333 None
1334 }
1335 }
1336}
1337
1338#[derive(Debug, Clone)]
1344pub struct NurbsDerivatives {
1345 pub derivs: Vec<[f64; 3]>,
1347 pub t: f64,
1349}
1350
1351impl NurbsDerivatives {
1352 pub fn compute(curve: &NurbsCurve, t: f64, n_deriv: usize) -> Self {
1354 let mut derivs = Vec::with_capacity(n_deriv + 1);
1355 derivs.push(curve.evaluate(t));
1356 if n_deriv >= 1 {
1357 derivs.push(curve.derivative(t));
1358 }
1359 for k in 2..=n_deriv {
1361 let h: f64 = 1.0e-4;
1362 let t0 = curve.knots.domain_start(curve.degree);
1363 let t1 = curve.knots.domain_end(curve.degree, curve.n_ctrl());
1364 let th = h.min((t1 - t0) / 4.0);
1365 let d_prev_hi = NurbsDerivatives::compute(curve, (t + th).min(t1), k - 1);
1366 let d_prev_lo = NurbsDerivatives::compute(curve, (t - th).max(t0), k - 1);
1367 let dk = [
1368 (d_prev_hi.derivs[k - 1][0] - d_prev_lo.derivs[k - 1][0]) / (2.0 * th),
1369 (d_prev_hi.derivs[k - 1][1] - d_prev_lo.derivs[k - 1][1]) / (2.0 * th),
1370 (d_prev_hi.derivs[k - 1][2] - d_prev_lo.derivs[k - 1][2]) / (2.0 * th),
1371 ];
1372 derivs.push(dk);
1373 }
1374 Self { derivs, t }
1375 }
1376}
1377
1378fn gauss_points_1d(n: usize) -> Vec<(f64, f64)> {
1384 match n {
1385 1 => vec![(0.0, 2.0)],
1386 2 => vec![(-1.0 / 3.0_f64.sqrt(), 1.0), (1.0 / 3.0_f64.sqrt(), 1.0)],
1387 3 => vec![
1388 (-0.774_596_669_241_483, 5.0 / 9.0),
1389 (0.0, 8.0 / 9.0),
1390 (0.774_596_669_241_483, 5.0 / 9.0),
1391 ],
1392 4 => vec![
1393 (-0.861_136_311_594_953, 0.347_854_845_137_454),
1394 (-0.339_981_043_584_856, 0.652_145_154_862_546),
1395 (0.339_981_043_584_856, 0.652_145_154_862_546),
1396 (0.861_136_311_594_953, 0.347_854_845_137_454),
1397 ],
1398 _ => {
1399 (0..n)
1401 .map(|i| {
1402 let xi = -1.0 + (2.0 * i as f64 + 1.0) / n as f64;
1403 let w = 2.0 / n as f64;
1404 (xi, w)
1405 })
1406 .collect()
1407 }
1408 }
1409}
1410
1411fn gauss_legendre_integral<F: Fn(f64) -> f64>(a: f64, b: f64, n: usize, f: F) -> f64 {
1413 let pts = gauss_points_1d(n);
1414 let mid = 0.5 * (a + b);
1415 let half = 0.5 * (b - a);
1416 pts.iter()
1417 .map(|(xi, w)| w * f(mid + half * xi) * half)
1418 .sum()
1419}
1420
1421#[cfg(test)]
1426mod tests {
1427 use super::*;
1428
1429 fn make_line_curve() -> NurbsCurve {
1430 let cps = vec![
1431 NurbsControlPoint::unweighted(0.0, 0.0, 0.0),
1432 NurbsControlPoint::unweighted(1.0, 0.0, 0.0),
1433 NurbsControlPoint::unweighted(2.0, 0.0, 0.0),
1434 ];
1435 let knots = NurbsKnotVector::clamped_uniform(3, 2);
1436 NurbsCurve::new(knots, cps, 2)
1437 }
1438
1439 fn make_quadratic_arc() -> NurbsCurve {
1440 let cps = vec![
1441 NurbsControlPoint::unweighted(0.0, 0.0, 0.0),
1442 NurbsControlPoint::unweighted(0.5, 1.0, 0.0),
1443 NurbsControlPoint::unweighted(1.0, 0.0, 0.0),
1444 ];
1445 let knots = NurbsKnotVector::clamped_uniform(3, 2);
1446 NurbsCurve::new(knots, cps, 2)
1447 }
1448
1449 #[test]
1450 fn test_knot_vector_clamped_uniform() {
1451 let kv = NurbsKnotVector::clamped_uniform(4, 2);
1452 assert_eq!(kv.len(), 7); assert_eq!(kv.knots[0], 0.0);
1454 assert_eq!(*kv.knots.last().unwrap(), 1.0);
1455 }
1456
1457 #[test]
1458 fn test_knot_vector_non_decreasing_panic() {
1459 let result = std::panic::catch_unwind(|| NurbsKnotVector::new(vec![0.0, 0.5, 0.3, 1.0]));
1460 assert!(result.is_err());
1461 }
1462
1463 #[test]
1464 fn test_knot_vector_find_span() {
1465 let kv = NurbsKnotVector::new(vec![0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0]);
1466 let span = kv.find_span(0.3, 2, 4);
1467 assert_eq!(span, 2);
1468 }
1469
1470 #[test]
1471 fn test_nurbs_basis_partition_of_unity() {
1472 let kv = NurbsKnotVector::clamped_uniform(5, 3);
1473 for t in [0.1, 0.3, 0.5, 0.7, 0.9_f64] {
1474 let basis = NurbsBasis::evaluate(&kv, t, 3, 5);
1475 let sum: f64 = basis.iter().sum();
1476 assert!(
1477 (sum - 1.0).abs() < 1e-10,
1478 "Partition of unity failed at t={t:.6}: sum={sum:.10}"
1479 );
1480 }
1481 }
1482
1483 #[test]
1484 fn test_nurbs_basis_non_negative() {
1485 let kv = NurbsKnotVector::clamped_uniform(4, 2);
1486 let basis = NurbsBasis::evaluate(&kv, 0.4, 2, 4);
1487 for &b in &basis {
1488 assert!(b >= -1e-14, "basis value negative: {:.8e}", b);
1489 }
1490 }
1491
1492 #[test]
1493 fn test_nurbs_curve_endpoints() {
1494 let curve = make_line_curve();
1495 let p0 = curve.evaluate(0.0);
1496 let p1 = curve.evaluate(1.0);
1497 assert!(p0[0].abs() < 1e-10, "start x: {:.6}", p0[0]);
1498 assert!((p1[0] - 2.0).abs() < 1e-10, "end x: {:.6}", p1[0]);
1499 }
1500
1501 #[test]
1502 fn test_nurbs_curve_midpoint() {
1503 let curve = make_line_curve();
1504 let mid = curve.evaluate(0.5);
1505 assert!((mid[0] - 1.0).abs() < 1e-8, "midpoint x: {:.6}", mid[0]);
1506 }
1507
1508 #[test]
1509 fn test_nurbs_curve_tangent_normalized() {
1510 let curve = make_line_curve();
1511 let t = curve.tangent(0.5);
1512 let n = vec3_norm(t);
1513 assert!((n - 1.0).abs() < 1e-8, "tangent norm: {:.6}", n);
1514 }
1515
1516 #[test]
1517 fn test_nurbs_curve_curvature_line() {
1518 let curve = make_line_curve();
1520 let kappa = curve.curvature(0.5);
1521 assert!(kappa.abs() < 1e-4, "line curvature: {:.8e}", kappa);
1522 }
1523
1524 #[test]
1525 fn test_nurbs_curve_arc_length() {
1526 let curve = make_line_curve();
1527 let l = curve.arc_length(8);
1528 assert!((l - 2.0).abs() < 0.01, "line arc length: {:.6}", l);
1529 }
1530
1531 #[test]
1532 fn test_nurbs_circle_on_unit_circle() {
1533 let circle = NurbsCurve::circle(1.0);
1534 for i in 0..8 {
1535 let t = i as f64 / 8.0;
1536 let p = circle.evaluate(t);
1537 let r = (p[0] * p[0] + p[1] * p[1]).sqrt();
1538 assert!((r - 1.0).abs() < 1e-8, "circle radius at t={t:.3}: {r:.8}");
1539 }
1540 }
1541
1542 #[test]
1543 fn test_nurbs_surface_flat_plane() {
1544 let surf = NurbsSurface::flat_plane(0.0, 2.0, 0.0, 3.0);
1545 let p = surf.evaluate(0.5, 0.5);
1546 assert!((p[0] - 1.0).abs() < 1e-8, "plane mid u: {:.6}", p[0]);
1548 assert!((p[1] - 1.5).abs() < 1e-8, "plane mid v: {:.6}", p[1]);
1549 }
1550
1551 #[test]
1552 fn test_nurbs_surface_normal_flat() {
1553 let surf = NurbsSurface::flat_plane(0.0, 1.0, 0.0, 1.0);
1554 let n = surf.normal(0.5, 0.5);
1555 assert!(n[2].abs() > 0.99, "flat plane normal z: {:.6}", n[2]);
1557 }
1558
1559 #[test]
1560 fn test_knot_insertion_count() {
1561 let curve = make_quadratic_arc();
1562 let new_cps =
1563 KnotInsertion::insert_once(&curve.control_points, &curve.knots, curve.degree, 0.5, 0);
1564 assert_eq!(new_cps.len(), curve.n_ctrl() + 1);
1566 }
1567
1568 #[test]
1569 fn test_degree_elevation_bezier_count() {
1570 let pts = vec![
1571 NurbsControlPoint::unweighted(0.0, 0.0, 0.0),
1572 NurbsControlPoint::unweighted(1.0, 1.0, 0.0),
1573 NurbsControlPoint::unweighted(2.0, 0.0, 0.0),
1574 ];
1575 let elevated = DegreeElevation::elevate_bezier(&pts);
1576 assert_eq!(elevated.len(), pts.len() + 1);
1577 }
1578
1579 #[test]
1580 fn test_bezier_decomposition_segments() {
1581 let curve = make_quadratic_arc();
1582 let segs = BezierDecomposition::decompose(&curve);
1583 assert!(
1584 !segs.is_empty(),
1585 "should produce at least one Bézier segment"
1586 );
1587 for seg in &segs {
1588 assert_eq!(seg.len(), curve.degree + 1, "segment must have p+1 points");
1589 }
1590 }
1591
1592 #[test]
1593 fn test_ruled_surface_boundary() {
1594 let c0 = make_line_curve();
1595 let c1_cps = vec![
1596 NurbsControlPoint::unweighted(0.0, 1.0, 0.0),
1597 NurbsControlPoint::unweighted(1.0, 1.0, 0.0),
1598 NurbsControlPoint::unweighted(2.0, 1.0, 0.0),
1599 ];
1600 let c1 = NurbsCurve::new(NurbsKnotVector::clamped_uniform(3, 2), c1_cps, 2);
1601 let ruled = RuledSurface::new(c0, c1);
1602 let p0 = ruled.evaluate(0.5, 0.0);
1603 let p1 = ruled.evaluate(0.5, 1.0);
1604 assert!(p0[1].abs() < 1e-8, "v=0 should have y=0: {:.6}", p0[1]);
1606 assert!(
1607 (p1[1] - 1.0).abs() < 1e-8,
1608 "v=1 should have y=1: {:.6}",
1609 p1[1]
1610 );
1611 }
1612
1613 #[test]
1614 fn test_ruled_surface_normal_nonzero() {
1615 let c0 = make_line_curve();
1616 let c1_cps = vec![
1617 NurbsControlPoint::unweighted(0.0, 1.0, 0.0),
1618 NurbsControlPoint::unweighted(1.0, 1.0, 0.0),
1619 NurbsControlPoint::unweighted(2.0, 1.0, 0.0),
1620 ];
1621 let c1 = NurbsCurve::new(NurbsKnotVector::clamped_uniform(3, 2), c1_cps, 2);
1622 let ruled = RuledSurface::new(c0, c1);
1623 let n = ruled.normal(0.5, 0.5);
1624 assert!(
1625 vec3_norm(n) > 0.5,
1626 "ruled surface normal degenerate: {:.6}",
1627 vec3_norm(n)
1628 );
1629 }
1630
1631 #[test]
1632 fn test_iga_element_build() {
1633 let surf = NurbsSurface::flat_plane(0.0, 1.0, 0.0, 1.0);
1634 let elem = IgaElement::build(&surf, 0.0, 1.0, 0.0, 1.0, 2, 0);
1635 assert_eq!(elem.n_gauss(), 4); for &j in &elem.jacobians {
1637 assert!(j >= 0.0, "Jacobian should be non-negative: {:.6}", j);
1638 }
1639 }
1640
1641 #[test]
1642 fn test_iga_element_integrate_unit_area() {
1643 let surf = NurbsSurface::flat_plane(0.0, 1.0, 0.0, 1.0);
1644 let elem = IgaElement::build(&surf, 0.0, 1.0, 0.0, 1.0, 3, 0);
1645 let area = elem.integrate(|_pt| 1.0);
1646 assert!((area - 1.0).abs() < 0.01, "unit area: {:.6}", area);
1648 }
1649
1650 #[test]
1651 fn test_nurbs_derivatives_count() {
1652 let curve = make_quadratic_arc();
1653 let d = NurbsDerivatives::compute(&curve, 0.5, 2);
1654 assert_eq!(d.derivs.len(), 3, "should have 0th, 1st, 2nd derivative");
1655 }
1656
1657 #[test]
1658 fn test_nurbs_trim_curve_evaluate() {
1659 let cps = vec![
1660 NurbsControlPoint::unweighted(0.0, 0.0, 0.0),
1661 NurbsControlPoint::unweighted(0.5, 0.5, 0.0),
1662 NurbsControlPoint::unweighted(1.0, 0.0, 0.0),
1663 ];
1664 let kv = NurbsKnotVector::clamped_uniform(3, 2);
1665 let trim = NurbsTrimCurve::new(cps, kv, 2);
1666 let uv = trim.evaluate(0.0);
1667 assert!(uv[0].abs() < 1e-8, "trim start u: {:.6}", uv[0]);
1668 }
1669
1670 #[test]
1671 fn test_nurbs_trimming_evaluate_inside() {
1672 let surf = NurbsSurface::flat_plane(0.0, 1.0, 0.0, 1.0);
1673 let trimmed = NurbsTrimming::new(surf);
1674 let result = trimmed.evaluate(0.5, 0.5);
1675 assert!(
1676 result.is_some(),
1677 "trimmed surface should return point inside"
1678 );
1679 }
1680
1681 #[test]
1682 fn test_surface_surface_intersector_same_plane() {
1683 let s1 = NurbsSurface::flat_plane(0.0, 1.0, 0.0, 1.0);
1685 let s2 = NurbsSurface::flat_plane(0.0, 1.0, 0.0, 1.0);
1686 let result =
1687 SurfaceSurfaceIntersector::intersect_newton(&s1, &s2, 0.5, 0.5, 0.5, 0.5, 50, 1e-6);
1688 assert!(result.is_some(), "identical planes should intersect");
1689 }
1690
1691 #[test]
1692 fn test_gauss_legendre_integral_constant() {
1693 let val = gauss_legendre_integral(0.0, 1.0, 3, |_t| 1.0);
1695 assert!((val - 1.0).abs() < 1e-10, "constant integral: {:.10}", val);
1696 }
1697
1698 #[test]
1699 fn test_gauss_legendre_integral_linear() {
1700 let val = gauss_legendre_integral(0.0, 1.0, 3, |t| t);
1702 assert!((val - 0.5).abs() < 1e-10, "linear integral: {:.10}", val);
1703 }
1704
1705 #[test]
1706 fn test_vec3_cross_orthogonal() {
1707 let x = [1.0, 0.0, 0.0];
1708 let y = [0.0, 1.0, 0.0];
1709 let z = vec3_cross(x, y);
1710 assert!((z[2] - 1.0).abs() < 1e-10);
1711 }
1712
1713 #[test]
1714 fn test_knot_vector_multiplicity() {
1715 let kv = NurbsKnotVector::new(vec![0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0]);
1716 assert_eq!(kv.multiplicity(0.0), 3);
1717 assert_eq!(kv.multiplicity(0.5), 1);
1718 }
1719
1720 #[test]
1721 fn test_pi_constant() {
1722 let pi = std::f64::consts::PI;
1723 assert!((pi - std::f64::consts::PI).abs() < 1e-15);
1724 }
1725}