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 is_affine(&self) -> Result<bool, String> {
615 if self.degree_u != 1
616 || self.degree_v != 1
617 || self.control_points.len() != 2
618 || self.control_points[0].len() != 2
619 || self.control_points[1].len() != 2
620 {
621 return Ok(false);
622 }
623 let weight = self.control_points[0][0].w;
624 if [
625 self.control_points[0][1].w,
626 self.control_points[1][0].w,
627 self.control_points[1][1].w,
628 ]
629 .iter()
630 .any(|other| (*other - weight).abs() > 1e-12)
631 {
632 return Ok(false);
633 }
634 let p00 = self.control_points[0][0].point()?;
635 let p01 = self.control_points[0][1].point()?;
636 let p10 = self.control_points[1][0].point()?;
637 let p11 = self.control_points[1][1].point()?;
638 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
639 Ok(gap <= 1e-9 * (1.0 + p00.length().max(p11.length())))
640 }
641
642 pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
643 self.ensure_valid()?;
644 let u = knot_clamp(&self.knots_u, self.degree_u, u);
645 let span = knot_find_span(&self.knots_u, self.degree_u, u);
646 let basis = if self.degree_u <= MAX_STACK_DEGREE {
647 let mut stack = [0.0f64; MAX_STACK_ORDER];
648 basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
649 stack[..=self.degree_u].to_vec()
650 } else {
651 KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
652 };
653 let mut points = Vec::with_capacity(self.control_points[0].len());
654 for column in 0..self.control_points[0].len() {
655 let mut point = Vec4 {
656 x: 0.0,
657 y: 0.0,
658 z: 0.0,
659 w: 0.0,
660 };
661 for (index, value) in basis.iter().enumerate() {
662 point = point
663 .add(self.control_points[span - self.degree_u + index][column].scale(*value));
664 }
665 points.push(point);
666 }
667 NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
668 }
669
670 pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
671 self.ensure_valid()?;
672 let v = knot_clamp(&self.knots_v, self.degree_v, v);
673 let span = knot_find_span(&self.knots_v, self.degree_v, v);
674 let basis = if self.degree_v <= MAX_STACK_DEGREE {
675 let mut stack = [0.0f64; MAX_STACK_ORDER];
676 basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
677 stack[..=self.degree_v].to_vec()
678 } else {
679 KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
680 };
681 let mut points = Vec::with_capacity(self.control_points.len());
682 for row in &self.control_points {
683 let mut point = Vec4 {
684 x: 0.0,
685 y: 0.0,
686 z: 0.0,
687 w: 0.0,
688 };
689 for (index, value) in basis.iter().enumerate() {
690 point = point.add(row[span - self.degree_v + index].scale(*value));
691 }
692 points.push(point);
693 }
694 NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
695 }
696}
697
698pub fn make_plane(
699 origin: Vec3,
700 u_direction: Vec3,
701 v_direction: Vec3,
702 u_max: f64,
703 v_max: f64,
704) -> Result<NurbsSurface, String> {
705 if u_max <= EPS || v_max <= EPS {
706 return Err("makePlane: parameter extents must be positive".into());
707 }
708 let p10 = origin.add(u_direction.scale(u_max));
709 let p01 = origin.add(v_direction.scale(v_max));
710 let p11 = p10.add(v_direction.scale(v_max));
711 NurbsSurface::new(
712 1,
713 1,
714 vec![0.0, 0.0, u_max, u_max],
715 vec![0.0, 0.0, v_max, v_max],
716 vec![
717 vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
718 vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
719 ],
720 )
721}
722
723pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
724 let rows = profile
725 .control_points
726 .iter()
727 .map(|point| {
728 vec![
729 *point,
730 Vec4 {
731 x: point.x + point.w * direction.x,
732 y: point.y + point.w * direction.y,
733 z: point.z + point.w * direction.z,
734 w: point.w,
735 },
736 ]
737 })
738 .collect();
739 NurbsSurface::new(
740 profile.degree,
741 1,
742 profile.knots.clone(),
743 vec![0.0, 0.0, 1.0, 1.0],
744 rows,
745 )
746}
747
748pub fn make_revolution(
749 axis_point: Vec3,
750 axis_direction: Vec3,
751 generatrix: &NurbsCurve,
752 theta: f64,
753) -> Result<NurbsSurface, String> {
754 if theta <= EPS || theta > std::f64::consts::TAU + EPS {
755 return Err("makeRevolution: theta must be in (0, 2*PI]".into());
756 }
757 let theta = theta.min(std::f64::consts::TAU);
758 let axis = axis_direction.normalized()?;
759 let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
760 let arc_angle = theta / arc_count as f64;
761 let middle_weight = (arc_angle / 2.0).cos();
762 let mut knots_u = vec![0.0, 0.0, 0.0];
763 for index in 1..arc_count {
764 let knot = index as f64 / arc_count as f64;
765 knots_u.extend([knot, knot]);
766 }
767 knots_u.extend([1.0, 1.0, 1.0]);
768 let mut rows = vec![
769 vec![
770 Vec4 {
771 x: 0.0,
772 y: 0.0,
773 z: 0.0,
774 w: 1.0,
775 };
776 generatrix.control_points.len()
777 ];
778 2 * arc_count + 1
779 ];
780
781 let mut radial_extent: f64 = 0.0;
784 for control_point in &generatrix.control_points {
785 let point = control_point.point()?;
786 let offset = point.sub(axis_point);
787 radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
788 }
789
790 for (column, control_point) in generatrix.control_points.iter().enumerate() {
791 let point = control_point.point()?;
792 let weight = control_point.w;
793 let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
794 let radial_vector = point.sub(axis_origin);
795 let measured_radius = radial_vector.length();
805 let pole_tolerance = EPS.max(1e-8 * radial_extent);
806 let radius = if measured_radius <= pole_tolerance {
807 0.0
808 } else {
809 measured_radius
810 };
811 let (x_axis, y_axis) = if radius <= EPS {
812 (Vec3::default(), Vec3::default())
813 } else {
814 if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
826 return Err(format!(
827 "makeRevolution: generatrix control point {column} sits {radius:e} from the \
828 axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
829 radial extent (a fuzzy pole)"
830 ));
831 }
832 let x_axis = radial_vector.scale(1.0 / radius);
833 (x_axis, axis.cross(x_axis))
834 };
835 rows[0][column] = Vec4::from_point(point, weight);
836 let mut start_point = point;
837 let mut start_tangent = y_axis.scale(radius);
838 let mut angle = 0.0;
839 for arc in 1..=arc_count {
840 angle += arc_angle;
841 let end_point = if radius <= EPS {
842 point
843 } else {
844 axis_origin
845 .add(x_axis.scale(radius * angle.cos()))
846 .add(y_axis.scale(radius * angle.sin()))
847 };
848 let end_tangent = if radius <= EPS {
849 Vec3::default()
850 } else {
851 x_axis
852 .scale(-radius * angle.sin())
853 .add(y_axis.scale(radius * angle.cos()))
854 };
855 let row = 2 * (arc - 1);
856 if radius <= EPS {
857 rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
858 } else {
859 let cross = start_tangent.cross(end_tangent);
860 let denominator = cross.length_squared();
861 let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
870 if denominator <= EPS * tangent_scale {
871 return Err("makeRevolution: arc tangents are parallel".into());
872 }
873 let distance =
874 end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
875 let middle = start_point.add(start_tangent.scale(distance));
876 rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
877 }
878 rows[row + 2][column] = Vec4::from_point(end_point, weight);
879 start_point = end_point;
880 start_tangent = end_tangent;
881 }
882 }
883 NurbsSurface::new(
884 2,
885 generatrix.degree,
886 knots_u,
887 generatrix.knots.clone(),
888 rows,
889 )
890}
891
892pub fn make_cylinder_surface(
893 base: Vec3,
894 axis_direction: Vec3,
895 radius: f64,
896 height: f64,
897) -> Result<NurbsSurface, String> {
898 if radius <= EPS || height <= EPS {
899 return Err("makeCylinder: radius and height must be positive".into());
900 }
901 let axis = axis_direction.normalized()?;
902 let x_axis = axis.perpendicular()?;
903 let start = base.add(x_axis.scale(radius));
904 let end = start.add(axis.scale(height));
905 let generatrix = crate::make_line(start, end)?;
906 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
907}
908
909pub fn make_cone_surface(
910 base: Vec3,
911 axis_direction: Vec3,
912 radius_bottom: f64,
913 radius_top: f64,
914 height: f64,
915) -> Result<NurbsSurface, String> {
916 if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
917 return Err("makeCone: invalid radius or height".into());
918 }
919 let axis = axis_direction.normalized()?;
920 let x_axis = axis.perpendicular()?;
921 let start = base.add(x_axis.scale(radius_bottom));
922 let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
923 let generatrix = crate::make_line(start, end)?;
924 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
925}
926
927pub fn make_sphere_surface(
928 center: Vec3,
929 radius: f64,
930 polar_axis: Vec3,
931) -> Result<NurbsSurface, String> {
932 if radius <= EPS {
933 return Err("makeSphere: radius must be positive".into());
934 }
935 let axis = polar_axis.normalized()?;
936 let x_axis = axis.perpendicular()?;
937 let meridian = crate::make_arc(
938 center,
939 x_axis,
940 axis,
941 radius,
942 -std::f64::consts::FRAC_PI_2,
943 std::f64::consts::FRAC_PI_2,
944 )?;
945 make_revolution(center, axis, &meridian, std::f64::consts::TAU)
946}
947
948pub fn make_torus_surface(
949 center: Vec3,
950 axis_direction: Vec3,
951 major_radius: f64,
952 minor_radius: f64,
953) -> Result<NurbsSurface, String> {
954 if minor_radius <= EPS || major_radius <= minor_radius {
955 return Err("makeTorus: requires positive minorRadius < majorRadius".into());
956 }
957 let axis = axis_direction.normalized()?;
958 let x_axis = axis.perpendicular()?;
959 let tube_center = center.add(x_axis.scale(major_radius));
960 let tube = crate::make_arc(
961 tube_center,
962 x_axis,
963 axis,
964 minor_radius,
965 0.0,
966 std::f64::consts::TAU,
967 )?;
968 make_revolution(center, axis, &tube, std::f64::consts::TAU)
969}
970
971fn binomial(n: usize, k: usize) -> f64 {
972 if k > n {
973 return 0.0;
974 }
975 let k = k.min(n - k);
976 (1..=k).fold(1.0, |value, index| {
977 value * (n - k + index) as f64 / index as f64
978 })
979}
980
981#[cfg(test)]
982mod extension_tests {
983 use crate::{make_cylinder_surface, make_line, make_plane, Vec3};
984
985 #[test]
986 fn curve_extension_wraps_closed_and_extends_open() {
987 let line = make_line(Vec3::default(), Vec3::new(10.0, 0.0, 0.0)).unwrap();
988 let [start, end] = line.domain().unwrap();
989 let beyond = line.evaluate_extended(end + (end - start) * 0.5).unwrap();
990 assert!(beyond.sub(Vec3::new(15.0, 0.0, 0.0)).length() < 1e-9);
991 let circle = crate::make_arc(
992 Vec3::default(),
993 Vec3::new(1.0, 0.0, 0.0),
994 Vec3::new(0.0, 1.0, 0.0),
995 2.0,
996 0.0,
997 std::f64::consts::TAU,
998 )
999 .unwrap();
1000 let [c0, c1] = circle.domain().unwrap();
1001 let wrapped = circle.evaluate_extended(c1 + (c1 - c0) * 0.25).unwrap();
1002 let reference = circle.evaluate(c0 + (c1 - c0) * 0.25).unwrap();
1003 assert!(wrapped.sub(reference).length() < 1e-9);
1004 }
1005
1006 #[test]
1007 fn surface_extension_is_exact_on_planes_and_wraps_closed_directions() {
1008 let plane = make_plane(
1009 Vec3::default(),
1010 Vec3::new(1.0, 0.0, 0.0),
1011 Vec3::new(0.0, 1.0, 0.0),
1012 4.0,
1013 3.0,
1014 )
1015 .unwrap();
1016 let [u0, u1] = plane.domain_u().unwrap();
1017 let [v0, v1] = plane.domain_v().unwrap();
1018 let du = (u1 - u0) * 0.5;
1020 let dv = (v1 - v0) * 0.25;
1021 let extended = plane.evaluate_extended(u1 + du, v1 + dv).unwrap();
1022 assert!(extended.sub(Vec3::new(6.0, 3.75, 0.0)).length() < 1e-9);
1023 let cylinder =
1024 make_cylinder_surface(Vec3::default(), Vec3::new(0.0, 0.0, 1.0), 2.0, 5.0).unwrap();
1025 let [cu0, cu1] = cylinder.domain_u().unwrap();
1026 let [cv0, cv1] = cylinder.domain_v().unwrap();
1027 let wrapped = cylinder
1028 .evaluate_extended(cu1 + (cu1 - cu0) * 0.125, (cv0 + cv1) * 0.5)
1029 .unwrap();
1030 let reference = cylinder
1031 .evaluate(cu0 + (cu1 - cu0) * 0.125, (cv0 + cv1) * 0.5)
1032 .unwrap();
1033 assert!(wrapped.sub(reference).length() < 1e-9);
1034 let beyond = cylinder
1036 .evaluate_extended(cu0, cv1 + (cv1 - cv0) * 0.2)
1037 .unwrap();
1038 let top = cylinder.evaluate(cu0, cv1).unwrap();
1039 assert!((beyond.z - (top.z + 1.0)).abs() < 1e-9);
1040 assert!((beyond.x - top.x).abs() < 1e-9 && (beyond.y - top.y).abs() < 1e-9);
1041 }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046 use super::*;
1047
1048 fn plane() -> NurbsSurface {
1049 NurbsSurface::new(
1050 1,
1051 1,
1052 vec![0.0, 0.0, 4.0, 4.0],
1053 vec![-2.0, -2.0, 3.0, 3.0],
1054 vec![
1055 vec![
1056 Vec4::from_point(Vec3::new(1.0, 2.0, 3.0), 1.0),
1057 Vec4::from_point(Vec3::new(1.0, 2.0, 13.0), 1.0),
1058 ],
1059 vec![
1060 Vec4::from_point(Vec3::new(9.0, 2.0, 3.0), 1.0),
1061 Vec4::from_point(Vec3::new(9.0, 2.0, 13.0), 1.0),
1062 ],
1063 ],
1064 )
1065 .unwrap()
1066 }
1067
1068 #[test]
1069 fn affine_surface_evaluation_and_partials_are_exact() {
1070 let surface = plane();
1071 let point = surface.evaluate(1.0, 0.5).unwrap();
1072 assert!(point.sub(Vec3::new(3.0, 2.0, 8.0)).length() < 1e-13);
1073 let derivatives = surface.derivatives(1.0, 0.5, 2).unwrap();
1074 assert!(derivatives[1][0].sub(Vec3::new(2.0, 0.0, 0.0)).length() < 1e-13);
1075 assert!(derivatives[0][1].sub(Vec3::new(0.0, 0.0, 2.0)).length() < 1e-13);
1076 assert!(
1077 surface
1078 .normal(1.0, 0.5)
1079 .unwrap()
1080 .sub(Vec3::new(0.0, -1.0, 0.0))
1081 .length()
1082 < 1e-13
1083 );
1084 }
1085
1086 #[test]
1092 fn revolution_builds_at_every_radius_scale() {
1093 for exponent in 0..12 {
1094 let radius = 10f64.powi(-exponent);
1095 let surface = make_cylinder_surface(
1096 Vec3::new(0.0, 0.0, 0.0),
1097 Vec3::new(0.0, 0.0, 1.0),
1098 radius,
1099 radius,
1100 )
1101 .unwrap_or_else(|error| panic!("radius {radius:e}: {error}"));
1102 let [u0, u1] = surface.domain_u().unwrap();
1103 let [v0, v1] = surface.domain_v().unwrap();
1104 for step in 0..=16 {
1105 let u = u0 + (u1 - u0) * step as f64 / 16.0;
1106 let point = surface.evaluate(u, 0.5 * (v0 + v1)).unwrap();
1107 let deviation = (point.x.hypot(point.y) - radius).abs();
1108 assert!(
1109 deviation <= 1e-12 * radius,
1110 "radius {radius:e} u {u}: off the cylinder by {deviation:e}"
1111 );
1112 }
1113 }
1114 }
1115
1116 #[test]
1119 fn revolution_rejects_a_degenerate_sweep() {
1120 let generatrix =
1121 crate::make_line(Vec3::new(1.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 1.0)).unwrap();
1122 let error = make_revolution(Vec3::default(), Vec3::new(0.0, 0.0, 1.0), &generatrix, 1e-9)
1123 .unwrap_err();
1124 assert!(error.contains("parallel"), "unexpected error: {error}");
1125 }
1126
1127 #[test]
1134 fn revolution_rejects_a_fuzzy_pole_at_any_scale() {
1135 for scale in [1e-3, 1.0, 1e3] {
1136 let generatrix = crate::make_arc(
1137 Vec3::default(),
1138 Vec3::new(1.0, 0.0, 0.0),
1139 Vec3::new(0.0, 0.0, 1.0),
1140 scale,
1141 0.0,
1142 std::f64::consts::FRAC_PI_2,
1143 )
1144 .unwrap();
1145 let mut control_points = generatrix.control_points.clone();
1148 let last = control_points.len() - 1;
1149 control_points[last].x += 1e-6 * scale * control_points[last].w;
1150 let fuzzy = NurbsCurve::new(2, generatrix.knots.clone(), control_points).unwrap();
1151 let error = make_revolution(
1152 Vec3::default(),
1153 Vec3::new(0.0, 0.0, 1.0),
1154 &fuzzy,
1155 std::f64::consts::TAU,
1156 )
1157 .unwrap_err();
1158 assert!(error.contains("fuzzy pole"), "scale {scale:e}: {error}");
1159
1160 make_revolution(
1162 Vec3::default(),
1163 Vec3::new(0.0, 0.0, 1.0),
1164 &generatrix,
1165 std::f64::consts::TAU,
1166 )
1167 .unwrap_or_else(|error| panic!("scale {scale:e}: exact pole rejected: {error}"));
1168 }
1169 }
1170}
1171
1172pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
1184 let inflate = if inflate.is_finite() {
1185 inflate.clamp(1.0, 16.0)
1186 } else {
1187 2.0
1188 };
1189 let (closed_u, closed_v) = surface.closed_directions()?;
1190 let [u0, u1] = surface.domain_u()?;
1191 let [v0, v1] = surface.domain_v()?;
1192 let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
1193 if closed || inflate <= 1.0 {
1194 (d0, d1)
1195 } else {
1196 let centre = 0.5 * (d0 + d1);
1197 let half = 0.5 * (d1 - d0) * inflate;
1198 (centre - half, centre + half)
1199 }
1200 };
1201 let (nu0, nu1) = stretch(u0, u1, closed_u);
1202 let (nv0, nv1) = stretch(v0, v1, closed_v);
1203 if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
1204 return Ok(surface.clone());
1205 }
1206 if surface.is_affine()? {
1207 let corners = [
1210 surface.evaluate_extended(nu0, nv0)?,
1211 surface.evaluate_extended(nu1, nv0)?,
1212 surface.evaluate_extended(nu0, nv1)?,
1213 surface.evaluate_extended(nu1, nv1)?,
1214 ];
1215 return NurbsSurface::new(
1216 1,
1217 1,
1218 vec![nu0, nu0, nu1, nu1],
1219 vec![nv0, nv0, nv1, nv1],
1220 vec![
1221 vec![
1222 Vec4::from_point(corners[0], 1.0),
1223 Vec4::from_point(corners[2], 1.0),
1224 ],
1225 vec![
1226 Vec4::from_point(corners[1], 1.0),
1227 Vec4::from_point(corners[3], 1.0),
1228 ],
1229 ],
1230 );
1231 }
1232 const SAMPLES: usize = 32;
1233 let u_params: Vec<f64> = (0..=SAMPLES)
1234 .map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
1235 .collect();
1236 let v_params: Vec<f64> = (0..=SAMPLES)
1237 .map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
1238 .collect();
1239 let mut column_curves = Vec::with_capacity(u_params.len());
1242 for &u in &u_params {
1243 let column: Vec<Vec3> = v_params
1244 .iter()
1245 .map(|&v| surface.evaluate_extended(u, v))
1246 .collect::<Result<_, _>>()?;
1247 column_curves.push(crate::interpolate_curve(
1248 &column,
1249 3,
1250 &normalized(&v_params),
1251 )?);
1252 }
1253 let v_knots = column_curves[0].knots.clone();
1254 let control_rows = column_curves[0].control_points.len();
1255 let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
1256 let mut u_knots = Vec::new();
1257 for row_index in 0..control_rows {
1258 let row: Vec<Vec3> = column_curves
1259 .iter()
1260 .map(|curve| curve.control_points[row_index].point())
1261 .collect::<Result<_, _>>()?;
1262 let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
1263 u_knots = fitted.knots.clone();
1264 for (u_index, control) in fitted.control_points.iter().enumerate() {
1265 if grid.len() <= u_index {
1266 grid.push(Vec::new());
1267 }
1268 grid[u_index].push(*control);
1269 }
1270 }
1271 let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
1274 knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
1275 };
1276 NurbsSurface::new(
1277 3,
1278 3,
1279 rescale(&u_knots, nu0, nu1),
1280 rescale(&v_knots, nv0, nv1),
1281 grid,
1282 )
1283}
1284
1285fn normalized(parameters: &[f64]) -> Vec<f64> {
1286 let first = parameters[0];
1287 let last = parameters[parameters.len() - 1];
1288 parameters
1289 .iter()
1290 .map(|p| (p - first) / (last - first))
1291 .collect()
1292}
1293
1294#[cfg(test)]
1295mod carrier_preview_tests {
1296 use super::*;
1297 use crate::{make_cylinder_surface, make_torus_surface, Vec3};
1298
1299 #[test]
1300 fn plane_preview_extends_exactly() {
1301 let plane = make_plane(
1302 Vec3::new(1.0, 2.0, 3.0),
1303 Vec3::new(1.0, 0.0, 0.0),
1304 Vec3::new(0.0, 1.0, 0.0),
1305 4.0,
1306 2.0,
1307 )
1308 .unwrap();
1309 let preview = carrier_preview_patch(&plane, 3.0).unwrap();
1310 let [u0, u1] = preview.domain_u().unwrap();
1311 let [v0, v1] = preview.domain_v().unwrap();
1312 assert!((u1 - u0 - 12.0).abs() < 1e-12, "u span tripled");
1313 assert!((v1 - v0 - 6.0).abs() < 1e-12, "v span tripled");
1314 for (u, v) in [
1316 (u0, v0),
1317 (u1, v1),
1318 (0.5 * (u0 + u1), v0),
1319 (u0, 0.5 * (v0 + v1)),
1320 ] {
1321 let point = preview.evaluate(u, v).unwrap();
1322 let expected = Vec3::new(1.0 + u, 2.0 + v, 3.0);
1323 assert!(
1324 point.sub(expected).length() < 1e-9,
1325 "{point:?} at ({u},{v})"
1326 );
1327 }
1328 }
1329
1330 #[test]
1331 fn cylinder_preview_keeps_the_period_and_extends_the_axis() {
1332 let cylinder =
1333 make_cylinder_surface(Vec3::default(), Vec3::new(0.0, 0.0, 1.0), 2.0, 4.0).unwrap();
1334 let [u0, u1] = cylinder.domain_u().unwrap();
1335 let preview = carrier_preview_patch(&cylinder, 2.0).unwrap();
1336 let [pu0, pu1] = preview.domain_u().unwrap();
1337 let [pv0, pv1] = preview.domain_v().unwrap();
1338 assert!(
1340 (pu0 - u0).abs() < 1e-12 && (pu1 - u1).abs() < 1e-12,
1341 "u untouched"
1342 );
1343 let [v0, v1] = cylinder.domain_v().unwrap();
1344 assert!((pv1 - pv0) > 1.9 * (v1 - v0), "v inflated");
1345 for sample in 0..=16 {
1349 let u = pu0 + (pu1 - pu0) * sample as f64 / 16.0;
1350 for v in [pv0, pv1] {
1351 let point = preview.evaluate(u, v).unwrap();
1352 let radial = (point.x * point.x + point.y * point.y).sqrt();
1353 assert!((radial - 2.0).abs() < 2e-3, "radius at ({u},{v}): {radial}");
1354 }
1355 }
1356 }
1357
1358 #[test]
1359 fn torus_preview_is_unchanged() {
1360 let torus =
1361 make_torus_surface(Vec3::default(), Vec3::new(0.0, 0.0, 1.0), 3.0, 1.0).unwrap();
1362 let preview = carrier_preview_patch(&torus, 4.0).unwrap();
1363 assert_eq!(preview.domain_u().unwrap(), torus.domain_u().unwrap());
1364 assert_eq!(preview.domain_v().unwrap(), torus.domain_v().unwrap());
1365 }
1366}