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<crate::projection::SampleGrid>,
67 #[serde(skip, default)]
71 pub(crate) projection_dense_grid: std::cell::OnceCell<crate::projection::SampleGrid>,
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 #[cfg_attr(feature = "profile-wall", track_caller)]
294 pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String> {
295 let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
296 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
297 crate::projection_profile::count(crate::projection_profile::LANE_EVALUATE, self);
298 self.ensure_valid()?;
299 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
300 return self.evaluate_homogeneous_heap(u, v);
301 }
302 let u = knot_clamp(&self.knots_u, self.degree_u, u);
303 let v = knot_clamp(&self.knots_v, self.degree_v, v);
304 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
305 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
306 if analytic_point_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
309 && matches!(self.analytic.get().and_then(|carrier| carrier.as_ref()),
310 Some(crate::AnalyticSurface::Plane { .. } | crate::AnalyticSurface::RuledRevolution { .. }
311 | crate::AnalyticSurface::Sphere { .. } | crate::AnalyticSurface::Torus { .. })) {
312 return Ok(match (self.degree_u, self.degree_v) {
313 (1, 1) => self.analytic_point_small::<1, 1>(u, v, span_u, span_v),
314 (2, 1) => self.analytic_point_small::<2, 1>(u, v, span_u, span_v),
315 (1, 2) => self.analytic_point_small::<1, 2>(u, v, span_u, span_v),
316 (2, 2) => self.analytic_point_small::<2, 2>(u, v, span_u, span_v),
317 _ => unreachable!(),
318 });
319 }
320 let mut basis_u = [0.0f64; MAX_STACK_ORDER];
321 let mut basis_v = [0.0f64; MAX_STACK_ORDER];
322 basis_functions_into(&self.knots_u, self.degree_u, span_u, u, &mut basis_u);
323 basis_functions_into(&self.knots_v, self.degree_v, span_v, v, &mut basis_v);
324 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
325 let mut point = Vec4 {
326 x: 0.0,
327 y: 0.0,
328 z: 0.0,
329 w: 0.0,
330 };
331 for (i, value_u) in basis_u[..=self.degree_u].iter().enumerate() {
332 let row = &self.control_points[span_u - self.degree_u + i];
333 for (j, value_v) in basis_v[..=self.degree_v].iter().enumerate() {
334 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
335 }
336 }
337 Ok(point)
338 }
339
340 fn analytic_point_small<const DU: usize, const DV: usize>(&self, u: f64, v: f64, span_u: usize, span_v: usize) -> Vec4 {
344 fn basis<const D: usize>(knots: &[f64], span: usize, parameter: f64) -> [f64; 3] {
345 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::BASIS);
346 let mut values = [0.0; 3];
347 let mut left = [0.0; 3];
348 let mut right = [0.0; 3];
349 values[0] = 1.0;
350 for j in 1..=D {
351 left[j] = parameter - knots[span + 1 - j];
352 right[j] = knots[span + j] - parameter;
353 let mut saved = 0.0;
354 for r in 0..j {
355 let denominator = right[r + 1] + left[j - r];
356 let temporary = if denominator.abs() <= EPS { 0.0 } else { values[r] / denominator };
357 values[r] = saved + right[r + 1] * temporary;
358 saved = left[j - r] * temporary;
359 }
360 values[j] = saved;
361 }
362 values
363 }
364 let basis_u = basis::<DU>(&self.knots_u, span_u, u);
365 let basis_v = basis::<DV>(&self.knots_v, span_v, v);
366 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
367 let mut point = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
368 for (i, value_u) in basis_u[..=DU].iter().enumerate() {
369 let row = &self.control_points[span_u - DU + i];
370 for (j, value_v) in basis_v[..=DV].iter().enumerate() {
371 point = point.add(row[span_v - DV + j].scale(value_u * value_v));
372 }
373 }
374 point
375 }
376
377 fn evaluate_homogeneous_heap(&self, u: f64, v: f64) -> Result<Vec4, String> {
378 let (knot_u, knot_v) = self.knot_vectors()?;
379 let u = knot_u.clamp_param(u);
380 let v = knot_v.clamp_param(v);
381 let span_u = knot_u.find_span(u);
382 let span_v = knot_v.find_span(v);
383 let basis_u = knot_u.basis_functions(span_u, u);
384 let basis_v = knot_v.basis_functions(span_v, v);
385 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
386 let mut point = Vec4 {
387 x: 0.0,
388 y: 0.0,
389 z: 0.0,
390 w: 0.0,
391 };
392 for (i, value_u) in basis_u.iter().enumerate() {
393 let row = &self.control_points[span_u - self.degree_u + i];
394 for (j, value_v) in basis_v.iter().enumerate() {
395 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
396 }
397 }
398 Ok(point)
399 }
400
401 #[cfg_attr(feature = "profile-wall", track_caller)]
402 pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String> {
403 let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
404 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
405 self.evaluate_homogeneous(u, v)?.point()
406 }
407
408 #[cfg_attr(feature = "profile-wall", track_caller)]
414 pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String> {
415 let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
416 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
417 if small_derivatives_enabled() {
418 return Ok(self.derivatives_extended_small(u, v, 0)?[0][0]);
419 }
420 Ok(self.derivatives_extended(u, v, 0)?[0][0])
421 }
422
423 pub(crate) fn derivatives_extended_small(
430 &self,
431 u: f64,
432 v: f64,
433 derivative_count: usize,
434 ) -> Result<[[Vec3; 3]; 3], String> {
435 debug_assert!(derivative_count <= 2);
436 let [u0, u1] = self.domain_u()?;
437 let [v0, v1] = self.domain_v()?;
438 let (closed_u, closed_v) = self.closed_directions()?;
439 let mut uu = u;
440 let mut vv = v;
441 if closed_u && (u < u0 || u > u1) {
442 uu = u0 + (u - u0).rem_euclid(u1 - u0);
443 }
444 if closed_v && (v < v0 || v > v1) {
445 vv = v0 + (v - v0).rem_euclid(v1 - v0);
446 }
447 let du_out = if uu < u0 { uu - u0 } else if uu > u1 { uu - u1 } else { 0.0 };
448 let dv_out = if vv < v0 { vv - v0 } else if vv > v1 { vv - v1 } else { 0.0 };
449 if du_out == 0.0 && dv_out == 0.0 {
450 return self.derivatives_small(uu, vv, derivative_count);
451 }
452 let anchor_u = uu - du_out;
453 let anchor_v = vv - dv_out;
454 let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
455 let mut result = [[Vec3::default(); 3]; 3];
456 result[0][0] = base[0][0]
457 .add(base[1][0].scale(du_out))
458 .add(base[0][1].scale(dv_out))
459 .add(base[1][1].scale(du_out * dv_out));
460 if derivative_count >= 1 {
461 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
462 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
463 result[1][1] = base[1][1];
464 }
465 if derivative_count >= 2 {
466 if du_out == 0.0 {
467 result[2][0] = base[2][0];
468 }
469 if dv_out == 0.0 {
470 result[0][2] = base[0][2];
471 }
472 }
473 Ok(result)
474 }
475
476 pub fn derivatives_extended(
481 &self,
482 u: f64,
483 v: f64,
484 derivative_count: usize,
485 ) -> Result<Vec<Vec<Vec3>>, String> {
486 if derivative_count <= 2 && small_derivatives_enabled() {
487 let small = self.derivatives_extended_small(u, v, derivative_count)?;
488 let order = derivative_count + 1;
489 return Ok((0..order).map(|k| (0..order).map(|l| small[k][l]).collect()).collect());
490 }
491 let [u0, u1] = self.domain_u()?;
492 let [v0, v1] = self.domain_v()?;
493 let (closed_u, closed_v) = self.closed_directions()?;
494 let mut uu = u;
495 let mut vv = v;
496 if closed_u && (u < u0 || u > u1) {
497 uu = u0 + (u - u0).rem_euclid(u1 - u0);
498 }
499 if closed_v && (v < v0 || v > v1) {
500 vv = v0 + (v - v0).rem_euclid(v1 - v0);
501 }
502 let du_out = if uu < u0 {
503 uu - u0
504 } else if uu > u1 {
505 uu - u1
506 } else {
507 0.0
508 };
509 let dv_out = if vv < v0 {
510 vv - v0
511 } else if vv > v1 {
512 vv - v1
513 } else {
514 0.0
515 };
516 if du_out == 0.0 && dv_out == 0.0 {
517 return self.derivatives(uu, vv, derivative_count);
518 }
519 let anchor_u = uu - du_out;
520 let anchor_v = vv - dv_out;
521 let base = self.derivatives(anchor_u, anchor_v, derivative_count.max(2))?;
522 let order = derivative_count + 1;
523 let mut result = vec![vec![Vec3::default(); order]; order];
524 result[0][0] = base[0][0]
528 .add(base[1][0].scale(du_out))
529 .add(base[0][1].scale(dv_out))
530 .add(base[1][1].scale(du_out * dv_out));
531 if derivative_count >= 1 {
532 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
533 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
534 result[1][1] = base[1][1];
535 }
536 if derivative_count >= 2 {
537 let second = self.derivatives(anchor_u, anchor_v, 2)?;
538 if du_out == 0.0 {
539 result[2][0] = second[2][0];
540 }
541 if dv_out == 0.0 {
542 result[0][2] = second[0][2];
543 }
544 }
545 Ok(result)
546 }
547
548 pub fn derivatives(
549 &self,
550 u: f64,
551 v: f64,
552 derivative_count: usize,
553 ) -> Result<Vec<Vec<Vec3>>, String> {
554 crate::projection_profile::count(crate::projection_profile::LANE_DERIVATIVES, self);
555 self.ensure_valid()?;
556 let du = derivative_count.min(self.degree_u);
557 let dv = derivative_count.min(self.degree_v);
558 let stride = derivative_count + 1;
559 let zero = Vec4 {
561 x: 0.0,
562 y: 0.0,
563 z: 0.0,
564 w: 0.0,
565 };
566 let mut homogeneous = vec![zero; stride * stride];
567 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
568 let u = knot_clamp(&self.knots_u, self.degree_u, u);
569 let v = knot_clamp(&self.knots_v, self.degree_v, v);
570 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
571 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
572 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
575 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
576 basis_derivatives_into(
577 &self.knots_u,
578 self.degree_u,
579 span_u,
580 u,
581 du,
582 &mut basis_u[..=du],
583 );
584 basis_derivatives_into(
585 &self.knots_v,
586 self.degree_v,
587 span_v,
588 v,
589 dv,
590 &mut basis_v[..=dv],
591 );
592 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
593 for k in 0..=du {
594 for l in 0..=dv {
595 if k + l > derivative_count {
596 continue;
597 }
598 let mut point = zero;
599 for i in 0..=self.degree_u {
600 let row = &self.control_points[span_u - self.degree_u + i];
601 for j in 0..=self.degree_v {
602 point = point.add(
603 row[span_v - self.degree_v + j]
604 .scale(basis_u[k][i] * basis_v[l][j]),
605 );
606 }
607 }
608 homogeneous[k * stride + l] = point;
609 }
610 }
611 } else {
612 let (knot_u, knot_v) = self.knot_vectors()?;
613 let u = knot_u.clamp_param(u);
614 let v = knot_v.clamp_param(v);
615 let span_u = knot_u.find_span(u);
616 let span_v = knot_v.find_span(v);
617 let basis_u = knot_u.basis_derivatives(span_u, u, du);
618 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
619 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
620 for k in 0..=du {
621 for l in 0..=dv {
622 if k + l > derivative_count {
623 continue;
624 }
625 let mut point = zero;
626 for i in 0..=self.degree_u {
627 let row = &self.control_points[span_u - self.degree_u + i];
628 for j in 0..=self.degree_v {
629 point = point.add(
630 row[span_v - self.degree_v + j]
631 .scale(basis_u[k][i] * basis_v[l][j]),
632 );
633 }
634 }
635 homogeneous[k * stride + l] = point;
636 }
637 }
638 }
639
640 let mut result = vec![vec![Vec3::default(); derivative_count + 1]; derivative_count + 1];
641 let weight = homogeneous[0].w;
642 if weight.abs() <= EPS {
643 return Err("NurbsSurface: zero evaluated weight".into());
644 }
645 for k in 0..=derivative_count {
646 for l in 0..=derivative_count - k {
647 if k > du || l > dv {
648 continue;
649 }
650 let at = |row: usize, column: usize| homogeneous[row * stride + column];
651 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
652 for j in 1..=l {
653 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
654 }
655 for i in 1..=k {
656 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
657 let mut mixed = Vec3::default();
658 for j in 1..=l {
659 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
660 }
661 value = value.sub(mixed.scale(binomial(k, i)));
662 }
663 result[k][l] = value.scale(1.0 / weight);
664 }
665 }
666 Ok(result)
667 }
668
669 pub(crate) fn derivatives_small(
684 &self,
685 u: f64,
686 v: f64,
687 derivative_count: usize,
688 ) -> Result<[[Vec3; 3]; 3], String> {
689 if derivative_count == 0 && switch!("BREP_SURFACE_POINT_ONLY", false)
695 && u.is_finite() && v.is_finite()
696 && self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
697 && !analytic_point_on() {
698 let point = self.evaluate_homogeneous(u, v)?;
699 if point.w.abs() <= EPS {
700 return Err("NurbsSurface: zero evaluated weight".into());
701 }
702 let mut result = [[Vec3::default(); 3]; 3];
703 result[0][0] = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
704 return Ok(result);
705 }
706 debug_assert!(derivative_count <= 2);
707 self.ensure_valid()?;
708 let du = derivative_count.min(self.degree_u);
709 let dv = derivative_count.min(self.degree_v);
710 let stride = derivative_count + 1;
711 let zero = Vec4 {
712 x: 0.0,
713 y: 0.0,
714 z: 0.0,
715 w: 0.0,
716 };
717 let mut homogeneous = [zero; 9];
718 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
719 let u = knot_clamp(&self.knots_u, self.degree_u, u);
720 let v = knot_clamp(&self.knots_v, self.degree_v, v);
721 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
722 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
723 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; 3];
727 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; 3];
728 basis_derivatives_into(
729 &self.knots_u,
730 self.degree_u,
731 span_u,
732 u,
733 du,
734 &mut basis_u[..=du],
735 );
736 basis_derivatives_into(
737 &self.knots_v,
738 self.degree_v,
739 span_v,
740 v,
741 dv,
742 &mut basis_v[..=dv],
743 );
744 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
745 for k in 0..=du {
746 for l in 0..=dv {
747 if k + l > derivative_count {
748 continue;
749 }
750 let mut point = zero;
751 for i in 0..=self.degree_u {
752 let row = &self.control_points[span_u - self.degree_u + i];
753 for j in 0..=self.degree_v {
754 point = point.add(
755 row[span_v - self.degree_v + j]
756 .scale(basis_u[k][i] * basis_v[l][j]),
757 );
758 }
759 }
760 homogeneous[k * stride + l] = point;
761 }
762 }
763 } else {
764 let (knot_u, knot_v) = self.knot_vectors()?;
765 let u = knot_u.clamp_param(u);
766 let v = knot_v.clamp_param(v);
767 let span_u = knot_u.find_span(u);
768 let span_v = knot_v.find_span(v);
769 let basis_u = knot_u.basis_derivatives(span_u, u, du);
770 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
771 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
772 for k in 0..=du {
773 for l in 0..=dv {
774 if k + l > derivative_count {
775 continue;
776 }
777 let mut point = zero;
778 for i in 0..=self.degree_u {
779 let row = &self.control_points[span_u - self.degree_u + i];
780 for j in 0..=self.degree_v {
781 point = point.add(
782 row[span_v - self.degree_v + j]
783 .scale(basis_u[k][i] * basis_v[l][j]),
784 );
785 }
786 }
787 homogeneous[k * stride + l] = point;
788 }
789 }
790 }
791
792 let mut result = [[Vec3::default(); 3]; 3];
793 let weight = homogeneous[0].w;
794 if weight.abs() <= EPS {
795 return Err("NurbsSurface: zero evaluated weight".into());
796 }
797 for k in 0..=derivative_count {
798 for l in 0..=derivative_count - k {
799 if k > du || l > dv {
800 continue;
801 }
802 let at = |row: usize, column: usize| homogeneous[row * stride + column];
803 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
804 for j in 1..=l {
805 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
806 }
807 for i in 1..=k {
808 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
809 let mut mixed = Vec3::default();
810 for j in 1..=l {
811 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
812 }
813 value = value.sub(mixed.scale(binomial(k, i)));
814 }
815 result[k][l] = value.scale(1.0 / weight);
816 }
817 }
818 Ok(result)
819 }
820
821 #[inline]
825 pub(crate) fn deriv1_tensor_supported(&self) -> bool {
826 self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
827 }
828
829 pub(crate) fn deriv1_tensor_each<F>(
851 &self,
852 us: &[f64],
853 vs: &[f64],
854 mut f: F,
855 ) -> Result<(), String>
856 where
857 F: FnMut(usize, usize, Vec3, Vec3, Vec3) -> Result<(), String>,
858 {
859 self.ensure_valid()?;
860 const MAX_BLOCK: usize = TENSOR_MAX_BLOCK;
862 if !self.deriv1_tensor_supported() || us.len() > MAX_BLOCK || vs.len() > MAX_BLOCK {
863 return Err("NurbsSurface: tensor block outside the stack evaluator".into());
864 }
865 let du = 1usize.min(self.degree_u);
866 let dv = 1usize.min(self.degree_v);
867 let mut span_u = [0usize; MAX_BLOCK];
868 let mut span_v = [0usize; MAX_BLOCK];
869 let mut basis_u = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
870 let mut basis_v = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
871 for (index, &value) in us.iter().enumerate() {
872 let value = knot_clamp(&self.knots_u, self.degree_u, value);
873 span_u[index] = knot_find_span(&self.knots_u, self.degree_u, value);
874 basis_derivatives_into(
875 &self.knots_u,
876 self.degree_u,
877 span_u[index],
878 value,
879 du,
880 &mut basis_u[index][..=du],
881 );
882 }
883 for (index, &value) in vs.iter().enumerate() {
884 let value = knot_clamp(&self.knots_v, self.degree_v, value);
885 span_v[index] = knot_find_span(&self.knots_v, self.degree_v, value);
886 basis_derivatives_into(
887 &self.knots_v,
888 self.degree_v,
889 span_v[index],
890 value,
891 dv,
892 &mut basis_v[index][..=dv],
893 );
894 }
895 let zero = Vec4 {
896 x: 0.0,
897 y: 0.0,
898 z: 0.0,
899 w: 0.0,
900 };
901 for i in 0..us.len() {
902 let rows = &self.control_points[span_u[i] - self.degree_u..];
903 for j in 0..vs.len() {
904 let column = span_v[j] - self.degree_v;
905 let sum = |k: usize, l: usize| {
909 let mut point = zero;
910 for row_index in 0..=self.degree_u {
911 let row = &rows[row_index];
912 for column_index in 0..=self.degree_v {
913 point = point.add(
914 row[column + column_index]
915 .scale(basis_u[i][k][row_index] * basis_v[j][l][column_index]),
916 );
917 }
918 }
919 point
920 };
921 let h00 = sum(0, 0);
922 let h01 = if dv >= 1 { sum(0, 1) } else { zero };
923 let h10 = if du >= 1 { sum(1, 0) } else { zero };
924 let weight = h00.w;
925 if weight.abs() <= EPS {
926 return Err("NurbsSurface: zero evaluated weight".into());
927 }
928 let inverse = 1.0 / weight;
929 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
930 let partial_v = if dv >= 1 {
931 Vec3::new(h01.x, h01.y, h01.z)
932 .sub(point.scale(h01.w))
933 .scale(inverse)
934 } else {
935 Vec3::default()
936 };
937 let partial_u = if du >= 1 {
938 Vec3::new(h10.x, h10.y, h10.z)
939 .sub(point.scale(h10.w))
940 .scale(inverse)
941 } else {
942 Vec3::default()
943 };
944 f(i, j, point, partial_u, partial_v)?;
945 }
946 }
947 Ok(())
948 }
949
950 #[inline]
953 pub(crate) fn deriv1(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
954 match (self.degree_u, self.degree_v) {
961 (1, 1) => self.deriv1_small_fixed::<1, 1>(u, v),
962 (2, 1) => self.deriv1_small_fixed::<2, 1>(u, v),
963 (1, 2) => self.deriv1_small_fixed::<1, 2>(u, v),
964 (2, 2) => self.deriv1_small_fixed::<2, 2>(u, v),
965 _ => {
966 let g = self.derivatives_small(u, v, 1)?;
967 Ok((g[0][0], g[1][0], g[0][1]))
968 }
969 }
970 }
971
972 fn deriv1_small_fixed<const DEGREE_U: usize, const DEGREE_V: usize>(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
985 self.ensure_valid()?;
986 let du = DEGREE_U.min(1);
987 let dv = DEGREE_V.min(1);
988 let u = knot_clamp(&self.knots_u, DEGREE_U, u);
989 let v = knot_clamp(&self.knots_v, DEGREE_V, v);
990 let span_u = knot_find_span(&self.knots_u, DEGREE_U, u);
991 let span_v = knot_find_span(&self.knots_v, DEGREE_V, v);
992 let mut basis_u = [[0.0; MAX_STACK_ORDER]; 2];
993 let mut basis_v = [[0.0; MAX_STACK_ORDER]; 2];
994 basis_derivatives_into(&self.knots_u, DEGREE_U, span_u, u, du, &mut basis_u[..=du]);
995 basis_derivatives_into(&self.knots_v, DEGREE_V, span_v, v, dv, &mut basis_v[..=dv]);
996 let zero = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
997 let sum = |k: usize, l: usize| {
998 let mut value = zero;
999 for i in 0..=DEGREE_U {
1000 let row = &self.control_points[span_u - DEGREE_U + i];
1001 for j in 0..=DEGREE_V {
1002 value = value.add(row[span_v - DEGREE_V + j].scale(basis_u[k][i] * basis_v[l][j]));
1003 }
1004 }
1005 value
1006 };
1007 let h00 = sum(0, 0);
1008 let h01 = if dv == 1 { sum(0, 1) } else { zero };
1009 let h10 = if du == 1 { sum(1, 0) } else { zero };
1010 if h00.w.abs() <= EPS { return Err("NurbsSurface: zero evaluated weight".into()); }
1011 let inverse = 1.0 / h00.w;
1012 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
1013 let partial_v = if dv == 1 {
1014 Vec3::new(h01.x, h01.y, h01.z).sub(point.scale(1.0 * h01.w)).scale(inverse)
1015 } else { Vec3::default() };
1016 let partial_u = if du == 1 {
1017 Vec3::new(h10.x, h10.y, h10.z).sub(point.scale(1.0 * h10.w))
1018 .sub(Vec3::default().scale(1.0)).scale(inverse)
1019 } else { Vec3::default() };
1020 Ok((point, partial_u, partial_v))
1021 }
1022
1023 pub(crate) fn deriv1_extended(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
1031 let [u0, u1] = self.domain_u()?;
1032 let [v0, v1] = self.domain_v()?;
1033 let (closed_u, closed_v) = self.closed_directions()?;
1034 let mut uu = u;
1035 let mut vv = v;
1036 if closed_u && (u < u0 || u > u1) {
1037 uu = u0 + (u - u0).rem_euclid(u1 - u0);
1038 }
1039 if closed_v && (v < v0 || v > v1) {
1040 vv = v0 + (v - v0).rem_euclid(v1 - v0);
1041 }
1042 let du_out = if uu < u0 {
1043 uu - u0
1044 } else if uu > u1 {
1045 uu - u1
1046 } else {
1047 0.0
1048 };
1049 let dv_out = if vv < v0 {
1050 vv - v0
1051 } else if vv > v1 {
1052 vv - v1
1053 } else {
1054 0.0
1055 };
1056 if du_out == 0.0 && dv_out == 0.0 {
1057 if analytic_deriv1_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
1058 && matches!(self.analytic(), Some(crate::AnalyticSurface::Plane { .. }
1059 | crate::AnalyticSurface::RuledRevolution { .. } | crate::AnalyticSurface::Sphere { .. }
1060 | crate::AnalyticSurface::Torus { .. })) {
1061 return match (self.degree_u, self.degree_v) {
1062 (1, 1) => self.deriv1_small_fixed::<1, 1>(uu, vv),
1063 (2, 1) => self.deriv1_small_fixed::<2, 1>(uu, vv),
1064 (1, 2) => self.deriv1_small_fixed::<1, 2>(uu, vv),
1065 (2, 2) => self.deriv1_small_fixed::<2, 2>(uu, vv),
1066 _ => unreachable!(),
1067 };
1068 }
1069 let g = self.derivatives_small(uu, vv, 1)?;
1070 return Ok((g[0][0], g[1][0], g[0][1]));
1071 }
1072 let anchor_u = uu - du_out;
1073 let anchor_v = vv - dv_out;
1074 let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
1075 let s = base[0][0]
1079 .add(base[1][0].scale(du_out))
1080 .add(base[0][1].scale(dv_out))
1081 .add(base[1][1].scale(du_out * dv_out));
1082 let su = base[1][0].add(base[1][1].scale(dv_out));
1083 let sv = base[0][1].add(base[1][1].scale(du_out));
1084 Ok((s, su, sv))
1085 }
1086
1087 pub(crate) fn partial_by_differences(
1112 &self,
1113 along_u: bool,
1114 u: f64,
1115 v: f64,
1116 ) -> Result<Vec3, String> {
1117 self.ensure_valid()?;
1118 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
1119 return Err("NurbsSurface: partial by differences exceeds the stack degree".into());
1120 }
1121 let (pu, pv) = (self.degree_u, self.degree_v);
1122 let u = knot_clamp(&self.knots_u, pu, u);
1123 let v = knot_clamp(&self.knots_v, pv, v);
1124 let span_u = knot_find_span(&self.knots_u, pu, u);
1125 let span_v = knot_find_span(&self.knots_v, pv, v);
1126 let mut full_u = [0.0f64; MAX_STACK_ORDER];
1127 let mut full_v = [0.0f64; MAX_STACK_ORDER];
1128 basis_functions_into(&self.knots_u, pu, span_u, u, &mut full_u);
1129 basis_functions_into(&self.knots_v, pv, span_v, v, &mut full_v);
1130 let zero = Vec4 {
1131 x: 0.0,
1132 y: 0.0,
1133 z: 0.0,
1134 w: 0.0,
1135 };
1136 let control = |i: usize, j: usize| self.control_points[span_u - pu + i][span_v - pv + j];
1137 let mut point = zero;
1138 for i in 0..=pu {
1139 for j in 0..=pv {
1140 point = point.add(control(i, j).scale(full_u[i] * full_v[j]));
1141 }
1142 }
1143 if point.w.abs() <= EPS {
1144 return Err("NurbsSurface: zero evaluated weight".into());
1145 }
1146 let source = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
1147 let (knots, degree, span, parameter) = if along_u {
1148 (&self.knots_u, pu, span_u, u)
1149 } else {
1150 (&self.knots_v, pv, span_v, v)
1151 };
1152 if degree == 0 {
1153 return Ok(Vec3::default());
1154 }
1155 let mut lowered = [0.0f64; MAX_STACK_ORDER];
1156 basis_functions_into(knots, degree - 1, span, parameter, &mut lowered);
1157 let mut hodograph = zero;
1160 for r in 0..degree {
1161 let index = span - degree + r;
1164 let width = knots[index + degree + 1] - knots[index + 1];
1165 if width.abs() <= EPS {
1166 continue;
1167 }
1168 let factor = lowered[r] * degree as f64 / width;
1169 if along_u {
1170 for j in 0..=pv {
1171 let difference = control(r + 1, j).add(control(r, j).scale(-1.0));
1172 hodograph = hodograph.add(difference.scale(factor * full_v[j]));
1173 }
1174 } else {
1175 for i in 0..=pu {
1176 let difference = control(i, r + 1).add(control(i, r).scale(-1.0));
1177 hodograph = hodograph.add(difference.scale(factor * full_u[i]));
1178 }
1179 }
1180 }
1181 Ok(Vec3::new(hodograph.x, hodograph.y, hodograph.z)
1182 .sub(source.scale(hodograph.w))
1183 .scale(1.0 / point.w))
1184 }
1185
1186 pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
1187 if small_derivatives_enabled() {
1188 let derivatives = self.derivatives_small(u, v, 1)?;
1189 return derivatives[1][0].cross(derivatives[0][1]).normalized();
1190 }
1191 let derivatives = self.derivatives(u, v, 1)?;
1192 derivatives[1][0].cross(derivatives[0][1]).normalized()
1193 }
1194
1195 pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String> {
1202 let derivatives: [[Vec3; 3]; 3] = if small_derivatives_enabled() {
1203 self.derivatives_small(u, v, 2)?
1204 } else {
1205 let heap = self.derivatives(u, v, 2)?;
1206 [[heap[0][0], heap[0][1], heap[0][2]], [heap[1][0], heap[1][1], Vec3::default()], [heap[2][0], Vec3::default(), Vec3::default()]]
1207 };
1208 let su = derivatives[1][0];
1209 let sv = derivatives[0][1];
1210 let cross = su.cross(sv);
1211 let cross_length = cross.length();
1212 if cross_length <= 1e-12 {
1213 return Err(format!(
1214 "degenerate parametrization at (u={u:.4}, v={v:.4})"
1215 ));
1216 }
1217 let normal = cross.scale(1.0 / cross_length);
1218 let e1 = su.dot(su);
1219 let f1 = su.dot(sv);
1220 let g1 = sv.dot(sv);
1221 let l2 = derivatives[2][0].dot(normal);
1222 let m2 = derivatives[1][1].dot(normal);
1223 let n2 = derivatives[0][2].dot(normal);
1224 let denominator = e1 * g1 - f1 * f1;
1225 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();
1228 Ok((
1229 mean_double * 0.5 - discriminant,
1230 mean_double * 0.5 + discriminant,
1231 ))
1232 }
1233
1234 pub fn is_affine(&self) -> Result<bool, String> {
1239 if self.degree_u != 1
1240 || self.degree_v != 1
1241 || self.control_points.len() != 2
1242 || self.control_points[0].len() != 2
1243 || self.control_points[1].len() != 2
1244 {
1245 return Ok(false);
1246 }
1247 let weight = self.control_points[0][0].w;
1248 if [
1249 self.control_points[0][1].w,
1250 self.control_points[1][0].w,
1251 self.control_points[1][1].w,
1252 ]
1253 .iter()
1254 .any(|other| (*other - weight).abs() > 1e-12 * other.abs().max(weight.abs()))
1255 {
1256 return Ok(false);
1257 }
1258 let p00 = self.control_points[0][0].point()?;
1259 let p01 = self.control_points[0][1].point()?;
1260 let p10 = self.control_points[1][0].point()?;
1261 let p11 = self.control_points[1][1].point()?;
1262 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
1263 let extent = [
1268 p01.sub(p00),
1269 p10.sub(p00),
1270 p11.sub(p00),
1271 p10.sub(p01),
1272 p11.sub(p01),
1273 p11.sub(p10),
1274 ]
1275 .iter()
1276 .map(|delta| delta.length())
1277 .fold(0.0, f64::max);
1278 Ok(gap <= 1e-9 * (1.0 + extent))
1279 }
1280
1281 pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
1282 self.ensure_valid()?;
1283 let u = knot_clamp(&self.knots_u, self.degree_u, u);
1284 let span = knot_find_span(&self.knots_u, self.degree_u, u);
1285 let basis = if self.degree_u <= MAX_STACK_DEGREE {
1286 let mut stack = [0.0f64; MAX_STACK_ORDER];
1287 basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
1288 stack[..=self.degree_u].to_vec()
1289 } else {
1290 KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
1291 };
1292 let mut points = Vec::with_capacity(self.control_points[0].len());
1293 for column in 0..self.control_points[0].len() {
1294 let mut point = Vec4 {
1295 x: 0.0,
1296 y: 0.0,
1297 z: 0.0,
1298 w: 0.0,
1299 };
1300 for (index, value) in basis.iter().enumerate() {
1301 point = point
1302 .add(self.control_points[span - self.degree_u + index][column].scale(*value));
1303 }
1304 points.push(point);
1305 }
1306 NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
1307 }
1308
1309 pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
1310 self.ensure_valid()?;
1311 let v = knot_clamp(&self.knots_v, self.degree_v, v);
1312 let span = knot_find_span(&self.knots_v, self.degree_v, v);
1313 let basis = if self.degree_v <= MAX_STACK_DEGREE {
1314 let mut stack = [0.0f64; MAX_STACK_ORDER];
1315 basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
1316 stack[..=self.degree_v].to_vec()
1317 } else {
1318 KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
1319 };
1320 let mut points = Vec::with_capacity(self.control_points.len());
1321 for row in &self.control_points {
1322 let mut point = Vec4 {
1323 x: 0.0,
1324 y: 0.0,
1325 z: 0.0,
1326 w: 0.0,
1327 };
1328 for (index, value) in basis.iter().enumerate() {
1329 point = point.add(row[span - self.degree_v + index].scale(*value));
1330 }
1331 points.push(point);
1332 }
1333 NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
1334 }
1335}
1336
1337pub fn make_plane(
1338 origin: Vec3,
1339 u_direction: Vec3,
1340 v_direction: Vec3,
1341 u_max: f64,
1342 v_max: f64,
1343) -> Result<NurbsSurface, String> {
1344 if u_max <= EPS || v_max <= EPS {
1345 return Err("makePlane: parameter extents must be positive".into());
1346 }
1347 let p10 = origin.add(u_direction.scale(u_max));
1348 let p01 = origin.add(v_direction.scale(v_max));
1349 let p11 = p10.add(v_direction.scale(v_max));
1350 NurbsSurface::new(
1351 1,
1352 1,
1353 vec![0.0, 0.0, u_max, u_max],
1354 vec![0.0, 0.0, v_max, v_max],
1355 vec![
1356 vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
1357 vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
1358 ],
1359 )
1360}
1361
1362pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
1363 let rows = profile
1364 .control_points
1365 .iter()
1366 .map(|point| {
1367 vec![
1368 *point,
1369 Vec4 {
1370 x: point.x + point.w * direction.x,
1371 y: point.y + point.w * direction.y,
1372 z: point.z + point.w * direction.z,
1373 w: point.w,
1374 },
1375 ]
1376 })
1377 .collect();
1378 NurbsSurface::new(
1379 profile.degree,
1380 1,
1381 profile.knots.clone(),
1382 vec![0.0, 0.0, 1.0, 1.0],
1383 rows,
1384 )
1385}
1386
1387pub fn make_revolution(
1388 axis_point: Vec3,
1389 axis_direction: Vec3,
1390 generatrix: &NurbsCurve,
1391 theta: f64,
1392) -> Result<NurbsSurface, String> {
1393 if theta <= EPS || theta > std::f64::consts::TAU + EPS {
1394 return Err("makeRevolution: theta must be in (0, 2*PI]".into());
1395 }
1396 let theta = theta.min(std::f64::consts::TAU);
1397 let axis = axis_direction.normalized()?;
1398 let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
1399 let arc_angle = theta / arc_count as f64;
1400 let middle_weight = (arc_angle / 2.0).cos();
1401 let mut knots_u = vec![0.0, 0.0, 0.0];
1402 for index in 1..arc_count {
1403 let knot = index as f64 / arc_count as f64;
1404 knots_u.extend([knot, knot]);
1405 }
1406 knots_u.extend([1.0, 1.0, 1.0]);
1407 let mut rows = vec![
1408 vec![
1409 Vec4 {
1410 x: 0.0,
1411 y: 0.0,
1412 z: 0.0,
1413 w: 1.0,
1414 };
1415 generatrix.control_points.len()
1416 ];
1417 2 * arc_count + 1
1418 ];
1419
1420 let mut radial_extent: f64 = 0.0;
1423 for control_point in &generatrix.control_points {
1424 let point = control_point.point()?;
1425 let offset = point.sub(axis_point);
1426 radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
1427 }
1428
1429 for (column, control_point) in generatrix.control_points.iter().enumerate() {
1430 let point = control_point.point()?;
1431 let weight = control_point.w;
1432 let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
1433 let radial_vector = point.sub(axis_origin);
1434 let measured_radius = radial_vector.length();
1444 let pole_tolerance = EPS.max(1e-8 * radial_extent);
1445 let radius = if measured_radius <= pole_tolerance {
1446 0.0
1447 } else {
1448 measured_radius
1449 };
1450 let (x_axis, y_axis) = if radius <= EPS {
1451 (Vec3::default(), Vec3::default())
1452 } else {
1453 if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
1465 return Err(format!(
1466 "makeRevolution: generatrix control point {column} sits {radius:e} from the \
1467 axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
1468 radial extent (a fuzzy pole)"
1469 ));
1470 }
1471 let x_axis = radial_vector.scale(1.0 / radius);
1472 (x_axis, axis.cross(x_axis))
1473 };
1474 rows[0][column] = Vec4::from_point(point, weight);
1475 let mut start_point = point;
1476 let mut start_tangent = y_axis.scale(radius);
1477 let mut angle = 0.0;
1478 for arc in 1..=arc_count {
1479 angle += arc_angle;
1480 let end_point = if radius <= EPS {
1481 point
1482 } else {
1483 axis_origin
1484 .add(x_axis.scale(radius * angle.cos()))
1485 .add(y_axis.scale(radius * angle.sin()))
1486 };
1487 let end_tangent = if radius <= EPS {
1488 Vec3::default()
1489 } else {
1490 x_axis
1491 .scale(-radius * angle.sin())
1492 .add(y_axis.scale(radius * angle.cos()))
1493 };
1494 let row = 2 * (arc - 1);
1495 if radius <= EPS {
1496 rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
1497 } else {
1498 let cross = start_tangent.cross(end_tangent);
1499 let denominator = cross.length_squared();
1500 let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
1509 if denominator <= EPS * tangent_scale {
1510 return Err("makeRevolution: arc tangents are parallel".into());
1511 }
1512 let distance =
1513 end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
1514 let middle = start_point.add(start_tangent.scale(distance));
1515 rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
1516 }
1517 rows[row + 2][column] = Vec4::from_point(end_point, weight);
1518 start_point = end_point;
1519 start_tangent = end_tangent;
1520 }
1521 }
1522 NurbsSurface::new(
1523 2,
1524 generatrix.degree,
1525 knots_u,
1526 generatrix.knots.clone(),
1527 rows,
1528 )
1529}
1530
1531pub fn make_cylinder_surface(
1532 base: Vec3,
1533 axis_direction: Vec3,
1534 radius: f64,
1535 height: f64,
1536) -> Result<NurbsSurface, String> {
1537 if radius <= EPS || height <= EPS {
1538 return Err("makeCylinder: radius and height must be positive".into());
1539 }
1540 let axis = axis_direction.normalized()?;
1541 let x_axis = axis.perpendicular()?;
1542 let start = base.add(x_axis.scale(radius));
1543 let end = start.add(axis.scale(height));
1544 let generatrix = crate::make_line(start, end)?;
1545 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1546}
1547
1548pub fn make_cone_surface(
1549 base: Vec3,
1550 axis_direction: Vec3,
1551 radius_bottom: f64,
1552 radius_top: f64,
1553 height: f64,
1554) -> Result<NurbsSurface, String> {
1555 if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
1556 return Err("makeCone: invalid radius or height".into());
1557 }
1558 let axis = axis_direction.normalized()?;
1559 let x_axis = axis.perpendicular()?;
1560 let start = base.add(x_axis.scale(radius_bottom));
1561 let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
1562 let generatrix = crate::make_line(start, end)?;
1563 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1564}
1565
1566pub fn make_sphere_surface(
1567 center: Vec3,
1568 radius: f64,
1569 polar_axis: Vec3,
1570) -> Result<NurbsSurface, String> {
1571 make_sphere_surface_framed(center, radius, polar_axis, None)
1572}
1573
1574pub fn make_sphere_surface_framed(
1586 center: Vec3,
1587 radius: f64,
1588 polar_axis: Vec3,
1589 seam_direction: Option<Vec3>,
1590) -> Result<NurbsSurface, String> {
1591 if radius <= EPS {
1592 return Err("makeSphere: radius must be positive".into());
1593 }
1594 let axis = polar_axis.normalized()?;
1595 let x_axis = match seam_direction {
1596 Some(seam) => {
1597 let planar = seam.sub(axis.scale(seam.dot(axis)));
1598 match planar.normalized() {
1599 Ok(unit) if planar.length() > EPS => unit,
1600 _ => axis.perpendicular()?,
1601 }
1602 }
1603 None => axis.perpendicular()?,
1604 };
1605 let meridian = crate::make_arc(
1606 center,
1607 x_axis,
1608 axis,
1609 radius,
1610 -std::f64::consts::FRAC_PI_2,
1611 std::f64::consts::FRAC_PI_2,
1612 )?;
1613 make_revolution(center, axis, &meridian, std::f64::consts::TAU)
1614}
1615
1616pub fn make_torus_surface(
1617 center: Vec3,
1618 axis_direction: Vec3,
1619 major_radius: f64,
1620 minor_radius: f64,
1621) -> Result<NurbsSurface, String> {
1622 if minor_radius <= EPS || major_radius <= minor_radius {
1623 return Err("makeTorus: requires positive minorRadius < majorRadius".into());
1624 }
1625 let axis = axis_direction.normalized()?;
1626 let x_axis = axis.perpendicular()?;
1627 let tube_center = center.add(x_axis.scale(major_radius));
1628 let tube = crate::make_arc(
1629 tube_center,
1630 x_axis,
1631 axis,
1632 minor_radius,
1633 0.0,
1634 std::f64::consts::TAU,
1635 )?;
1636 make_revolution(center, axis, &tube, std::f64::consts::TAU)
1637}
1638
1639fn binomial(n: usize, k: usize) -> f64 {
1640 if k > n {
1641 return 0.0;
1642 }
1643 let k = k.min(n - k);
1644 (1..=k).fold(1.0, |value, index| {
1645 value * (n - k + index) as f64 / index as f64
1646 })
1647}
1648
1649
1650
1651pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
1663 let inflate = if inflate.is_finite() {
1664 inflate.clamp(1.0, 16.0)
1665 } else {
1666 2.0
1667 };
1668 let (closed_u, closed_v) = surface.closed_directions()?;
1669 let [u0, u1] = surface.domain_u()?;
1670 let [v0, v1] = surface.domain_v()?;
1671 let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
1672 if closed || inflate <= 1.0 {
1673 (d0, d1)
1674 } else {
1675 let centre = 0.5 * (d0 + d1);
1676 let half = 0.5 * (d1 - d0) * inflate;
1677 (centre - half, centre + half)
1678 }
1679 };
1680 let (nu0, nu1) = stretch(u0, u1, closed_u);
1681 let (nv0, nv1) = stretch(v0, v1, closed_v);
1682 if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
1683 return Ok(surface.clone());
1684 }
1685 if surface.is_affine()? {
1686 let corners = [
1689 surface.evaluate_extended(nu0, nv0)?,
1690 surface.evaluate_extended(nu1, nv0)?,
1691 surface.evaluate_extended(nu0, nv1)?,
1692 surface.evaluate_extended(nu1, nv1)?,
1693 ];
1694 return NurbsSurface::new(
1695 1,
1696 1,
1697 vec![nu0, nu0, nu1, nu1],
1698 vec![nv0, nv0, nv1, nv1],
1699 vec![
1700 vec![
1701 Vec4::from_point(corners[0], 1.0),
1702 Vec4::from_point(corners[2], 1.0),
1703 ],
1704 vec![
1705 Vec4::from_point(corners[1], 1.0),
1706 Vec4::from_point(corners[3], 1.0),
1707 ],
1708 ],
1709 );
1710 }
1711 const SAMPLES: usize = 32;
1712 let u_params: Vec<f64> = (0..=SAMPLES)
1713 .map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
1714 .collect();
1715 let v_params: Vec<f64> = (0..=SAMPLES)
1716 .map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
1717 .collect();
1718 let mut column_curves = Vec::with_capacity(u_params.len());
1721 for &u in &u_params {
1722 let column: Vec<Vec3> = v_params
1723 .iter()
1724 .map(|&v| surface.evaluate_extended(u, v))
1725 .collect::<Result<_, _>>()?;
1726 column_curves.push(crate::interpolate_curve(
1727 &column,
1728 3,
1729 &normalized(&v_params),
1730 )?);
1731 }
1732 let v_knots = column_curves[0].knots.clone();
1733 let control_rows = column_curves[0].control_points.len();
1734 let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
1735 let mut u_knots = Vec::new();
1736 for row_index in 0..control_rows {
1737 let row: Vec<Vec3> = column_curves
1738 .iter()
1739 .map(|curve| curve.control_points[row_index].point())
1740 .collect::<Result<_, _>>()?;
1741 let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
1742 u_knots = fitted.knots.clone();
1743 for (u_index, control) in fitted.control_points.iter().enumerate() {
1744 if grid.len() <= u_index {
1745 grid.push(Vec::new());
1746 }
1747 grid[u_index].push(*control);
1748 }
1749 }
1750 let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
1753 knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
1754 };
1755 NurbsSurface::new(
1756 3,
1757 3,
1758 rescale(&u_knots, nu0, nu1),
1759 rescale(&v_knots, nv0, nv1),
1760 grid,
1761 )
1762}
1763
1764fn normalized(parameters: &[f64]) -> Vec<f64> {
1765 let first = parameters[0];
1766 let last = parameters[parameters.len() - 1];
1767 parameters
1768 .iter()
1769 .map(|p| (p - first) / (last - first))
1770 .collect()
1771}
1772
1773
1774
1775
1776
1777fn analytic_deriv1_on() -> bool {
1778 switch!("BREP_ANALYTIC_DERIV1", true)
1779}
1780
1781
1782fn analytic_point_on() -> bool {
1784 switch!("BREP_ANALYTIC_POINT", false)
1785}
1786
1787pub(crate) fn small_derivatives_enabled() -> bool {
1794 switch!("BREP_SURFACE_SMALL_DERIVATIVES", true)
1795}
1796
1797
1798