1use crate::curve::{
2 basis_derivatives_into, basis_functions_into, knot_clamp, knot_domain, knot_find_span,
3 validate_knots, MAX_STACK_DEGREE, MAX_STACK_ORDER,
4};
5use crate::{KnotVector, NurbsCurve, Vec3, Vec4};
6use serde::{Deserialize, Serialize};
7
8const EPS: f64 = 1e-12;
9const NEAR_AXIS_RELATIVE_TOLERANCE: f64 = 1e-4;
13
14#[derive(Clone, Debug, Deserialize, Serialize)]
15pub struct NurbsSurface {
16 pub degree_u: usize,
17 pub degree_v: usize,
18 pub knots_u: Vec<f64>,
19 pub knots_v: Vec<f64>,
20 pub control_points: Vec<Vec<Vec4>>,
21 #[serde(skip, default)]
25 analytic: std::cell::OnceCell<Option<crate::AnalyticSurface>>,
26 #[serde(skip, default)]
28 validated: std::cell::Cell<bool>,
29 #[serde(skip, default)]
32 closed_directions: std::cell::OnceCell<(bool, bool)>,
33 #[serde(skip, default)]
36 pub(crate) projection_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
37 #[serde(skip, default)]
41 pub(crate) projection_dense_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
42 #[serde(skip, default)]
46 pub(crate) projection_ring_grid: std::cell::OnceCell<Vec<Vec3>>,
47}
48
49impl NurbsSurface {
50 pub fn new(
51 degree_u: usize,
52 degree_v: usize,
53 knots_u: Vec<f64>,
54 knots_v: Vec<f64>,
55 control_points: Vec<Vec<Vec4>>,
56 ) -> Result<Self, String> {
57 let surface = Self {
58 degree_u,
59 degree_v,
60 knots_u,
61 knots_v,
62 control_points,
63 analytic: std::cell::OnceCell::new(),
64 validated: std::cell::Cell::new(false),
65 closed_directions: std::cell::OnceCell::new(),
66 projection_grid: std::cell::OnceCell::new(),
67 projection_dense_grid: std::cell::OnceCell::new(),
68 projection_ring_grid: std::cell::OnceCell::new(),
69 };
70 surface.ensure_valid()?;
71 Ok(surface)
72 }
73
74 pub fn closed_directions(&self) -> Result<(bool, bool), String> {
79 if let Some(&cached) = self.closed_directions.get() {
80 return Ok(cached);
81 }
82 const CLOSED_SEAM_TOLERANCE: f64 = 1e-6;
83 let [u0, u1] = self.domain_u()?;
84 let [v0, v1] = self.domain_v()?;
85 let closed_along = |direction_u: bool| -> Result<bool, String> {
86 for fraction in [0.17, 0.5, 0.83] {
87 let (a, b) = if direction_u {
88 let v = v0 + (v1 - v0) * fraction;
89 (self.evaluate(u0, v)?, self.evaluate(u1, v)?)
90 } else {
91 let u = u0 + (u1 - u0) * fraction;
92 (self.evaluate(u, v0)?, self.evaluate(u, v1)?)
93 };
94 if a.sub(b).length() > CLOSED_SEAM_TOLERANCE {
95 return Ok(false);
96 }
97 }
98 Ok(true)
99 };
100 let value = (closed_along(true)?, closed_along(false)?);
101 Ok(*self.closed_directions.get_or_init(|| value))
102 }
103
104 fn ensure_valid(&self) -> Result<(), String> {
106 if self.validated.get() {
107 return Ok(());
108 }
109 validate_knots(&self.knots_u, self.degree_u)?;
110 validate_knots(&self.knots_v, self.degree_v)?;
111 let rows = self.knots_u.len() - self.degree_u - 1;
112 let columns = self.knots_v.len() - self.degree_v - 1;
113 if self.control_points.len() != rows {
114 return Err(format!(
115 "NurbsSurface: u knot vector implies {} rows, got {}",
116 rows,
117 self.control_points.len()
118 ));
119 }
120 for row in &self.control_points {
121 if row.len() != columns {
122 return Err(format!(
123 "NurbsSurface: v knot vector implies {} columns, got {}",
124 columns,
125 row.len()
126 ));
127 }
128 if row.iter().any(|point| {
129 point.w <= EPS
130 || ![point.x, point.y, point.z, point.w]
131 .iter()
132 .all(|value| value.is_finite())
133 }) {
134 return Err(
135 "NurbsSurface: control points must be finite with positive weights".into(),
136 );
137 }
138 }
139 self.validated.set(true);
140 Ok(())
141 }
142
143 pub fn domain_u(&self) -> Result<[f64; 2], String> {
144 self.ensure_valid()?;
145 Ok(knot_domain(&self.knots_u, self.degree_u))
146 }
147
148 pub fn domain_v(&self) -> Result<[f64; 2], String> {
149 self.ensure_valid()?;
150 Ok(knot_domain(&self.knots_v, self.degree_v))
151 }
152
153 pub fn analytic(&self) -> Option<&crate::AnalyticSurface> {
156 self.analytic
157 .get_or_init(|| crate::analytic_surface::recognize(self))
158 .as_ref()
159 }
160
161 fn knot_vectors(&self) -> Result<(KnotVector, KnotVector), String> {
162 Ok((
163 KnotVector::new(self.knots_u.clone(), self.degree_u)?,
164 KnotVector::new(self.knots_v.clone(), self.degree_v)?,
165 ))
166 }
167
168 pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String> {
169 self.ensure_valid()?;
170 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
171 return self.evaluate_homogeneous_heap(u, v);
172 }
173 let u = knot_clamp(&self.knots_u, self.degree_u, u);
174 let v = knot_clamp(&self.knots_v, self.degree_v, v);
175 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
176 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
177 let mut basis_u = [0.0f64; MAX_STACK_ORDER];
178 let mut basis_v = [0.0f64; MAX_STACK_ORDER];
179 basis_functions_into(&self.knots_u, self.degree_u, span_u, u, &mut basis_u);
180 basis_functions_into(&self.knots_v, self.degree_v, span_v, v, &mut basis_v);
181 let mut point = Vec4 {
182 x: 0.0,
183 y: 0.0,
184 z: 0.0,
185 w: 0.0,
186 };
187 for (i, value_u) in basis_u[..=self.degree_u].iter().enumerate() {
188 let row = &self.control_points[span_u - self.degree_u + i];
189 for (j, value_v) in basis_v[..=self.degree_v].iter().enumerate() {
190 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
191 }
192 }
193 Ok(point)
194 }
195
196 fn evaluate_homogeneous_heap(&self, u: f64, v: f64) -> Result<Vec4, String> {
197 let (knot_u, knot_v) = self.knot_vectors()?;
198 let u = knot_u.clamp_param(u);
199 let v = knot_v.clamp_param(v);
200 let span_u = knot_u.find_span(u);
201 let span_v = knot_v.find_span(v);
202 let basis_u = knot_u.basis_functions(span_u, u);
203 let basis_v = knot_v.basis_functions(span_v, v);
204 let mut point = Vec4 {
205 x: 0.0,
206 y: 0.0,
207 z: 0.0,
208 w: 0.0,
209 };
210 for (i, value_u) in basis_u.iter().enumerate() {
211 let row = &self.control_points[span_u - self.degree_u + i];
212 for (j, value_v) in basis_v.iter().enumerate() {
213 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
214 }
215 }
216 Ok(point)
217 }
218
219 pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String> {
220 self.evaluate_homogeneous(u, v)?.point()
221 }
222
223 pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String> {
229 Ok(self.derivatives_extended(u, v, 0)?[0][0])
230 }
231
232 pub fn derivatives_extended(
237 &self,
238 u: f64,
239 v: f64,
240 derivative_count: usize,
241 ) -> Result<Vec<Vec<Vec3>>, String> {
242 let [u0, u1] = self.domain_u()?;
243 let [v0, v1] = self.domain_v()?;
244 let (closed_u, closed_v) = self.closed_directions()?;
245 let mut uu = u;
246 let mut vv = v;
247 if closed_u && (u < u0 || u > u1) {
248 uu = u0 + (u - u0).rem_euclid(u1 - u0);
249 }
250 if closed_v && (v < v0 || v > v1) {
251 vv = v0 + (v - v0).rem_euclid(v1 - v0);
252 }
253 let du_out = if uu < u0 {
254 uu - u0
255 } else if uu > u1 {
256 uu - u1
257 } else {
258 0.0
259 };
260 let dv_out = if vv < v0 {
261 vv - v0
262 } else if vv > v1 {
263 vv - v1
264 } else {
265 0.0
266 };
267 if du_out == 0.0 && dv_out == 0.0 {
268 return self.derivatives(uu, vv, derivative_count);
269 }
270 let anchor_u = uu - du_out;
271 let anchor_v = vv - dv_out;
272 let base = self.derivatives(anchor_u, anchor_v, derivative_count.max(1))?;
273 let order = derivative_count + 1;
274 let mut result = vec![vec![Vec3::default(); order]; order];
275 result[0][0] = base[0][0]
279 .add(base[1][0].scale(du_out))
280 .add(base[0][1].scale(dv_out))
281 .add(base[1][1].scale(du_out * dv_out));
282 if derivative_count >= 1 {
283 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
284 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
285 result[1][1] = base[1][1];
286 }
287 if derivative_count >= 2 {
288 let second = self.derivatives(anchor_u, anchor_v, 2)?;
289 if du_out == 0.0 {
290 result[2][0] = second[2][0];
291 }
292 if dv_out == 0.0 {
293 result[0][2] = second[0][2];
294 }
295 }
296 Ok(result)
297 }
298
299 pub fn derivatives(
300 &self,
301 u: f64,
302 v: f64,
303 derivative_count: usize,
304 ) -> Result<Vec<Vec<Vec3>>, String> {
305 self.ensure_valid()?;
306 let du = derivative_count.min(self.degree_u);
307 let dv = derivative_count.min(self.degree_v);
308 let stride = derivative_count + 1;
309 let zero = Vec4 {
311 x: 0.0,
312 y: 0.0,
313 z: 0.0,
314 w: 0.0,
315 };
316 let mut homogeneous = vec![zero; stride * stride];
317 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
318 let u = knot_clamp(&self.knots_u, self.degree_u, u);
319 let v = knot_clamp(&self.knots_v, self.degree_v, v);
320 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
321 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
322 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
323 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
324 basis_derivatives_into(
325 &self.knots_u,
326 self.degree_u,
327 span_u,
328 u,
329 du,
330 &mut basis_u[..=du],
331 );
332 basis_derivatives_into(
333 &self.knots_v,
334 self.degree_v,
335 span_v,
336 v,
337 dv,
338 &mut basis_v[..=dv],
339 );
340 for k in 0..=du {
341 for l in 0..=dv {
342 if k + l > derivative_count {
343 continue;
344 }
345 let mut point = zero;
346 for i in 0..=self.degree_u {
347 let row = &self.control_points[span_u - self.degree_u + i];
348 for j in 0..=self.degree_v {
349 point = point.add(
350 row[span_v - self.degree_v + j]
351 .scale(basis_u[k][i] * basis_v[l][j]),
352 );
353 }
354 }
355 homogeneous[k * stride + l] = point;
356 }
357 }
358 } else {
359 let (knot_u, knot_v) = self.knot_vectors()?;
360 let u = knot_u.clamp_param(u);
361 let v = knot_v.clamp_param(v);
362 let span_u = knot_u.find_span(u);
363 let span_v = knot_v.find_span(v);
364 let basis_u = knot_u.basis_derivatives(span_u, u, du);
365 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
366 for k in 0..=du {
367 for l in 0..=dv {
368 if k + l > derivative_count {
369 continue;
370 }
371 let mut point = zero;
372 for i in 0..=self.degree_u {
373 let row = &self.control_points[span_u - self.degree_u + i];
374 for j in 0..=self.degree_v {
375 point = point.add(
376 row[span_v - self.degree_v + j]
377 .scale(basis_u[k][i] * basis_v[l][j]),
378 );
379 }
380 }
381 homogeneous[k * stride + l] = point;
382 }
383 }
384 }
385
386 let mut result = vec![vec![Vec3::default(); derivative_count + 1]; derivative_count + 1];
387 let weight = homogeneous[0].w;
388 if weight.abs() <= EPS {
389 return Err("NurbsSurface: zero evaluated weight".into());
390 }
391 for k in 0..=derivative_count {
392 for l in 0..=derivative_count - k {
393 if k > du || l > dv {
394 continue;
395 }
396 let at = |row: usize, column: usize| homogeneous[row * stride + column];
397 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
398 for j in 1..=l {
399 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
400 }
401 for i in 1..=k {
402 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
403 let mut mixed = Vec3::default();
404 for j in 1..=l {
405 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
406 }
407 value = value.sub(mixed.scale(binomial(k, i)));
408 }
409 result[k][l] = value.scale(1.0 / weight);
410 }
411 }
412 Ok(result)
413 }
414
415 pub(crate) fn derivatives_small(
430 &self,
431 u: f64,
432 v: f64,
433 derivative_count: usize,
434 ) -> Result<[[Vec3; 3]; 3], String> {
435 debug_assert!(derivative_count <= 2);
436 self.ensure_valid()?;
437 let du = derivative_count.min(self.degree_u);
438 let dv = derivative_count.min(self.degree_v);
439 let stride = derivative_count + 1;
440 let zero = Vec4 {
441 x: 0.0,
442 y: 0.0,
443 z: 0.0,
444 w: 0.0,
445 };
446 let mut homogeneous = [zero; 9];
447 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
448 let u = knot_clamp(&self.knots_u, self.degree_u, u);
449 let v = knot_clamp(&self.knots_v, self.degree_v, v);
450 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
451 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
452 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
453 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
454 basis_derivatives_into(
455 &self.knots_u,
456 self.degree_u,
457 span_u,
458 u,
459 du,
460 &mut basis_u[..=du],
461 );
462 basis_derivatives_into(
463 &self.knots_v,
464 self.degree_v,
465 span_v,
466 v,
467 dv,
468 &mut basis_v[..=dv],
469 );
470 for k in 0..=du {
471 for l in 0..=dv {
472 if k + l > derivative_count {
473 continue;
474 }
475 let mut point = zero;
476 for i in 0..=self.degree_u {
477 let row = &self.control_points[span_u - self.degree_u + i];
478 for j in 0..=self.degree_v {
479 point = point.add(
480 row[span_v - self.degree_v + j]
481 .scale(basis_u[k][i] * basis_v[l][j]),
482 );
483 }
484 }
485 homogeneous[k * stride + l] = point;
486 }
487 }
488 } else {
489 let (knot_u, knot_v) = self.knot_vectors()?;
490 let u = knot_u.clamp_param(u);
491 let v = knot_v.clamp_param(v);
492 let span_u = knot_u.find_span(u);
493 let span_v = knot_v.find_span(v);
494 let basis_u = knot_u.basis_derivatives(span_u, u, du);
495 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
496 for k in 0..=du {
497 for l in 0..=dv {
498 if k + l > derivative_count {
499 continue;
500 }
501 let mut point = zero;
502 for i in 0..=self.degree_u {
503 let row = &self.control_points[span_u - self.degree_u + i];
504 for j in 0..=self.degree_v {
505 point = point.add(
506 row[span_v - self.degree_v + j]
507 .scale(basis_u[k][i] * basis_v[l][j]),
508 );
509 }
510 }
511 homogeneous[k * stride + l] = point;
512 }
513 }
514 }
515
516 let mut result = [[Vec3::default(); 3]; 3];
517 let weight = homogeneous[0].w;
518 if weight.abs() <= EPS {
519 return Err("NurbsSurface: zero evaluated weight".into());
520 }
521 for k in 0..=derivative_count {
522 for l in 0..=derivative_count - k {
523 if k > du || l > dv {
524 continue;
525 }
526 let at = |row: usize, column: usize| homogeneous[row * stride + column];
527 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
528 for j in 1..=l {
529 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
530 }
531 for i in 1..=k {
532 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
533 let mut mixed = Vec3::default();
534 for j in 1..=l {
535 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
536 }
537 value = value.sub(mixed.scale(binomial(k, i)));
538 }
539 result[k][l] = value.scale(1.0 / weight);
540 }
541 }
542 Ok(result)
543 }
544
545 #[inline]
548 pub(crate) fn deriv1(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
549 let g = self.derivatives_small(u, v, 1)?;
550 Ok((g[0][0], g[1][0], g[0][1]))
551 }
552
553 pub(crate) fn deriv1_extended(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
561 let [u0, u1] = self.domain_u()?;
562 let [v0, v1] = self.domain_v()?;
563 let (closed_u, closed_v) = self.closed_directions()?;
564 let mut uu = u;
565 let mut vv = v;
566 if closed_u && (u < u0 || u > u1) {
567 uu = u0 + (u - u0).rem_euclid(u1 - u0);
568 }
569 if closed_v && (v < v0 || v > v1) {
570 vv = v0 + (v - v0).rem_euclid(v1 - v0);
571 }
572 let du_out = if uu < u0 {
573 uu - u0
574 } else if uu > u1 {
575 uu - u1
576 } else {
577 0.0
578 };
579 let dv_out = if vv < v0 {
580 vv - v0
581 } else if vv > v1 {
582 vv - v1
583 } else {
584 0.0
585 };
586 if du_out == 0.0 && dv_out == 0.0 {
587 let g = self.derivatives_small(uu, vv, 1)?;
588 return Ok((g[0][0], g[1][0], g[0][1]));
589 }
590 let anchor_u = uu - du_out;
591 let anchor_v = vv - dv_out;
592 let base = self.derivatives_small(anchor_u, anchor_v, 1)?;
593 let s = base[0][0]
597 .add(base[1][0].scale(du_out))
598 .add(base[0][1].scale(dv_out))
599 .add(base[1][1].scale(du_out * dv_out));
600 let su = base[1][0].add(base[1][1].scale(dv_out));
601 let sv = base[0][1].add(base[1][1].scale(du_out));
602 Ok((s, su, sv))
603 }
604
605 pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
606 let derivatives = self.derivatives(u, v, 1)?;
607 derivatives[1][0].cross(derivatives[0][1]).normalized()
608 }
609
610 pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String> {
617 let derivatives = self.derivatives(u, v, 2)?;
618 let su = derivatives[1][0];
619 let sv = derivatives[0][1];
620 let cross = su.cross(sv);
621 let cross_length = cross.length();
622 if cross_length <= 1e-12 {
623 return Err(format!(
624 "degenerate parametrization at (u={u:.4}, v={v:.4})"
625 ));
626 }
627 let normal = cross.scale(1.0 / cross_length);
628 let e1 = su.dot(su);
629 let f1 = su.dot(sv);
630 let g1 = sv.dot(sv);
631 let l2 = derivatives[2][0].dot(normal);
632 let m2 = derivatives[1][1].dot(normal);
633 let n2 = derivatives[0][2].dot(normal);
634 let denominator = e1 * g1 - f1 * f1;
635 let mean_double = (l2 * g1 - 2.0 * m2 * f1 + n2 * e1) / denominator; let gauss = (l2 * n2 - m2 * m2) / denominator; let discriminant = (mean_double * mean_double * 0.25 - gauss).max(0.0).sqrt();
638 Ok((
639 mean_double * 0.5 - discriminant,
640 mean_double * 0.5 + discriminant,
641 ))
642 }
643
644 pub fn is_affine(&self) -> Result<bool, String> {
649 if self.degree_u != 1
650 || self.degree_v != 1
651 || self.control_points.len() != 2
652 || self.control_points[0].len() != 2
653 || self.control_points[1].len() != 2
654 {
655 return Ok(false);
656 }
657 let weight = self.control_points[0][0].w;
658 if [
659 self.control_points[0][1].w,
660 self.control_points[1][0].w,
661 self.control_points[1][1].w,
662 ]
663 .iter()
664 .any(|other| (*other - weight).abs() > 1e-12 * other.abs().max(weight.abs()))
665 {
666 return Ok(false);
667 }
668 let p00 = self.control_points[0][0].point()?;
669 let p01 = self.control_points[0][1].point()?;
670 let p10 = self.control_points[1][0].point()?;
671 let p11 = self.control_points[1][1].point()?;
672 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
673 let extent = [
678 p01.sub(p00),
679 p10.sub(p00),
680 p11.sub(p00),
681 p10.sub(p01),
682 p11.sub(p01),
683 p11.sub(p10),
684 ]
685 .iter()
686 .map(|delta| delta.length())
687 .fold(0.0, f64::max);
688 Ok(gap <= 1e-9 * (1.0 + extent))
689 }
690
691 pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
692 self.ensure_valid()?;
693 let u = knot_clamp(&self.knots_u, self.degree_u, u);
694 let span = knot_find_span(&self.knots_u, self.degree_u, u);
695 let basis = if self.degree_u <= MAX_STACK_DEGREE {
696 let mut stack = [0.0f64; MAX_STACK_ORDER];
697 basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
698 stack[..=self.degree_u].to_vec()
699 } else {
700 KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
701 };
702 let mut points = Vec::with_capacity(self.control_points[0].len());
703 for column in 0..self.control_points[0].len() {
704 let mut point = Vec4 {
705 x: 0.0,
706 y: 0.0,
707 z: 0.0,
708 w: 0.0,
709 };
710 for (index, value) in basis.iter().enumerate() {
711 point = point
712 .add(self.control_points[span - self.degree_u + index][column].scale(*value));
713 }
714 points.push(point);
715 }
716 NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
717 }
718
719 pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
720 self.ensure_valid()?;
721 let v = knot_clamp(&self.knots_v, self.degree_v, v);
722 let span = knot_find_span(&self.knots_v, self.degree_v, v);
723 let basis = if self.degree_v <= MAX_STACK_DEGREE {
724 let mut stack = [0.0f64; MAX_STACK_ORDER];
725 basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
726 stack[..=self.degree_v].to_vec()
727 } else {
728 KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
729 };
730 let mut points = Vec::with_capacity(self.control_points.len());
731 for row in &self.control_points {
732 let mut point = Vec4 {
733 x: 0.0,
734 y: 0.0,
735 z: 0.0,
736 w: 0.0,
737 };
738 for (index, value) in basis.iter().enumerate() {
739 point = point.add(row[span - self.degree_v + index].scale(*value));
740 }
741 points.push(point);
742 }
743 NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
744 }
745}
746
747pub fn make_plane(
748 origin: Vec3,
749 u_direction: Vec3,
750 v_direction: Vec3,
751 u_max: f64,
752 v_max: f64,
753) -> Result<NurbsSurface, String> {
754 if u_max <= EPS || v_max <= EPS {
755 return Err("makePlane: parameter extents must be positive".into());
756 }
757 let p10 = origin.add(u_direction.scale(u_max));
758 let p01 = origin.add(v_direction.scale(v_max));
759 let p11 = p10.add(v_direction.scale(v_max));
760 NurbsSurface::new(
761 1,
762 1,
763 vec![0.0, 0.0, u_max, u_max],
764 vec![0.0, 0.0, v_max, v_max],
765 vec![
766 vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
767 vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
768 ],
769 )
770}
771
772pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
773 let rows = profile
774 .control_points
775 .iter()
776 .map(|point| {
777 vec![
778 *point,
779 Vec4 {
780 x: point.x + point.w * direction.x,
781 y: point.y + point.w * direction.y,
782 z: point.z + point.w * direction.z,
783 w: point.w,
784 },
785 ]
786 })
787 .collect();
788 NurbsSurface::new(
789 profile.degree,
790 1,
791 profile.knots.clone(),
792 vec![0.0, 0.0, 1.0, 1.0],
793 rows,
794 )
795}
796
797pub fn make_revolution(
798 axis_point: Vec3,
799 axis_direction: Vec3,
800 generatrix: &NurbsCurve,
801 theta: f64,
802) -> Result<NurbsSurface, String> {
803 if theta <= EPS || theta > std::f64::consts::TAU + EPS {
804 return Err("makeRevolution: theta must be in (0, 2*PI]".into());
805 }
806 let theta = theta.min(std::f64::consts::TAU);
807 let axis = axis_direction.normalized()?;
808 let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
809 let arc_angle = theta / arc_count as f64;
810 let middle_weight = (arc_angle / 2.0).cos();
811 let mut knots_u = vec![0.0, 0.0, 0.0];
812 for index in 1..arc_count {
813 let knot = index as f64 / arc_count as f64;
814 knots_u.extend([knot, knot]);
815 }
816 knots_u.extend([1.0, 1.0, 1.0]);
817 let mut rows = vec![
818 vec![
819 Vec4 {
820 x: 0.0,
821 y: 0.0,
822 z: 0.0,
823 w: 1.0,
824 };
825 generatrix.control_points.len()
826 ];
827 2 * arc_count + 1
828 ];
829
830 let mut radial_extent: f64 = 0.0;
833 for control_point in &generatrix.control_points {
834 let point = control_point.point()?;
835 let offset = point.sub(axis_point);
836 radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
837 }
838
839 for (column, control_point) in generatrix.control_points.iter().enumerate() {
840 let point = control_point.point()?;
841 let weight = control_point.w;
842 let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
843 let radial_vector = point.sub(axis_origin);
844 let measured_radius = radial_vector.length();
854 let pole_tolerance = EPS.max(1e-8 * radial_extent);
855 let radius = if measured_radius <= pole_tolerance {
856 0.0
857 } else {
858 measured_radius
859 };
860 let (x_axis, y_axis) = if radius <= EPS {
861 (Vec3::default(), Vec3::default())
862 } else {
863 if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
875 return Err(format!(
876 "makeRevolution: generatrix control point {column} sits {radius:e} from the \
877 axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
878 radial extent (a fuzzy pole)"
879 ));
880 }
881 let x_axis = radial_vector.scale(1.0 / radius);
882 (x_axis, axis.cross(x_axis))
883 };
884 rows[0][column] = Vec4::from_point(point, weight);
885 let mut start_point = point;
886 let mut start_tangent = y_axis.scale(radius);
887 let mut angle = 0.0;
888 for arc in 1..=arc_count {
889 angle += arc_angle;
890 let end_point = if radius <= EPS {
891 point
892 } else {
893 axis_origin
894 .add(x_axis.scale(radius * angle.cos()))
895 .add(y_axis.scale(radius * angle.sin()))
896 };
897 let end_tangent = if radius <= EPS {
898 Vec3::default()
899 } else {
900 x_axis
901 .scale(-radius * angle.sin())
902 .add(y_axis.scale(radius * angle.cos()))
903 };
904 let row = 2 * (arc - 1);
905 if radius <= EPS {
906 rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
907 } else {
908 let cross = start_tangent.cross(end_tangent);
909 let denominator = cross.length_squared();
910 let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
919 if denominator <= EPS * tangent_scale {
920 return Err("makeRevolution: arc tangents are parallel".into());
921 }
922 let distance =
923 end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
924 let middle = start_point.add(start_tangent.scale(distance));
925 rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
926 }
927 rows[row + 2][column] = Vec4::from_point(end_point, weight);
928 start_point = end_point;
929 start_tangent = end_tangent;
930 }
931 }
932 NurbsSurface::new(
933 2,
934 generatrix.degree,
935 knots_u,
936 generatrix.knots.clone(),
937 rows,
938 )
939}
940
941pub fn make_cylinder_surface(
942 base: Vec3,
943 axis_direction: Vec3,
944 radius: f64,
945 height: f64,
946) -> Result<NurbsSurface, String> {
947 if radius <= EPS || height <= EPS {
948 return Err("makeCylinder: radius and height must be positive".into());
949 }
950 let axis = axis_direction.normalized()?;
951 let x_axis = axis.perpendicular()?;
952 let start = base.add(x_axis.scale(radius));
953 let end = start.add(axis.scale(height));
954 let generatrix = crate::make_line(start, end)?;
955 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
956}
957
958pub fn make_cone_surface(
959 base: Vec3,
960 axis_direction: Vec3,
961 radius_bottom: f64,
962 radius_top: f64,
963 height: f64,
964) -> Result<NurbsSurface, String> {
965 if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
966 return Err("makeCone: invalid radius or height".into());
967 }
968 let axis = axis_direction.normalized()?;
969 let x_axis = axis.perpendicular()?;
970 let start = base.add(x_axis.scale(radius_bottom));
971 let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
972 let generatrix = crate::make_line(start, end)?;
973 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
974}
975
976pub fn make_sphere_surface(
977 center: Vec3,
978 radius: f64,
979 polar_axis: Vec3,
980) -> Result<NurbsSurface, String> {
981 make_sphere_surface_framed(center, radius, polar_axis, None)
982}
983
984pub fn make_sphere_surface_framed(
996 center: Vec3,
997 radius: f64,
998 polar_axis: Vec3,
999 seam_direction: Option<Vec3>,
1000) -> Result<NurbsSurface, String> {
1001 if radius <= EPS {
1002 return Err("makeSphere: radius must be positive".into());
1003 }
1004 let axis = polar_axis.normalized()?;
1005 let x_axis = match seam_direction {
1006 Some(seam) => {
1007 let planar = seam.sub(axis.scale(seam.dot(axis)));
1008 match planar.normalized() {
1009 Ok(unit) if planar.length() > EPS => unit,
1010 _ => axis.perpendicular()?,
1011 }
1012 }
1013 None => axis.perpendicular()?,
1014 };
1015 let meridian = crate::make_arc(
1016 center,
1017 x_axis,
1018 axis,
1019 radius,
1020 -std::f64::consts::FRAC_PI_2,
1021 std::f64::consts::FRAC_PI_2,
1022 )?;
1023 make_revolution(center, axis, &meridian, std::f64::consts::TAU)
1024}
1025
1026pub fn make_torus_surface(
1027 center: Vec3,
1028 axis_direction: Vec3,
1029 major_radius: f64,
1030 minor_radius: f64,
1031) -> Result<NurbsSurface, String> {
1032 if minor_radius <= EPS || major_radius <= minor_radius {
1033 return Err("makeTorus: requires positive minorRadius < majorRadius".into());
1034 }
1035 let axis = axis_direction.normalized()?;
1036 let x_axis = axis.perpendicular()?;
1037 let tube_center = center.add(x_axis.scale(major_radius));
1038 let tube = crate::make_arc(
1039 tube_center,
1040 x_axis,
1041 axis,
1042 minor_radius,
1043 0.0,
1044 std::f64::consts::TAU,
1045 )?;
1046 make_revolution(center, axis, &tube, std::f64::consts::TAU)
1047}
1048
1049fn binomial(n: usize, k: usize) -> f64 {
1050 if k > n {
1051 return 0.0;
1052 }
1053 let k = k.min(n - k);
1054 (1..=k).fold(1.0, |value, index| {
1055 value * (n - k + index) as f64 / index as f64
1056 })
1057}
1058
1059pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
1075 let inflate = if inflate.is_finite() {
1076 inflate.clamp(1.0, 16.0)
1077 } else {
1078 2.0
1079 };
1080 let (closed_u, closed_v) = surface.closed_directions()?;
1081 let [u0, u1] = surface.domain_u()?;
1082 let [v0, v1] = surface.domain_v()?;
1083 let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
1084 if closed || inflate <= 1.0 {
1085 (d0, d1)
1086 } else {
1087 let centre = 0.5 * (d0 + d1);
1088 let half = 0.5 * (d1 - d0) * inflate;
1089 (centre - half, centre + half)
1090 }
1091 };
1092 let (nu0, nu1) = stretch(u0, u1, closed_u);
1093 let (nv0, nv1) = stretch(v0, v1, closed_v);
1094 if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
1095 return Ok(surface.clone());
1096 }
1097 if surface.is_affine()? {
1098 let corners = [
1101 surface.evaluate_extended(nu0, nv0)?,
1102 surface.evaluate_extended(nu1, nv0)?,
1103 surface.evaluate_extended(nu0, nv1)?,
1104 surface.evaluate_extended(nu1, nv1)?,
1105 ];
1106 return NurbsSurface::new(
1107 1,
1108 1,
1109 vec![nu0, nu0, nu1, nu1],
1110 vec![nv0, nv0, nv1, nv1],
1111 vec![
1112 vec![
1113 Vec4::from_point(corners[0], 1.0),
1114 Vec4::from_point(corners[2], 1.0),
1115 ],
1116 vec![
1117 Vec4::from_point(corners[1], 1.0),
1118 Vec4::from_point(corners[3], 1.0),
1119 ],
1120 ],
1121 );
1122 }
1123 const SAMPLES: usize = 32;
1124 let u_params: Vec<f64> = (0..=SAMPLES)
1125 .map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
1126 .collect();
1127 let v_params: Vec<f64> = (0..=SAMPLES)
1128 .map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
1129 .collect();
1130 let mut column_curves = Vec::with_capacity(u_params.len());
1133 for &u in &u_params {
1134 let column: Vec<Vec3> = v_params
1135 .iter()
1136 .map(|&v| surface.evaluate_extended(u, v))
1137 .collect::<Result<_, _>>()?;
1138 column_curves.push(crate::interpolate_curve(
1139 &column,
1140 3,
1141 &normalized(&v_params),
1142 )?);
1143 }
1144 let v_knots = column_curves[0].knots.clone();
1145 let control_rows = column_curves[0].control_points.len();
1146 let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
1147 let mut u_knots = Vec::new();
1148 for row_index in 0..control_rows {
1149 let row: Vec<Vec3> = column_curves
1150 .iter()
1151 .map(|curve| curve.control_points[row_index].point())
1152 .collect::<Result<_, _>>()?;
1153 let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
1154 u_knots = fitted.knots.clone();
1155 for (u_index, control) in fitted.control_points.iter().enumerate() {
1156 if grid.len() <= u_index {
1157 grid.push(Vec::new());
1158 }
1159 grid[u_index].push(*control);
1160 }
1161 }
1162 let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
1165 knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
1166 };
1167 NurbsSurface::new(
1168 3,
1169 3,
1170 rescale(&u_knots, nu0, nu1),
1171 rescale(&v_knots, nv0, nv1),
1172 grid,
1173 )
1174}
1175
1176fn normalized(parameters: &[f64]) -> Vec<f64> {
1177 let first = parameters[0];
1178 let last = parameters[parameters.len() - 1];
1179 parameters
1180 .iter()
1181 .map(|p| (p - first) / (last - first))
1182 .collect()
1183}
1184
1185