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 = crate::tolerance::FP_FLOOR;
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)]
55 affine_sphere: std::cell::OnceCell<Option<crate::affine_sphere::AffineSphere>>,
56 #[serde(skip, default)]
58 validated: std::cell::Cell<bool>,
59 #[serde(skip, default)]
62 closed_directions: std::cell::OnceCell<(bool, bool)>,
63 #[serde(skip, default)]
66 pub(crate) projection_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
67 #[serde(skip, default)]
71 pub(crate) projection_dense_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
72 #[serde(skip, default)]
76 pub(crate) projection_ring_grid: std::cell::OnceCell<Vec<Vec3>>,
77 #[serde(skip, default)]
81 pub(crate) projection_cells: std::cell::OnceCell<Option<crate::projection::ProjectionCells>>,
82}
83
84impl NurbsSurface {
85 pub fn new(
86 degree_u: usize,
87 degree_v: usize,
88 knots_u: Vec<f64>,
89 knots_v: Vec<f64>,
90 control_points: Vec<Vec<Vec4>>,
91 ) -> Result<Self, String> {
92 let surface = Self {
93 degree_u,
94 degree_v,
95 knots_u,
96 knots_v,
97 control_points,
98 analytic: std::cell::OnceCell::new(),
99 affine_sphere: std::cell::OnceCell::new(),
100 validated: std::cell::Cell::new(false),
101 closed_directions: std::cell::OnceCell::new(),
102 projection_grid: std::cell::OnceCell::new(),
103 projection_dense_grid: std::cell::OnceCell::new(),
104 projection_ring_grid: std::cell::OnceCell::new(),
105 projection_cells: std::cell::OnceCell::new(),
106 };
107 surface.ensure_valid()?;
108 Ok(surface)
109 }
110
111 pub fn closed_directions(&self) -> Result<(bool, bool), String> {
116 if let Some(&cached) = self.closed_directions.get() {
117 return Ok(cached);
118 }
119 const CLOSED_SEAM_TOLERANCE: f64 = 1e-6;
120 let [u0, u1] = self.domain_u()?;
121 let [v0, v1] = self.domain_v()?;
122 let closed_along = |direction_u: bool| -> Result<bool, String> {
123 let mut travels = false;
131 for fraction in [0.17, 0.5, 0.83] {
132 let (a, b, middle) = if direction_u {
133 let v = v0 + (v1 - v0) * fraction;
134 (
135 self.evaluate(u0, v)?,
136 self.evaluate(u1, v)?,
137 self.evaluate(0.5 * (u0 + u1), v)?,
138 )
139 } else {
140 let u = u0 + (u1 - u0) * fraction;
141 (
142 self.evaluate(u, v0)?,
143 self.evaluate(u, v1)?,
144 self.evaluate(u, 0.5 * (v0 + v1))?,
145 )
146 };
147 if a.sub(b).length() > CLOSED_SEAM_TOLERANCE {
148 return Ok(false);
149 }
150 if middle.sub(a).length() > CLOSED_SEAM_TOLERANCE {
151 travels = true;
152 }
153 }
154 Ok(travels)
155 };
156 let value = (closed_along(true)?, closed_along(false)?);
157 Ok(*self.closed_directions.get_or_init(|| value))
158 }
159
160 fn ensure_valid(&self) -> Result<(), String> {
162 if self.validated.get() {
163 return Ok(());
164 }
165 validate_knots(&self.knots_u, self.degree_u)?;
166 validate_knots(&self.knots_v, self.degree_v)?;
167 let rows = self.knots_u.len() - self.degree_u - 1;
168 let columns = self.knots_v.len() - self.degree_v - 1;
169 if self.control_points.len() != rows {
170 return Err(format!(
171 "NurbsSurface: u knot vector implies {} rows, got {}",
172 rows,
173 self.control_points.len()
174 ));
175 }
176 for row in &self.control_points {
177 if row.len() != columns {
178 return Err(format!(
179 "NurbsSurface: v knot vector implies {} columns, got {}",
180 columns,
181 row.len()
182 ));
183 }
184 if row.iter().any(|point| {
185 point.w <= EPS
186 || ![point.x, point.y, point.z, point.w]
187 .iter()
188 .all(|value| value.is_finite())
189 }) {
190 return Err(
191 "NurbsSurface: control points must be finite with positive weights".into(),
192 );
193 }
194 }
195 self.validated.set(true);
196 Ok(())
197 }
198
199 pub fn domain_u(&self) -> Result<[f64; 2], String> {
200 self.ensure_valid()?;
201 Ok(knot_domain(&self.knots_u, self.degree_u))
202 }
203
204 pub fn domain_v(&self) -> Result<[f64; 2], String> {
205 self.ensure_valid()?;
206 Ok(knot_domain(&self.knots_v, self.degree_v))
207 }
208
209 pub fn analytic(&self) -> Option<&crate::AnalyticSurface> {
212 self.analytic
213 .get_or_init(|| crate::analytic_surface::recognize(self))
214 .as_ref()
215 }
216
217 pub(crate) fn affine_sphere(&self) -> Option<&crate::affine_sphere::AffineSphere> {
220 self.affine_sphere
221 .get_or_init(|| {
222 if !crate::affine_sphere::affine_sphere_enabled() || self.analytic().is_some() {
223 None
224 } else {
225 crate::affine_sphere::recognize(self)
226 }
227 })
228 .as_ref()
229 }
230
231 pub(crate) fn warm_caches(&self) -> Vec<&'static str> {
235 let mut warm = Vec::new();
236 let cells = [
237 ("analytic", self.analytic.get().is_some()),
238 ("affine_sphere", self.affine_sphere.get().is_some()),
239 ("validated", self.validated.get()),
240 ("closed", self.closed_directions.get().is_some()),
241 ("grid", self.projection_grid.get().is_some()),
242 ("dense", self.projection_dense_grid.get().is_some()),
243 ("ring", self.projection_ring_grid.get().is_some()),
244 ("cells", self.projection_cells.get().is_some()),
245 ];
246 for (name, held) in cells {
247 if held {
248 warm.push(name);
249 }
250 }
251 warm
252 }
253
254 pub(crate) fn exchange_caches(&mut self, names: &[&str], from: Option<&Self>) {
259 let wants = |name: &str| names.iter().any(|&n| n == name || n == "all");
260 if wants("analytic") {
261 self.analytic = from.map(|f| f.analytic.clone()).unwrap_or_default();
262 }
263 if wants("affine_sphere") {
264 self.affine_sphere = from.map(|f| f.affine_sphere.clone()).unwrap_or_default();
265 }
266 if wants("validated") {
267 self.validated = std::cell::Cell::new(from.is_some_and(|f| f.validated.get()));
268 }
269 if wants("closed") {
270 self.closed_directions = from.map(|f| f.closed_directions.clone()).unwrap_or_default();
271 }
272 if wants("grid") {
273 self.projection_grid = from.map(|f| f.projection_grid.clone()).unwrap_or_default();
274 }
275 if wants("dense") {
276 self.projection_dense_grid = from.map(|f| f.projection_dense_grid.clone()).unwrap_or_default();
277 }
278 if wants("ring") {
279 self.projection_ring_grid = from.map(|f| f.projection_ring_grid.clone()).unwrap_or_default();
280 }
281 if wants("cells") {
282 self.projection_cells = from.map(|f| f.projection_cells.clone()).unwrap_or_default();
283 }
284 }
285
286 fn knot_vectors(&self) -> Result<(KnotVector, KnotVector), String> {
287 Ok((
288 KnotVector::new(self.knots_u.clone(), self.degree_u)?,
289 KnotVector::new(self.knots_v.clone(), self.degree_v)?,
290 ))
291 }
292
293 pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String> {
294 crate::projection_profile::count(crate::projection_profile::LANE_EVALUATE, self);
295 self.ensure_valid()?;
296 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
297 return self.evaluate_homogeneous_heap(u, v);
298 }
299 let u = knot_clamp(&self.knots_u, self.degree_u, u);
300 let v = knot_clamp(&self.knots_v, self.degree_v, v);
301 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
302 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
303 if analytic_point_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
306 && matches!(self.analytic.get().and_then(|carrier| carrier.as_ref()),
307 Some(crate::AnalyticSurface::Plane { .. } | crate::AnalyticSurface::RuledRevolution { .. }
308 | crate::AnalyticSurface::Sphere { .. } | crate::AnalyticSurface::Torus { .. })) {
309 return Ok(match (self.degree_u, self.degree_v) {
310 (1, 1) => self.analytic_point_small::<1, 1>(u, v, span_u, span_v),
311 (2, 1) => self.analytic_point_small::<2, 1>(u, v, span_u, span_v),
312 (1, 2) => self.analytic_point_small::<1, 2>(u, v, span_u, span_v),
313 (2, 2) => self.analytic_point_small::<2, 2>(u, v, span_u, span_v),
314 _ => unreachable!(),
315 });
316 }
317 let mut basis_u = [0.0f64; MAX_STACK_ORDER];
318 let mut basis_v = [0.0f64; MAX_STACK_ORDER];
319 basis_functions_into(&self.knots_u, self.degree_u, span_u, u, &mut basis_u);
320 basis_functions_into(&self.knots_v, self.degree_v, span_v, v, &mut basis_v);
321 let mut point = Vec4 {
322 x: 0.0,
323 y: 0.0,
324 z: 0.0,
325 w: 0.0,
326 };
327 for (i, value_u) in basis_u[..=self.degree_u].iter().enumerate() {
328 let row = &self.control_points[span_u - self.degree_u + i];
329 for (j, value_v) in basis_v[..=self.degree_v].iter().enumerate() {
330 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
331 }
332 }
333 Ok(point)
334 }
335
336 fn analytic_point_small<const DU: usize, const DV: usize>(&self, u: f64, v: f64, span_u: usize, span_v: usize) -> Vec4 {
340 fn basis<const D: usize>(knots: &[f64], span: usize, parameter: f64) -> [f64; 3] {
341 let mut values = [0.0; 3];
342 let mut left = [0.0; 3];
343 let mut right = [0.0; 3];
344 values[0] = 1.0;
345 for j in 1..=D {
346 left[j] = parameter - knots[span + 1 - j];
347 right[j] = knots[span + j] - parameter;
348 let mut saved = 0.0;
349 for r in 0..j {
350 let denominator = right[r + 1] + left[j - r];
351 let temporary = if denominator.abs() <= EPS { 0.0 } else { values[r] / denominator };
352 values[r] = saved + right[r + 1] * temporary;
353 saved = left[j - r] * temporary;
354 }
355 values[j] = saved;
356 }
357 values
358 }
359 let basis_u = basis::<DU>(&self.knots_u, span_u, u);
360 let basis_v = basis::<DV>(&self.knots_v, span_v, v);
361 let mut point = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
362 for (i, value_u) in basis_u[..=DU].iter().enumerate() {
363 let row = &self.control_points[span_u - DU + i];
364 for (j, value_v) in basis_v[..=DV].iter().enumerate() {
365 point = point.add(row[span_v - DV + j].scale(value_u * value_v));
366 }
367 }
368 point
369 }
370
371 fn evaluate_homogeneous_heap(&self, u: f64, v: f64) -> Result<Vec4, String> {
372 let (knot_u, knot_v) = self.knot_vectors()?;
373 let u = knot_u.clamp_param(u);
374 let v = knot_v.clamp_param(v);
375 let span_u = knot_u.find_span(u);
376 let span_v = knot_v.find_span(v);
377 let basis_u = knot_u.basis_functions(span_u, u);
378 let basis_v = knot_v.basis_functions(span_v, v);
379 let mut point = Vec4 {
380 x: 0.0,
381 y: 0.0,
382 z: 0.0,
383 w: 0.0,
384 };
385 for (i, value_u) in basis_u.iter().enumerate() {
386 let row = &self.control_points[span_u - self.degree_u + i];
387 for (j, value_v) in basis_v.iter().enumerate() {
388 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
389 }
390 }
391 Ok(point)
392 }
393
394 pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String> {
395 self.evaluate_homogeneous(u, v)?.point()
396 }
397
398 pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String> {
404 if small_derivatives_enabled() {
405 return Ok(self.derivatives_extended_small(u, v, 0)?[0][0]);
406 }
407 Ok(self.derivatives_extended(u, v, 0)?[0][0])
408 }
409
410 pub(crate) fn derivatives_extended_small(
417 &self,
418 u: f64,
419 v: f64,
420 derivative_count: usize,
421 ) -> Result<[[Vec3; 3]; 3], String> {
422 debug_assert!(derivative_count <= 2);
423 let [u0, u1] = self.domain_u()?;
424 let [v0, v1] = self.domain_v()?;
425 let (closed_u, closed_v) = self.closed_directions()?;
426 let mut uu = u;
427 let mut vv = v;
428 if closed_u && (u < u0 || u > u1) {
429 uu = u0 + (u - u0).rem_euclid(u1 - u0);
430 }
431 if closed_v && (v < v0 || v > v1) {
432 vv = v0 + (v - v0).rem_euclid(v1 - v0);
433 }
434 let du_out = if uu < u0 { uu - u0 } else if uu > u1 { uu - u1 } else { 0.0 };
435 let dv_out = if vv < v0 { vv - v0 } else if vv > v1 { vv - v1 } else { 0.0 };
436 if du_out == 0.0 && dv_out == 0.0 {
437 return self.derivatives_small(uu, vv, derivative_count);
438 }
439 let anchor_u = uu - du_out;
440 let anchor_v = vv - dv_out;
441 let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
442 let mut result = [[Vec3::default(); 3]; 3];
443 result[0][0] = base[0][0]
444 .add(base[1][0].scale(du_out))
445 .add(base[0][1].scale(dv_out))
446 .add(base[1][1].scale(du_out * dv_out));
447 if derivative_count >= 1 {
448 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
449 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
450 result[1][1] = base[1][1];
451 }
452 if derivative_count >= 2 {
453 if du_out == 0.0 {
454 result[2][0] = base[2][0];
455 }
456 if dv_out == 0.0 {
457 result[0][2] = base[0][2];
458 }
459 }
460 Ok(result)
461 }
462
463 pub fn derivatives_extended(
468 &self,
469 u: f64,
470 v: f64,
471 derivative_count: usize,
472 ) -> Result<Vec<Vec<Vec3>>, String> {
473 if derivative_count <= 2 && small_derivatives_enabled() {
474 let small = self.derivatives_extended_small(u, v, derivative_count)?;
475 let order = derivative_count + 1;
476 return Ok((0..order).map(|k| (0..order).map(|l| small[k][l]).collect()).collect());
477 }
478 let [u0, u1] = self.domain_u()?;
479 let [v0, v1] = self.domain_v()?;
480 let (closed_u, closed_v) = self.closed_directions()?;
481 let mut uu = u;
482 let mut vv = v;
483 if closed_u && (u < u0 || u > u1) {
484 uu = u0 + (u - u0).rem_euclid(u1 - u0);
485 }
486 if closed_v && (v < v0 || v > v1) {
487 vv = v0 + (v - v0).rem_euclid(v1 - v0);
488 }
489 let du_out = if uu < u0 {
490 uu - u0
491 } else if uu > u1 {
492 uu - u1
493 } else {
494 0.0
495 };
496 let dv_out = if vv < v0 {
497 vv - v0
498 } else if vv > v1 {
499 vv - v1
500 } else {
501 0.0
502 };
503 if du_out == 0.0 && dv_out == 0.0 {
504 return self.derivatives(uu, vv, derivative_count);
505 }
506 let anchor_u = uu - du_out;
507 let anchor_v = vv - dv_out;
508 let base = self.derivatives(anchor_u, anchor_v, derivative_count.max(2))?;
509 let order = derivative_count + 1;
510 let mut result = vec![vec![Vec3::default(); order]; order];
511 result[0][0] = base[0][0]
515 .add(base[1][0].scale(du_out))
516 .add(base[0][1].scale(dv_out))
517 .add(base[1][1].scale(du_out * dv_out));
518 if derivative_count >= 1 {
519 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
520 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
521 result[1][1] = base[1][1];
522 }
523 if derivative_count >= 2 {
524 let second = self.derivatives(anchor_u, anchor_v, 2)?;
525 if du_out == 0.0 {
526 result[2][0] = second[2][0];
527 }
528 if dv_out == 0.0 {
529 result[0][2] = second[0][2];
530 }
531 }
532 Ok(result)
533 }
534
535 pub fn derivatives(
536 &self,
537 u: f64,
538 v: f64,
539 derivative_count: usize,
540 ) -> Result<Vec<Vec<Vec3>>, String> {
541 crate::projection_profile::count(crate::projection_profile::LANE_DERIVATIVES, self);
542 self.ensure_valid()?;
543 let du = derivative_count.min(self.degree_u);
544 let dv = derivative_count.min(self.degree_v);
545 let stride = derivative_count + 1;
546 let zero = Vec4 {
548 x: 0.0,
549 y: 0.0,
550 z: 0.0,
551 w: 0.0,
552 };
553 let mut homogeneous = vec![zero; stride * stride];
554 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
555 let u = knot_clamp(&self.knots_u, self.degree_u, u);
556 let v = knot_clamp(&self.knots_v, self.degree_v, v);
557 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
558 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
559 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
562 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
563 basis_derivatives_into(
564 &self.knots_u,
565 self.degree_u,
566 span_u,
567 u,
568 du,
569 &mut basis_u[..=du],
570 );
571 basis_derivatives_into(
572 &self.knots_v,
573 self.degree_v,
574 span_v,
575 v,
576 dv,
577 &mut basis_v[..=dv],
578 );
579 for k in 0..=du {
580 for l in 0..=dv {
581 if k + l > derivative_count {
582 continue;
583 }
584 let mut point = zero;
585 for i in 0..=self.degree_u {
586 let row = &self.control_points[span_u - self.degree_u + i];
587 for j in 0..=self.degree_v {
588 point = point.add(
589 row[span_v - self.degree_v + j]
590 .scale(basis_u[k][i] * basis_v[l][j]),
591 );
592 }
593 }
594 homogeneous[k * stride + l] = point;
595 }
596 }
597 } else {
598 let (knot_u, knot_v) = self.knot_vectors()?;
599 let u = knot_u.clamp_param(u);
600 let v = knot_v.clamp_param(v);
601 let span_u = knot_u.find_span(u);
602 let span_v = knot_v.find_span(v);
603 let basis_u = knot_u.basis_derivatives(span_u, u, du);
604 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
605 for k in 0..=du {
606 for l in 0..=dv {
607 if k + l > derivative_count {
608 continue;
609 }
610 let mut point = zero;
611 for i in 0..=self.degree_u {
612 let row = &self.control_points[span_u - self.degree_u + i];
613 for j in 0..=self.degree_v {
614 point = point.add(
615 row[span_v - self.degree_v + j]
616 .scale(basis_u[k][i] * basis_v[l][j]),
617 );
618 }
619 }
620 homogeneous[k * stride + l] = point;
621 }
622 }
623 }
624
625 let mut result = vec![vec![Vec3::default(); derivative_count + 1]; derivative_count + 1];
626 let weight = homogeneous[0].w;
627 if weight.abs() <= EPS {
628 return Err("NurbsSurface: zero evaluated weight".into());
629 }
630 for k in 0..=derivative_count {
631 for l in 0..=derivative_count - k {
632 if k > du || l > dv {
633 continue;
634 }
635 let at = |row: usize, column: usize| homogeneous[row * stride + column];
636 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
637 for j in 1..=l {
638 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
639 }
640 for i in 1..=k {
641 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
642 let mut mixed = Vec3::default();
643 for j in 1..=l {
644 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
645 }
646 value = value.sub(mixed.scale(binomial(k, i)));
647 }
648 result[k][l] = value.scale(1.0 / weight);
649 }
650 }
651 Ok(result)
652 }
653
654 pub(crate) fn derivatives_small(
669 &self,
670 u: f64,
671 v: f64,
672 derivative_count: usize,
673 ) -> Result<[[Vec3; 3]; 3], String> {
674 debug_assert!(derivative_count <= 2);
675 self.ensure_valid()?;
676 let du = derivative_count.min(self.degree_u);
677 let dv = derivative_count.min(self.degree_v);
678 let stride = derivative_count + 1;
679 let zero = Vec4 {
680 x: 0.0,
681 y: 0.0,
682 z: 0.0,
683 w: 0.0,
684 };
685 let mut homogeneous = [zero; 9];
686 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
687 let u = knot_clamp(&self.knots_u, self.degree_u, u);
688 let v = knot_clamp(&self.knots_v, self.degree_v, v);
689 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
690 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
691 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; 3];
695 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; 3];
696 basis_derivatives_into(
697 &self.knots_u,
698 self.degree_u,
699 span_u,
700 u,
701 du,
702 &mut basis_u[..=du],
703 );
704 basis_derivatives_into(
705 &self.knots_v,
706 self.degree_v,
707 span_v,
708 v,
709 dv,
710 &mut basis_v[..=dv],
711 );
712 for k in 0..=du {
713 for l in 0..=dv {
714 if k + l > derivative_count {
715 continue;
716 }
717 let mut point = zero;
718 for i in 0..=self.degree_u {
719 let row = &self.control_points[span_u - self.degree_u + i];
720 for j in 0..=self.degree_v {
721 point = point.add(
722 row[span_v - self.degree_v + j]
723 .scale(basis_u[k][i] * basis_v[l][j]),
724 );
725 }
726 }
727 homogeneous[k * stride + l] = point;
728 }
729 }
730 } else {
731 let (knot_u, knot_v) = self.knot_vectors()?;
732 let u = knot_u.clamp_param(u);
733 let v = knot_v.clamp_param(v);
734 let span_u = knot_u.find_span(u);
735 let span_v = knot_v.find_span(v);
736 let basis_u = knot_u.basis_derivatives(span_u, u, du);
737 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
738 for k in 0..=du {
739 for l in 0..=dv {
740 if k + l > derivative_count {
741 continue;
742 }
743 let mut point = zero;
744 for i in 0..=self.degree_u {
745 let row = &self.control_points[span_u - self.degree_u + i];
746 for j in 0..=self.degree_v {
747 point = point.add(
748 row[span_v - self.degree_v + j]
749 .scale(basis_u[k][i] * basis_v[l][j]),
750 );
751 }
752 }
753 homogeneous[k * stride + l] = point;
754 }
755 }
756 }
757
758 let mut result = [[Vec3::default(); 3]; 3];
759 let weight = homogeneous[0].w;
760 if weight.abs() <= EPS {
761 return Err("NurbsSurface: zero evaluated weight".into());
762 }
763 for k in 0..=derivative_count {
764 for l in 0..=derivative_count - k {
765 if k > du || l > dv {
766 continue;
767 }
768 let at = |row: usize, column: usize| homogeneous[row * stride + column];
769 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
770 for j in 1..=l {
771 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
772 }
773 for i in 1..=k {
774 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
775 let mut mixed = Vec3::default();
776 for j in 1..=l {
777 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
778 }
779 value = value.sub(mixed.scale(binomial(k, i)));
780 }
781 result[k][l] = value.scale(1.0 / weight);
782 }
783 }
784 Ok(result)
785 }
786
787 #[inline]
791 pub(crate) fn deriv1_tensor_supported(&self) -> bool {
792 self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
793 }
794
795 pub(crate) fn deriv1_tensor_each<F>(
817 &self,
818 us: &[f64],
819 vs: &[f64],
820 mut f: F,
821 ) -> Result<(), String>
822 where
823 F: FnMut(usize, usize, Vec3, Vec3, Vec3) -> Result<(), String>,
824 {
825 self.ensure_valid()?;
826 const MAX_BLOCK: usize = TENSOR_MAX_BLOCK;
828 if !self.deriv1_tensor_supported() || us.len() > MAX_BLOCK || vs.len() > MAX_BLOCK {
829 return Err("NurbsSurface: tensor block outside the stack evaluator".into());
830 }
831 let du = 1usize.min(self.degree_u);
832 let dv = 1usize.min(self.degree_v);
833 let mut span_u = [0usize; MAX_BLOCK];
834 let mut span_v = [0usize; MAX_BLOCK];
835 let mut basis_u = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
836 let mut basis_v = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
837 for (index, &value) in us.iter().enumerate() {
838 let value = knot_clamp(&self.knots_u, self.degree_u, value);
839 span_u[index] = knot_find_span(&self.knots_u, self.degree_u, value);
840 basis_derivatives_into(
841 &self.knots_u,
842 self.degree_u,
843 span_u[index],
844 value,
845 du,
846 &mut basis_u[index][..=du],
847 );
848 }
849 for (index, &value) in vs.iter().enumerate() {
850 let value = knot_clamp(&self.knots_v, self.degree_v, value);
851 span_v[index] = knot_find_span(&self.knots_v, self.degree_v, value);
852 basis_derivatives_into(
853 &self.knots_v,
854 self.degree_v,
855 span_v[index],
856 value,
857 dv,
858 &mut basis_v[index][..=dv],
859 );
860 }
861 let zero = Vec4 {
862 x: 0.0,
863 y: 0.0,
864 z: 0.0,
865 w: 0.0,
866 };
867 for i in 0..us.len() {
868 let rows = &self.control_points[span_u[i] - self.degree_u..];
869 for j in 0..vs.len() {
870 let column = span_v[j] - self.degree_v;
871 let sum = |k: usize, l: usize| {
875 let mut point = zero;
876 for row_index in 0..=self.degree_u {
877 let row = &rows[row_index];
878 for column_index in 0..=self.degree_v {
879 point = point.add(
880 row[column + column_index]
881 .scale(basis_u[i][k][row_index] * basis_v[j][l][column_index]),
882 );
883 }
884 }
885 point
886 };
887 let h00 = sum(0, 0);
888 let h01 = if dv >= 1 { sum(0, 1) } else { zero };
889 let h10 = if du >= 1 { sum(1, 0) } else { zero };
890 let weight = h00.w;
891 if weight.abs() <= EPS {
892 return Err("NurbsSurface: zero evaluated weight".into());
893 }
894 let inverse = 1.0 / weight;
895 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
896 let partial_v = if dv >= 1 {
897 Vec3::new(h01.x, h01.y, h01.z)
898 .sub(point.scale(h01.w))
899 .scale(inverse)
900 } else {
901 Vec3::default()
902 };
903 let partial_u = if du >= 1 {
904 Vec3::new(h10.x, h10.y, h10.z)
905 .sub(point.scale(h10.w))
906 .scale(inverse)
907 } else {
908 Vec3::default()
909 };
910 f(i, j, point, partial_u, partial_v)?;
911 }
912 }
913 Ok(())
914 }
915
916 #[inline]
919 pub(crate) fn deriv1(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
920 match (self.degree_u, self.degree_v) {
927 (1, 1) => self.deriv1_small_fixed::<1, 1>(u, v),
928 (2, 1) => self.deriv1_small_fixed::<2, 1>(u, v),
929 (1, 2) => self.deriv1_small_fixed::<1, 2>(u, v),
930 (2, 2) => self.deriv1_small_fixed::<2, 2>(u, v),
931 _ => {
932 let g = self.derivatives_small(u, v, 1)?;
933 Ok((g[0][0], g[1][0], g[0][1]))
934 }
935 }
936 }
937
938 fn deriv1_small_fixed<const DEGREE_U: usize, const DEGREE_V: usize>(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
951 self.ensure_valid()?;
952 let du = DEGREE_U.min(1);
953 let dv = DEGREE_V.min(1);
954 let u = knot_clamp(&self.knots_u, DEGREE_U, u);
955 let v = knot_clamp(&self.knots_v, DEGREE_V, v);
956 let span_u = knot_find_span(&self.knots_u, DEGREE_U, u);
957 let span_v = knot_find_span(&self.knots_v, DEGREE_V, v);
958 let mut basis_u = [[0.0; MAX_STACK_ORDER]; 2];
959 let mut basis_v = [[0.0; MAX_STACK_ORDER]; 2];
960 basis_derivatives_into(&self.knots_u, DEGREE_U, span_u, u, du, &mut basis_u[..=du]);
961 basis_derivatives_into(&self.knots_v, DEGREE_V, span_v, v, dv, &mut basis_v[..=dv]);
962 let zero = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
963 let sum = |k: usize, l: usize| {
964 let mut value = zero;
965 for i in 0..=DEGREE_U {
966 let row = &self.control_points[span_u - DEGREE_U + i];
967 for j in 0..=DEGREE_V {
968 value = value.add(row[span_v - DEGREE_V + j].scale(basis_u[k][i] * basis_v[l][j]));
969 }
970 }
971 value
972 };
973 let h00 = sum(0, 0);
974 let h01 = if dv == 1 { sum(0, 1) } else { zero };
975 let h10 = if du == 1 { sum(1, 0) } else { zero };
976 if h00.w.abs() <= EPS { return Err("NurbsSurface: zero evaluated weight".into()); }
977 let inverse = 1.0 / h00.w;
978 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
979 let partial_v = if dv == 1 {
980 Vec3::new(h01.x, h01.y, h01.z).sub(point.scale(1.0 * h01.w)).scale(inverse)
981 } else { Vec3::default() };
982 let partial_u = if du == 1 {
983 Vec3::new(h10.x, h10.y, h10.z).sub(point.scale(1.0 * h10.w))
984 .sub(Vec3::default().scale(1.0)).scale(inverse)
985 } else { Vec3::default() };
986 Ok((point, partial_u, partial_v))
987 }
988
989 pub(crate) fn deriv1_extended(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
997 let [u0, u1] = self.domain_u()?;
998 let [v0, v1] = self.domain_v()?;
999 let (closed_u, closed_v) = self.closed_directions()?;
1000 let mut uu = u;
1001 let mut vv = v;
1002 if closed_u && (u < u0 || u > u1) {
1003 uu = u0 + (u - u0).rem_euclid(u1 - u0);
1004 }
1005 if closed_v && (v < v0 || v > v1) {
1006 vv = v0 + (v - v0).rem_euclid(v1 - v0);
1007 }
1008 let du_out = if uu < u0 {
1009 uu - u0
1010 } else if uu > u1 {
1011 uu - u1
1012 } else {
1013 0.0
1014 };
1015 let dv_out = if vv < v0 {
1016 vv - v0
1017 } else if vv > v1 {
1018 vv - v1
1019 } else {
1020 0.0
1021 };
1022 if du_out == 0.0 && dv_out == 0.0 {
1023 if analytic_deriv1_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
1024 && matches!(self.analytic(), Some(crate::AnalyticSurface::Plane { .. }
1025 | crate::AnalyticSurface::RuledRevolution { .. } | crate::AnalyticSurface::Sphere { .. }
1026 | crate::AnalyticSurface::Torus { .. })) {
1027 return match (self.degree_u, self.degree_v) {
1028 (1, 1) => self.deriv1_small_fixed::<1, 1>(uu, vv),
1029 (2, 1) => self.deriv1_small_fixed::<2, 1>(uu, vv),
1030 (1, 2) => self.deriv1_small_fixed::<1, 2>(uu, vv),
1031 (2, 2) => self.deriv1_small_fixed::<2, 2>(uu, vv),
1032 _ => unreachable!(),
1033 };
1034 }
1035 let g = self.derivatives_small(uu, vv, 1)?;
1036 return Ok((g[0][0], g[1][0], g[0][1]));
1037 }
1038 let anchor_u = uu - du_out;
1039 let anchor_v = vv - dv_out;
1040 let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
1041 let s = base[0][0]
1045 .add(base[1][0].scale(du_out))
1046 .add(base[0][1].scale(dv_out))
1047 .add(base[1][1].scale(du_out * dv_out));
1048 let su = base[1][0].add(base[1][1].scale(dv_out));
1049 let sv = base[0][1].add(base[1][1].scale(du_out));
1050 Ok((s, su, sv))
1051 }
1052
1053 pub(crate) fn partial_by_differences(
1078 &self,
1079 along_u: bool,
1080 u: f64,
1081 v: f64,
1082 ) -> Result<Vec3, String> {
1083 self.ensure_valid()?;
1084 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
1085 return Err("NurbsSurface: partial by differences exceeds the stack degree".into());
1086 }
1087 let (pu, pv) = (self.degree_u, self.degree_v);
1088 let u = knot_clamp(&self.knots_u, pu, u);
1089 let v = knot_clamp(&self.knots_v, pv, v);
1090 let span_u = knot_find_span(&self.knots_u, pu, u);
1091 let span_v = knot_find_span(&self.knots_v, pv, v);
1092 let mut full_u = [0.0f64; MAX_STACK_ORDER];
1093 let mut full_v = [0.0f64; MAX_STACK_ORDER];
1094 basis_functions_into(&self.knots_u, pu, span_u, u, &mut full_u);
1095 basis_functions_into(&self.knots_v, pv, span_v, v, &mut full_v);
1096 let zero = Vec4 {
1097 x: 0.0,
1098 y: 0.0,
1099 z: 0.0,
1100 w: 0.0,
1101 };
1102 let control = |i: usize, j: usize| self.control_points[span_u - pu + i][span_v - pv + j];
1103 let mut point = zero;
1104 for i in 0..=pu {
1105 for j in 0..=pv {
1106 point = point.add(control(i, j).scale(full_u[i] * full_v[j]));
1107 }
1108 }
1109 if point.w.abs() <= EPS {
1110 return Err("NurbsSurface: zero evaluated weight".into());
1111 }
1112 let source = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
1113 let (knots, degree, span, parameter) = if along_u {
1114 (&self.knots_u, pu, span_u, u)
1115 } else {
1116 (&self.knots_v, pv, span_v, v)
1117 };
1118 if degree == 0 {
1119 return Ok(Vec3::default());
1120 }
1121 let mut lowered = [0.0f64; MAX_STACK_ORDER];
1122 basis_functions_into(knots, degree - 1, span, parameter, &mut lowered);
1123 let mut hodograph = zero;
1126 for r in 0..degree {
1127 let index = span - degree + r;
1130 let width = knots[index + degree + 1] - knots[index + 1];
1131 if width.abs() <= EPS {
1132 continue;
1133 }
1134 let factor = lowered[r] * degree as f64 / width;
1135 if along_u {
1136 for j in 0..=pv {
1137 let difference = control(r + 1, j).add(control(r, j).scale(-1.0));
1138 hodograph = hodograph.add(difference.scale(factor * full_v[j]));
1139 }
1140 } else {
1141 for i in 0..=pu {
1142 let difference = control(i, r + 1).add(control(i, r).scale(-1.0));
1143 hodograph = hodograph.add(difference.scale(factor * full_u[i]));
1144 }
1145 }
1146 }
1147 Ok(Vec3::new(hodograph.x, hodograph.y, hodograph.z)
1148 .sub(source.scale(hodograph.w))
1149 .scale(1.0 / point.w))
1150 }
1151
1152 pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
1153 if small_derivatives_enabled() {
1154 let derivatives = self.derivatives_small(u, v, 1)?;
1155 return derivatives[1][0].cross(derivatives[0][1]).normalized();
1156 }
1157 let derivatives = self.derivatives(u, v, 1)?;
1158 derivatives[1][0].cross(derivatives[0][1]).normalized()
1159 }
1160
1161 pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String> {
1168 let derivatives: [[Vec3; 3]; 3] = if small_derivatives_enabled() {
1169 self.derivatives_small(u, v, 2)?
1170 } else {
1171 let heap = self.derivatives(u, v, 2)?;
1172 [[heap[0][0], heap[0][1], heap[0][2]], [heap[1][0], heap[1][1], Vec3::default()], [heap[2][0], Vec3::default(), Vec3::default()]]
1173 };
1174 let su = derivatives[1][0];
1175 let sv = derivatives[0][1];
1176 let cross = su.cross(sv);
1177 let cross_length = cross.length();
1178 if cross_length <= 1e-12 {
1179 return Err(format!(
1180 "degenerate parametrization at (u={u:.4}, v={v:.4})"
1181 ));
1182 }
1183 let normal = cross.scale(1.0 / cross_length);
1184 let e1 = su.dot(su);
1185 let f1 = su.dot(sv);
1186 let g1 = sv.dot(sv);
1187 let l2 = derivatives[2][0].dot(normal);
1188 let m2 = derivatives[1][1].dot(normal);
1189 let n2 = derivatives[0][2].dot(normal);
1190 let denominator = e1 * g1 - f1 * f1;
1191 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();
1194 Ok((
1195 mean_double * 0.5 - discriminant,
1196 mean_double * 0.5 + discriminant,
1197 ))
1198 }
1199
1200 pub fn is_affine(&self) -> Result<bool, String> {
1205 if self.degree_u != 1
1206 || self.degree_v != 1
1207 || self.control_points.len() != 2
1208 || self.control_points[0].len() != 2
1209 || self.control_points[1].len() != 2
1210 {
1211 return Ok(false);
1212 }
1213 let weight = self.control_points[0][0].w;
1214 if [
1215 self.control_points[0][1].w,
1216 self.control_points[1][0].w,
1217 self.control_points[1][1].w,
1218 ]
1219 .iter()
1220 .any(|other| (*other - weight).abs() > 1e-12 * other.abs().max(weight.abs()))
1221 {
1222 return Ok(false);
1223 }
1224 let p00 = self.control_points[0][0].point()?;
1225 let p01 = self.control_points[0][1].point()?;
1226 let p10 = self.control_points[1][0].point()?;
1227 let p11 = self.control_points[1][1].point()?;
1228 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
1229 let extent = [
1234 p01.sub(p00),
1235 p10.sub(p00),
1236 p11.sub(p00),
1237 p10.sub(p01),
1238 p11.sub(p01),
1239 p11.sub(p10),
1240 ]
1241 .iter()
1242 .map(|delta| delta.length())
1243 .fold(0.0, f64::max);
1244 Ok(gap <= 1e-9 * (1.0 + extent))
1245 }
1246
1247 pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
1248 self.ensure_valid()?;
1249 let u = knot_clamp(&self.knots_u, self.degree_u, u);
1250 let span = knot_find_span(&self.knots_u, self.degree_u, u);
1251 let basis = if self.degree_u <= MAX_STACK_DEGREE {
1252 let mut stack = [0.0f64; MAX_STACK_ORDER];
1253 basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
1254 stack[..=self.degree_u].to_vec()
1255 } else {
1256 KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
1257 };
1258 let mut points = Vec::with_capacity(self.control_points[0].len());
1259 for column in 0..self.control_points[0].len() {
1260 let mut point = Vec4 {
1261 x: 0.0,
1262 y: 0.0,
1263 z: 0.0,
1264 w: 0.0,
1265 };
1266 for (index, value) in basis.iter().enumerate() {
1267 point = point
1268 .add(self.control_points[span - self.degree_u + index][column].scale(*value));
1269 }
1270 points.push(point);
1271 }
1272 NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
1273 }
1274
1275 pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
1276 self.ensure_valid()?;
1277 let v = knot_clamp(&self.knots_v, self.degree_v, v);
1278 let span = knot_find_span(&self.knots_v, self.degree_v, v);
1279 let basis = if self.degree_v <= MAX_STACK_DEGREE {
1280 let mut stack = [0.0f64; MAX_STACK_ORDER];
1281 basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
1282 stack[..=self.degree_v].to_vec()
1283 } else {
1284 KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
1285 };
1286 let mut points = Vec::with_capacity(self.control_points.len());
1287 for row in &self.control_points {
1288 let mut point = Vec4 {
1289 x: 0.0,
1290 y: 0.0,
1291 z: 0.0,
1292 w: 0.0,
1293 };
1294 for (index, value) in basis.iter().enumerate() {
1295 point = point.add(row[span - self.degree_v + index].scale(*value));
1296 }
1297 points.push(point);
1298 }
1299 NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
1300 }
1301}
1302
1303pub fn make_plane(
1304 origin: Vec3,
1305 u_direction: Vec3,
1306 v_direction: Vec3,
1307 u_max: f64,
1308 v_max: f64,
1309) -> Result<NurbsSurface, String> {
1310 if u_max <= EPS || v_max <= EPS {
1311 return Err("makePlane: parameter extents must be positive".into());
1312 }
1313 let p10 = origin.add(u_direction.scale(u_max));
1314 let p01 = origin.add(v_direction.scale(v_max));
1315 let p11 = p10.add(v_direction.scale(v_max));
1316 NurbsSurface::new(
1317 1,
1318 1,
1319 vec![0.0, 0.0, u_max, u_max],
1320 vec![0.0, 0.0, v_max, v_max],
1321 vec![
1322 vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
1323 vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
1324 ],
1325 )
1326}
1327
1328pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
1329 let rows = profile
1330 .control_points
1331 .iter()
1332 .map(|point| {
1333 vec![
1334 *point,
1335 Vec4 {
1336 x: point.x + point.w * direction.x,
1337 y: point.y + point.w * direction.y,
1338 z: point.z + point.w * direction.z,
1339 w: point.w,
1340 },
1341 ]
1342 })
1343 .collect();
1344 NurbsSurface::new(
1345 profile.degree,
1346 1,
1347 profile.knots.clone(),
1348 vec![0.0, 0.0, 1.0, 1.0],
1349 rows,
1350 )
1351}
1352
1353pub fn make_revolution(
1354 axis_point: Vec3,
1355 axis_direction: Vec3,
1356 generatrix: &NurbsCurve,
1357 theta: f64,
1358) -> Result<NurbsSurface, String> {
1359 if theta <= EPS || theta > std::f64::consts::TAU + EPS {
1360 return Err("makeRevolution: theta must be in (0, 2*PI]".into());
1361 }
1362 let theta = theta.min(std::f64::consts::TAU);
1363 let axis = axis_direction.normalized()?;
1364 let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
1365 let arc_angle = theta / arc_count as f64;
1366 let middle_weight = (arc_angle / 2.0).cos();
1367 let mut knots_u = vec![0.0, 0.0, 0.0];
1368 for index in 1..arc_count {
1369 let knot = index as f64 / arc_count as f64;
1370 knots_u.extend([knot, knot]);
1371 }
1372 knots_u.extend([1.0, 1.0, 1.0]);
1373 let mut rows = vec![
1374 vec![
1375 Vec4 {
1376 x: 0.0,
1377 y: 0.0,
1378 z: 0.0,
1379 w: 1.0,
1380 };
1381 generatrix.control_points.len()
1382 ];
1383 2 * arc_count + 1
1384 ];
1385
1386 let mut radial_extent: f64 = 0.0;
1389 for control_point in &generatrix.control_points {
1390 let point = control_point.point()?;
1391 let offset = point.sub(axis_point);
1392 radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
1393 }
1394
1395 for (column, control_point) in generatrix.control_points.iter().enumerate() {
1396 let point = control_point.point()?;
1397 let weight = control_point.w;
1398 let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
1399 let radial_vector = point.sub(axis_origin);
1400 let measured_radius = radial_vector.length();
1410 let pole_tolerance = EPS.max(1e-8 * radial_extent);
1411 let radius = if measured_radius <= pole_tolerance {
1412 0.0
1413 } else {
1414 measured_radius
1415 };
1416 let (x_axis, y_axis) = if radius <= EPS {
1417 (Vec3::default(), Vec3::default())
1418 } else {
1419 if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
1431 return Err(format!(
1432 "makeRevolution: generatrix control point {column} sits {radius:e} from the \
1433 axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
1434 radial extent (a fuzzy pole)"
1435 ));
1436 }
1437 let x_axis = radial_vector.scale(1.0 / radius);
1438 (x_axis, axis.cross(x_axis))
1439 };
1440 rows[0][column] = Vec4::from_point(point, weight);
1441 let mut start_point = point;
1442 let mut start_tangent = y_axis.scale(radius);
1443 let mut angle = 0.0;
1444 for arc in 1..=arc_count {
1445 angle += arc_angle;
1446 let end_point = if radius <= EPS {
1447 point
1448 } else {
1449 axis_origin
1450 .add(x_axis.scale(radius * angle.cos()))
1451 .add(y_axis.scale(radius * angle.sin()))
1452 };
1453 let end_tangent = if radius <= EPS {
1454 Vec3::default()
1455 } else {
1456 x_axis
1457 .scale(-radius * angle.sin())
1458 .add(y_axis.scale(radius * angle.cos()))
1459 };
1460 let row = 2 * (arc - 1);
1461 if radius <= EPS {
1462 rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
1463 } else {
1464 let cross = start_tangent.cross(end_tangent);
1465 let denominator = cross.length_squared();
1466 let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
1475 if denominator <= EPS * tangent_scale {
1476 return Err("makeRevolution: arc tangents are parallel".into());
1477 }
1478 let distance =
1479 end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
1480 let middle = start_point.add(start_tangent.scale(distance));
1481 rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
1482 }
1483 rows[row + 2][column] = Vec4::from_point(end_point, weight);
1484 start_point = end_point;
1485 start_tangent = end_tangent;
1486 }
1487 }
1488 NurbsSurface::new(
1489 2,
1490 generatrix.degree,
1491 knots_u,
1492 generatrix.knots.clone(),
1493 rows,
1494 )
1495}
1496
1497pub fn make_cylinder_surface(
1498 base: Vec3,
1499 axis_direction: Vec3,
1500 radius: f64,
1501 height: f64,
1502) -> Result<NurbsSurface, String> {
1503 if radius <= EPS || height <= EPS {
1504 return Err("makeCylinder: radius and height must be positive".into());
1505 }
1506 let axis = axis_direction.normalized()?;
1507 let x_axis = axis.perpendicular()?;
1508 let start = base.add(x_axis.scale(radius));
1509 let end = start.add(axis.scale(height));
1510 let generatrix = crate::make_line(start, end)?;
1511 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1512}
1513
1514pub fn make_cone_surface(
1515 base: Vec3,
1516 axis_direction: Vec3,
1517 radius_bottom: f64,
1518 radius_top: f64,
1519 height: f64,
1520) -> Result<NurbsSurface, String> {
1521 if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
1522 return Err("makeCone: invalid radius or height".into());
1523 }
1524 let axis = axis_direction.normalized()?;
1525 let x_axis = axis.perpendicular()?;
1526 let start = base.add(x_axis.scale(radius_bottom));
1527 let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
1528 let generatrix = crate::make_line(start, end)?;
1529 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1530}
1531
1532pub fn make_sphere_surface(
1533 center: Vec3,
1534 radius: f64,
1535 polar_axis: Vec3,
1536) -> Result<NurbsSurface, String> {
1537 make_sphere_surface_framed(center, radius, polar_axis, None)
1538}
1539
1540pub fn make_sphere_surface_framed(
1552 center: Vec3,
1553 radius: f64,
1554 polar_axis: Vec3,
1555 seam_direction: Option<Vec3>,
1556) -> Result<NurbsSurface, String> {
1557 if radius <= EPS {
1558 return Err("makeSphere: radius must be positive".into());
1559 }
1560 let axis = polar_axis.normalized()?;
1561 let x_axis = match seam_direction {
1562 Some(seam) => {
1563 let planar = seam.sub(axis.scale(seam.dot(axis)));
1564 match planar.normalized() {
1565 Ok(unit) if planar.length() > EPS => unit,
1566 _ => axis.perpendicular()?,
1567 }
1568 }
1569 None => axis.perpendicular()?,
1570 };
1571 let meridian = crate::make_arc(
1572 center,
1573 x_axis,
1574 axis,
1575 radius,
1576 -std::f64::consts::FRAC_PI_2,
1577 std::f64::consts::FRAC_PI_2,
1578 )?;
1579 make_revolution(center, axis, &meridian, std::f64::consts::TAU)
1580}
1581
1582pub fn make_torus_surface(
1583 center: Vec3,
1584 axis_direction: Vec3,
1585 major_radius: f64,
1586 minor_radius: f64,
1587) -> Result<NurbsSurface, String> {
1588 if minor_radius <= EPS || major_radius <= minor_radius {
1589 return Err("makeTorus: requires positive minorRadius < majorRadius".into());
1590 }
1591 let axis = axis_direction.normalized()?;
1592 let x_axis = axis.perpendicular()?;
1593 let tube_center = center.add(x_axis.scale(major_radius));
1594 let tube = crate::make_arc(
1595 tube_center,
1596 x_axis,
1597 axis,
1598 minor_radius,
1599 0.0,
1600 std::f64::consts::TAU,
1601 )?;
1602 make_revolution(center, axis, &tube, std::f64::consts::TAU)
1603}
1604
1605fn binomial(n: usize, k: usize) -> f64 {
1606 if k > n {
1607 return 0.0;
1608 }
1609 let k = k.min(n - k);
1610 (1..=k).fold(1.0, |value, index| {
1611 value * (n - k + index) as f64 / index as f64
1612 })
1613}
1614
1615
1616
1617pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
1629 let inflate = if inflate.is_finite() {
1630 inflate.clamp(1.0, 16.0)
1631 } else {
1632 2.0
1633 };
1634 let (closed_u, closed_v) = surface.closed_directions()?;
1635 let [u0, u1] = surface.domain_u()?;
1636 let [v0, v1] = surface.domain_v()?;
1637 let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
1638 if closed || inflate <= 1.0 {
1639 (d0, d1)
1640 } else {
1641 let centre = 0.5 * (d0 + d1);
1642 let half = 0.5 * (d1 - d0) * inflate;
1643 (centre - half, centre + half)
1644 }
1645 };
1646 let (nu0, nu1) = stretch(u0, u1, closed_u);
1647 let (nv0, nv1) = stretch(v0, v1, closed_v);
1648 if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
1649 return Ok(surface.clone());
1650 }
1651 if surface.is_affine()? {
1652 let corners = [
1655 surface.evaluate_extended(nu0, nv0)?,
1656 surface.evaluate_extended(nu1, nv0)?,
1657 surface.evaluate_extended(nu0, nv1)?,
1658 surface.evaluate_extended(nu1, nv1)?,
1659 ];
1660 return NurbsSurface::new(
1661 1,
1662 1,
1663 vec![nu0, nu0, nu1, nu1],
1664 vec![nv0, nv0, nv1, nv1],
1665 vec![
1666 vec![
1667 Vec4::from_point(corners[0], 1.0),
1668 Vec4::from_point(corners[2], 1.0),
1669 ],
1670 vec![
1671 Vec4::from_point(corners[1], 1.0),
1672 Vec4::from_point(corners[3], 1.0),
1673 ],
1674 ],
1675 );
1676 }
1677 const SAMPLES: usize = 32;
1678 let u_params: Vec<f64> = (0..=SAMPLES)
1679 .map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
1680 .collect();
1681 let v_params: Vec<f64> = (0..=SAMPLES)
1682 .map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
1683 .collect();
1684 let mut column_curves = Vec::with_capacity(u_params.len());
1687 for &u in &u_params {
1688 let column: Vec<Vec3> = v_params
1689 .iter()
1690 .map(|&v| surface.evaluate_extended(u, v))
1691 .collect::<Result<_, _>>()?;
1692 column_curves.push(crate::interpolate_curve(
1693 &column,
1694 3,
1695 &normalized(&v_params),
1696 )?);
1697 }
1698 let v_knots = column_curves[0].knots.clone();
1699 let control_rows = column_curves[0].control_points.len();
1700 let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
1701 let mut u_knots = Vec::new();
1702 for row_index in 0..control_rows {
1703 let row: Vec<Vec3> = column_curves
1704 .iter()
1705 .map(|curve| curve.control_points[row_index].point())
1706 .collect::<Result<_, _>>()?;
1707 let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
1708 u_knots = fitted.knots.clone();
1709 for (u_index, control) in fitted.control_points.iter().enumerate() {
1710 if grid.len() <= u_index {
1711 grid.push(Vec::new());
1712 }
1713 grid[u_index].push(*control);
1714 }
1715 }
1716 let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
1719 knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
1720 };
1721 NurbsSurface::new(
1722 3,
1723 3,
1724 rescale(&u_knots, nu0, nu1),
1725 rescale(&v_knots, nv0, nv1),
1726 grid,
1727 )
1728}
1729
1730fn normalized(parameters: &[f64]) -> Vec<f64> {
1731 let first = parameters[0];
1732 let last = parameters[parameters.len() - 1];
1733 parameters
1734 .iter()
1735 .map(|p| (p - first) / (last - first))
1736 .collect()
1737}
1738
1739
1740
1741
1742
1743fn analytic_deriv1_on() -> bool {
1744 switch!("BREP_ANALYTIC_DERIV1", true)
1745}
1746
1747
1748fn analytic_point_on() -> bool {
1750 switch!("BREP_ANALYTIC_POINT", false)
1751}
1752
1753pub(crate) fn small_derivatives_enabled() -> bool {
1760 switch!("BREP_SURFACE_SMALL_DERIVATIVES", true)
1761}
1762
1763