1use crate::MathError;
8use crate::aabb::Aabb3;
9use crate::frame::Frame3;
10use crate::nurbs::surface::NurbsSurface;
11use crate::vec::{Point3, Vec3};
12
13#[derive(Debug, Clone)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct CylindricalSurface {
20 origin: Point3,
21 axis: Vec3,
22 radius: f64,
23 x_axis: Vec3,
24 y_axis: Vec3,
25}
26
27impl CylindricalSurface {
28 pub fn new(origin: Point3, axis: Vec3, radius: f64) -> Result<Self, MathError> {
33 if radius <= 0.0 {
34 return Err(MathError::ParameterOutOfRange {
35 value: radius,
36 min: f64::EPSILON,
37 max: f64::MAX,
38 });
39 }
40 let f = Frame3::from_normal(origin, axis)?;
41 Ok(Self {
42 origin,
43 axis: f.z,
44 radius,
45 x_axis: f.x,
46 y_axis: f.y,
47 })
48 }
49
50 #[must_use]
52 pub fn evaluate(&self, u: f64, v: f64) -> Point3 {
53 let (sin_u, cos_u) = u.sin_cos();
54 self.origin
55 + self.x_axis * (self.radius * cos_u)
56 + self.y_axis * (self.radius * sin_u)
57 + self.axis * v
58 }
59
60 #[must_use]
62 pub fn normal(&self, u: f64, _v: f64) -> Vec3 {
63 let (sin_u, cos_u) = u.sin_cos();
64 self.x_axis * cos_u + self.y_axis * sin_u
65 }
66
67 #[must_use]
69 pub const fn origin(&self) -> Point3 {
70 self.origin
71 }
72
73 #[must_use]
75 pub const fn axis(&self) -> Vec3 {
76 self.axis
77 }
78
79 #[must_use]
81 pub const fn radius(&self) -> f64 {
82 self.radius
83 }
84
85 #[must_use]
87 pub const fn x_axis(&self) -> Vec3 {
88 self.x_axis
89 }
90
91 #[must_use]
93 pub const fn y_axis(&self) -> Vec3 {
94 self.y_axis
95 }
96
97 pub fn with_ref_dir(
106 origin: Point3,
107 axis: Vec3,
108 radius: f64,
109 ref_dir: Vec3,
110 ) -> Result<Self, MathError> {
111 if radius <= 0.0 {
112 return Err(MathError::ParameterOutOfRange {
113 value: radius,
114 min: f64::EPSILON,
115 max: f64::MAX,
116 });
117 }
118 let f = Frame3::from_normal_and_ref(origin, axis, ref_dir)?;
119 Ok(Self {
120 origin,
121 axis: f.z,
122 radius,
123 x_axis: f.x,
124 y_axis: f.y,
125 })
126 }
127
128 #[must_use]
130 pub fn translated(&self, offset: Vec3) -> Self {
131 Self {
132 origin: self.origin + offset,
133 ..self.clone()
134 }
135 }
136
137 #[must_use]
141 pub fn project_point(&self, point: Point3) -> (f64, f64) {
142 let to_pt = Vec3::new(
143 point.x() - self.origin.x(),
144 point.y() - self.origin.y(),
145 point.z() - self.origin.z(),
146 );
147 let v = self.axis.dot(to_pt);
148 let radial = to_pt - self.axis * v;
149 let x = self.x_axis.dot(radial);
150 let y = self.y_axis.dot(radial);
151 let u = y.atan2(x).rem_euclid(std::f64::consts::TAU);
152 (u, v)
153 }
154
155 pub fn to_nurbs(&self, v_min: f64, v_max: f64) -> Result<NurbsSurface, MathError> {
164 let w1 = std::f64::consts::FRAC_1_SQRT_2;
166 let circle_weights = [1.0, w1, 1.0, w1, 1.0, w1, 1.0, w1, 1.0];
167 let dirs: [(f64, f64); 9] = [
169 (1.0, 0.0),
170 (1.0, 1.0),
171 (0.0, 1.0),
172 (-1.0, 1.0),
173 (-1.0, 0.0),
174 (-1.0, -1.0),
175 (0.0, -1.0),
176 (1.0, -1.0),
177 (1.0, 0.0),
178 ];
179
180 let mut cps = Vec::with_capacity(9);
181 let mut ws = Vec::with_capacity(9);
182 for (i, &(dx, dy)) in dirs.iter().enumerate() {
183 let radial = self.x_axis * (self.radius * dx) + self.y_axis * (self.radius * dy);
184 let p_bot = self.origin + radial + self.axis * v_min;
185 let p_top = self.origin + radial + self.axis * v_max;
186 cps.push(vec![p_bot, p_top]);
187 ws.push(vec![circle_weights[i], circle_weights[i]]);
188 }
189
190 let knots_u = vec![
191 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0,
192 ];
193 let knots_v = vec![0.0, 0.0, 1.0, 1.0];
194 NurbsSurface::new(2, 1, knots_u, knots_v, cps, ws)
195 }
196}
197
198#[derive(Debug, Clone)]
203#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
204pub struct ConicalSurface {
205 apex: Point3,
206 axis: Vec3,
207 half_angle: f64,
208 x_axis: Vec3,
209 y_axis: Vec3,
210}
211
212impl ConicalSurface {
213 pub fn new(apex: Point3, axis: Vec3, half_angle: f64) -> Result<Self, MathError> {
223 if half_angle <= 0.0 || half_angle >= std::f64::consts::FRAC_PI_2 {
224 return Err(MathError::ParameterOutOfRange {
225 value: half_angle,
226 min: f64::EPSILON,
227 max: std::f64::consts::FRAC_PI_2,
228 });
229 }
230 let f = Frame3::from_normal(apex, axis)?;
231 Ok(Self {
232 apex,
233 axis: f.z,
234 half_angle,
235 x_axis: f.x,
236 y_axis: f.y,
237 })
238 }
239
240 #[must_use]
242 pub fn evaluate(&self, u: f64, v: f64) -> Point3 {
243 let (sin_u, cos_u) = u.sin_cos();
244 let (sin_a, cos_a) = self.half_angle.sin_cos();
245 let radial = self.x_axis * cos_u + self.y_axis * sin_u;
246 self.apex + (radial * cos_a + self.axis * sin_a) * v
247 }
248
249 #[must_use]
251 pub fn normal(&self, u: f64, _v: f64) -> Vec3 {
252 let (sin_u, cos_u) = u.sin_cos();
253 let (sin_a, cos_a) = self.half_angle.sin_cos();
254 let radial = self.x_axis * cos_u + self.y_axis * sin_u;
255 radial * sin_a + self.axis * (-cos_a)
257 }
258
259 #[must_use]
261 pub const fn apex(&self) -> Point3 {
262 self.apex
263 }
264
265 #[must_use]
267 pub const fn axis(&self) -> Vec3 {
268 self.axis
269 }
270
271 #[must_use]
273 pub const fn half_angle(&self) -> f64 {
274 self.half_angle
275 }
276
277 #[must_use]
279 pub const fn x_axis(&self) -> Vec3 {
280 self.x_axis
281 }
282
283 #[must_use]
285 pub const fn y_axis(&self) -> Vec3 {
286 self.y_axis
287 }
288
289 pub fn with_ref_dir(
298 apex: Point3,
299 axis: Vec3,
300 half_angle: f64,
301 ref_dir: Vec3,
302 ) -> Result<Self, MathError> {
303 if half_angle <= 0.0 || half_angle >= std::f64::consts::FRAC_PI_2 {
304 return Err(MathError::ParameterOutOfRange {
305 value: half_angle,
306 min: f64::EPSILON,
307 max: std::f64::consts::FRAC_PI_2,
308 });
309 }
310 let f = Frame3::from_normal_and_ref(apex, axis, ref_dir)?;
311 Ok(Self {
312 apex,
313 axis: f.z,
314 half_angle,
315 x_axis: f.x,
316 y_axis: f.y,
317 })
318 }
319
320 #[must_use]
322 pub fn translated(&self, offset: Vec3) -> Self {
323 Self {
324 apex: self.apex + offset,
325 ..self.clone()
326 }
327 }
328
329 #[must_use]
331 pub fn radius_at(&self, v: f64) -> f64 {
332 v * self.half_angle.cos()
333 }
334
335 #[must_use]
340 pub fn project_point(&self, point: Point3) -> (f64, f64) {
341 let to_pt = Vec3::new(
342 point.x() - self.apex.x(),
343 point.y() - self.apex.y(),
344 point.z() - self.apex.z(),
345 );
346
347 let h = self.axis.dot(to_pt);
348 let radial = to_pt - self.axis * h;
349 let x = self.x_axis.dot(radial);
350 let y = self.y_axis.dot(radial);
351
352 let u = y.atan2(x).rem_euclid(std::f64::consts::TAU);
353
354 let sin_a = self.half_angle.sin();
355 let v = if sin_a.abs() > 1e-15 {
356 h / sin_a
357 } else {
358 let cos_a = self.half_angle.cos();
359 if cos_a.abs() > 1e-15 {
360 radial.length() / cos_a
361 } else {
362 0.0
363 }
364 };
365
366 (u, v)
367 }
368
369 pub fn to_nurbs(&self, v_min: f64, v_max: f64) -> Result<NurbsSurface, MathError> {
375 analytic_to_nurbs_sampled(
376 |u, v| self.evaluate(u, v),
377 (0.0, std::f64::consts::TAU),
378 (v_min, v_max),
379 )
380 }
381}
382
383#[derive(Debug, Clone)]
388#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
389pub struct SphericalSurface {
390 center: Point3,
391 radius: f64,
392 x_axis: Vec3,
393 y_axis: Vec3,
394 z_axis: Vec3,
395}
396
397impl SphericalSurface {
398 pub fn new(center: Point3, radius: f64) -> Result<Self, MathError> {
403 if radius <= 0.0 {
404 return Err(MathError::ParameterOutOfRange {
405 value: radius,
406 min: f64::EPSILON,
407 max: f64::MAX,
408 });
409 }
410 Ok(Self {
411 center,
412 radius,
413 x_axis: Vec3::new(1.0, 0.0, 0.0),
414 y_axis: Vec3::new(0.0, 1.0, 0.0),
415 z_axis: Vec3::new(0.0, 0.0, 1.0),
416 })
417 }
418
419 pub fn with_axis(center: Point3, radius: f64, z_axis: Vec3) -> Result<Self, MathError> {
424 if radius <= 0.0 {
425 return Err(MathError::ParameterOutOfRange {
426 value: radius,
427 min: f64::EPSILON,
428 max: f64::MAX,
429 });
430 }
431 let f = Frame3::from_normal(center, z_axis)?;
432 Ok(Self {
433 center,
434 radius,
435 x_axis: f.x,
436 y_axis: f.y,
437 z_axis: f.z,
438 })
439 }
440
441 #[must_use]
443 pub fn evaluate(&self, u: f64, v: f64) -> Point3 {
444 let (sin_u, cos_u) = u.sin_cos();
445 let (sin_v, cos_v) = v.sin_cos();
446 self.center
447 + self.x_axis * (self.radius * cos_v * cos_u)
448 + self.y_axis * (self.radius * cos_v * sin_u)
449 + self.z_axis * (self.radius * sin_v)
450 }
451
452 #[must_use]
454 pub fn normal(&self, u: f64, v: f64) -> Vec3 {
455 let (sin_u, cos_u) = u.sin_cos();
456 let (sin_v, cos_v) = v.sin_cos();
457 self.x_axis * (cos_v * cos_u) + self.y_axis * (cos_v * sin_u) + self.z_axis * sin_v
458 }
459
460 #[must_use]
462 pub const fn center(&self) -> Point3 {
463 self.center
464 }
465
466 #[must_use]
468 pub const fn radius(&self) -> f64 {
469 self.radius
470 }
471
472 #[must_use]
474 pub const fn x_axis(&self) -> Vec3 {
475 self.x_axis
476 }
477
478 #[must_use]
480 pub const fn y_axis(&self) -> Vec3 {
481 self.y_axis
482 }
483
484 #[must_use]
486 pub const fn z_axis(&self) -> Vec3 {
487 self.z_axis
488 }
489
490 #[must_use]
492 pub fn translated(&self, offset: Vec3) -> Self {
493 Self {
494 center: self.center + offset,
495 ..self.clone()
496 }
497 }
498
499 #[must_use]
505 pub fn aabb(&self) -> Aabb3 {
506 let r = self.radius;
507 Aabb3 {
508 min: Point3::new(
509 self.center.x() - r,
510 self.center.y() - r,
511 self.center.z() - r,
512 ),
513 max: Point3::new(
514 self.center.x() + r,
515 self.center.y() + r,
516 self.center.z() + r,
517 ),
518 }
519 }
520
521 #[must_use]
528 pub fn aabb_region(&self, pole: Vec3) -> Aabb3 {
529 let Ok(n) = pole.normalize() else {
530 return self.aabb();
531 };
532 let c = self.center;
533 let r = self.radius;
534 let span = |en: f64| -> (f64, f64) {
538 let s = (1.0 - en * en).max(0.0).sqrt();
539 let hi = if en >= 0.0 { 1.0 } else { s };
540 let lo = if en <= 0.0 { -1.0 } else { -s };
541 (lo, hi)
542 };
543 let (lx, hx) = span(n.x());
544 let (ly, hy) = span(n.y());
545 let (lz, hz) = span(n.z());
546 Aabb3 {
547 min: Point3::new(c.x() + r * lx, c.y() + r * ly, c.z() + r * lz),
548 max: Point3::new(c.x() + r * hx, c.y() + r * hy, c.z() + r * hz),
549 }
550 }
551
552 #[must_use]
556 pub fn project_point(&self, point: Point3) -> (f64, f64) {
557 let to_pt = Vec3::new(
558 point.x() - self.center.x(),
559 point.y() - self.center.y(),
560 point.z() - self.center.z(),
561 );
562 let r = to_pt.length();
563 if r < 1e-15 {
564 return (0.0, 0.0);
565 }
566 let x = self.x_axis.dot(to_pt);
567 let y = self.y_axis.dot(to_pt);
568 let z = self.z_axis.dot(to_pt);
569 let u = y.atan2(x).rem_euclid(std::f64::consts::TAU);
570 let v = (z / r).clamp(-1.0, 1.0).asin();
571 (u, v)
572 }
573
574 pub fn to_nurbs(&self) -> Result<NurbsSurface, MathError> {
580 analytic_to_nurbs_sampled(
581 |u, v| self.evaluate(u, v),
582 (0.0, std::f64::consts::TAU),
583 (-std::f64::consts::FRAC_PI_2, std::f64::consts::FRAC_PI_2),
584 )
585 }
586}
587
588#[derive(Debug, Clone)]
594#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
595pub struct ToroidalSurface {
596 center: Point3,
597 major_radius: f64,
598 minor_radius: f64,
599 x_axis: Vec3,
600 y_axis: Vec3,
601 z_axis: Vec3,
602}
603
604impl ToroidalSurface {
605 pub fn new(center: Point3, major_radius: f64, minor_radius: f64) -> Result<Self, MathError> {
610 if major_radius <= 0.0 {
611 return Err(MathError::ParameterOutOfRange {
612 value: major_radius,
613 min: f64::EPSILON,
614 max: f64::MAX,
615 });
616 }
617 if minor_radius <= 0.0 {
618 return Err(MathError::ParameterOutOfRange {
619 value: minor_radius,
620 min: f64::EPSILON,
621 max: f64::MAX,
622 });
623 }
624 Ok(Self {
625 center,
626 major_radius,
627 minor_radius,
628 x_axis: Vec3::new(1.0, 0.0, 0.0),
629 y_axis: Vec3::new(0.0, 1.0, 0.0),
630 z_axis: Vec3::new(0.0, 0.0, 1.0),
631 })
632 }
633
634 pub fn with_axis(
642 center: Point3,
643 major_radius: f64,
644 minor_radius: f64,
645 z_axis: Vec3,
646 ) -> Result<Self, MathError> {
647 if major_radius <= 0.0 {
648 return Err(MathError::ParameterOutOfRange {
649 value: major_radius,
650 min: f64::EPSILON,
651 max: f64::MAX,
652 });
653 }
654 if minor_radius <= 0.0 {
655 return Err(MathError::ParameterOutOfRange {
656 value: minor_radius,
657 min: f64::EPSILON,
658 max: f64::MAX,
659 });
660 }
661 let f = Frame3::from_normal(center, z_axis)?;
662 Ok(Self {
663 center,
664 major_radius,
665 minor_radius,
666 x_axis: f.x,
667 y_axis: f.y,
668 z_axis: f.z,
669 })
670 }
671
672 pub fn with_axis_and_ref_dir(
688 center: Point3,
689 major_radius: f64,
690 minor_radius: f64,
691 z_axis: Vec3,
692 ref_dir: Vec3,
693 ) -> Result<Self, MathError> {
694 if major_radius <= 0.0 {
695 return Err(MathError::ParameterOutOfRange {
696 value: major_radius,
697 min: f64::EPSILON,
698 max: f64::MAX,
699 });
700 }
701 if minor_radius <= 0.0 {
702 return Err(MathError::ParameterOutOfRange {
703 value: minor_radius,
704 min: f64::EPSILON,
705 max: f64::MAX,
706 });
707 }
708 let f = Frame3::from_normal_and_ref(center, z_axis, ref_dir)?;
709 Ok(Self {
710 center,
711 major_radius,
712 minor_radius,
713 x_axis: f.x,
714 y_axis: f.y,
715 z_axis: f.z,
716 })
717 }
718
719 #[must_use]
721 pub fn evaluate(&self, u: f64, v: f64) -> Point3 {
722 let (sin_u, cos_u) = u.sin_cos();
723 let (sin_v, cos_v) = v.sin_cos();
724 let tube_radius = self.minor_radius.mul_add(cos_v, self.major_radius);
725 self.center
726 + self.x_axis * (tube_radius * cos_u)
727 + self.y_axis * (tube_radius * sin_u)
728 + self.z_axis * (self.minor_radius * sin_v)
729 }
730
731 #[must_use]
733 pub fn normal(&self, u: f64, v: f64) -> Vec3 {
734 let (sin_u, cos_u) = u.sin_cos();
735 let (sin_v, cos_v) = v.sin_cos();
736 let radial = self.x_axis * cos_u + self.y_axis * sin_u;
737 radial * cos_v + self.z_axis * sin_v
738 }
739
740 #[must_use]
742 pub const fn center(&self) -> Point3 {
743 self.center
744 }
745
746 #[must_use]
748 pub fn translated(&self, offset: Vec3) -> Self {
749 Self {
750 center: self.center + offset,
751 ..self.clone()
752 }
753 }
754
755 #[must_use]
762 pub fn aabb(&self) -> Aabb3 {
763 let rr = self.major_radius + self.minor_radius;
764 let r = self.minor_radius;
765 let hx = rr * self.x_axis.x().hypot(self.y_axis.x()) + r * self.z_axis.x().abs();
766 let hy = rr * self.x_axis.y().hypot(self.y_axis.y()) + r * self.z_axis.y().abs();
767 let hz = rr * self.x_axis.z().hypot(self.y_axis.z()) + r * self.z_axis.z().abs();
768 Aabb3 {
769 min: Point3::new(
770 self.center.x() - hx,
771 self.center.y() - hy,
772 self.center.z() - hz,
773 ),
774 max: Point3::new(
775 self.center.x() + hx,
776 self.center.y() + hy,
777 self.center.z() + hz,
778 ),
779 }
780 }
781
782 #[must_use]
784 pub const fn major_radius(&self) -> f64 {
785 self.major_radius
786 }
787
788 #[must_use]
790 pub const fn minor_radius(&self) -> f64 {
791 self.minor_radius
792 }
793
794 #[must_use]
796 pub const fn x_axis(&self) -> Vec3 {
797 self.x_axis
798 }
799
800 #[must_use]
802 pub const fn y_axis(&self) -> Vec3 {
803 self.y_axis
804 }
805
806 #[must_use]
808 pub const fn z_axis(&self) -> Vec3 {
809 self.z_axis
810 }
811
812 #[must_use]
817 pub fn project_point(&self, point: Point3) -> (f64, f64) {
818 let to_pt = Vec3::new(
819 point.x() - self.center.x(),
820 point.y() - self.center.y(),
821 point.z() - self.center.z(),
822 );
823
824 let x_comp = self.x_axis.dot(to_pt);
825 let y_comp = self.y_axis.dot(to_pt);
826 let u = y_comp.atan2(x_comp).rem_euclid(std::f64::consts::TAU);
827
828 let (sin_u, cos_u) = u.sin_cos();
829 let tube_center = self.center
830 + self.x_axis * (self.major_radius * cos_u)
831 + self.y_axis * (self.major_radius * sin_u);
832
833 let to_tube = Vec3::new(
834 point.x() - tube_center.x(),
835 point.y() - tube_center.y(),
836 point.z() - tube_center.z(),
837 );
838
839 let radial_dir = self.x_axis * cos_u + self.y_axis * sin_u;
840 let r_comp = radial_dir.dot(to_tube);
841 let z_comp = self.z_axis.dot(to_tube);
842
843 let v = z_comp.atan2(r_comp).rem_euclid(std::f64::consts::TAU);
844 (u, v)
845 }
846
847 pub fn to_nurbs(&self) -> Result<NurbsSurface, MathError> {
853 analytic_to_nurbs_sampled(
854 |u, v| self.evaluate(u, v),
855 (0.0, std::f64::consts::TAU),
856 (0.0, std::f64::consts::TAU),
857 )
858 }
859}
860
861#[derive(Debug, Clone)]
867pub struct RevolutionSurface {
868 origin: Point3,
869 axis: Vec3,
870 x_axis: Vec3,
871 y_axis: Vec3,
872 generatrix_radii: Vec<f64>,
874 generatrix_heights: Vec<f64>,
875}
876
877impl RevolutionSurface {
878 pub fn new(
885 origin: Point3,
886 axis: Vec3,
887 radii: Vec<f64>,
888 heights: Vec<f64>,
889 ) -> Result<Self, MathError> {
890 if radii.is_empty() || heights.is_empty() {
891 return Err(MathError::EmptyInput);
892 }
893 if radii.len() != heights.len() {
894 return Err(MathError::InvalidWeights {
895 expected: radii.len(),
896 got: heights.len(),
897 });
898 }
899 let f = Frame3::from_normal(origin, axis)?;
900 Ok(Self {
901 origin,
902 axis: f.z,
903 x_axis: f.x,
904 y_axis: f.y,
905 generatrix_radii: radii,
906 generatrix_heights: heights,
907 })
908 }
909
910 #[must_use]
913 #[allow(
914 clippy::cast_precision_loss,
915 clippy::cast_possible_truncation,
916 clippy::cast_sign_loss
917 )]
918 pub fn evaluate(&self, u: f64, v: f64) -> Point3 {
919 let num_pts = self.generatrix_radii.len();
920 let param = v.clamp(0.0, 1.0) * (num_pts - 1) as f64;
921 let idx = (param as usize).min(num_pts - 2);
922 let frac = param - idx as f64;
923
924 let r = frac.mul_add(
925 self.generatrix_radii[idx + 1] - self.generatrix_radii[idx],
926 self.generatrix_radii[idx],
927 );
928 let height = frac.mul_add(
929 self.generatrix_heights[idx + 1] - self.generatrix_heights[idx],
930 self.generatrix_heights[idx],
931 );
932
933 let (sin_u, cos_u) = u.sin_cos();
934 self.origin + self.x_axis * (r * cos_u) + self.y_axis * (r * sin_u) + self.axis * height
935 }
936
937 #[must_use]
939 pub const fn origin(&self) -> Point3 {
940 self.origin
941 }
942
943 #[must_use]
945 pub const fn axis(&self) -> Vec3 {
946 self.axis
947 }
948}
949
950fn analytic_to_nurbs_sampled(
961 surface_fn: impl Fn(f64, f64) -> Point3,
962 u_range: (f64, f64),
963 v_range: (f64, f64),
964) -> Result<NurbsSurface, MathError> {
965 let nu = 33;
969 let nv = 9;
970
971 let mut cps = Vec::with_capacity(nu);
972 let mut weights = Vec::with_capacity(nu);
973
974 #[allow(clippy::cast_precision_loss)]
975 for iu in 0..nu {
976 let u = u_range.0 + (u_range.1 - u_range.0) * (iu as f64 / (nu - 1) as f64);
977 let mut row = Vec::with_capacity(nv);
978 let mut w_row = Vec::with_capacity(nv);
979 for iv in 0..nv {
980 let v = v_range.0 + (v_range.1 - v_range.0) * (iv as f64 / (nv - 1) as f64);
981 row.push(surface_fn(u, v));
982 w_row.push(1.0);
983 }
984 cps.push(row);
985 weights.push(w_row);
986 }
987
988 let knots_u = uniform_clamped_knots(nu, 1);
991 let knots_v = uniform_clamped_knots(nv, 1);
992
993 NurbsSurface::new(1, 1, knots_u, knots_v, cps, weights)
994}
995
996#[allow(clippy::cast_precision_loss)]
1001fn uniform_clamped_knots(n: usize, degree: usize) -> Vec<f64> {
1002 let mut k = vec![0.0; degree + 1];
1003 for i in 1..n - degree {
1004 k.push(i as f64 / (n - degree) as f64);
1005 }
1006 k.extend(vec![1.0; degree + 1]);
1007 k
1008}
1009
1010#[cfg(test)]
1011#[allow(clippy::unwrap_used, clippy::expect_used)]
1012mod tests;