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
30fn tensor_fused_on() -> bool {
35 switch!("BREP_SURFACE_TENSOR_FUSED", true)
36}
37
38#[derive(Clone, Debug, Deserialize, Serialize)]
48pub struct NurbsSurface {
49 pub degree_u: usize,
50 pub degree_v: usize,
51 pub knots_u: Vec<f64>,
52 pub knots_v: Vec<f64>,
53 pub control_points: Vec<Vec<Vec4>>,
54 #[serde(skip, default)]
58 analytic: std::cell::OnceCell<Option<crate::AnalyticSurface>>,
59 #[serde(skip, default)]
63 affine_sphere: std::cell::OnceCell<Option<crate::affine_sphere::AffineSphere>>,
64 #[serde(skip, default)]
66 validated: std::cell::Cell<bool>,
67 #[serde(skip, default)]
70 closed_directions: std::cell::OnceCell<(bool, bool)>,
71 #[serde(skip, default)]
74 pub(crate) projection_grid: std::cell::OnceCell<crate::projection::SampleGrid>,
75 #[serde(skip, default)]
79 pub(crate) projection_dense_grid: std::cell::OnceCell<crate::projection::SampleGrid>,
80 #[serde(skip, default)]
84 pub(crate) projection_ring_grid: std::cell::OnceCell<Vec<Vec3>>,
85 #[serde(skip, default)]
89 pub(crate) projection_cells: std::cell::OnceCell<Option<crate::projection::ProjectionCells>>,
90}
91
92impl NurbsSurface {
93 pub fn new(
94 degree_u: usize,
95 degree_v: usize,
96 knots_u: Vec<f64>,
97 knots_v: Vec<f64>,
98 control_points: Vec<Vec<Vec4>>,
99 ) -> Result<Self, String> {
100 let surface = Self {
101 degree_u,
102 degree_v,
103 knots_u,
104 knots_v,
105 control_points,
106 analytic: std::cell::OnceCell::new(),
107 affine_sphere: std::cell::OnceCell::new(),
108 validated: std::cell::Cell::new(false),
109 closed_directions: std::cell::OnceCell::new(),
110 projection_grid: std::cell::OnceCell::new(),
111 projection_dense_grid: std::cell::OnceCell::new(),
112 projection_ring_grid: std::cell::OnceCell::new(),
113 projection_cells: std::cell::OnceCell::new(),
114 };
115 surface.ensure_valid()?;
116 Ok(surface)
117 }
118
119 pub fn closed_directions(&self) -> Result<(bool, bool), String> {
124 if let Some(&cached) = self.closed_directions.get() {
125 return Ok(cached);
126 }
127 const CLOSED_SEAM_TOLERANCE: f64 = 1e-6;
128 let [u0, u1] = self.domain_u()?;
129 let [v0, v1] = self.domain_v()?;
130 let closed_along = |direction_u: bool| -> Result<bool, String> {
131 let mut travels = false;
139 for fraction in [0.17, 0.5, 0.83] {
140 let (a, b, middle) = if direction_u {
141 let v = v0 + (v1 - v0) * fraction;
142 (
143 self.evaluate(u0, v)?,
144 self.evaluate(u1, v)?,
145 self.evaluate(0.5 * (u0 + u1), v)?,
146 )
147 } else {
148 let u = u0 + (u1 - u0) * fraction;
149 (
150 self.evaluate(u, v0)?,
151 self.evaluate(u, v1)?,
152 self.evaluate(u, 0.5 * (v0 + v1))?,
153 )
154 };
155 if a.sub(b).length() > CLOSED_SEAM_TOLERANCE {
156 return Ok(false);
157 }
158 if middle.sub(a).length() > CLOSED_SEAM_TOLERANCE {
159 travels = true;
160 }
161 }
162 Ok(travels)
163 };
164 let value = (closed_along(true)?, closed_along(false)?);
165 Ok(*self.closed_directions.get_or_init(|| value))
166 }
167
168 fn ensure_valid(&self) -> Result<(), String> {
170 if self.validated.get() {
171 return Ok(());
172 }
173 validate_knots(&self.knots_u, self.degree_u)?;
174 validate_knots(&self.knots_v, self.degree_v)?;
175 let rows = self.knots_u.len() - self.degree_u - 1;
176 let columns = self.knots_v.len() - self.degree_v - 1;
177 if self.control_points.len() != rows {
178 return Err(format!(
179 "NurbsSurface: u knot vector implies {} rows, got {}",
180 rows,
181 self.control_points.len()
182 ));
183 }
184 for row in &self.control_points {
185 if row.len() != columns {
186 return Err(format!(
187 "NurbsSurface: v knot vector implies {} columns, got {}",
188 columns,
189 row.len()
190 ));
191 }
192 if row.iter().any(|point| {
193 point.w <= EPS
194 || ![point.x, point.y, point.z, point.w]
195 .iter()
196 .all(|value| value.is_finite())
197 }) {
198 return Err(
199 "NurbsSurface: control points must be finite with positive weights".into(),
200 );
201 }
202 }
203 self.validated.set(true);
204 Ok(())
205 }
206
207 pub fn domain_u(&self) -> Result<[f64; 2], String> {
208 self.ensure_valid()?;
209 Ok(knot_domain(&self.knots_u, self.degree_u))
210 }
211
212 pub fn domain_v(&self) -> Result<[f64; 2], String> {
213 self.ensure_valid()?;
214 Ok(knot_domain(&self.knots_v, self.degree_v))
215 }
216
217 pub fn analytic(&self) -> Option<&crate::AnalyticSurface> {
220 self.analytic
221 .get_or_init(|| crate::analytic_surface::recognize(self))
222 .as_ref()
223 }
224
225 pub(crate) fn affine_sphere(&self) -> Option<&crate::affine_sphere::AffineSphere> {
228 self.affine_sphere
229 .get_or_init(|| {
230 if !crate::affine_sphere::affine_sphere_enabled() || self.analytic().is_some() {
231 None
232 } else {
233 crate::affine_sphere::recognize(self)
234 }
235 })
236 .as_ref()
237 }
238
239 pub(crate) fn warm_caches(&self) -> Vec<&'static str> {
243 let mut warm = Vec::new();
244 let cells = [
245 ("analytic", self.analytic.get().is_some()),
246 ("affine_sphere", self.affine_sphere.get().is_some()),
247 ("validated", self.validated.get()),
248 ("closed", self.closed_directions.get().is_some()),
249 ("grid", self.projection_grid.get().is_some()),
250 ("dense", self.projection_dense_grid.get().is_some()),
251 ("ring", self.projection_ring_grid.get().is_some()),
252 ("cells", self.projection_cells.get().is_some()),
253 ];
254 for (name, held) in cells {
255 if held {
256 warm.push(name);
257 }
258 }
259 warm
260 }
261
262 pub(crate) fn exchange_caches(&mut self, names: &[&str], from: Option<&Self>) {
267 let wants = |name: &str| names.iter().any(|&n| n == name || n == "all");
268 if wants("analytic") {
269 self.analytic = from.map(|f| f.analytic.clone()).unwrap_or_default();
270 }
271 if wants("affine_sphere") {
272 self.affine_sphere = from.map(|f| f.affine_sphere.clone()).unwrap_or_default();
273 }
274 if wants("validated") {
275 self.validated = std::cell::Cell::new(from.is_some_and(|f| f.validated.get()));
276 }
277 if wants("closed") {
278 self.closed_directions = from.map(|f| f.closed_directions.clone()).unwrap_or_default();
279 }
280 if wants("grid") {
281 self.projection_grid = from.map(|f| f.projection_grid.clone()).unwrap_or_default();
282 }
283 if wants("dense") {
284 self.projection_dense_grid = from.map(|f| f.projection_dense_grid.clone()).unwrap_or_default();
285 }
286 if wants("ring") {
287 self.projection_ring_grid = from.map(|f| f.projection_ring_grid.clone()).unwrap_or_default();
288 }
289 if wants("cells") {
290 self.projection_cells = from.map(|f| f.projection_cells.clone()).unwrap_or_default();
291 }
292 }
293
294 fn knot_vectors(&self) -> Result<(KnotVector, KnotVector), String> {
295 Ok((
296 KnotVector::new(self.knots_u.clone(), self.degree_u)?,
297 KnotVector::new(self.knots_v.clone(), self.degree_v)?,
298 ))
299 }
300
301 #[cfg_attr(feature = "profile-wall", track_caller)]
302 pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String> {
303 let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
304 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
305 crate::projection_profile::count(crate::projection_profile::LANE_EVALUATE, self);
306 self.ensure_valid()?;
307 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
308 return self.evaluate_homogeneous_heap(u, v);
309 }
310 let u = knot_clamp(&self.knots_u, self.degree_u, u);
311 let v = knot_clamp(&self.knots_v, self.degree_v, v);
312 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
313 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
314 if analytic_point_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
317 && matches!(self.analytic.get().and_then(|carrier| carrier.as_ref()),
318 Some(crate::AnalyticSurface::Plane { .. } | crate::AnalyticSurface::RuledRevolution { .. }
319 | crate::AnalyticSurface::Sphere { .. } | crate::AnalyticSurface::Torus { .. })) {
320 return Ok(match (self.degree_u, self.degree_v) {
321 (1, 1) => self.analytic_point_small::<1, 1>(u, v, span_u, span_v),
322 (2, 1) => self.analytic_point_small::<2, 1>(u, v, span_u, span_v),
323 (1, 2) => self.analytic_point_small::<1, 2>(u, v, span_u, span_v),
324 (2, 2) => self.analytic_point_small::<2, 2>(u, v, span_u, span_v),
325 _ => unreachable!(),
326 });
327 }
328 let mut basis_u = [0.0f64; MAX_STACK_ORDER];
329 let mut basis_v = [0.0f64; MAX_STACK_ORDER];
330 basis_functions_into(&self.knots_u, self.degree_u, span_u, u, &mut basis_u);
331 basis_functions_into(&self.knots_v, self.degree_v, span_v, v, &mut basis_v);
332 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
333 let mut point = Vec4 {
334 x: 0.0,
335 y: 0.0,
336 z: 0.0,
337 w: 0.0,
338 };
339 for (i, value_u) in basis_u[..=self.degree_u].iter().enumerate() {
340 let row = &self.control_points[span_u - self.degree_u + i];
341 for (j, value_v) in basis_v[..=self.degree_v].iter().enumerate() {
342 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
343 }
344 }
345 Ok(point)
346 }
347
348 fn analytic_point_small<const DU: usize, const DV: usize>(&self, u: f64, v: f64, span_u: usize, span_v: usize) -> Vec4 {
352 fn basis<const D: usize>(knots: &[f64], span: usize, parameter: f64) -> [f64; 3] {
353 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::BASIS);
354 let mut values = [0.0; 3];
355 let mut left = [0.0; 3];
356 let mut right = [0.0; 3];
357 values[0] = 1.0;
358 for j in 1..=D {
359 left[j] = parameter - knots[span + 1 - j];
360 right[j] = knots[span + j] - parameter;
361 let mut saved = 0.0;
362 for r in 0..j {
363 let denominator = right[r + 1] + left[j - r];
364 let temporary = if denominator.abs() <= EPS { 0.0 } else { values[r] / denominator };
365 values[r] = saved + right[r + 1] * temporary;
366 saved = left[j - r] * temporary;
367 }
368 values[j] = saved;
369 }
370 values
371 }
372 let basis_u = basis::<DU>(&self.knots_u, span_u, u);
373 let basis_v = basis::<DV>(&self.knots_v, span_v, v);
374 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
375 let mut point = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
376 for (i, value_u) in basis_u[..=DU].iter().enumerate() {
377 let row = &self.control_points[span_u - DU + i];
378 for (j, value_v) in basis_v[..=DV].iter().enumerate() {
379 point = point.add(row[span_v - DV + j].scale(value_u * value_v));
380 }
381 }
382 point
383 }
384
385 fn evaluate_homogeneous_heap(&self, u: f64, v: f64) -> Result<Vec4, String> {
386 let (knot_u, knot_v) = self.knot_vectors()?;
387 let u = knot_u.clamp_param(u);
388 let v = knot_v.clamp_param(v);
389 let span_u = knot_u.find_span(u);
390 let span_v = knot_v.find_span(v);
391 let basis_u = knot_u.basis_functions(span_u, u);
392 let basis_v = knot_v.basis_functions(span_v, v);
393 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
394 let mut point = Vec4 {
395 x: 0.0,
396 y: 0.0,
397 z: 0.0,
398 w: 0.0,
399 };
400 for (i, value_u) in basis_u.iter().enumerate() {
401 let row = &self.control_points[span_u - self.degree_u + i];
402 for (j, value_v) in basis_v.iter().enumerate() {
403 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
404 }
405 }
406 Ok(point)
407 }
408
409 #[cfg_attr(feature = "profile-wall", track_caller)]
410 pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String> {
411 let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
412 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
413 self.evaluate_homogeneous(u, v)?.point()
414 }
415
416 #[cfg_attr(feature = "profile-wall", track_caller)]
422 pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String> {
423 let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
424 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
425 if small_derivatives_enabled() {
426 return Ok(self.derivatives_extended_small(u, v, 0)?[0][0]);
427 }
428 Ok(self.derivatives_extended(u, v, 0)?[0][0])
429 }
430
431 pub(crate) fn derivatives_extended_small(
438 &self,
439 u: f64,
440 v: f64,
441 derivative_count: usize,
442 ) -> Result<[[Vec3; 3]; 3], String> {
443 debug_assert!(derivative_count <= 2);
444 let [u0, u1] = self.domain_u()?;
445 let [v0, v1] = self.domain_v()?;
446 let (closed_u, closed_v) = self.closed_directions()?;
447 let mut uu = u;
448 let mut vv = v;
449 if closed_u && (u < u0 || u > u1) {
450 uu = u0 + (u - u0).rem_euclid(u1 - u0);
451 }
452 if closed_v && (v < v0 || v > v1) {
453 vv = v0 + (v - v0).rem_euclid(v1 - v0);
454 }
455 let du_out = if uu < u0 { uu - u0 } else if uu > u1 { uu - u1 } else { 0.0 };
456 let dv_out = if vv < v0 { vv - v0 } else if vv > v1 { vv - v1 } else { 0.0 };
457 if du_out == 0.0 && dv_out == 0.0 {
458 return self.derivatives_small(uu, vv, derivative_count);
459 }
460 let anchor_u = uu - du_out;
461 let anchor_v = vv - dv_out;
462 let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
463 let mut result = [[Vec3::default(); 3]; 3];
464 result[0][0] = base[0][0]
465 .add(base[1][0].scale(du_out))
466 .add(base[0][1].scale(dv_out))
467 .add(base[1][1].scale(du_out * dv_out));
468 if derivative_count >= 1 {
469 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
470 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
471 result[1][1] = base[1][1];
472 }
473 if derivative_count >= 2 {
474 if du_out == 0.0 {
475 result[2][0] = base[2][0];
476 }
477 if dv_out == 0.0 {
478 result[0][2] = base[0][2];
479 }
480 }
481 Ok(result)
482 }
483
484 pub fn derivatives_extended(
489 &self,
490 u: f64,
491 v: f64,
492 derivative_count: usize,
493 ) -> Result<Vec<Vec<Vec3>>, String> {
494 if derivative_count <= 2 && small_derivatives_enabled() {
495 let small = self.derivatives_extended_small(u, v, derivative_count)?;
496 let order = derivative_count + 1;
497 return Ok((0..order).map(|k| (0..order).map(|l| small[k][l]).collect()).collect());
498 }
499 let [u0, u1] = self.domain_u()?;
500 let [v0, v1] = self.domain_v()?;
501 let (closed_u, closed_v) = self.closed_directions()?;
502 let mut uu = u;
503 let mut vv = v;
504 if closed_u && (u < u0 || u > u1) {
505 uu = u0 + (u - u0).rem_euclid(u1 - u0);
506 }
507 if closed_v && (v < v0 || v > v1) {
508 vv = v0 + (v - v0).rem_euclid(v1 - v0);
509 }
510 let du_out = if uu < u0 {
511 uu - u0
512 } else if uu > u1 {
513 uu - u1
514 } else {
515 0.0
516 };
517 let dv_out = if vv < v0 {
518 vv - v0
519 } else if vv > v1 {
520 vv - v1
521 } else {
522 0.0
523 };
524 if du_out == 0.0 && dv_out == 0.0 {
525 return self.derivatives(uu, vv, derivative_count);
526 }
527 let anchor_u = uu - du_out;
528 let anchor_v = vv - dv_out;
529 let base = self.derivatives(anchor_u, anchor_v, derivative_count.max(2))?;
530 let order = derivative_count + 1;
531 let mut result = vec![vec![Vec3::default(); order]; order];
532 result[0][0] = base[0][0]
536 .add(base[1][0].scale(du_out))
537 .add(base[0][1].scale(dv_out))
538 .add(base[1][1].scale(du_out * dv_out));
539 if derivative_count >= 1 {
540 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
541 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
542 result[1][1] = base[1][1];
543 }
544 if derivative_count >= 2 {
545 let second = self.derivatives(anchor_u, anchor_v, 2)?;
546 if du_out == 0.0 {
547 result[2][0] = second[2][0];
548 }
549 if dv_out == 0.0 {
550 result[0][2] = second[0][2];
551 }
552 }
553 Ok(result)
554 }
555
556 pub fn derivatives(
557 &self,
558 u: f64,
559 v: f64,
560 derivative_count: usize,
561 ) -> Result<Vec<Vec<Vec3>>, String> {
562 crate::projection_profile::count(crate::projection_profile::LANE_DERIVATIVES, self);
563 self.ensure_valid()?;
564 let du = derivative_count.min(self.degree_u);
565 let dv = derivative_count.min(self.degree_v);
566 let stride = derivative_count + 1;
567 let zero = Vec4 {
569 x: 0.0,
570 y: 0.0,
571 z: 0.0,
572 w: 0.0,
573 };
574 let mut homogeneous = vec![zero; stride * stride];
575 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
576 let u = knot_clamp(&self.knots_u, self.degree_u, u);
577 let v = knot_clamp(&self.knots_v, self.degree_v, v);
578 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
579 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
580 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
583 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
584 basis_derivatives_into(
585 &self.knots_u,
586 self.degree_u,
587 span_u,
588 u,
589 du,
590 &mut basis_u[..=du],
591 );
592 basis_derivatives_into(
593 &self.knots_v,
594 self.degree_v,
595 span_v,
596 v,
597 dv,
598 &mut basis_v[..=dv],
599 );
600 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
601 for k in 0..=du {
602 for l in 0..=dv {
603 if k + l > derivative_count {
604 continue;
605 }
606 let mut point = zero;
607 for i in 0..=self.degree_u {
608 let row = &self.control_points[span_u - self.degree_u + i];
609 for j in 0..=self.degree_v {
610 point = point.add(
611 row[span_v - self.degree_v + j]
612 .scale(basis_u[k][i] * basis_v[l][j]),
613 );
614 }
615 }
616 homogeneous[k * stride + l] = point;
617 }
618 }
619 } else {
620 let (knot_u, knot_v) = self.knot_vectors()?;
621 let u = knot_u.clamp_param(u);
622 let v = knot_v.clamp_param(v);
623 let span_u = knot_u.find_span(u);
624 let span_v = knot_v.find_span(v);
625 let basis_u = knot_u.basis_derivatives(span_u, u, du);
626 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
627 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
628 for k in 0..=du {
629 for l in 0..=dv {
630 if k + l > derivative_count {
631 continue;
632 }
633 let mut point = zero;
634 for i in 0..=self.degree_u {
635 let row = &self.control_points[span_u - self.degree_u + i];
636 for j in 0..=self.degree_v {
637 point = point.add(
638 row[span_v - self.degree_v + j]
639 .scale(basis_u[k][i] * basis_v[l][j]),
640 );
641 }
642 }
643 homogeneous[k * stride + l] = point;
644 }
645 }
646 }
647
648 let mut result = vec![vec![Vec3::default(); derivative_count + 1]; derivative_count + 1];
649 let weight = homogeneous[0].w;
650 if weight.abs() <= EPS {
651 return Err("NurbsSurface: zero evaluated weight".into());
652 }
653 for k in 0..=derivative_count {
654 for l in 0..=derivative_count - k {
655 if k > du || l > dv {
656 continue;
657 }
658 let at = |row: usize, column: usize| homogeneous[row * stride + column];
659 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
660 for j in 1..=l {
661 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
662 }
663 for i in 1..=k {
664 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
665 let mut mixed = Vec3::default();
666 for j in 1..=l {
667 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
668 }
669 value = value.sub(mixed.scale(binomial(k, i)));
670 }
671 result[k][l] = value.scale(1.0 / weight);
672 }
673 }
674 Ok(result)
675 }
676
677 pub(crate) fn derivatives_small(
692 &self,
693 u: f64,
694 v: f64,
695 derivative_count: usize,
696 ) -> Result<[[Vec3; 3]; 3], String> {
697 if derivative_count == 0 && switch!("BREP_SURFACE_POINT_ONLY", false)
703 && u.is_finite() && v.is_finite()
704 && self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
705 && !analytic_point_on() {
706 let point = self.evaluate_homogeneous(u, v)?;
707 if point.w.abs() <= EPS {
708 return Err("NurbsSurface: zero evaluated weight".into());
709 }
710 let mut result = [[Vec3::default(); 3]; 3];
711 result[0][0] = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
712 return Ok(result);
713 }
714 debug_assert!(derivative_count <= 2);
715 self.ensure_valid()?;
716 let du = derivative_count.min(self.degree_u);
717 let dv = derivative_count.min(self.degree_v);
718 let stride = derivative_count + 1;
719 let zero = Vec4 {
720 x: 0.0,
721 y: 0.0,
722 z: 0.0,
723 w: 0.0,
724 };
725 let mut homogeneous = [zero; 9];
726 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
727 let u = knot_clamp(&self.knots_u, self.degree_u, u);
728 let v = knot_clamp(&self.knots_v, self.degree_v, v);
729 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
730 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
731 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; 3];
735 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; 3];
736 basis_derivatives_into(
737 &self.knots_u,
738 self.degree_u,
739 span_u,
740 u,
741 du,
742 &mut basis_u[..=du],
743 );
744 basis_derivatives_into(
745 &self.knots_v,
746 self.degree_v,
747 span_v,
748 v,
749 dv,
750 &mut basis_v[..=dv],
751 );
752 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
753 for k in 0..=du {
754 for l in 0..=dv {
755 if k + l > derivative_count {
756 continue;
757 }
758 let mut point = zero;
759 for i in 0..=self.degree_u {
760 let row = &self.control_points[span_u - self.degree_u + i];
761 for j in 0..=self.degree_v {
762 point = point.add(
763 row[span_v - self.degree_v + j]
764 .scale(basis_u[k][i] * basis_v[l][j]),
765 );
766 }
767 }
768 homogeneous[k * stride + l] = point;
769 }
770 }
771 } else {
772 let (knot_u, knot_v) = self.knot_vectors()?;
773 let u = knot_u.clamp_param(u);
774 let v = knot_v.clamp_param(v);
775 let span_u = knot_u.find_span(u);
776 let span_v = knot_v.find_span(v);
777 let basis_u = knot_u.basis_derivatives(span_u, u, du);
778 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
779 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
780 for k in 0..=du {
781 for l in 0..=dv {
782 if k + l > derivative_count {
783 continue;
784 }
785 let mut point = zero;
786 for i in 0..=self.degree_u {
787 let row = &self.control_points[span_u - self.degree_u + i];
788 for j in 0..=self.degree_v {
789 point = point.add(
790 row[span_v - self.degree_v + j]
791 .scale(basis_u[k][i] * basis_v[l][j]),
792 );
793 }
794 }
795 homogeneous[k * stride + l] = point;
796 }
797 }
798 }
799
800 let mut result = [[Vec3::default(); 3]; 3];
801 let weight = homogeneous[0].w;
802 if weight.abs() <= EPS {
803 return Err("NurbsSurface: zero evaluated weight".into());
804 }
805 for k in 0..=derivative_count {
806 for l in 0..=derivative_count - k {
807 if k > du || l > dv {
808 continue;
809 }
810 let at = |row: usize, column: usize| homogeneous[row * stride + column];
811 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
812 for j in 1..=l {
813 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
814 }
815 for i in 1..=k {
816 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
817 let mut mixed = Vec3::default();
818 for j in 1..=l {
819 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
820 }
821 value = value.sub(mixed.scale(binomial(k, i)));
822 }
823 result[k][l] = value.scale(1.0 / weight);
824 }
825 }
826 Ok(result)
827 }
828
829 #[inline]
833 pub(crate) fn deriv1_tensor_supported(&self) -> bool {
834 self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
835 }
836
837 pub(crate) fn deriv1_tensor_each<F>(
859 &self,
860 us: &[f64],
861 vs: &[f64],
862 mut f: F,
863 ) -> Result<(), String>
864 where
865 F: FnMut(usize, usize, Vec3, Vec3, Vec3) -> Result<(), String>,
866 {
867 self.ensure_valid()?;
868 const MAX_BLOCK: usize = TENSOR_MAX_BLOCK;
870 if !self.deriv1_tensor_supported() || us.len() > MAX_BLOCK || vs.len() > MAX_BLOCK {
871 return Err("NurbsSurface: tensor block outside the stack evaluator".into());
872 }
873 let du = 1usize.min(self.degree_u);
874 let dv = 1usize.min(self.degree_v);
875 let mut span_u = [0usize; MAX_BLOCK];
876 let mut span_v = [0usize; MAX_BLOCK];
877 let mut basis_u = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
878 let mut basis_v = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
879 for (index, &value) in us.iter().enumerate() {
880 let value = knot_clamp(&self.knots_u, self.degree_u, value);
881 span_u[index] = knot_find_span(&self.knots_u, self.degree_u, value);
882 basis_derivatives_into(
883 &self.knots_u,
884 self.degree_u,
885 span_u[index],
886 value,
887 du,
888 &mut basis_u[index][..=du],
889 );
890 }
891 for (index, &value) in vs.iter().enumerate() {
892 let value = knot_clamp(&self.knots_v, self.degree_v, value);
893 span_v[index] = knot_find_span(&self.knots_v, self.degree_v, value);
894 basis_derivatives_into(
895 &self.knots_v,
896 self.degree_v,
897 span_v[index],
898 value,
899 dv,
900 &mut basis_v[index][..=dv],
901 );
902 }
903 if tensor_fused_on() {
904 let (count_u, count_v) = (us.len(), vs.len());
905 let tables = (&span_u, &span_v, &basis_u, &basis_v);
906 match (self.degree_u, self.degree_v) {
907 (1, 1) => return self.deriv1_tensor_fused::<1, 1, F>(count_u, count_v, tables, &mut f),
908 (1, 2) => return self.deriv1_tensor_fused::<1, 2, F>(count_u, count_v, tables, &mut f),
909 (1, 3) => return self.deriv1_tensor_fused::<1, 3, F>(count_u, count_v, tables, &mut f),
910 (2, 1) => return self.deriv1_tensor_fused::<2, 1, F>(count_u, count_v, tables, &mut f),
911 (2, 2) => return self.deriv1_tensor_fused::<2, 2, F>(count_u, count_v, tables, &mut f),
912 (2, 3) => return self.deriv1_tensor_fused::<2, 3, F>(count_u, count_v, tables, &mut f),
913 (3, 1) => return self.deriv1_tensor_fused::<3, 1, F>(count_u, count_v, tables, &mut f),
914 (3, 2) => return self.deriv1_tensor_fused::<3, 2, F>(count_u, count_v, tables, &mut f),
915 (3, 3) => return self.deriv1_tensor_fused::<3, 3, F>(count_u, count_v, tables, &mut f),
916 _ => {}
917 }
918 }
919 let zero = Vec4 {
920 x: 0.0,
921 y: 0.0,
922 z: 0.0,
923 w: 0.0,
924 };
925 for i in 0..us.len() {
926 let rows = &self.control_points[span_u[i] - self.degree_u..];
927 for j in 0..vs.len() {
928 let column = span_v[j] - self.degree_v;
929 let sum = |k: usize, l: usize| {
933 let mut point = zero;
934 for row_index in 0..=self.degree_u {
935 let row = &rows[row_index];
936 for column_index in 0..=self.degree_v {
937 point = point.add(
938 row[column + column_index]
939 .scale(basis_u[i][k][row_index] * basis_v[j][l][column_index]),
940 );
941 }
942 }
943 point
944 };
945 let h00 = sum(0, 0);
946 let h01 = if dv >= 1 { sum(0, 1) } else { zero };
947 let h10 = if du >= 1 { sum(1, 0) } else { zero };
948 let weight = h00.w;
949 if weight.abs() <= EPS {
950 return Err("NurbsSurface: zero evaluated weight".into());
951 }
952 let inverse = 1.0 / weight;
953 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
954 let partial_v = if dv >= 1 {
955 Vec3::new(h01.x, h01.y, h01.z)
956 .sub(point.scale(h01.w))
957 .scale(inverse)
958 } else {
959 Vec3::default()
960 };
961 let partial_u = if du >= 1 {
962 Vec3::new(h10.x, h10.y, h10.z)
963 .sub(point.scale(h10.w))
964 .scale(inverse)
965 } else {
966 Vec3::default()
967 };
968 f(i, j, point, partial_u, partial_v)?;
969 }
970 }
971 Ok(())
972 }
973
974 #[allow(clippy::type_complexity)]
986 #[inline(always)]
987 fn deriv1_tensor_fused<const DEGREE_U: usize, const DEGREE_V: usize, F>(
988 &self,
989 count_u: usize,
990 count_v: usize,
991 (span_u, span_v, basis_u, basis_v): (
992 &[usize; TENSOR_MAX_BLOCK],
993 &[usize; TENSOR_MAX_BLOCK],
994 &[[[f64; MAX_STACK_ORDER]; 2]; TENSOR_MAX_BLOCK],
995 &[[[f64; MAX_STACK_ORDER]; 2]; TENSOR_MAX_BLOCK],
996 ),
997 f: &mut F,
998 ) -> Result<(), String>
999 where
1000 F: FnMut(usize, usize, Vec3, Vec3, Vec3) -> Result<(), String>,
1001 {
1002 let zero = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
1003 for i in 0..count_u {
1004 let rows = &self.control_points[span_u[i] - DEGREE_U..=span_u[i]];
1005 let (value_u, slope_u) = (&basis_u[i][0], &basis_u[i][1]);
1006 for j in 0..count_v {
1007 let column = span_v[j] - DEGREE_V;
1008 let (value_v, slope_v) = (&basis_v[j][0], &basis_v[j][1]);
1009 let (mut h00, mut h01, mut h10) = (zero, zero, zero);
1010 for row_index in 0..=DEGREE_U {
1011 let row = &rows[row_index][column..=column + DEGREE_V];
1012 for column_index in 0..=DEGREE_V {
1013 let control = row[column_index];
1014 h00 = h00.add(control.scale(value_u[row_index] * value_v[column_index]));
1015 h01 = h01.add(control.scale(value_u[row_index] * slope_v[column_index]));
1016 h10 = h10.add(control.scale(slope_u[row_index] * value_v[column_index]));
1017 }
1018 }
1019 let weight = h00.w;
1020 if weight.abs() <= EPS {
1021 return Err("NurbsSurface: zero evaluated weight".into());
1022 }
1023 let inverse = 1.0 / weight;
1024 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
1025 let partial_v = Vec3::new(h01.x, h01.y, h01.z).sub(point.scale(h01.w)).scale(inverse);
1026 let partial_u = Vec3::new(h10.x, h10.y, h10.z).sub(point.scale(h10.w)).scale(inverse);
1027 f(i, j, point, partial_u, partial_v)?;
1028 }
1029 }
1030 Ok(())
1031 }
1032
1033 #[inline]
1036 pub(crate) fn deriv1(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
1037 match (self.degree_u, self.degree_v) {
1044 (1, 1) => self.deriv1_small_fixed::<1, 1>(u, v),
1045 (2, 1) => self.deriv1_small_fixed::<2, 1>(u, v),
1046 (1, 2) => self.deriv1_small_fixed::<1, 2>(u, v),
1047 (2, 2) => self.deriv1_small_fixed::<2, 2>(u, v),
1048 _ => {
1049 let g = self.derivatives_small(u, v, 1)?;
1050 Ok((g[0][0], g[1][0], g[0][1]))
1051 }
1052 }
1053 }
1054
1055 fn deriv1_small_fixed<const DEGREE_U: usize, const DEGREE_V: usize>(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
1068 self.ensure_valid()?;
1069 let du = DEGREE_U.min(1);
1070 let dv = DEGREE_V.min(1);
1071 let u = knot_clamp(&self.knots_u, DEGREE_U, u);
1072 let v = knot_clamp(&self.knots_v, DEGREE_V, v);
1073 let span_u = knot_find_span(&self.knots_u, DEGREE_U, u);
1074 let span_v = knot_find_span(&self.knots_v, DEGREE_V, v);
1075 let mut basis_u = [[0.0; MAX_STACK_ORDER]; 2];
1076 let mut basis_v = [[0.0; MAX_STACK_ORDER]; 2];
1077 basis_derivatives_into(&self.knots_u, DEGREE_U, span_u, u, du, &mut basis_u[..=du]);
1078 basis_derivatives_into(&self.knots_v, DEGREE_V, span_v, v, dv, &mut basis_v[..=dv]);
1079 let zero = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
1080 let sum = |k: usize, l: usize| {
1081 let mut value = zero;
1082 for i in 0..=DEGREE_U {
1083 let row = &self.control_points[span_u - DEGREE_U + i];
1084 for j in 0..=DEGREE_V {
1085 value = value.add(row[span_v - DEGREE_V + j].scale(basis_u[k][i] * basis_v[l][j]));
1086 }
1087 }
1088 value
1089 };
1090 let h00 = sum(0, 0);
1091 let h01 = if dv == 1 { sum(0, 1) } else { zero };
1092 let h10 = if du == 1 { sum(1, 0) } else { zero };
1093 if h00.w.abs() <= EPS { return Err("NurbsSurface: zero evaluated weight".into()); }
1094 let inverse = 1.0 / h00.w;
1095 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
1096 let partial_v = if dv == 1 {
1097 Vec3::new(h01.x, h01.y, h01.z).sub(point.scale(1.0 * h01.w)).scale(inverse)
1098 } else { Vec3::default() };
1099 let partial_u = if du == 1 {
1100 Vec3::new(h10.x, h10.y, h10.z).sub(point.scale(1.0 * h10.w))
1101 .sub(Vec3::default().scale(1.0)).scale(inverse)
1102 } else { Vec3::default() };
1103 Ok((point, partial_u, partial_v))
1104 }
1105
1106 pub(crate) fn deriv1_extended(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
1114 let [u0, u1] = self.domain_u()?;
1115 let [v0, v1] = self.domain_v()?;
1116 let (closed_u, closed_v) = self.closed_directions()?;
1117 let mut uu = u;
1118 let mut vv = v;
1119 if closed_u && (u < u0 || u > u1) {
1120 uu = u0 + (u - u0).rem_euclid(u1 - u0);
1121 }
1122 if closed_v && (v < v0 || v > v1) {
1123 vv = v0 + (v - v0).rem_euclid(v1 - v0);
1124 }
1125 let du_out = if uu < u0 {
1126 uu - u0
1127 } else if uu > u1 {
1128 uu - u1
1129 } else {
1130 0.0
1131 };
1132 let dv_out = if vv < v0 {
1133 vv - v0
1134 } else if vv > v1 {
1135 vv - v1
1136 } else {
1137 0.0
1138 };
1139 if du_out == 0.0 && dv_out == 0.0 {
1140 if analytic_deriv1_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
1141 && matches!(self.analytic(), Some(crate::AnalyticSurface::Plane { .. }
1142 | crate::AnalyticSurface::RuledRevolution { .. } | crate::AnalyticSurface::Sphere { .. }
1143 | crate::AnalyticSurface::Torus { .. })) {
1144 return match (self.degree_u, self.degree_v) {
1145 (1, 1) => self.deriv1_small_fixed::<1, 1>(uu, vv),
1146 (2, 1) => self.deriv1_small_fixed::<2, 1>(uu, vv),
1147 (1, 2) => self.deriv1_small_fixed::<1, 2>(uu, vv),
1148 (2, 2) => self.deriv1_small_fixed::<2, 2>(uu, vv),
1149 _ => unreachable!(),
1150 };
1151 }
1152 let g = self.derivatives_small(uu, vv, 1)?;
1153 return Ok((g[0][0], g[1][0], g[0][1]));
1154 }
1155 let anchor_u = uu - du_out;
1156 let anchor_v = vv - dv_out;
1157 let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
1158 let s = base[0][0]
1162 .add(base[1][0].scale(du_out))
1163 .add(base[0][1].scale(dv_out))
1164 .add(base[1][1].scale(du_out * dv_out));
1165 let su = base[1][0].add(base[1][1].scale(dv_out));
1166 let sv = base[0][1].add(base[1][1].scale(du_out));
1167 Ok((s, su, sv))
1168 }
1169
1170 pub(crate) fn partial_by_differences(
1195 &self,
1196 along_u: bool,
1197 u: f64,
1198 v: f64,
1199 ) -> Result<Vec3, String> {
1200 self.ensure_valid()?;
1201 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
1202 return Err("NurbsSurface: partial by differences exceeds the stack degree".into());
1203 }
1204 let (pu, pv) = (self.degree_u, self.degree_v);
1205 let u = knot_clamp(&self.knots_u, pu, u);
1206 let v = knot_clamp(&self.knots_v, pv, v);
1207 let span_u = knot_find_span(&self.knots_u, pu, u);
1208 let span_v = knot_find_span(&self.knots_v, pv, v);
1209 let mut full_u = [0.0f64; MAX_STACK_ORDER];
1210 let mut full_v = [0.0f64; MAX_STACK_ORDER];
1211 basis_functions_into(&self.knots_u, pu, span_u, u, &mut full_u);
1212 basis_functions_into(&self.knots_v, pv, span_v, v, &mut full_v);
1213 let zero = Vec4 {
1214 x: 0.0,
1215 y: 0.0,
1216 z: 0.0,
1217 w: 0.0,
1218 };
1219 let control = |i: usize, j: usize| self.control_points[span_u - pu + i][span_v - pv + j];
1220 let mut point = zero;
1221 for i in 0..=pu {
1222 for j in 0..=pv {
1223 point = point.add(control(i, j).scale(full_u[i] * full_v[j]));
1224 }
1225 }
1226 if point.w.abs() <= EPS {
1227 return Err("NurbsSurface: zero evaluated weight".into());
1228 }
1229 let source = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
1230 let (knots, degree, span, parameter) = if along_u {
1231 (&self.knots_u, pu, span_u, u)
1232 } else {
1233 (&self.knots_v, pv, span_v, v)
1234 };
1235 if degree == 0 {
1236 return Ok(Vec3::default());
1237 }
1238 let mut lowered = [0.0f64; MAX_STACK_ORDER];
1239 basis_functions_into(knots, degree - 1, span, parameter, &mut lowered);
1240 let mut hodograph = zero;
1243 for r in 0..degree {
1244 let index = span - degree + r;
1247 let width = knots[index + degree + 1] - knots[index + 1];
1248 if width.abs() <= EPS {
1249 continue;
1250 }
1251 let factor = lowered[r] * degree as f64 / width;
1252 if along_u {
1253 for j in 0..=pv {
1254 let difference = control(r + 1, j).add(control(r, j).scale(-1.0));
1255 hodograph = hodograph.add(difference.scale(factor * full_v[j]));
1256 }
1257 } else {
1258 for i in 0..=pu {
1259 let difference = control(i, r + 1).add(control(i, r).scale(-1.0));
1260 hodograph = hodograph.add(difference.scale(factor * full_u[i]));
1261 }
1262 }
1263 }
1264 Ok(Vec3::new(hodograph.x, hodograph.y, hodograph.z)
1265 .sub(source.scale(hodograph.w))
1266 .scale(1.0 / point.w))
1267 }
1268
1269 pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
1270 if small_derivatives_enabled() {
1271 let derivatives = self.derivatives_small(u, v, 1)?;
1272 return derivatives[1][0].cross(derivatives[0][1]).normalized();
1273 }
1274 let derivatives = self.derivatives(u, v, 1)?;
1275 derivatives[1][0].cross(derivatives[0][1]).normalized()
1276 }
1277
1278 pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String> {
1285 let derivatives: [[Vec3; 3]; 3] = if small_derivatives_enabled() {
1286 self.derivatives_small(u, v, 2)?
1287 } else {
1288 let heap = self.derivatives(u, v, 2)?;
1289 [[heap[0][0], heap[0][1], heap[0][2]], [heap[1][0], heap[1][1], Vec3::default()], [heap[2][0], Vec3::default(), Vec3::default()]]
1290 };
1291 let su = derivatives[1][0];
1292 let sv = derivatives[0][1];
1293 let cross = su.cross(sv);
1294 let cross_length = cross.length();
1295 if cross_length <= 1e-12 {
1296 return Err(format!(
1297 "degenerate parametrization at (u={u:.4}, v={v:.4})"
1298 ));
1299 }
1300 let normal = cross.scale(1.0 / cross_length);
1301 let e1 = su.dot(su);
1302 let f1 = su.dot(sv);
1303 let g1 = sv.dot(sv);
1304 let l2 = derivatives[2][0].dot(normal);
1305 let m2 = derivatives[1][1].dot(normal);
1306 let n2 = derivatives[0][2].dot(normal);
1307 let denominator = e1 * g1 - f1 * f1;
1308 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();
1311 Ok((
1312 mean_double * 0.5 - discriminant,
1313 mean_double * 0.5 + discriminant,
1314 ))
1315 }
1316
1317 pub fn is_affine(&self) -> Result<bool, String> {
1322 if self.degree_u != 1
1323 || self.degree_v != 1
1324 || self.control_points.len() != 2
1325 || self.control_points[0].len() != 2
1326 || self.control_points[1].len() != 2
1327 {
1328 return Ok(false);
1329 }
1330 let weight = self.control_points[0][0].w;
1331 if [
1332 self.control_points[0][1].w,
1333 self.control_points[1][0].w,
1334 self.control_points[1][1].w,
1335 ]
1336 .iter()
1337 .any(|other| (*other - weight).abs() > 1e-12 * other.abs().max(weight.abs()))
1338 {
1339 return Ok(false);
1340 }
1341 let p00 = self.control_points[0][0].point()?;
1342 let p01 = self.control_points[0][1].point()?;
1343 let p10 = self.control_points[1][0].point()?;
1344 let p11 = self.control_points[1][1].point()?;
1345 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
1346 let extent = [
1351 p01.sub(p00),
1352 p10.sub(p00),
1353 p11.sub(p00),
1354 p10.sub(p01),
1355 p11.sub(p01),
1356 p11.sub(p10),
1357 ]
1358 .iter()
1359 .map(|delta| delta.length())
1360 .fold(0.0, f64::max);
1361 Ok(gap <= 1e-9 * (1.0 + extent))
1362 }
1363
1364 pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
1365 self.ensure_valid()?;
1366 let u = knot_clamp(&self.knots_u, self.degree_u, u);
1367 let span = knot_find_span(&self.knots_u, self.degree_u, u);
1368 let basis = if self.degree_u <= MAX_STACK_DEGREE {
1369 let mut stack = [0.0f64; MAX_STACK_ORDER];
1370 basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
1371 stack[..=self.degree_u].to_vec()
1372 } else {
1373 KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
1374 };
1375 let mut points = Vec::with_capacity(self.control_points[0].len());
1376 for column in 0..self.control_points[0].len() {
1377 let mut point = Vec4 {
1378 x: 0.0,
1379 y: 0.0,
1380 z: 0.0,
1381 w: 0.0,
1382 };
1383 for (index, value) in basis.iter().enumerate() {
1384 point = point
1385 .add(self.control_points[span - self.degree_u + index][column].scale(*value));
1386 }
1387 points.push(point);
1388 }
1389 NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
1390 }
1391
1392 pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
1393 self.ensure_valid()?;
1394 let v = knot_clamp(&self.knots_v, self.degree_v, v);
1395 let span = knot_find_span(&self.knots_v, self.degree_v, v);
1396 let basis = if self.degree_v <= MAX_STACK_DEGREE {
1397 let mut stack = [0.0f64; MAX_STACK_ORDER];
1398 basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
1399 stack[..=self.degree_v].to_vec()
1400 } else {
1401 KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
1402 };
1403 let mut points = Vec::with_capacity(self.control_points.len());
1404 for row in &self.control_points {
1405 let mut point = Vec4 {
1406 x: 0.0,
1407 y: 0.0,
1408 z: 0.0,
1409 w: 0.0,
1410 };
1411 for (index, value) in basis.iter().enumerate() {
1412 point = point.add(row[span - self.degree_v + index].scale(*value));
1413 }
1414 points.push(point);
1415 }
1416 NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
1417 }
1418}
1419
1420pub fn make_plane(
1421 origin: Vec3,
1422 u_direction: Vec3,
1423 v_direction: Vec3,
1424 u_max: f64,
1425 v_max: f64,
1426) -> Result<NurbsSurface, String> {
1427 if u_max <= EPS || v_max <= EPS {
1428 return Err("makePlane: parameter extents must be positive".into());
1429 }
1430 let p10 = origin.add(u_direction.scale(u_max));
1431 let p01 = origin.add(v_direction.scale(v_max));
1432 let p11 = p10.add(v_direction.scale(v_max));
1433 NurbsSurface::new(
1434 1,
1435 1,
1436 vec![0.0, 0.0, u_max, u_max],
1437 vec![0.0, 0.0, v_max, v_max],
1438 vec![
1439 vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
1440 vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
1441 ],
1442 )
1443}
1444
1445pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
1446 let rows = profile
1447 .control_points
1448 .iter()
1449 .map(|point| {
1450 vec![
1451 *point,
1452 Vec4 {
1453 x: point.x + point.w * direction.x,
1454 y: point.y + point.w * direction.y,
1455 z: point.z + point.w * direction.z,
1456 w: point.w,
1457 },
1458 ]
1459 })
1460 .collect();
1461 NurbsSurface::new(
1462 profile.degree,
1463 1,
1464 profile.knots.clone(),
1465 vec![0.0, 0.0, 1.0, 1.0],
1466 rows,
1467 )
1468}
1469
1470pub fn make_revolution(
1471 axis_point: Vec3,
1472 axis_direction: Vec3,
1473 generatrix: &NurbsCurve,
1474 theta: f64,
1475) -> Result<NurbsSurface, String> {
1476 if theta <= EPS || theta > std::f64::consts::TAU + EPS {
1477 return Err("makeRevolution: theta must be in (0, 2*PI]".into());
1478 }
1479 let theta = theta.min(std::f64::consts::TAU);
1480 let axis = axis_direction.normalized()?;
1481 let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
1482 let arc_angle = theta / arc_count as f64;
1483 let middle_weight = (arc_angle / 2.0).cos();
1484 let mut knots_u = vec![0.0, 0.0, 0.0];
1485 for index in 1..arc_count {
1486 let knot = index as f64 / arc_count as f64;
1487 knots_u.extend([knot, knot]);
1488 }
1489 knots_u.extend([1.0, 1.0, 1.0]);
1490 let mut rows = vec![
1491 vec![
1492 Vec4 {
1493 x: 0.0,
1494 y: 0.0,
1495 z: 0.0,
1496 w: 1.0,
1497 };
1498 generatrix.control_points.len()
1499 ];
1500 2 * arc_count + 1
1501 ];
1502
1503 let mut radial_extent: f64 = 0.0;
1506 for control_point in &generatrix.control_points {
1507 let point = control_point.point()?;
1508 let offset = point.sub(axis_point);
1509 radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
1510 }
1511
1512 for (column, control_point) in generatrix.control_points.iter().enumerate() {
1513 let point = control_point.point()?;
1514 let weight = control_point.w;
1515 let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
1516 let radial_vector = point.sub(axis_origin);
1517 let measured_radius = radial_vector.length();
1527 let pole_tolerance = EPS.max(1e-8 * radial_extent);
1528 let radius = if measured_radius <= pole_tolerance {
1529 0.0
1530 } else {
1531 measured_radius
1532 };
1533 let (x_axis, y_axis) = if radius <= EPS {
1534 (Vec3::default(), Vec3::default())
1535 } else {
1536 if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
1548 return Err(format!(
1549 "makeRevolution: generatrix control point {column} sits {radius:e} from the \
1550 axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
1551 radial extent (a fuzzy pole)"
1552 ));
1553 }
1554 let x_axis = radial_vector.scale(1.0 / radius);
1555 (x_axis, axis.cross(x_axis))
1556 };
1557 rows[0][column] = Vec4::from_point(point, weight);
1558 let mut start_point = point;
1559 let mut start_tangent = y_axis.scale(radius);
1560 let mut angle = 0.0;
1561 for arc in 1..=arc_count {
1562 angle += arc_angle;
1563 let end_point = if radius <= EPS {
1564 point
1565 } else {
1566 axis_origin
1567 .add(x_axis.scale(radius * angle.cos()))
1568 .add(y_axis.scale(radius * angle.sin()))
1569 };
1570 let end_tangent = if radius <= EPS {
1571 Vec3::default()
1572 } else {
1573 x_axis
1574 .scale(-radius * angle.sin())
1575 .add(y_axis.scale(radius * angle.cos()))
1576 };
1577 let row = 2 * (arc - 1);
1578 if radius <= EPS {
1579 rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
1580 } else {
1581 let cross = start_tangent.cross(end_tangent);
1582 let denominator = cross.length_squared();
1583 let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
1592 if denominator <= EPS * tangent_scale {
1593 return Err("makeRevolution: arc tangents are parallel".into());
1594 }
1595 let distance =
1596 end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
1597 let middle = start_point.add(start_tangent.scale(distance));
1598 rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
1599 }
1600 rows[row + 2][column] = Vec4::from_point(end_point, weight);
1601 start_point = end_point;
1602 start_tangent = end_tangent;
1603 }
1604 }
1605 NurbsSurface::new(
1606 2,
1607 generatrix.degree,
1608 knots_u,
1609 generatrix.knots.clone(),
1610 rows,
1611 )
1612}
1613
1614pub fn make_cylinder_surface(
1615 base: Vec3,
1616 axis_direction: Vec3,
1617 radius: f64,
1618 height: f64,
1619) -> Result<NurbsSurface, String> {
1620 if radius <= EPS || height <= EPS {
1621 return Err("makeCylinder: radius and height must be positive".into());
1622 }
1623 let axis = axis_direction.normalized()?;
1624 let x_axis = axis.perpendicular()?;
1625 let start = base.add(x_axis.scale(radius));
1626 let end = start.add(axis.scale(height));
1627 let generatrix = crate::make_line(start, end)?;
1628 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1629}
1630
1631pub fn make_cone_surface(
1632 base: Vec3,
1633 axis_direction: Vec3,
1634 radius_bottom: f64,
1635 radius_top: f64,
1636 height: f64,
1637) -> Result<NurbsSurface, String> {
1638 if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
1639 return Err("makeCone: invalid radius or height".into());
1640 }
1641 let axis = axis_direction.normalized()?;
1642 let x_axis = axis.perpendicular()?;
1643 let start = base.add(x_axis.scale(radius_bottom));
1644 let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
1645 let generatrix = crate::make_line(start, end)?;
1646 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1647}
1648
1649pub fn make_sphere_surface(
1650 center: Vec3,
1651 radius: f64,
1652 polar_axis: Vec3,
1653) -> Result<NurbsSurface, String> {
1654 make_sphere_surface_framed(center, radius, polar_axis, None)
1655}
1656
1657pub fn make_sphere_surface_framed(
1669 center: Vec3,
1670 radius: f64,
1671 polar_axis: Vec3,
1672 seam_direction: Option<Vec3>,
1673) -> Result<NurbsSurface, String> {
1674 if radius <= EPS {
1675 return Err("makeSphere: radius must be positive".into());
1676 }
1677 let axis = polar_axis.normalized()?;
1678 let x_axis = match seam_direction {
1679 Some(seam) => {
1680 let planar = seam.sub(axis.scale(seam.dot(axis)));
1681 match planar.normalized() {
1682 Ok(unit) if planar.length() > EPS => unit,
1683 _ => axis.perpendicular()?,
1684 }
1685 }
1686 None => axis.perpendicular()?,
1687 };
1688 let meridian = crate::make_arc(
1689 center,
1690 x_axis,
1691 axis,
1692 radius,
1693 -std::f64::consts::FRAC_PI_2,
1694 std::f64::consts::FRAC_PI_2,
1695 )?;
1696 make_revolution(center, axis, &meridian, std::f64::consts::TAU)
1697}
1698
1699pub fn make_torus_surface(
1700 center: Vec3,
1701 axis_direction: Vec3,
1702 major_radius: f64,
1703 minor_radius: f64,
1704) -> Result<NurbsSurface, String> {
1705 if minor_radius <= EPS || major_radius <= minor_radius {
1706 return Err("makeTorus: requires positive minorRadius < majorRadius".into());
1707 }
1708 let axis = axis_direction.normalized()?;
1709 let x_axis = axis.perpendicular()?;
1710 let tube_center = center.add(x_axis.scale(major_radius));
1711 let tube = crate::make_arc(
1712 tube_center,
1713 x_axis,
1714 axis,
1715 minor_radius,
1716 0.0,
1717 std::f64::consts::TAU,
1718 )?;
1719 make_revolution(center, axis, &tube, std::f64::consts::TAU)
1720}
1721
1722fn binomial(n: usize, k: usize) -> f64 {
1723 if k > n {
1724 return 0.0;
1725 }
1726 let k = k.min(n - k);
1727 (1..=k).fold(1.0, |value, index| {
1728 value * (n - k + index) as f64 / index as f64
1729 })
1730}
1731
1732
1733
1734pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
1746 let inflate = if inflate.is_finite() {
1747 inflate.clamp(1.0, 16.0)
1748 } else {
1749 2.0
1750 };
1751 let (closed_u, closed_v) = surface.closed_directions()?;
1752 let [u0, u1] = surface.domain_u()?;
1753 let [v0, v1] = surface.domain_v()?;
1754 let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
1755 if closed || inflate <= 1.0 {
1756 (d0, d1)
1757 } else {
1758 let centre = 0.5 * (d0 + d1);
1759 let half = 0.5 * (d1 - d0) * inflate;
1760 (centre - half, centre + half)
1761 }
1762 };
1763 let (nu0, nu1) = stretch(u0, u1, closed_u);
1764 let (nv0, nv1) = stretch(v0, v1, closed_v);
1765 if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
1766 return Ok(surface.clone());
1767 }
1768 if surface.is_affine()? {
1769 let corners = [
1772 surface.evaluate_extended(nu0, nv0)?,
1773 surface.evaluate_extended(nu1, nv0)?,
1774 surface.evaluate_extended(nu0, nv1)?,
1775 surface.evaluate_extended(nu1, nv1)?,
1776 ];
1777 return NurbsSurface::new(
1778 1,
1779 1,
1780 vec![nu0, nu0, nu1, nu1],
1781 vec![nv0, nv0, nv1, nv1],
1782 vec![
1783 vec![
1784 Vec4::from_point(corners[0], 1.0),
1785 Vec4::from_point(corners[2], 1.0),
1786 ],
1787 vec![
1788 Vec4::from_point(corners[1], 1.0),
1789 Vec4::from_point(corners[3], 1.0),
1790 ],
1791 ],
1792 );
1793 }
1794 const SAMPLES: usize = 32;
1795 let u_params: Vec<f64> = (0..=SAMPLES)
1796 .map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
1797 .collect();
1798 let v_params: Vec<f64> = (0..=SAMPLES)
1799 .map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
1800 .collect();
1801 let mut column_curves = Vec::with_capacity(u_params.len());
1804 for &u in &u_params {
1805 let column: Vec<Vec3> = v_params
1806 .iter()
1807 .map(|&v| surface.evaluate_extended(u, v))
1808 .collect::<Result<_, _>>()?;
1809 column_curves.push(crate::interpolate_curve(
1810 &column,
1811 3,
1812 &normalized(&v_params),
1813 )?);
1814 }
1815 let v_knots = column_curves[0].knots.clone();
1816 let control_rows = column_curves[0].control_points.len();
1817 let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
1818 let mut u_knots = Vec::new();
1819 for row_index in 0..control_rows {
1820 let row: Vec<Vec3> = column_curves
1821 .iter()
1822 .map(|curve| curve.control_points[row_index].point())
1823 .collect::<Result<_, _>>()?;
1824 let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
1825 u_knots = fitted.knots.clone();
1826 for (u_index, control) in fitted.control_points.iter().enumerate() {
1827 if grid.len() <= u_index {
1828 grid.push(Vec::new());
1829 }
1830 grid[u_index].push(*control);
1831 }
1832 }
1833 let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
1836 knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
1837 };
1838 NurbsSurface::new(
1839 3,
1840 3,
1841 rescale(&u_knots, nu0, nu1),
1842 rescale(&v_knots, nv0, nv1),
1843 grid,
1844 )
1845}
1846
1847fn normalized(parameters: &[f64]) -> Vec<f64> {
1848 let first = parameters[0];
1849 let last = parameters[parameters.len() - 1];
1850 parameters
1851 .iter()
1852 .map(|p| (p - first) / (last - first))
1853 .collect()
1854}
1855
1856
1857
1858
1859
1860fn analytic_deriv1_on() -> bool {
1861 switch!("BREP_ANALYTIC_DERIV1", true)
1862}
1863
1864
1865fn analytic_point_on() -> bool {
1867 switch!("BREP_ANALYTIC_POINT", false)
1868}
1869
1870pub(crate) fn small_derivatives_enabled() -> bool {
1877 switch!("BREP_SURFACE_SMALL_DERIVATIVES", true)
1878}
1879
1880
1881
1882