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
8#[path = "surface/extension.rs"]
9mod extension;
10pub use extension::{
11 ExtendRefusal, SurfaceSide, WeightSite, MAXIMUM_FOLD_ANGLE, MAXIMUM_GROWTH_RATIO,
12};
13
14const EPS: f64 = 1e-12;
15const NEAR_AXIS_RELATIVE_TOLERANCE: f64 = 1e-4;
19
20pub(crate) const TENSOR_MAX_BLOCK: usize = 24;
29
30#[derive(Clone, Debug, Deserialize, Serialize)]
31pub struct NurbsSurface {
32 pub degree_u: usize,
33 pub degree_v: usize,
34 pub knots_u: Vec<f64>,
35 pub knots_v: Vec<f64>,
36 pub control_points: Vec<Vec<Vec4>>,
37 #[serde(skip, default)]
41 analytic: std::cell::OnceCell<Option<crate::AnalyticSurface>>,
42 #[serde(skip, default)]
44 validated: std::cell::Cell<bool>,
45 #[serde(skip, default)]
48 closed_directions: std::cell::OnceCell<(bool, bool)>,
49 #[serde(skip, default)]
52 pub(crate) projection_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
53 #[serde(skip, default)]
57 pub(crate) projection_dense_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
58 #[serde(skip, default)]
62 pub(crate) projection_ring_grid: std::cell::OnceCell<Vec<Vec3>>,
63 #[serde(skip, default)]
67 pub(crate) projection_cells: std::cell::OnceCell<Option<crate::projection::ProjectionCells>>,
68}
69
70impl NurbsSurface {
71 pub fn new(
72 degree_u: usize,
73 degree_v: usize,
74 knots_u: Vec<f64>,
75 knots_v: Vec<f64>,
76 control_points: Vec<Vec<Vec4>>,
77 ) -> Result<Self, String> {
78 let surface = Self {
79 degree_u,
80 degree_v,
81 knots_u,
82 knots_v,
83 control_points,
84 analytic: std::cell::OnceCell::new(),
85 validated: std::cell::Cell::new(false),
86 closed_directions: std::cell::OnceCell::new(),
87 projection_grid: std::cell::OnceCell::new(),
88 projection_dense_grid: std::cell::OnceCell::new(),
89 projection_ring_grid: std::cell::OnceCell::new(),
90 projection_cells: std::cell::OnceCell::new(),
91 };
92 surface.ensure_valid()?;
93 Ok(surface)
94 }
95
96 pub fn closed_directions(&self) -> Result<(bool, bool), String> {
101 if let Some(&cached) = self.closed_directions.get() {
102 return Ok(cached);
103 }
104 const CLOSED_SEAM_TOLERANCE: f64 = 1e-6;
105 let [u0, u1] = self.domain_u()?;
106 let [v0, v1] = self.domain_v()?;
107 let closed_along = |direction_u: bool| -> Result<bool, String> {
108 for fraction in [0.17, 0.5, 0.83] {
109 let (a, b) = if direction_u {
110 let v = v0 + (v1 - v0) * fraction;
111 (self.evaluate(u0, v)?, self.evaluate(u1, v)?)
112 } else {
113 let u = u0 + (u1 - u0) * fraction;
114 (self.evaluate(u, v0)?, self.evaluate(u, v1)?)
115 };
116 if a.sub(b).length() > CLOSED_SEAM_TOLERANCE {
117 return Ok(false);
118 }
119 }
120 Ok(true)
121 };
122 let value = (closed_along(true)?, closed_along(false)?);
123 Ok(*self.closed_directions.get_or_init(|| value))
124 }
125
126 fn ensure_valid(&self) -> Result<(), String> {
128 if self.validated.get() {
129 return Ok(());
130 }
131 validate_knots(&self.knots_u, self.degree_u)?;
132 validate_knots(&self.knots_v, self.degree_v)?;
133 let rows = self.knots_u.len() - self.degree_u - 1;
134 let columns = self.knots_v.len() - self.degree_v - 1;
135 if self.control_points.len() != rows {
136 return Err(format!(
137 "NurbsSurface: u knot vector implies {} rows, got {}",
138 rows,
139 self.control_points.len()
140 ));
141 }
142 for row in &self.control_points {
143 if row.len() != columns {
144 return Err(format!(
145 "NurbsSurface: v knot vector implies {} columns, got {}",
146 columns,
147 row.len()
148 ));
149 }
150 if row.iter().any(|point| {
151 point.w <= EPS
152 || ![point.x, point.y, point.z, point.w]
153 .iter()
154 .all(|value| value.is_finite())
155 }) {
156 return Err(
157 "NurbsSurface: control points must be finite with positive weights".into(),
158 );
159 }
160 }
161 self.validated.set(true);
162 Ok(())
163 }
164
165 pub fn domain_u(&self) -> Result<[f64; 2], String> {
166 self.ensure_valid()?;
167 Ok(knot_domain(&self.knots_u, self.degree_u))
168 }
169
170 pub fn domain_v(&self) -> Result<[f64; 2], String> {
171 self.ensure_valid()?;
172 Ok(knot_domain(&self.knots_v, self.degree_v))
173 }
174
175 pub fn analytic(&self) -> Option<&crate::AnalyticSurface> {
178 self.analytic
179 .get_or_init(|| crate::analytic_surface::recognize(self))
180 .as_ref()
181 }
182
183 pub(crate) fn warm_caches(&self) -> Vec<&'static str> {
187 let mut warm = Vec::new();
188 let cells = [
189 ("analytic", self.analytic.get().is_some()),
190 ("validated", self.validated.get()),
191 ("closed", self.closed_directions.get().is_some()),
192 ("grid", self.projection_grid.get().is_some()),
193 ("dense", self.projection_dense_grid.get().is_some()),
194 ("ring", self.projection_ring_grid.get().is_some()),
195 ("cells", self.projection_cells.get().is_some()),
196 ];
197 for (name, held) in cells {
198 if held {
199 warm.push(name);
200 }
201 }
202 warm
203 }
204
205 pub(crate) fn exchange_caches(&mut self, names: &[&str], from: Option<&Self>) {
210 let wants = |name: &str| names.iter().any(|&n| n == name || n == "all");
211 if wants("analytic") {
212 self.analytic = from.map(|f| f.analytic.clone()).unwrap_or_default();
213 }
214 if wants("validated") {
215 self.validated = std::cell::Cell::new(from.is_some_and(|f| f.validated.get()));
216 }
217 if wants("closed") {
218 self.closed_directions = from.map(|f| f.closed_directions.clone()).unwrap_or_default();
219 }
220 if wants("grid") {
221 self.projection_grid = from.map(|f| f.projection_grid.clone()).unwrap_or_default();
222 }
223 if wants("dense") {
224 self.projection_dense_grid = from.map(|f| f.projection_dense_grid.clone()).unwrap_or_default();
225 }
226 if wants("ring") {
227 self.projection_ring_grid = from.map(|f| f.projection_ring_grid.clone()).unwrap_or_default();
228 }
229 if wants("cells") {
230 self.projection_cells = from.map(|f| f.projection_cells.clone()).unwrap_or_default();
231 }
232 }
233
234 fn knot_vectors(&self) -> Result<(KnotVector, KnotVector), String> {
235 Ok((
236 KnotVector::new(self.knots_u.clone(), self.degree_u)?,
237 KnotVector::new(self.knots_v.clone(), self.degree_v)?,
238 ))
239 }
240
241 pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String> {
242 self.ensure_valid()?;
243 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
244 return self.evaluate_homogeneous_heap(u, v);
245 }
246 let u = knot_clamp(&self.knots_u, self.degree_u, u);
247 let v = knot_clamp(&self.knots_v, self.degree_v, v);
248 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
249 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
250 let mut basis_u = [0.0f64; MAX_STACK_ORDER];
251 let mut basis_v = [0.0f64; MAX_STACK_ORDER];
252 basis_functions_into(&self.knots_u, self.degree_u, span_u, u, &mut basis_u);
253 basis_functions_into(&self.knots_v, self.degree_v, span_v, v, &mut basis_v);
254 let mut point = Vec4 {
255 x: 0.0,
256 y: 0.0,
257 z: 0.0,
258 w: 0.0,
259 };
260 for (i, value_u) in basis_u[..=self.degree_u].iter().enumerate() {
261 let row = &self.control_points[span_u - self.degree_u + i];
262 for (j, value_v) in basis_v[..=self.degree_v].iter().enumerate() {
263 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
264 }
265 }
266 Ok(point)
267 }
268
269 fn evaluate_homogeneous_heap(&self, u: f64, v: f64) -> Result<Vec4, String> {
270 let (knot_u, knot_v) = self.knot_vectors()?;
271 let u = knot_u.clamp_param(u);
272 let v = knot_v.clamp_param(v);
273 let span_u = knot_u.find_span(u);
274 let span_v = knot_v.find_span(v);
275 let basis_u = knot_u.basis_functions(span_u, u);
276 let basis_v = knot_v.basis_functions(span_v, v);
277 let mut point = Vec4 {
278 x: 0.0,
279 y: 0.0,
280 z: 0.0,
281 w: 0.0,
282 };
283 for (i, value_u) in basis_u.iter().enumerate() {
284 let row = &self.control_points[span_u - self.degree_u + i];
285 for (j, value_v) in basis_v.iter().enumerate() {
286 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
287 }
288 }
289 Ok(point)
290 }
291
292 pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String> {
293 self.evaluate_homogeneous(u, v)?.point()
294 }
295
296 pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String> {
302 Ok(self.derivatives_extended(u, v, 0)?[0][0])
303 }
304
305 pub fn derivatives_extended(
310 &self,
311 u: f64,
312 v: f64,
313 derivative_count: usize,
314 ) -> Result<Vec<Vec<Vec3>>, String> {
315 let [u0, u1] = self.domain_u()?;
316 let [v0, v1] = self.domain_v()?;
317 let (closed_u, closed_v) = self.closed_directions()?;
318 let mut uu = u;
319 let mut vv = v;
320 if closed_u && (u < u0 || u > u1) {
321 uu = u0 + (u - u0).rem_euclid(u1 - u0);
322 }
323 if closed_v && (v < v0 || v > v1) {
324 vv = v0 + (v - v0).rem_euclid(v1 - v0);
325 }
326 let du_out = if uu < u0 {
327 uu - u0
328 } else if uu > u1 {
329 uu - u1
330 } else {
331 0.0
332 };
333 let dv_out = if vv < v0 {
334 vv - v0
335 } else if vv > v1 {
336 vv - v1
337 } else {
338 0.0
339 };
340 if du_out == 0.0 && dv_out == 0.0 {
341 return self.derivatives(uu, vv, derivative_count);
342 }
343 let anchor_u = uu - du_out;
344 let anchor_v = vv - dv_out;
345 let base = self.derivatives(anchor_u, anchor_v, derivative_count.max(1))?;
346 let order = derivative_count + 1;
347 let mut result = vec![vec![Vec3::default(); order]; order];
348 result[0][0] = base[0][0]
352 .add(base[1][0].scale(du_out))
353 .add(base[0][1].scale(dv_out))
354 .add(base[1][1].scale(du_out * dv_out));
355 if derivative_count >= 1 {
356 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
357 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
358 result[1][1] = base[1][1];
359 }
360 if derivative_count >= 2 {
361 let second = self.derivatives(anchor_u, anchor_v, 2)?;
362 if du_out == 0.0 {
363 result[2][0] = second[2][0];
364 }
365 if dv_out == 0.0 {
366 result[0][2] = second[0][2];
367 }
368 }
369 Ok(result)
370 }
371
372 pub fn derivatives(
373 &self,
374 u: f64,
375 v: f64,
376 derivative_count: usize,
377 ) -> Result<Vec<Vec<Vec3>>, String> {
378 self.ensure_valid()?;
379 let du = derivative_count.min(self.degree_u);
380 let dv = derivative_count.min(self.degree_v);
381 let stride = derivative_count + 1;
382 let zero = Vec4 {
384 x: 0.0,
385 y: 0.0,
386 z: 0.0,
387 w: 0.0,
388 };
389 let mut homogeneous = vec![zero; stride * stride];
390 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
391 let u = knot_clamp(&self.knots_u, self.degree_u, u);
392 let v = knot_clamp(&self.knots_v, self.degree_v, v);
393 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
394 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
395 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
396 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
397 basis_derivatives_into(
398 &self.knots_u,
399 self.degree_u,
400 span_u,
401 u,
402 du,
403 &mut basis_u[..=du],
404 );
405 basis_derivatives_into(
406 &self.knots_v,
407 self.degree_v,
408 span_v,
409 v,
410 dv,
411 &mut basis_v[..=dv],
412 );
413 for k in 0..=du {
414 for l in 0..=dv {
415 if k + l > derivative_count {
416 continue;
417 }
418 let mut point = zero;
419 for i in 0..=self.degree_u {
420 let row = &self.control_points[span_u - self.degree_u + i];
421 for j in 0..=self.degree_v {
422 point = point.add(
423 row[span_v - self.degree_v + j]
424 .scale(basis_u[k][i] * basis_v[l][j]),
425 );
426 }
427 }
428 homogeneous[k * stride + l] = point;
429 }
430 }
431 } else {
432 let (knot_u, knot_v) = self.knot_vectors()?;
433 let u = knot_u.clamp_param(u);
434 let v = knot_v.clamp_param(v);
435 let span_u = knot_u.find_span(u);
436 let span_v = knot_v.find_span(v);
437 let basis_u = knot_u.basis_derivatives(span_u, u, du);
438 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
439 for k in 0..=du {
440 for l in 0..=dv {
441 if k + l > derivative_count {
442 continue;
443 }
444 let mut point = zero;
445 for i in 0..=self.degree_u {
446 let row = &self.control_points[span_u - self.degree_u + i];
447 for j in 0..=self.degree_v {
448 point = point.add(
449 row[span_v - self.degree_v + j]
450 .scale(basis_u[k][i] * basis_v[l][j]),
451 );
452 }
453 }
454 homogeneous[k * stride + l] = point;
455 }
456 }
457 }
458
459 let mut result = vec![vec![Vec3::default(); derivative_count + 1]; derivative_count + 1];
460 let weight = homogeneous[0].w;
461 if weight.abs() <= EPS {
462 return Err("NurbsSurface: zero evaluated weight".into());
463 }
464 for k in 0..=derivative_count {
465 for l in 0..=derivative_count - k {
466 if k > du || l > dv {
467 continue;
468 }
469 let at = |row: usize, column: usize| homogeneous[row * stride + column];
470 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
471 for j in 1..=l {
472 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
473 }
474 for i in 1..=k {
475 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
476 let mut mixed = Vec3::default();
477 for j in 1..=l {
478 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
479 }
480 value = value.sub(mixed.scale(binomial(k, i)));
481 }
482 result[k][l] = value.scale(1.0 / weight);
483 }
484 }
485 Ok(result)
486 }
487
488 pub(crate) fn derivatives_small(
503 &self,
504 u: f64,
505 v: f64,
506 derivative_count: usize,
507 ) -> Result<[[Vec3; 3]; 3], String> {
508 debug_assert!(derivative_count <= 2);
509 self.ensure_valid()?;
510 let du = derivative_count.min(self.degree_u);
511 let dv = derivative_count.min(self.degree_v);
512 let stride = derivative_count + 1;
513 let zero = Vec4 {
514 x: 0.0,
515 y: 0.0,
516 z: 0.0,
517 w: 0.0,
518 };
519 let mut homogeneous = [zero; 9];
520 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
521 let u = knot_clamp(&self.knots_u, self.degree_u, u);
522 let v = knot_clamp(&self.knots_v, self.degree_v, v);
523 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
524 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
525 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
526 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
527 basis_derivatives_into(
528 &self.knots_u,
529 self.degree_u,
530 span_u,
531 u,
532 du,
533 &mut basis_u[..=du],
534 );
535 basis_derivatives_into(
536 &self.knots_v,
537 self.degree_v,
538 span_v,
539 v,
540 dv,
541 &mut basis_v[..=dv],
542 );
543 for k in 0..=du {
544 for l in 0..=dv {
545 if k + l > derivative_count {
546 continue;
547 }
548 let mut point = zero;
549 for i in 0..=self.degree_u {
550 let row = &self.control_points[span_u - self.degree_u + i];
551 for j in 0..=self.degree_v {
552 point = point.add(
553 row[span_v - self.degree_v + j]
554 .scale(basis_u[k][i] * basis_v[l][j]),
555 );
556 }
557 }
558 homogeneous[k * stride + l] = point;
559 }
560 }
561 } else {
562 let (knot_u, knot_v) = self.knot_vectors()?;
563 let u = knot_u.clamp_param(u);
564 let v = knot_v.clamp_param(v);
565 let span_u = knot_u.find_span(u);
566 let span_v = knot_v.find_span(v);
567 let basis_u = knot_u.basis_derivatives(span_u, u, du);
568 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
569 for k in 0..=du {
570 for l in 0..=dv {
571 if k + l > derivative_count {
572 continue;
573 }
574 let mut point = zero;
575 for i in 0..=self.degree_u {
576 let row = &self.control_points[span_u - self.degree_u + i];
577 for j in 0..=self.degree_v {
578 point = point.add(
579 row[span_v - self.degree_v + j]
580 .scale(basis_u[k][i] * basis_v[l][j]),
581 );
582 }
583 }
584 homogeneous[k * stride + l] = point;
585 }
586 }
587 }
588
589 let mut result = [[Vec3::default(); 3]; 3];
590 let weight = homogeneous[0].w;
591 if weight.abs() <= EPS {
592 return Err("NurbsSurface: zero evaluated weight".into());
593 }
594 for k in 0..=derivative_count {
595 for l in 0..=derivative_count - k {
596 if k > du || l > dv {
597 continue;
598 }
599 let at = |row: usize, column: usize| homogeneous[row * stride + column];
600 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
601 for j in 1..=l {
602 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
603 }
604 for i in 1..=k {
605 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
606 let mut mixed = Vec3::default();
607 for j in 1..=l {
608 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
609 }
610 value = value.sub(mixed.scale(binomial(k, i)));
611 }
612 result[k][l] = value.scale(1.0 / weight);
613 }
614 }
615 Ok(result)
616 }
617
618 #[inline]
622 pub(crate) fn deriv1_tensor_supported(&self) -> bool {
623 self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
624 }
625
626 pub(crate) fn deriv1_tensor_each<F>(
648 &self,
649 us: &[f64],
650 vs: &[f64],
651 mut f: F,
652 ) -> Result<(), String>
653 where
654 F: FnMut(usize, usize, Vec3, Vec3, Vec3) -> Result<(), String>,
655 {
656 self.ensure_valid()?;
657 const MAX_BLOCK: usize = TENSOR_MAX_BLOCK;
659 if !self.deriv1_tensor_supported() || us.len() > MAX_BLOCK || vs.len() > MAX_BLOCK {
660 return Err("NurbsSurface: tensor block outside the stack evaluator".into());
661 }
662 let du = 1usize.min(self.degree_u);
663 let dv = 1usize.min(self.degree_v);
664 let mut span_u = [0usize; MAX_BLOCK];
665 let mut span_v = [0usize; MAX_BLOCK];
666 let mut basis_u = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
667 let mut basis_v = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
668 for (index, &value) in us.iter().enumerate() {
669 let value = knot_clamp(&self.knots_u, self.degree_u, value);
670 span_u[index] = knot_find_span(&self.knots_u, self.degree_u, value);
671 basis_derivatives_into(
672 &self.knots_u,
673 self.degree_u,
674 span_u[index],
675 value,
676 du,
677 &mut basis_u[index][..=du],
678 );
679 }
680 for (index, &value) in vs.iter().enumerate() {
681 let value = knot_clamp(&self.knots_v, self.degree_v, value);
682 span_v[index] = knot_find_span(&self.knots_v, self.degree_v, value);
683 basis_derivatives_into(
684 &self.knots_v,
685 self.degree_v,
686 span_v[index],
687 value,
688 dv,
689 &mut basis_v[index][..=dv],
690 );
691 }
692 let zero = Vec4 {
693 x: 0.0,
694 y: 0.0,
695 z: 0.0,
696 w: 0.0,
697 };
698 for i in 0..us.len() {
699 let rows = &self.control_points[span_u[i] - self.degree_u..];
700 for j in 0..vs.len() {
701 let column = span_v[j] - self.degree_v;
702 let sum = |k: usize, l: usize| {
706 let mut point = zero;
707 for row_index in 0..=self.degree_u {
708 let row = &rows[row_index];
709 for column_index in 0..=self.degree_v {
710 point = point.add(
711 row[column + column_index]
712 .scale(basis_u[i][k][row_index] * basis_v[j][l][column_index]),
713 );
714 }
715 }
716 point
717 };
718 let h00 = sum(0, 0);
719 let h01 = if dv >= 1 { sum(0, 1) } else { zero };
720 let h10 = if du >= 1 { sum(1, 0) } else { zero };
721 let weight = h00.w;
722 if weight.abs() <= EPS {
723 return Err("NurbsSurface: zero evaluated weight".into());
724 }
725 let inverse = 1.0 / weight;
726 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
727 let partial_v = if dv >= 1 {
728 Vec3::new(h01.x, h01.y, h01.z)
729 .sub(point.scale(h01.w))
730 .scale(inverse)
731 } else {
732 Vec3::default()
733 };
734 let partial_u = if du >= 1 {
735 Vec3::new(h10.x, h10.y, h10.z)
736 .sub(point.scale(h10.w))
737 .scale(inverse)
738 } else {
739 Vec3::default()
740 };
741 f(i, j, point, partial_u, partial_v)?;
742 }
743 }
744 Ok(())
745 }
746
747 #[inline]
750 pub(crate) fn deriv1(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
751 let g = self.derivatives_small(u, v, 1)?;
752 Ok((g[0][0], g[1][0], g[0][1]))
753 }
754
755 pub(crate) fn deriv1_extended(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
763 let [u0, u1] = self.domain_u()?;
764 let [v0, v1] = self.domain_v()?;
765 let (closed_u, closed_v) = self.closed_directions()?;
766 let mut uu = u;
767 let mut vv = v;
768 if closed_u && (u < u0 || u > u1) {
769 uu = u0 + (u - u0).rem_euclid(u1 - u0);
770 }
771 if closed_v && (v < v0 || v > v1) {
772 vv = v0 + (v - v0).rem_euclid(v1 - v0);
773 }
774 let du_out = if uu < u0 {
775 uu - u0
776 } else if uu > u1 {
777 uu - u1
778 } else {
779 0.0
780 };
781 let dv_out = if vv < v0 {
782 vv - v0
783 } else if vv > v1 {
784 vv - v1
785 } else {
786 0.0
787 };
788 if du_out == 0.0 && dv_out == 0.0 {
789 let g = self.derivatives_small(uu, vv, 1)?;
790 return Ok((g[0][0], g[1][0], g[0][1]));
791 }
792 let anchor_u = uu - du_out;
793 let anchor_v = vv - dv_out;
794 let base = self.derivatives_small(anchor_u, anchor_v, 1)?;
795 let s = base[0][0]
799 .add(base[1][0].scale(du_out))
800 .add(base[0][1].scale(dv_out))
801 .add(base[1][1].scale(du_out * dv_out));
802 let su = base[1][0].add(base[1][1].scale(dv_out));
803 let sv = base[0][1].add(base[1][1].scale(du_out));
804 Ok((s, su, sv))
805 }
806
807 pub(crate) fn partial_by_differences(
832 &self,
833 along_u: bool,
834 u: f64,
835 v: f64,
836 ) -> Result<Vec3, String> {
837 self.ensure_valid()?;
838 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
839 return Err("NurbsSurface: partial by differences exceeds the stack degree".into());
840 }
841 let (pu, pv) = (self.degree_u, self.degree_v);
842 let u = knot_clamp(&self.knots_u, pu, u);
843 let v = knot_clamp(&self.knots_v, pv, v);
844 let span_u = knot_find_span(&self.knots_u, pu, u);
845 let span_v = knot_find_span(&self.knots_v, pv, v);
846 let mut full_u = [0.0f64; MAX_STACK_ORDER];
847 let mut full_v = [0.0f64; MAX_STACK_ORDER];
848 basis_functions_into(&self.knots_u, pu, span_u, u, &mut full_u);
849 basis_functions_into(&self.knots_v, pv, span_v, v, &mut full_v);
850 let zero = Vec4 {
851 x: 0.0,
852 y: 0.0,
853 z: 0.0,
854 w: 0.0,
855 };
856 let control = |i: usize, j: usize| self.control_points[span_u - pu + i][span_v - pv + j];
857 let mut point = zero;
858 for i in 0..=pu {
859 for j in 0..=pv {
860 point = point.add(control(i, j).scale(full_u[i] * full_v[j]));
861 }
862 }
863 if point.w.abs() <= EPS {
864 return Err("NurbsSurface: zero evaluated weight".into());
865 }
866 let source = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
867 let (knots, degree, span, parameter) = if along_u {
868 (&self.knots_u, pu, span_u, u)
869 } else {
870 (&self.knots_v, pv, span_v, v)
871 };
872 if degree == 0 {
873 return Ok(Vec3::default());
874 }
875 let mut lowered = [0.0f64; MAX_STACK_ORDER];
876 basis_functions_into(knots, degree - 1, span, parameter, &mut lowered);
877 let mut hodograph = zero;
880 for r in 0..degree {
881 let index = span - degree + r;
884 let width = knots[index + degree + 1] - knots[index + 1];
885 if width.abs() <= EPS {
886 continue;
887 }
888 let factor = lowered[r] * degree as f64 / width;
889 if along_u {
890 for j in 0..=pv {
891 let difference = control(r + 1, j).add(control(r, j).scale(-1.0));
892 hodograph = hodograph.add(difference.scale(factor * full_v[j]));
893 }
894 } else {
895 for i in 0..=pu {
896 let difference = control(i, r + 1).add(control(i, r).scale(-1.0));
897 hodograph = hodograph.add(difference.scale(factor * full_u[i]));
898 }
899 }
900 }
901 Ok(Vec3::new(hodograph.x, hodograph.y, hodograph.z)
902 .sub(source.scale(hodograph.w))
903 .scale(1.0 / point.w))
904 }
905
906 pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
907 let derivatives = self.derivatives(u, v, 1)?;
908 derivatives[1][0].cross(derivatives[0][1]).normalized()
909 }
910
911 pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String> {
918 let derivatives = self.derivatives(u, v, 2)?;
919 let su = derivatives[1][0];
920 let sv = derivatives[0][1];
921 let cross = su.cross(sv);
922 let cross_length = cross.length();
923 if cross_length <= 1e-12 {
924 return Err(format!(
925 "degenerate parametrization at (u={u:.4}, v={v:.4})"
926 ));
927 }
928 let normal = cross.scale(1.0 / cross_length);
929 let e1 = su.dot(su);
930 let f1 = su.dot(sv);
931 let g1 = sv.dot(sv);
932 let l2 = derivatives[2][0].dot(normal);
933 let m2 = derivatives[1][1].dot(normal);
934 let n2 = derivatives[0][2].dot(normal);
935 let denominator = e1 * g1 - f1 * f1;
936 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();
939 Ok((
940 mean_double * 0.5 - discriminant,
941 mean_double * 0.5 + discriminant,
942 ))
943 }
944
945 pub fn is_affine(&self) -> Result<bool, String> {
950 if self.degree_u != 1
951 || self.degree_v != 1
952 || self.control_points.len() != 2
953 || self.control_points[0].len() != 2
954 || self.control_points[1].len() != 2
955 {
956 return Ok(false);
957 }
958 let weight = self.control_points[0][0].w;
959 if [
960 self.control_points[0][1].w,
961 self.control_points[1][0].w,
962 self.control_points[1][1].w,
963 ]
964 .iter()
965 .any(|other| (*other - weight).abs() > 1e-12 * other.abs().max(weight.abs()))
966 {
967 return Ok(false);
968 }
969 let p00 = self.control_points[0][0].point()?;
970 let p01 = self.control_points[0][1].point()?;
971 let p10 = self.control_points[1][0].point()?;
972 let p11 = self.control_points[1][1].point()?;
973 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
974 let extent = [
979 p01.sub(p00),
980 p10.sub(p00),
981 p11.sub(p00),
982 p10.sub(p01),
983 p11.sub(p01),
984 p11.sub(p10),
985 ]
986 .iter()
987 .map(|delta| delta.length())
988 .fold(0.0, f64::max);
989 Ok(gap <= 1e-9 * (1.0 + extent))
990 }
991
992 pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
993 self.ensure_valid()?;
994 let u = knot_clamp(&self.knots_u, self.degree_u, u);
995 let span = knot_find_span(&self.knots_u, self.degree_u, u);
996 let basis = if self.degree_u <= MAX_STACK_DEGREE {
997 let mut stack = [0.0f64; MAX_STACK_ORDER];
998 basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
999 stack[..=self.degree_u].to_vec()
1000 } else {
1001 KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
1002 };
1003 let mut points = Vec::with_capacity(self.control_points[0].len());
1004 for column in 0..self.control_points[0].len() {
1005 let mut point = Vec4 {
1006 x: 0.0,
1007 y: 0.0,
1008 z: 0.0,
1009 w: 0.0,
1010 };
1011 for (index, value) in basis.iter().enumerate() {
1012 point = point
1013 .add(self.control_points[span - self.degree_u + index][column].scale(*value));
1014 }
1015 points.push(point);
1016 }
1017 NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
1018 }
1019
1020 pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
1021 self.ensure_valid()?;
1022 let v = knot_clamp(&self.knots_v, self.degree_v, v);
1023 let span = knot_find_span(&self.knots_v, self.degree_v, v);
1024 let basis = if self.degree_v <= MAX_STACK_DEGREE {
1025 let mut stack = [0.0f64; MAX_STACK_ORDER];
1026 basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
1027 stack[..=self.degree_v].to_vec()
1028 } else {
1029 KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
1030 };
1031 let mut points = Vec::with_capacity(self.control_points.len());
1032 for row in &self.control_points {
1033 let mut point = Vec4 {
1034 x: 0.0,
1035 y: 0.0,
1036 z: 0.0,
1037 w: 0.0,
1038 };
1039 for (index, value) in basis.iter().enumerate() {
1040 point = point.add(row[span - self.degree_v + index].scale(*value));
1041 }
1042 points.push(point);
1043 }
1044 NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
1045 }
1046}
1047
1048pub fn make_plane(
1049 origin: Vec3,
1050 u_direction: Vec3,
1051 v_direction: Vec3,
1052 u_max: f64,
1053 v_max: f64,
1054) -> Result<NurbsSurface, String> {
1055 if u_max <= EPS || v_max <= EPS {
1056 return Err("makePlane: parameter extents must be positive".into());
1057 }
1058 let p10 = origin.add(u_direction.scale(u_max));
1059 let p01 = origin.add(v_direction.scale(v_max));
1060 let p11 = p10.add(v_direction.scale(v_max));
1061 NurbsSurface::new(
1062 1,
1063 1,
1064 vec![0.0, 0.0, u_max, u_max],
1065 vec![0.0, 0.0, v_max, v_max],
1066 vec![
1067 vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
1068 vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
1069 ],
1070 )
1071}
1072
1073pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
1074 let rows = profile
1075 .control_points
1076 .iter()
1077 .map(|point| {
1078 vec![
1079 *point,
1080 Vec4 {
1081 x: point.x + point.w * direction.x,
1082 y: point.y + point.w * direction.y,
1083 z: point.z + point.w * direction.z,
1084 w: point.w,
1085 },
1086 ]
1087 })
1088 .collect();
1089 NurbsSurface::new(
1090 profile.degree,
1091 1,
1092 profile.knots.clone(),
1093 vec![0.0, 0.0, 1.0, 1.0],
1094 rows,
1095 )
1096}
1097
1098pub fn make_revolution(
1099 axis_point: Vec3,
1100 axis_direction: Vec3,
1101 generatrix: &NurbsCurve,
1102 theta: f64,
1103) -> Result<NurbsSurface, String> {
1104 if theta <= EPS || theta > std::f64::consts::TAU + EPS {
1105 return Err("makeRevolution: theta must be in (0, 2*PI]".into());
1106 }
1107 let theta = theta.min(std::f64::consts::TAU);
1108 let axis = axis_direction.normalized()?;
1109 let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
1110 let arc_angle = theta / arc_count as f64;
1111 let middle_weight = (arc_angle / 2.0).cos();
1112 let mut knots_u = vec![0.0, 0.0, 0.0];
1113 for index in 1..arc_count {
1114 let knot = index as f64 / arc_count as f64;
1115 knots_u.extend([knot, knot]);
1116 }
1117 knots_u.extend([1.0, 1.0, 1.0]);
1118 let mut rows = vec![
1119 vec![
1120 Vec4 {
1121 x: 0.0,
1122 y: 0.0,
1123 z: 0.0,
1124 w: 1.0,
1125 };
1126 generatrix.control_points.len()
1127 ];
1128 2 * arc_count + 1
1129 ];
1130
1131 let mut radial_extent: f64 = 0.0;
1134 for control_point in &generatrix.control_points {
1135 let point = control_point.point()?;
1136 let offset = point.sub(axis_point);
1137 radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
1138 }
1139
1140 for (column, control_point) in generatrix.control_points.iter().enumerate() {
1141 let point = control_point.point()?;
1142 let weight = control_point.w;
1143 let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
1144 let radial_vector = point.sub(axis_origin);
1145 let measured_radius = radial_vector.length();
1155 let pole_tolerance = EPS.max(1e-8 * radial_extent);
1156 let radius = if measured_radius <= pole_tolerance {
1157 0.0
1158 } else {
1159 measured_radius
1160 };
1161 let (x_axis, y_axis) = if radius <= EPS {
1162 (Vec3::default(), Vec3::default())
1163 } else {
1164 if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
1176 return Err(format!(
1177 "makeRevolution: generatrix control point {column} sits {radius:e} from the \
1178 axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
1179 radial extent (a fuzzy pole)"
1180 ));
1181 }
1182 let x_axis = radial_vector.scale(1.0 / radius);
1183 (x_axis, axis.cross(x_axis))
1184 };
1185 rows[0][column] = Vec4::from_point(point, weight);
1186 let mut start_point = point;
1187 let mut start_tangent = y_axis.scale(radius);
1188 let mut angle = 0.0;
1189 for arc in 1..=arc_count {
1190 angle += arc_angle;
1191 let end_point = if radius <= EPS {
1192 point
1193 } else {
1194 axis_origin
1195 .add(x_axis.scale(radius * angle.cos()))
1196 .add(y_axis.scale(radius * angle.sin()))
1197 };
1198 let end_tangent = if radius <= EPS {
1199 Vec3::default()
1200 } else {
1201 x_axis
1202 .scale(-radius * angle.sin())
1203 .add(y_axis.scale(radius * angle.cos()))
1204 };
1205 let row = 2 * (arc - 1);
1206 if radius <= EPS {
1207 rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
1208 } else {
1209 let cross = start_tangent.cross(end_tangent);
1210 let denominator = cross.length_squared();
1211 let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
1220 if denominator <= EPS * tangent_scale {
1221 return Err("makeRevolution: arc tangents are parallel".into());
1222 }
1223 let distance =
1224 end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
1225 let middle = start_point.add(start_tangent.scale(distance));
1226 rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
1227 }
1228 rows[row + 2][column] = Vec4::from_point(end_point, weight);
1229 start_point = end_point;
1230 start_tangent = end_tangent;
1231 }
1232 }
1233 NurbsSurface::new(
1234 2,
1235 generatrix.degree,
1236 knots_u,
1237 generatrix.knots.clone(),
1238 rows,
1239 )
1240}
1241
1242pub fn make_cylinder_surface(
1243 base: Vec3,
1244 axis_direction: Vec3,
1245 radius: f64,
1246 height: f64,
1247) -> Result<NurbsSurface, String> {
1248 if radius <= EPS || height <= EPS {
1249 return Err("makeCylinder: radius and height must be positive".into());
1250 }
1251 let axis = axis_direction.normalized()?;
1252 let x_axis = axis.perpendicular()?;
1253 let start = base.add(x_axis.scale(radius));
1254 let end = start.add(axis.scale(height));
1255 let generatrix = crate::make_line(start, end)?;
1256 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1257}
1258
1259pub fn make_cone_surface(
1260 base: Vec3,
1261 axis_direction: Vec3,
1262 radius_bottom: f64,
1263 radius_top: f64,
1264 height: f64,
1265) -> Result<NurbsSurface, String> {
1266 if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
1267 return Err("makeCone: invalid radius or height".into());
1268 }
1269 let axis = axis_direction.normalized()?;
1270 let x_axis = axis.perpendicular()?;
1271 let start = base.add(x_axis.scale(radius_bottom));
1272 let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
1273 let generatrix = crate::make_line(start, end)?;
1274 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1275}
1276
1277pub fn make_sphere_surface(
1278 center: Vec3,
1279 radius: f64,
1280 polar_axis: Vec3,
1281) -> Result<NurbsSurface, String> {
1282 make_sphere_surface_framed(center, radius, polar_axis, None)
1283}
1284
1285pub fn make_sphere_surface_framed(
1297 center: Vec3,
1298 radius: f64,
1299 polar_axis: Vec3,
1300 seam_direction: Option<Vec3>,
1301) -> Result<NurbsSurface, String> {
1302 if radius <= EPS {
1303 return Err("makeSphere: radius must be positive".into());
1304 }
1305 let axis = polar_axis.normalized()?;
1306 let x_axis = match seam_direction {
1307 Some(seam) => {
1308 let planar = seam.sub(axis.scale(seam.dot(axis)));
1309 match planar.normalized() {
1310 Ok(unit) if planar.length() > EPS => unit,
1311 _ => axis.perpendicular()?,
1312 }
1313 }
1314 None => axis.perpendicular()?,
1315 };
1316 let meridian = crate::make_arc(
1317 center,
1318 x_axis,
1319 axis,
1320 radius,
1321 -std::f64::consts::FRAC_PI_2,
1322 std::f64::consts::FRAC_PI_2,
1323 )?;
1324 make_revolution(center, axis, &meridian, std::f64::consts::TAU)
1325}
1326
1327pub fn make_torus_surface(
1328 center: Vec3,
1329 axis_direction: Vec3,
1330 major_radius: f64,
1331 minor_radius: f64,
1332) -> Result<NurbsSurface, String> {
1333 if minor_radius <= EPS || major_radius <= minor_radius {
1334 return Err("makeTorus: requires positive minorRadius < majorRadius".into());
1335 }
1336 let axis = axis_direction.normalized()?;
1337 let x_axis = axis.perpendicular()?;
1338 let tube_center = center.add(x_axis.scale(major_radius));
1339 let tube = crate::make_arc(
1340 tube_center,
1341 x_axis,
1342 axis,
1343 minor_radius,
1344 0.0,
1345 std::f64::consts::TAU,
1346 )?;
1347 make_revolution(center, axis, &tube, std::f64::consts::TAU)
1348}
1349
1350fn binomial(n: usize, k: usize) -> f64 {
1351 if k > n {
1352 return 0.0;
1353 }
1354 let k = k.min(n - k);
1355 (1..=k).fold(1.0, |value, index| {
1356 value * (n - k + index) as f64 / index as f64
1357 })
1358}
1359
1360
1361
1362pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
1374 let inflate = if inflate.is_finite() {
1375 inflate.clamp(1.0, 16.0)
1376 } else {
1377 2.0
1378 };
1379 let (closed_u, closed_v) = surface.closed_directions()?;
1380 let [u0, u1] = surface.domain_u()?;
1381 let [v0, v1] = surface.domain_v()?;
1382 let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
1383 if closed || inflate <= 1.0 {
1384 (d0, d1)
1385 } else {
1386 let centre = 0.5 * (d0 + d1);
1387 let half = 0.5 * (d1 - d0) * inflate;
1388 (centre - half, centre + half)
1389 }
1390 };
1391 let (nu0, nu1) = stretch(u0, u1, closed_u);
1392 let (nv0, nv1) = stretch(v0, v1, closed_v);
1393 if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
1394 return Ok(surface.clone());
1395 }
1396 if surface.is_affine()? {
1397 let corners = [
1400 surface.evaluate_extended(nu0, nv0)?,
1401 surface.evaluate_extended(nu1, nv0)?,
1402 surface.evaluate_extended(nu0, nv1)?,
1403 surface.evaluate_extended(nu1, nv1)?,
1404 ];
1405 return NurbsSurface::new(
1406 1,
1407 1,
1408 vec![nu0, nu0, nu1, nu1],
1409 vec![nv0, nv0, nv1, nv1],
1410 vec![
1411 vec![
1412 Vec4::from_point(corners[0], 1.0),
1413 Vec4::from_point(corners[2], 1.0),
1414 ],
1415 vec![
1416 Vec4::from_point(corners[1], 1.0),
1417 Vec4::from_point(corners[3], 1.0),
1418 ],
1419 ],
1420 );
1421 }
1422 const SAMPLES: usize = 32;
1423 let u_params: Vec<f64> = (0..=SAMPLES)
1424 .map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
1425 .collect();
1426 let v_params: Vec<f64> = (0..=SAMPLES)
1427 .map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
1428 .collect();
1429 let mut column_curves = Vec::with_capacity(u_params.len());
1432 for &u in &u_params {
1433 let column: Vec<Vec3> = v_params
1434 .iter()
1435 .map(|&v| surface.evaluate_extended(u, v))
1436 .collect::<Result<_, _>>()?;
1437 column_curves.push(crate::interpolate_curve(
1438 &column,
1439 3,
1440 &normalized(&v_params),
1441 )?);
1442 }
1443 let v_knots = column_curves[0].knots.clone();
1444 let control_rows = column_curves[0].control_points.len();
1445 let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
1446 let mut u_knots = Vec::new();
1447 for row_index in 0..control_rows {
1448 let row: Vec<Vec3> = column_curves
1449 .iter()
1450 .map(|curve| curve.control_points[row_index].point())
1451 .collect::<Result<_, _>>()?;
1452 let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
1453 u_knots = fitted.knots.clone();
1454 for (u_index, control) in fitted.control_points.iter().enumerate() {
1455 if grid.len() <= u_index {
1456 grid.push(Vec::new());
1457 }
1458 grid[u_index].push(*control);
1459 }
1460 }
1461 let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
1464 knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
1465 };
1466 NurbsSurface::new(
1467 3,
1468 3,
1469 rescale(&u_knots, nu0, nu1),
1470 rescale(&v_knots, nv0, nv1),
1471 grid,
1472 )
1473}
1474
1475fn normalized(parameters: &[f64]) -> Vec<f64> {
1476 let first = parameters[0];
1477 let last = parameters[parameters.len() - 1];
1478 parameters
1479 .iter()
1480 .map(|p| (p - first) / (last - first))
1481 .collect()
1482}
1483