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)]
40pub struct NurbsSurface {
41 pub degree_u: usize,
42 pub degree_v: usize,
43 pub knots_u: Vec<f64>,
44 pub knots_v: Vec<f64>,
45 pub control_points: Vec<Vec<Vec4>>,
46 #[serde(skip, default)]
50 analytic: std::cell::OnceCell<Option<crate::AnalyticSurface>>,
51 #[serde(skip, default)]
53 validated: std::cell::Cell<bool>,
54 #[serde(skip, default)]
57 closed_directions: std::cell::OnceCell<(bool, bool)>,
58 #[serde(skip, default)]
61 pub(crate) projection_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
62 #[serde(skip, default)]
66 pub(crate) projection_dense_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
67 #[serde(skip, default)]
71 pub(crate) projection_ring_grid: std::cell::OnceCell<Vec<Vec3>>,
72 #[serde(skip, default)]
76 pub(crate) projection_cells: std::cell::OnceCell<Option<crate::projection::ProjectionCells>>,
77}
78
79impl NurbsSurface {
80 pub fn new(
81 degree_u: usize,
82 degree_v: usize,
83 knots_u: Vec<f64>,
84 knots_v: Vec<f64>,
85 control_points: Vec<Vec<Vec4>>,
86 ) -> Result<Self, String> {
87 let surface = Self {
88 degree_u,
89 degree_v,
90 knots_u,
91 knots_v,
92 control_points,
93 analytic: std::cell::OnceCell::new(),
94 validated: std::cell::Cell::new(false),
95 closed_directions: std::cell::OnceCell::new(),
96 projection_grid: std::cell::OnceCell::new(),
97 projection_dense_grid: std::cell::OnceCell::new(),
98 projection_ring_grid: std::cell::OnceCell::new(),
99 projection_cells: std::cell::OnceCell::new(),
100 };
101 surface.ensure_valid()?;
102 Ok(surface)
103 }
104
105 pub fn closed_directions(&self) -> Result<(bool, bool), String> {
110 if let Some(&cached) = self.closed_directions.get() {
111 return Ok(cached);
112 }
113 const CLOSED_SEAM_TOLERANCE: f64 = 1e-6;
114 let [u0, u1] = self.domain_u()?;
115 let [v0, v1] = self.domain_v()?;
116 let closed_along = |direction_u: bool| -> Result<bool, String> {
117 let mut travels = false;
125 for fraction in [0.17, 0.5, 0.83] {
126 let (a, b, middle) = if direction_u {
127 let v = v0 + (v1 - v0) * fraction;
128 (
129 self.evaluate(u0, v)?,
130 self.evaluate(u1, v)?,
131 self.evaluate(0.5 * (u0 + u1), v)?,
132 )
133 } else {
134 let u = u0 + (u1 - u0) * fraction;
135 (
136 self.evaluate(u, v0)?,
137 self.evaluate(u, v1)?,
138 self.evaluate(u, 0.5 * (v0 + v1))?,
139 )
140 };
141 if a.sub(b).length() > CLOSED_SEAM_TOLERANCE {
142 return Ok(false);
143 }
144 if middle.sub(a).length() > CLOSED_SEAM_TOLERANCE {
145 travels = true;
146 }
147 }
148 Ok(travels)
149 };
150 let value = (closed_along(true)?, closed_along(false)?);
151 Ok(*self.closed_directions.get_or_init(|| value))
152 }
153
154 fn ensure_valid(&self) -> Result<(), String> {
156 if self.validated.get() {
157 return Ok(());
158 }
159 validate_knots(&self.knots_u, self.degree_u)?;
160 validate_knots(&self.knots_v, self.degree_v)?;
161 let rows = self.knots_u.len() - self.degree_u - 1;
162 let columns = self.knots_v.len() - self.degree_v - 1;
163 if self.control_points.len() != rows {
164 return Err(format!(
165 "NurbsSurface: u knot vector implies {} rows, got {}",
166 rows,
167 self.control_points.len()
168 ));
169 }
170 for row in &self.control_points {
171 if row.len() != columns {
172 return Err(format!(
173 "NurbsSurface: v knot vector implies {} columns, got {}",
174 columns,
175 row.len()
176 ));
177 }
178 if row.iter().any(|point| {
179 point.w <= EPS
180 || ![point.x, point.y, point.z, point.w]
181 .iter()
182 .all(|value| value.is_finite())
183 }) {
184 return Err(
185 "NurbsSurface: control points must be finite with positive weights".into(),
186 );
187 }
188 }
189 self.validated.set(true);
190 Ok(())
191 }
192
193 pub fn domain_u(&self) -> Result<[f64; 2], String> {
194 self.ensure_valid()?;
195 Ok(knot_domain(&self.knots_u, self.degree_u))
196 }
197
198 pub fn domain_v(&self) -> Result<[f64; 2], String> {
199 self.ensure_valid()?;
200 Ok(knot_domain(&self.knots_v, self.degree_v))
201 }
202
203 pub fn analytic(&self) -> Option<&crate::AnalyticSurface> {
206 self.analytic
207 .get_or_init(|| crate::analytic_surface::recognize(self))
208 .as_ref()
209 }
210
211 pub(crate) fn warm_caches(&self) -> Vec<&'static str> {
215 let mut warm = Vec::new();
216 let cells = [
217 ("analytic", self.analytic.get().is_some()),
218 ("validated", self.validated.get()),
219 ("closed", self.closed_directions.get().is_some()),
220 ("grid", self.projection_grid.get().is_some()),
221 ("dense", self.projection_dense_grid.get().is_some()),
222 ("ring", self.projection_ring_grid.get().is_some()),
223 ("cells", self.projection_cells.get().is_some()),
224 ];
225 for (name, held) in cells {
226 if held {
227 warm.push(name);
228 }
229 }
230 warm
231 }
232
233 pub(crate) fn exchange_caches(&mut self, names: &[&str], from: Option<&Self>) {
238 let wants = |name: &str| names.iter().any(|&n| n == name || n == "all");
239 if wants("analytic") {
240 self.analytic = from.map(|f| f.analytic.clone()).unwrap_or_default();
241 }
242 if wants("validated") {
243 self.validated = std::cell::Cell::new(from.is_some_and(|f| f.validated.get()));
244 }
245 if wants("closed") {
246 self.closed_directions = from.map(|f| f.closed_directions.clone()).unwrap_or_default();
247 }
248 if wants("grid") {
249 self.projection_grid = from.map(|f| f.projection_grid.clone()).unwrap_or_default();
250 }
251 if wants("dense") {
252 self.projection_dense_grid = from.map(|f| f.projection_dense_grid.clone()).unwrap_or_default();
253 }
254 if wants("ring") {
255 self.projection_ring_grid = from.map(|f| f.projection_ring_grid.clone()).unwrap_or_default();
256 }
257 if wants("cells") {
258 self.projection_cells = from.map(|f| f.projection_cells.clone()).unwrap_or_default();
259 }
260 }
261
262 fn knot_vectors(&self) -> Result<(KnotVector, KnotVector), String> {
263 Ok((
264 KnotVector::new(self.knots_u.clone(), self.degree_u)?,
265 KnotVector::new(self.knots_v.clone(), self.degree_v)?,
266 ))
267 }
268
269 pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String> {
270 self.ensure_valid()?;
271 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
272 return self.evaluate_homogeneous_heap(u, v);
273 }
274 let u = knot_clamp(&self.knots_u, self.degree_u, u);
275 let v = knot_clamp(&self.knots_v, self.degree_v, v);
276 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
277 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
278 if analytic_point_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
281 && matches!(self.analytic.get().and_then(|carrier| carrier.as_ref()),
282 Some(crate::AnalyticSurface::Plane { .. } | crate::AnalyticSurface::RuledRevolution { .. }
283 | crate::AnalyticSurface::Sphere { .. } | crate::AnalyticSurface::Torus { .. })) {
284 return Ok(match (self.degree_u, self.degree_v) {
285 (1, 1) => self.analytic_point_small::<1, 1>(u, v, span_u, span_v),
286 (2, 1) => self.analytic_point_small::<2, 1>(u, v, span_u, span_v),
287 (1, 2) => self.analytic_point_small::<1, 2>(u, v, span_u, span_v),
288 (2, 2) => self.analytic_point_small::<2, 2>(u, v, span_u, span_v),
289 _ => unreachable!(),
290 });
291 }
292 let mut basis_u = [0.0f64; MAX_STACK_ORDER];
293 let mut basis_v = [0.0f64; MAX_STACK_ORDER];
294 basis_functions_into(&self.knots_u, self.degree_u, span_u, u, &mut basis_u);
295 basis_functions_into(&self.knots_v, self.degree_v, span_v, v, &mut basis_v);
296 let mut point = Vec4 {
297 x: 0.0,
298 y: 0.0,
299 z: 0.0,
300 w: 0.0,
301 };
302 for (i, value_u) in basis_u[..=self.degree_u].iter().enumerate() {
303 let row = &self.control_points[span_u - self.degree_u + i];
304 for (j, value_v) in basis_v[..=self.degree_v].iter().enumerate() {
305 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
306 }
307 }
308 Ok(point)
309 }
310
311 fn analytic_point_small<const DU: usize, const DV: usize>(&self, u: f64, v: f64, span_u: usize, span_v: usize) -> Vec4 {
315 fn basis<const D: usize>(knots: &[f64], span: usize, parameter: f64) -> [f64; 3] {
316 let mut values = [0.0; 3];
317 let mut left = [0.0; 3];
318 let mut right = [0.0; 3];
319 values[0] = 1.0;
320 for j in 1..=D {
321 left[j] = parameter - knots[span + 1 - j];
322 right[j] = knots[span + j] - parameter;
323 let mut saved = 0.0;
324 for r in 0..j {
325 let denominator = right[r + 1] + left[j - r];
326 let temporary = if denominator.abs() <= EPS { 0.0 } else { values[r] / denominator };
327 values[r] = saved + right[r + 1] * temporary;
328 saved = left[j - r] * temporary;
329 }
330 values[j] = saved;
331 }
332 values
333 }
334 let basis_u = basis::<DU>(&self.knots_u, span_u, u);
335 let basis_v = basis::<DV>(&self.knots_v, span_v, v);
336 let mut point = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
337 for (i, value_u) in basis_u[..=DU].iter().enumerate() {
338 let row = &self.control_points[span_u - DU + i];
339 for (j, value_v) in basis_v[..=DV].iter().enumerate() {
340 point = point.add(row[span_v - DV + j].scale(value_u * value_v));
341 }
342 }
343 point
344 }
345
346 fn evaluate_homogeneous_heap(&self, u: f64, v: f64) -> Result<Vec4, String> {
347 let (knot_u, knot_v) = self.knot_vectors()?;
348 let u = knot_u.clamp_param(u);
349 let v = knot_v.clamp_param(v);
350 let span_u = knot_u.find_span(u);
351 let span_v = knot_v.find_span(v);
352 let basis_u = knot_u.basis_functions(span_u, u);
353 let basis_v = knot_v.basis_functions(span_v, v);
354 let mut point = Vec4 {
355 x: 0.0,
356 y: 0.0,
357 z: 0.0,
358 w: 0.0,
359 };
360 for (i, value_u) in basis_u.iter().enumerate() {
361 let row = &self.control_points[span_u - self.degree_u + i];
362 for (j, value_v) in basis_v.iter().enumerate() {
363 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
364 }
365 }
366 Ok(point)
367 }
368
369 pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String> {
370 self.evaluate_homogeneous(u, v)?.point()
371 }
372
373 pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String> {
379 Ok(self.derivatives_extended(u, v, 0)?[0][0])
380 }
381
382 pub fn derivatives_extended(
387 &self,
388 u: f64,
389 v: f64,
390 derivative_count: usize,
391 ) -> Result<Vec<Vec<Vec3>>, String> {
392 let [u0, u1] = self.domain_u()?;
393 let [v0, v1] = self.domain_v()?;
394 let (closed_u, closed_v) = self.closed_directions()?;
395 let mut uu = u;
396 let mut vv = v;
397 if closed_u && (u < u0 || u > u1) {
398 uu = u0 + (u - u0).rem_euclid(u1 - u0);
399 }
400 if closed_v && (v < v0 || v > v1) {
401 vv = v0 + (v - v0).rem_euclid(v1 - v0);
402 }
403 let du_out = if uu < u0 {
404 uu - u0
405 } else if uu > u1 {
406 uu - u1
407 } else {
408 0.0
409 };
410 let dv_out = if vv < v0 {
411 vv - v0
412 } else if vv > v1 {
413 vv - v1
414 } else {
415 0.0
416 };
417 if du_out == 0.0 && dv_out == 0.0 {
418 return self.derivatives(uu, vv, derivative_count);
419 }
420 let anchor_u = uu - du_out;
421 let anchor_v = vv - dv_out;
422 let base = self.derivatives(anchor_u, anchor_v, derivative_count.max(2))?;
423 let order = derivative_count + 1;
424 let mut result = vec![vec![Vec3::default(); order]; order];
425 result[0][0] = base[0][0]
429 .add(base[1][0].scale(du_out))
430 .add(base[0][1].scale(dv_out))
431 .add(base[1][1].scale(du_out * dv_out));
432 if derivative_count >= 1 {
433 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
434 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
435 result[1][1] = base[1][1];
436 }
437 if derivative_count >= 2 {
438 let second = self.derivatives(anchor_u, anchor_v, 2)?;
439 if du_out == 0.0 {
440 result[2][0] = second[2][0];
441 }
442 if dv_out == 0.0 {
443 result[0][2] = second[0][2];
444 }
445 }
446 Ok(result)
447 }
448
449 pub fn derivatives(
450 &self,
451 u: f64,
452 v: f64,
453 derivative_count: usize,
454 ) -> Result<Vec<Vec<Vec3>>, String> {
455 self.ensure_valid()?;
456 let du = derivative_count.min(self.degree_u);
457 let dv = derivative_count.min(self.degree_v);
458 let stride = derivative_count + 1;
459 let zero = Vec4 {
461 x: 0.0,
462 y: 0.0,
463 z: 0.0,
464 w: 0.0,
465 };
466 let mut homogeneous = vec![zero; stride * stride];
467 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
468 let u = knot_clamp(&self.knots_u, self.degree_u, u);
469 let v = knot_clamp(&self.knots_v, self.degree_v, v);
470 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
471 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
472 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
473 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
474 basis_derivatives_into(
475 &self.knots_u,
476 self.degree_u,
477 span_u,
478 u,
479 du,
480 &mut basis_u[..=du],
481 );
482 basis_derivatives_into(
483 &self.knots_v,
484 self.degree_v,
485 span_v,
486 v,
487 dv,
488 &mut basis_v[..=dv],
489 );
490 for k in 0..=du {
491 for l in 0..=dv {
492 if k + l > derivative_count {
493 continue;
494 }
495 let mut point = zero;
496 for i in 0..=self.degree_u {
497 let row = &self.control_points[span_u - self.degree_u + i];
498 for j in 0..=self.degree_v {
499 point = point.add(
500 row[span_v - self.degree_v + j]
501 .scale(basis_u[k][i] * basis_v[l][j]),
502 );
503 }
504 }
505 homogeneous[k * stride + l] = point;
506 }
507 }
508 } else {
509 let (knot_u, knot_v) = self.knot_vectors()?;
510 let u = knot_u.clamp_param(u);
511 let v = knot_v.clamp_param(v);
512 let span_u = knot_u.find_span(u);
513 let span_v = knot_v.find_span(v);
514 let basis_u = knot_u.basis_derivatives(span_u, u, du);
515 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
516 for k in 0..=du {
517 for l in 0..=dv {
518 if k + l > derivative_count {
519 continue;
520 }
521 let mut point = zero;
522 for i in 0..=self.degree_u {
523 let row = &self.control_points[span_u - self.degree_u + i];
524 for j in 0..=self.degree_v {
525 point = point.add(
526 row[span_v - self.degree_v + j]
527 .scale(basis_u[k][i] * basis_v[l][j]),
528 );
529 }
530 }
531 homogeneous[k * stride + l] = point;
532 }
533 }
534 }
535
536 let mut result = vec![vec![Vec3::default(); derivative_count + 1]; derivative_count + 1];
537 let weight = homogeneous[0].w;
538 if weight.abs() <= EPS {
539 return Err("NurbsSurface: zero evaluated weight".into());
540 }
541 for k in 0..=derivative_count {
542 for l in 0..=derivative_count - k {
543 if k > du || l > dv {
544 continue;
545 }
546 let at = |row: usize, column: usize| homogeneous[row * stride + column];
547 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
548 for j in 1..=l {
549 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
550 }
551 for i in 1..=k {
552 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
553 let mut mixed = Vec3::default();
554 for j in 1..=l {
555 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
556 }
557 value = value.sub(mixed.scale(binomial(k, i)));
558 }
559 result[k][l] = value.scale(1.0 / weight);
560 }
561 }
562 Ok(result)
563 }
564
565 pub(crate) fn derivatives_small(
580 &self,
581 u: f64,
582 v: f64,
583 derivative_count: usize,
584 ) -> Result<[[Vec3; 3]; 3], String> {
585 debug_assert!(derivative_count <= 2);
586 self.ensure_valid()?;
587 let du = derivative_count.min(self.degree_u);
588 let dv = derivative_count.min(self.degree_v);
589 let stride = derivative_count + 1;
590 let zero = Vec4 {
591 x: 0.0,
592 y: 0.0,
593 z: 0.0,
594 w: 0.0,
595 };
596 let mut homogeneous = [zero; 9];
597 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
598 let u = knot_clamp(&self.knots_u, self.degree_u, u);
599 let v = knot_clamp(&self.knots_v, self.degree_v, v);
600 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
601 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
602 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
603 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
604 basis_derivatives_into(
605 &self.knots_u,
606 self.degree_u,
607 span_u,
608 u,
609 du,
610 &mut basis_u[..=du],
611 );
612 basis_derivatives_into(
613 &self.knots_v,
614 self.degree_v,
615 span_v,
616 v,
617 dv,
618 &mut basis_v[..=dv],
619 );
620 for k in 0..=du {
621 for l in 0..=dv {
622 if k + l > derivative_count {
623 continue;
624 }
625 let mut point = zero;
626 for i in 0..=self.degree_u {
627 let row = &self.control_points[span_u - self.degree_u + i];
628 for j in 0..=self.degree_v {
629 point = point.add(
630 row[span_v - self.degree_v + j]
631 .scale(basis_u[k][i] * basis_v[l][j]),
632 );
633 }
634 }
635 homogeneous[k * stride + l] = point;
636 }
637 }
638 } else {
639 let (knot_u, knot_v) = self.knot_vectors()?;
640 let u = knot_u.clamp_param(u);
641 let v = knot_v.clamp_param(v);
642 let span_u = knot_u.find_span(u);
643 let span_v = knot_v.find_span(v);
644 let basis_u = knot_u.basis_derivatives(span_u, u, du);
645 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
646 for k in 0..=du {
647 for l in 0..=dv {
648 if k + l > derivative_count {
649 continue;
650 }
651 let mut point = zero;
652 for i in 0..=self.degree_u {
653 let row = &self.control_points[span_u - self.degree_u + i];
654 for j in 0..=self.degree_v {
655 point = point.add(
656 row[span_v - self.degree_v + j]
657 .scale(basis_u[k][i] * basis_v[l][j]),
658 );
659 }
660 }
661 homogeneous[k * stride + l] = point;
662 }
663 }
664 }
665
666 let mut result = [[Vec3::default(); 3]; 3];
667 let weight = homogeneous[0].w;
668 if weight.abs() <= EPS {
669 return Err("NurbsSurface: zero evaluated weight".into());
670 }
671 for k in 0..=derivative_count {
672 for l in 0..=derivative_count - k {
673 if k > du || l > dv {
674 continue;
675 }
676 let at = |row: usize, column: usize| homogeneous[row * stride + column];
677 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
678 for j in 1..=l {
679 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
680 }
681 for i in 1..=k {
682 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
683 let mut mixed = Vec3::default();
684 for j in 1..=l {
685 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
686 }
687 value = value.sub(mixed.scale(binomial(k, i)));
688 }
689 result[k][l] = value.scale(1.0 / weight);
690 }
691 }
692 Ok(result)
693 }
694
695 #[inline]
699 pub(crate) fn deriv1_tensor_supported(&self) -> bool {
700 self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
701 }
702
703 pub(crate) fn deriv1_tensor_each<F>(
725 &self,
726 us: &[f64],
727 vs: &[f64],
728 mut f: F,
729 ) -> Result<(), String>
730 where
731 F: FnMut(usize, usize, Vec3, Vec3, Vec3) -> Result<(), String>,
732 {
733 self.ensure_valid()?;
734 const MAX_BLOCK: usize = TENSOR_MAX_BLOCK;
736 if !self.deriv1_tensor_supported() || us.len() > MAX_BLOCK || vs.len() > MAX_BLOCK {
737 return Err("NurbsSurface: tensor block outside the stack evaluator".into());
738 }
739 let du = 1usize.min(self.degree_u);
740 let dv = 1usize.min(self.degree_v);
741 let mut span_u = [0usize; MAX_BLOCK];
742 let mut span_v = [0usize; MAX_BLOCK];
743 let mut basis_u = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
744 let mut basis_v = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
745 for (index, &value) in us.iter().enumerate() {
746 let value = knot_clamp(&self.knots_u, self.degree_u, value);
747 span_u[index] = knot_find_span(&self.knots_u, self.degree_u, value);
748 basis_derivatives_into(
749 &self.knots_u,
750 self.degree_u,
751 span_u[index],
752 value,
753 du,
754 &mut basis_u[index][..=du],
755 );
756 }
757 for (index, &value) in vs.iter().enumerate() {
758 let value = knot_clamp(&self.knots_v, self.degree_v, value);
759 span_v[index] = knot_find_span(&self.knots_v, self.degree_v, value);
760 basis_derivatives_into(
761 &self.knots_v,
762 self.degree_v,
763 span_v[index],
764 value,
765 dv,
766 &mut basis_v[index][..=dv],
767 );
768 }
769 let zero = Vec4 {
770 x: 0.0,
771 y: 0.0,
772 z: 0.0,
773 w: 0.0,
774 };
775 for i in 0..us.len() {
776 let rows = &self.control_points[span_u[i] - self.degree_u..];
777 for j in 0..vs.len() {
778 let column = span_v[j] - self.degree_v;
779 let sum = |k: usize, l: usize| {
783 let mut point = zero;
784 for row_index in 0..=self.degree_u {
785 let row = &rows[row_index];
786 for column_index in 0..=self.degree_v {
787 point = point.add(
788 row[column + column_index]
789 .scale(basis_u[i][k][row_index] * basis_v[j][l][column_index]),
790 );
791 }
792 }
793 point
794 };
795 let h00 = sum(0, 0);
796 let h01 = if dv >= 1 { sum(0, 1) } else { zero };
797 let h10 = if du >= 1 { sum(1, 0) } else { zero };
798 let weight = h00.w;
799 if weight.abs() <= EPS {
800 return Err("NurbsSurface: zero evaluated weight".into());
801 }
802 let inverse = 1.0 / weight;
803 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
804 let partial_v = if dv >= 1 {
805 Vec3::new(h01.x, h01.y, h01.z)
806 .sub(point.scale(h01.w))
807 .scale(inverse)
808 } else {
809 Vec3::default()
810 };
811 let partial_u = if du >= 1 {
812 Vec3::new(h10.x, h10.y, h10.z)
813 .sub(point.scale(h10.w))
814 .scale(inverse)
815 } else {
816 Vec3::default()
817 };
818 f(i, j, point, partial_u, partial_v)?;
819 }
820 }
821 Ok(())
822 }
823
824 #[inline]
827 pub(crate) fn deriv1(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
828 let g = self.derivatives_small(u, v, 1)?;
829 Ok((g[0][0], g[1][0], g[0][1]))
830 }
831
832 fn analytic_deriv1_small<const DEGREE_U: usize, const DEGREE_V: usize>(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
836 self.ensure_valid()?;
837 let du = DEGREE_U.min(1);
838 let dv = DEGREE_V.min(1);
839 let u = knot_clamp(&self.knots_u, DEGREE_U, u);
840 let v = knot_clamp(&self.knots_v, DEGREE_V, v);
841 let span_u = knot_find_span(&self.knots_u, DEGREE_U, u);
842 let span_v = knot_find_span(&self.knots_v, DEGREE_V, v);
843 let mut basis_u = [[0.0; MAX_STACK_ORDER]; 2];
844 let mut basis_v = [[0.0; MAX_STACK_ORDER]; 2];
845 basis_derivatives_into(&self.knots_u, DEGREE_U, span_u, u, du, &mut basis_u[..=du]);
846 basis_derivatives_into(&self.knots_v, DEGREE_V, span_v, v, dv, &mut basis_v[..=dv]);
847 let zero = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
848 let sum = |k: usize, l: usize| {
849 let mut value = zero;
850 for i in 0..=DEGREE_U {
851 let row = &self.control_points[span_u - DEGREE_U + i];
852 for j in 0..=DEGREE_V {
853 value = value.add(row[span_v - DEGREE_V + j].scale(basis_u[k][i] * basis_v[l][j]));
854 }
855 }
856 value
857 };
858 let h00 = sum(0, 0);
859 let h01 = if dv == 1 { sum(0, 1) } else { zero };
860 let h10 = if du == 1 { sum(1, 0) } else { zero };
861 if h00.w.abs() <= EPS { return Err("NurbsSurface: zero evaluated weight".into()); }
862 let inverse = 1.0 / h00.w;
863 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
864 let partial_v = if dv == 1 {
865 Vec3::new(h01.x, h01.y, h01.z).sub(point.scale(1.0 * h01.w)).scale(inverse)
866 } else { Vec3::default() };
867 let partial_u = if du == 1 {
868 Vec3::new(h10.x, h10.y, h10.z).sub(point.scale(1.0 * h10.w))
869 .sub(Vec3::default().scale(1.0)).scale(inverse)
870 } else { Vec3::default() };
871 Ok((point, partial_u, partial_v))
872 }
873
874 pub(crate) fn deriv1_extended(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
882 let [u0, u1] = self.domain_u()?;
883 let [v0, v1] = self.domain_v()?;
884 let (closed_u, closed_v) = self.closed_directions()?;
885 let mut uu = u;
886 let mut vv = v;
887 if closed_u && (u < u0 || u > u1) {
888 uu = u0 + (u - u0).rem_euclid(u1 - u0);
889 }
890 if closed_v && (v < v0 || v > v1) {
891 vv = v0 + (v - v0).rem_euclid(v1 - v0);
892 }
893 let du_out = if uu < u0 {
894 uu - u0
895 } else if uu > u1 {
896 uu - u1
897 } else {
898 0.0
899 };
900 let dv_out = if vv < v0 {
901 vv - v0
902 } else if vv > v1 {
903 vv - v1
904 } else {
905 0.0
906 };
907 if du_out == 0.0 && dv_out == 0.0 {
908 if analytic_deriv1_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
909 && matches!(self.analytic(), Some(crate::AnalyticSurface::Plane { .. }
910 | crate::AnalyticSurface::RuledRevolution { .. } | crate::AnalyticSurface::Sphere { .. }
911 | crate::AnalyticSurface::Torus { .. })) {
912 return match (self.degree_u, self.degree_v) {
913 (1, 1) => self.analytic_deriv1_small::<1, 1>(uu, vv),
914 (2, 1) => self.analytic_deriv1_small::<2, 1>(uu, vv),
915 (1, 2) => self.analytic_deriv1_small::<1, 2>(uu, vv),
916 (2, 2) => self.analytic_deriv1_small::<2, 2>(uu, vv),
917 _ => unreachable!(),
918 };
919 }
920 let g = self.derivatives_small(uu, vv, 1)?;
921 return Ok((g[0][0], g[1][0], g[0][1]));
922 }
923 let anchor_u = uu - du_out;
924 let anchor_v = vv - dv_out;
925 let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
926 let s = base[0][0]
930 .add(base[1][0].scale(du_out))
931 .add(base[0][1].scale(dv_out))
932 .add(base[1][1].scale(du_out * dv_out));
933 let su = base[1][0].add(base[1][1].scale(dv_out));
934 let sv = base[0][1].add(base[1][1].scale(du_out));
935 Ok((s, su, sv))
936 }
937
938 pub(crate) fn partial_by_differences(
963 &self,
964 along_u: bool,
965 u: f64,
966 v: f64,
967 ) -> Result<Vec3, String> {
968 self.ensure_valid()?;
969 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
970 return Err("NurbsSurface: partial by differences exceeds the stack degree".into());
971 }
972 let (pu, pv) = (self.degree_u, self.degree_v);
973 let u = knot_clamp(&self.knots_u, pu, u);
974 let v = knot_clamp(&self.knots_v, pv, v);
975 let span_u = knot_find_span(&self.knots_u, pu, u);
976 let span_v = knot_find_span(&self.knots_v, pv, v);
977 let mut full_u = [0.0f64; MAX_STACK_ORDER];
978 let mut full_v = [0.0f64; MAX_STACK_ORDER];
979 basis_functions_into(&self.knots_u, pu, span_u, u, &mut full_u);
980 basis_functions_into(&self.knots_v, pv, span_v, v, &mut full_v);
981 let zero = Vec4 {
982 x: 0.0,
983 y: 0.0,
984 z: 0.0,
985 w: 0.0,
986 };
987 let control = |i: usize, j: usize| self.control_points[span_u - pu + i][span_v - pv + j];
988 let mut point = zero;
989 for i in 0..=pu {
990 for j in 0..=pv {
991 point = point.add(control(i, j).scale(full_u[i] * full_v[j]));
992 }
993 }
994 if point.w.abs() <= EPS {
995 return Err("NurbsSurface: zero evaluated weight".into());
996 }
997 let source = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
998 let (knots, degree, span, parameter) = if along_u {
999 (&self.knots_u, pu, span_u, u)
1000 } else {
1001 (&self.knots_v, pv, span_v, v)
1002 };
1003 if degree == 0 {
1004 return Ok(Vec3::default());
1005 }
1006 let mut lowered = [0.0f64; MAX_STACK_ORDER];
1007 basis_functions_into(knots, degree - 1, span, parameter, &mut lowered);
1008 let mut hodograph = zero;
1011 for r in 0..degree {
1012 let index = span - degree + r;
1015 let width = knots[index + degree + 1] - knots[index + 1];
1016 if width.abs() <= EPS {
1017 continue;
1018 }
1019 let factor = lowered[r] * degree as f64 / width;
1020 if along_u {
1021 for j in 0..=pv {
1022 let difference = control(r + 1, j).add(control(r, j).scale(-1.0));
1023 hodograph = hodograph.add(difference.scale(factor * full_v[j]));
1024 }
1025 } else {
1026 for i in 0..=pu {
1027 let difference = control(i, r + 1).add(control(i, r).scale(-1.0));
1028 hodograph = hodograph.add(difference.scale(factor * full_u[i]));
1029 }
1030 }
1031 }
1032 Ok(Vec3::new(hodograph.x, hodograph.y, hodograph.z)
1033 .sub(source.scale(hodograph.w))
1034 .scale(1.0 / point.w))
1035 }
1036
1037 pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
1038 let derivatives = self.derivatives(u, v, 1)?;
1039 derivatives[1][0].cross(derivatives[0][1]).normalized()
1040 }
1041
1042 pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String> {
1049 let derivatives = self.derivatives(u, v, 2)?;
1050 let su = derivatives[1][0];
1051 let sv = derivatives[0][1];
1052 let cross = su.cross(sv);
1053 let cross_length = cross.length();
1054 if cross_length <= 1e-12 {
1055 return Err(format!(
1056 "degenerate parametrization at (u={u:.4}, v={v:.4})"
1057 ));
1058 }
1059 let normal = cross.scale(1.0 / cross_length);
1060 let e1 = su.dot(su);
1061 let f1 = su.dot(sv);
1062 let g1 = sv.dot(sv);
1063 let l2 = derivatives[2][0].dot(normal);
1064 let m2 = derivatives[1][1].dot(normal);
1065 let n2 = derivatives[0][2].dot(normal);
1066 let denominator = e1 * g1 - f1 * f1;
1067 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();
1070 Ok((
1071 mean_double * 0.5 - discriminant,
1072 mean_double * 0.5 + discriminant,
1073 ))
1074 }
1075
1076 pub fn is_affine(&self) -> Result<bool, String> {
1081 if self.degree_u != 1
1082 || self.degree_v != 1
1083 || self.control_points.len() != 2
1084 || self.control_points[0].len() != 2
1085 || self.control_points[1].len() != 2
1086 {
1087 return Ok(false);
1088 }
1089 let weight = self.control_points[0][0].w;
1090 if [
1091 self.control_points[0][1].w,
1092 self.control_points[1][0].w,
1093 self.control_points[1][1].w,
1094 ]
1095 .iter()
1096 .any(|other| (*other - weight).abs() > 1e-12 * other.abs().max(weight.abs()))
1097 {
1098 return Ok(false);
1099 }
1100 let p00 = self.control_points[0][0].point()?;
1101 let p01 = self.control_points[0][1].point()?;
1102 let p10 = self.control_points[1][0].point()?;
1103 let p11 = self.control_points[1][1].point()?;
1104 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
1105 let extent = [
1110 p01.sub(p00),
1111 p10.sub(p00),
1112 p11.sub(p00),
1113 p10.sub(p01),
1114 p11.sub(p01),
1115 p11.sub(p10),
1116 ]
1117 .iter()
1118 .map(|delta| delta.length())
1119 .fold(0.0, f64::max);
1120 Ok(gap <= 1e-9 * (1.0 + extent))
1121 }
1122
1123 pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
1124 self.ensure_valid()?;
1125 let u = knot_clamp(&self.knots_u, self.degree_u, u);
1126 let span = knot_find_span(&self.knots_u, self.degree_u, u);
1127 let basis = if self.degree_u <= MAX_STACK_DEGREE {
1128 let mut stack = [0.0f64; MAX_STACK_ORDER];
1129 basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
1130 stack[..=self.degree_u].to_vec()
1131 } else {
1132 KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
1133 };
1134 let mut points = Vec::with_capacity(self.control_points[0].len());
1135 for column in 0..self.control_points[0].len() {
1136 let mut point = Vec4 {
1137 x: 0.0,
1138 y: 0.0,
1139 z: 0.0,
1140 w: 0.0,
1141 };
1142 for (index, value) in basis.iter().enumerate() {
1143 point = point
1144 .add(self.control_points[span - self.degree_u + index][column].scale(*value));
1145 }
1146 points.push(point);
1147 }
1148 NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
1149 }
1150
1151 pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
1152 self.ensure_valid()?;
1153 let v = knot_clamp(&self.knots_v, self.degree_v, v);
1154 let span = knot_find_span(&self.knots_v, self.degree_v, v);
1155 let basis = if self.degree_v <= MAX_STACK_DEGREE {
1156 let mut stack = [0.0f64; MAX_STACK_ORDER];
1157 basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
1158 stack[..=self.degree_v].to_vec()
1159 } else {
1160 KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
1161 };
1162 let mut points = Vec::with_capacity(self.control_points.len());
1163 for row in &self.control_points {
1164 let mut point = Vec4 {
1165 x: 0.0,
1166 y: 0.0,
1167 z: 0.0,
1168 w: 0.0,
1169 };
1170 for (index, value) in basis.iter().enumerate() {
1171 point = point.add(row[span - self.degree_v + index].scale(*value));
1172 }
1173 points.push(point);
1174 }
1175 NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
1176 }
1177}
1178
1179pub fn make_plane(
1180 origin: Vec3,
1181 u_direction: Vec3,
1182 v_direction: Vec3,
1183 u_max: f64,
1184 v_max: f64,
1185) -> Result<NurbsSurface, String> {
1186 if u_max <= EPS || v_max <= EPS {
1187 return Err("makePlane: parameter extents must be positive".into());
1188 }
1189 let p10 = origin.add(u_direction.scale(u_max));
1190 let p01 = origin.add(v_direction.scale(v_max));
1191 let p11 = p10.add(v_direction.scale(v_max));
1192 NurbsSurface::new(
1193 1,
1194 1,
1195 vec![0.0, 0.0, u_max, u_max],
1196 vec![0.0, 0.0, v_max, v_max],
1197 vec![
1198 vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
1199 vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
1200 ],
1201 )
1202}
1203
1204pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
1205 let rows = profile
1206 .control_points
1207 .iter()
1208 .map(|point| {
1209 vec![
1210 *point,
1211 Vec4 {
1212 x: point.x + point.w * direction.x,
1213 y: point.y + point.w * direction.y,
1214 z: point.z + point.w * direction.z,
1215 w: point.w,
1216 },
1217 ]
1218 })
1219 .collect();
1220 NurbsSurface::new(
1221 profile.degree,
1222 1,
1223 profile.knots.clone(),
1224 vec![0.0, 0.0, 1.0, 1.0],
1225 rows,
1226 )
1227}
1228
1229pub fn make_revolution(
1230 axis_point: Vec3,
1231 axis_direction: Vec3,
1232 generatrix: &NurbsCurve,
1233 theta: f64,
1234) -> Result<NurbsSurface, String> {
1235 if theta <= EPS || theta > std::f64::consts::TAU + EPS {
1236 return Err("makeRevolution: theta must be in (0, 2*PI]".into());
1237 }
1238 let theta = theta.min(std::f64::consts::TAU);
1239 let axis = axis_direction.normalized()?;
1240 let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
1241 let arc_angle = theta / arc_count as f64;
1242 let middle_weight = (arc_angle / 2.0).cos();
1243 let mut knots_u = vec![0.0, 0.0, 0.0];
1244 for index in 1..arc_count {
1245 let knot = index as f64 / arc_count as f64;
1246 knots_u.extend([knot, knot]);
1247 }
1248 knots_u.extend([1.0, 1.0, 1.0]);
1249 let mut rows = vec![
1250 vec![
1251 Vec4 {
1252 x: 0.0,
1253 y: 0.0,
1254 z: 0.0,
1255 w: 1.0,
1256 };
1257 generatrix.control_points.len()
1258 ];
1259 2 * arc_count + 1
1260 ];
1261
1262 let mut radial_extent: f64 = 0.0;
1265 for control_point in &generatrix.control_points {
1266 let point = control_point.point()?;
1267 let offset = point.sub(axis_point);
1268 radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
1269 }
1270
1271 for (column, control_point) in generatrix.control_points.iter().enumerate() {
1272 let point = control_point.point()?;
1273 let weight = control_point.w;
1274 let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
1275 let radial_vector = point.sub(axis_origin);
1276 let measured_radius = radial_vector.length();
1286 let pole_tolerance = EPS.max(1e-8 * radial_extent);
1287 let radius = if measured_radius <= pole_tolerance {
1288 0.0
1289 } else {
1290 measured_radius
1291 };
1292 let (x_axis, y_axis) = if radius <= EPS {
1293 (Vec3::default(), Vec3::default())
1294 } else {
1295 if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
1307 return Err(format!(
1308 "makeRevolution: generatrix control point {column} sits {radius:e} from the \
1309 axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
1310 radial extent (a fuzzy pole)"
1311 ));
1312 }
1313 let x_axis = radial_vector.scale(1.0 / radius);
1314 (x_axis, axis.cross(x_axis))
1315 };
1316 rows[0][column] = Vec4::from_point(point, weight);
1317 let mut start_point = point;
1318 let mut start_tangent = y_axis.scale(radius);
1319 let mut angle = 0.0;
1320 for arc in 1..=arc_count {
1321 angle += arc_angle;
1322 let end_point = if radius <= EPS {
1323 point
1324 } else {
1325 axis_origin
1326 .add(x_axis.scale(radius * angle.cos()))
1327 .add(y_axis.scale(radius * angle.sin()))
1328 };
1329 let end_tangent = if radius <= EPS {
1330 Vec3::default()
1331 } else {
1332 x_axis
1333 .scale(-radius * angle.sin())
1334 .add(y_axis.scale(radius * angle.cos()))
1335 };
1336 let row = 2 * (arc - 1);
1337 if radius <= EPS {
1338 rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
1339 } else {
1340 let cross = start_tangent.cross(end_tangent);
1341 let denominator = cross.length_squared();
1342 let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
1351 if denominator <= EPS * tangent_scale {
1352 return Err("makeRevolution: arc tangents are parallel".into());
1353 }
1354 let distance =
1355 end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
1356 let middle = start_point.add(start_tangent.scale(distance));
1357 rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
1358 }
1359 rows[row + 2][column] = Vec4::from_point(end_point, weight);
1360 start_point = end_point;
1361 start_tangent = end_tangent;
1362 }
1363 }
1364 NurbsSurface::new(
1365 2,
1366 generatrix.degree,
1367 knots_u,
1368 generatrix.knots.clone(),
1369 rows,
1370 )
1371}
1372
1373pub fn make_cylinder_surface(
1374 base: Vec3,
1375 axis_direction: Vec3,
1376 radius: f64,
1377 height: f64,
1378) -> Result<NurbsSurface, String> {
1379 if radius <= EPS || height <= EPS {
1380 return Err("makeCylinder: radius and height must be positive".into());
1381 }
1382 let axis = axis_direction.normalized()?;
1383 let x_axis = axis.perpendicular()?;
1384 let start = base.add(x_axis.scale(radius));
1385 let end = start.add(axis.scale(height));
1386 let generatrix = crate::make_line(start, end)?;
1387 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1388}
1389
1390pub fn make_cone_surface(
1391 base: Vec3,
1392 axis_direction: Vec3,
1393 radius_bottom: f64,
1394 radius_top: f64,
1395 height: f64,
1396) -> Result<NurbsSurface, String> {
1397 if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
1398 return Err("makeCone: invalid radius or height".into());
1399 }
1400 let axis = axis_direction.normalized()?;
1401 let x_axis = axis.perpendicular()?;
1402 let start = base.add(x_axis.scale(radius_bottom));
1403 let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
1404 let generatrix = crate::make_line(start, end)?;
1405 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1406}
1407
1408pub fn make_sphere_surface(
1409 center: Vec3,
1410 radius: f64,
1411 polar_axis: Vec3,
1412) -> Result<NurbsSurface, String> {
1413 make_sphere_surface_framed(center, radius, polar_axis, None)
1414}
1415
1416pub fn make_sphere_surface_framed(
1428 center: Vec3,
1429 radius: f64,
1430 polar_axis: Vec3,
1431 seam_direction: Option<Vec3>,
1432) -> Result<NurbsSurface, String> {
1433 if radius <= EPS {
1434 return Err("makeSphere: radius must be positive".into());
1435 }
1436 let axis = polar_axis.normalized()?;
1437 let x_axis = match seam_direction {
1438 Some(seam) => {
1439 let planar = seam.sub(axis.scale(seam.dot(axis)));
1440 match planar.normalized() {
1441 Ok(unit) if planar.length() > EPS => unit,
1442 _ => axis.perpendicular()?,
1443 }
1444 }
1445 None => axis.perpendicular()?,
1446 };
1447 let meridian = crate::make_arc(
1448 center,
1449 x_axis,
1450 axis,
1451 radius,
1452 -std::f64::consts::FRAC_PI_2,
1453 std::f64::consts::FRAC_PI_2,
1454 )?;
1455 make_revolution(center, axis, &meridian, std::f64::consts::TAU)
1456}
1457
1458pub fn make_torus_surface(
1459 center: Vec3,
1460 axis_direction: Vec3,
1461 major_radius: f64,
1462 minor_radius: f64,
1463) -> Result<NurbsSurface, String> {
1464 if minor_radius <= EPS || major_radius <= minor_radius {
1465 return Err("makeTorus: requires positive minorRadius < majorRadius".into());
1466 }
1467 let axis = axis_direction.normalized()?;
1468 let x_axis = axis.perpendicular()?;
1469 let tube_center = center.add(x_axis.scale(major_radius));
1470 let tube = crate::make_arc(
1471 tube_center,
1472 x_axis,
1473 axis,
1474 minor_radius,
1475 0.0,
1476 std::f64::consts::TAU,
1477 )?;
1478 make_revolution(center, axis, &tube, std::f64::consts::TAU)
1479}
1480
1481fn binomial(n: usize, k: usize) -> f64 {
1482 if k > n {
1483 return 0.0;
1484 }
1485 let k = k.min(n - k);
1486 (1..=k).fold(1.0, |value, index| {
1487 value * (n - k + index) as f64 / index as f64
1488 })
1489}
1490
1491
1492
1493pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
1505 let inflate = if inflate.is_finite() {
1506 inflate.clamp(1.0, 16.0)
1507 } else {
1508 2.0
1509 };
1510 let (closed_u, closed_v) = surface.closed_directions()?;
1511 let [u0, u1] = surface.domain_u()?;
1512 let [v0, v1] = surface.domain_v()?;
1513 let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
1514 if closed || inflate <= 1.0 {
1515 (d0, d1)
1516 } else {
1517 let centre = 0.5 * (d0 + d1);
1518 let half = 0.5 * (d1 - d0) * inflate;
1519 (centre - half, centre + half)
1520 }
1521 };
1522 let (nu0, nu1) = stretch(u0, u1, closed_u);
1523 let (nv0, nv1) = stretch(v0, v1, closed_v);
1524 if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
1525 return Ok(surface.clone());
1526 }
1527 if surface.is_affine()? {
1528 let corners = [
1531 surface.evaluate_extended(nu0, nv0)?,
1532 surface.evaluate_extended(nu1, nv0)?,
1533 surface.evaluate_extended(nu0, nv1)?,
1534 surface.evaluate_extended(nu1, nv1)?,
1535 ];
1536 return NurbsSurface::new(
1537 1,
1538 1,
1539 vec![nu0, nu0, nu1, nu1],
1540 vec![nv0, nv0, nv1, nv1],
1541 vec![
1542 vec![
1543 Vec4::from_point(corners[0], 1.0),
1544 Vec4::from_point(corners[2], 1.0),
1545 ],
1546 vec![
1547 Vec4::from_point(corners[1], 1.0),
1548 Vec4::from_point(corners[3], 1.0),
1549 ],
1550 ],
1551 );
1552 }
1553 const SAMPLES: usize = 32;
1554 let u_params: Vec<f64> = (0..=SAMPLES)
1555 .map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
1556 .collect();
1557 let v_params: Vec<f64> = (0..=SAMPLES)
1558 .map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
1559 .collect();
1560 let mut column_curves = Vec::with_capacity(u_params.len());
1563 for &u in &u_params {
1564 let column: Vec<Vec3> = v_params
1565 .iter()
1566 .map(|&v| surface.evaluate_extended(u, v))
1567 .collect::<Result<_, _>>()?;
1568 column_curves.push(crate::interpolate_curve(
1569 &column,
1570 3,
1571 &normalized(&v_params),
1572 )?);
1573 }
1574 let v_knots = column_curves[0].knots.clone();
1575 let control_rows = column_curves[0].control_points.len();
1576 let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
1577 let mut u_knots = Vec::new();
1578 for row_index in 0..control_rows {
1579 let row: Vec<Vec3> = column_curves
1580 .iter()
1581 .map(|curve| curve.control_points[row_index].point())
1582 .collect::<Result<_, _>>()?;
1583 let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
1584 u_knots = fitted.knots.clone();
1585 for (u_index, control) in fitted.control_points.iter().enumerate() {
1586 if grid.len() <= u_index {
1587 grid.push(Vec::new());
1588 }
1589 grid[u_index].push(*control);
1590 }
1591 }
1592 let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
1595 knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
1596 };
1597 NurbsSurface::new(
1598 3,
1599 3,
1600 rescale(&u_knots, nu0, nu1),
1601 rescale(&v_knots, nv0, nv1),
1602 grid,
1603 )
1604}
1605
1606fn normalized(parameters: &[f64]) -> Vec<f64> {
1607 let first = parameters[0];
1608 let last = parameters[parameters.len() - 1];
1609 parameters
1610 .iter()
1611 .map(|p| (p - first) / (last - first))
1612 .collect()
1613}
1614
1615
1616
1617
1618fn analytic_deriv1_on() -> bool {
1619 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1620 *ON.get_or_init(|| std::env::var("BREP_ANALYTIC_DERIV1").ok().as_deref() != Some("0"))
1621}
1622
1623
1624fn analytic_point_on() -> bool {
1626 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1627 *ON.get_or_init(|| std::env::var("BREP_ANALYTIC_POINT").ok().as_deref() == Some("1"))
1628}