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 pub fn with_axis_and_ref_dir(
451 center: Point3,
452 radius: f64,
453 z_axis: Vec3,
454 ref_dir: Vec3,
455 ) -> Result<Self, MathError> {
456 if radius <= 0.0 {
457 return Err(MathError::ParameterOutOfRange {
458 value: radius,
459 min: f64::EPSILON,
460 max: f64::MAX,
461 });
462 }
463 let f = Frame3::from_normal_and_ref(center, z_axis, ref_dir)?;
464 Ok(Self {
465 center,
466 radius,
467 x_axis: f.x,
468 y_axis: f.y,
469 z_axis: f.z,
470 })
471 }
472
473 #[must_use]
475 pub fn evaluate(&self, u: f64, v: f64) -> Point3 {
476 let (sin_u, cos_u) = u.sin_cos();
477 let (sin_v, cos_v) = v.sin_cos();
478 self.center
479 + self.x_axis * (self.radius * cos_v * cos_u)
480 + self.y_axis * (self.radius * cos_v * sin_u)
481 + self.z_axis * (self.radius * sin_v)
482 }
483
484 #[must_use]
486 pub fn normal(&self, u: f64, v: f64) -> Vec3 {
487 let (sin_u, cos_u) = u.sin_cos();
488 let (sin_v, cos_v) = v.sin_cos();
489 self.x_axis * (cos_v * cos_u) + self.y_axis * (cos_v * sin_u) + self.z_axis * sin_v
490 }
491
492 #[must_use]
494 pub const fn center(&self) -> Point3 {
495 self.center
496 }
497
498 #[must_use]
500 pub const fn radius(&self) -> f64 {
501 self.radius
502 }
503
504 #[must_use]
506 pub const fn x_axis(&self) -> Vec3 {
507 self.x_axis
508 }
509
510 #[must_use]
512 pub const fn y_axis(&self) -> Vec3 {
513 self.y_axis
514 }
515
516 #[must_use]
518 pub const fn z_axis(&self) -> Vec3 {
519 self.z_axis
520 }
521
522 #[must_use]
524 pub fn translated(&self, offset: Vec3) -> Self {
525 Self {
526 center: self.center + offset,
527 ..self.clone()
528 }
529 }
530
531 #[must_use]
537 pub fn aabb(&self) -> Aabb3 {
538 let r = self.radius;
539 Aabb3 {
540 min: Point3::new(
541 self.center.x() - r,
542 self.center.y() - r,
543 self.center.z() - r,
544 ),
545 max: Point3::new(
546 self.center.x() + r,
547 self.center.y() + r,
548 self.center.z() + r,
549 ),
550 }
551 }
552
553 #[must_use]
560 pub fn aabb_region(&self, pole: Vec3) -> Aabb3 {
561 let Ok(n) = pole.normalize() else {
562 return self.aabb();
563 };
564 let c = self.center;
565 let r = self.radius;
566 let span = |en: f64| -> (f64, f64) {
570 let s = (1.0 - en * en).max(0.0).sqrt();
571 let hi = if en >= 0.0 { 1.0 } else { s };
572 let lo = if en <= 0.0 { -1.0 } else { -s };
573 (lo, hi)
574 };
575 let (lx, hx) = span(n.x());
576 let (ly, hy) = span(n.y());
577 let (lz, hz) = span(n.z());
578 Aabb3 {
579 min: Point3::new(c.x() + r * lx, c.y() + r * ly, c.z() + r * lz),
580 max: Point3::new(c.x() + r * hx, c.y() + r * hy, c.z() + r * hz),
581 }
582 }
583
584 #[must_use]
588 pub fn project_point(&self, point: Point3) -> (f64, f64) {
589 let to_pt = Vec3::new(
590 point.x() - self.center.x(),
591 point.y() - self.center.y(),
592 point.z() - self.center.z(),
593 );
594 let r = to_pt.length();
595 if r < 1e-15 {
596 return (0.0, 0.0);
597 }
598 let x = self.x_axis.dot(to_pt);
599 let y = self.y_axis.dot(to_pt);
600 let z = self.z_axis.dot(to_pt);
601 let u = y.atan2(x).rem_euclid(std::f64::consts::TAU);
602 let v = (z / r).clamp(-1.0, 1.0).asin();
603 (u, v)
604 }
605
606 pub fn to_nurbs(&self) -> Result<NurbsSurface, MathError> {
612 analytic_to_nurbs_sampled(
613 |u, v| self.evaluate(u, v),
614 (0.0, std::f64::consts::TAU),
615 (-std::f64::consts::FRAC_PI_2, std::f64::consts::FRAC_PI_2),
616 )
617 }
618}
619
620#[derive(Debug, Clone)]
626#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
627pub struct ToroidalSurface {
628 center: Point3,
629 major_radius: f64,
630 minor_radius: f64,
631 x_axis: Vec3,
632 y_axis: Vec3,
633 z_axis: Vec3,
634}
635
636impl ToroidalSurface {
637 pub fn new(center: Point3, major_radius: f64, minor_radius: f64) -> Result<Self, MathError> {
642 if major_radius <= 0.0 {
643 return Err(MathError::ParameterOutOfRange {
644 value: major_radius,
645 min: f64::EPSILON,
646 max: f64::MAX,
647 });
648 }
649 if minor_radius <= 0.0 {
650 return Err(MathError::ParameterOutOfRange {
651 value: minor_radius,
652 min: f64::EPSILON,
653 max: f64::MAX,
654 });
655 }
656 Ok(Self {
657 center,
658 major_radius,
659 minor_radius,
660 x_axis: Vec3::new(1.0, 0.0, 0.0),
661 y_axis: Vec3::new(0.0, 1.0, 0.0),
662 z_axis: Vec3::new(0.0, 0.0, 1.0),
663 })
664 }
665
666 pub fn with_axis(
674 center: Point3,
675 major_radius: f64,
676 minor_radius: f64,
677 z_axis: Vec3,
678 ) -> Result<Self, MathError> {
679 if major_radius <= 0.0 {
680 return Err(MathError::ParameterOutOfRange {
681 value: major_radius,
682 min: f64::EPSILON,
683 max: f64::MAX,
684 });
685 }
686 if minor_radius <= 0.0 {
687 return Err(MathError::ParameterOutOfRange {
688 value: minor_radius,
689 min: f64::EPSILON,
690 max: f64::MAX,
691 });
692 }
693 let f = Frame3::from_normal(center, z_axis)?;
694 Ok(Self {
695 center,
696 major_radius,
697 minor_radius,
698 x_axis: f.x,
699 y_axis: f.y,
700 z_axis: f.z,
701 })
702 }
703
704 pub fn with_axis_and_ref_dir(
720 center: Point3,
721 major_radius: f64,
722 minor_radius: f64,
723 z_axis: Vec3,
724 ref_dir: Vec3,
725 ) -> Result<Self, MathError> {
726 if major_radius <= 0.0 {
727 return Err(MathError::ParameterOutOfRange {
728 value: major_radius,
729 min: f64::EPSILON,
730 max: f64::MAX,
731 });
732 }
733 if minor_radius <= 0.0 {
734 return Err(MathError::ParameterOutOfRange {
735 value: minor_radius,
736 min: f64::EPSILON,
737 max: f64::MAX,
738 });
739 }
740 let f = Frame3::from_normal_and_ref(center, z_axis, ref_dir)?;
741 Ok(Self {
742 center,
743 major_radius,
744 minor_radius,
745 x_axis: f.x,
746 y_axis: f.y,
747 z_axis: f.z,
748 })
749 }
750
751 #[must_use]
753 pub fn evaluate(&self, u: f64, v: f64) -> Point3 {
754 let (sin_u, cos_u) = u.sin_cos();
755 let (sin_v, cos_v) = v.sin_cos();
756 let tube_radius = self.minor_radius.mul_add(cos_v, self.major_radius);
757 self.center
758 + self.x_axis * (tube_radius * cos_u)
759 + self.y_axis * (tube_radius * sin_u)
760 + self.z_axis * (self.minor_radius * sin_v)
761 }
762
763 #[must_use]
765 pub fn normal(&self, u: f64, v: f64) -> Vec3 {
766 let (sin_u, cos_u) = u.sin_cos();
767 let (sin_v, cos_v) = v.sin_cos();
768 let radial = self.x_axis * cos_u + self.y_axis * sin_u;
769 radial * cos_v + self.z_axis * sin_v
770 }
771
772 #[must_use]
774 pub const fn center(&self) -> Point3 {
775 self.center
776 }
777
778 #[must_use]
780 pub fn translated(&self, offset: Vec3) -> Self {
781 Self {
782 center: self.center + offset,
783 ..self.clone()
784 }
785 }
786
787 #[must_use]
794 pub fn aabb(&self) -> Aabb3 {
795 let rr = self.major_radius + self.minor_radius;
796 let r = self.minor_radius;
797 let hx = rr * self.x_axis.x().hypot(self.y_axis.x()) + r * self.z_axis.x().abs();
798 let hy = rr * self.x_axis.y().hypot(self.y_axis.y()) + r * self.z_axis.y().abs();
799 let hz = rr * self.x_axis.z().hypot(self.y_axis.z()) + r * self.z_axis.z().abs();
800 Aabb3 {
801 min: Point3::new(
802 self.center.x() - hx,
803 self.center.y() - hy,
804 self.center.z() - hz,
805 ),
806 max: Point3::new(
807 self.center.x() + hx,
808 self.center.y() + hy,
809 self.center.z() + hz,
810 ),
811 }
812 }
813
814 #[must_use]
816 pub const fn major_radius(&self) -> f64 {
817 self.major_radius
818 }
819
820 #[must_use]
822 pub const fn minor_radius(&self) -> f64 {
823 self.minor_radius
824 }
825
826 #[must_use]
828 pub const fn x_axis(&self) -> Vec3 {
829 self.x_axis
830 }
831
832 #[must_use]
834 pub const fn y_axis(&self) -> Vec3 {
835 self.y_axis
836 }
837
838 #[must_use]
840 pub const fn z_axis(&self) -> Vec3 {
841 self.z_axis
842 }
843
844 #[must_use]
849 pub fn project_point(&self, point: Point3) -> (f64, f64) {
850 let to_pt = Vec3::new(
851 point.x() - self.center.x(),
852 point.y() - self.center.y(),
853 point.z() - self.center.z(),
854 );
855
856 let x_comp = self.x_axis.dot(to_pt);
857 let y_comp = self.y_axis.dot(to_pt);
858 let u = y_comp.atan2(x_comp).rem_euclid(std::f64::consts::TAU);
859
860 let (sin_u, cos_u) = u.sin_cos();
861 let tube_center = self.center
862 + self.x_axis * (self.major_radius * cos_u)
863 + self.y_axis * (self.major_radius * sin_u);
864
865 let to_tube = Vec3::new(
866 point.x() - tube_center.x(),
867 point.y() - tube_center.y(),
868 point.z() - tube_center.z(),
869 );
870
871 let radial_dir = self.x_axis * cos_u + self.y_axis * sin_u;
872 let r_comp = radial_dir.dot(to_tube);
873 let z_comp = self.z_axis.dot(to_tube);
874
875 let v = z_comp.atan2(r_comp).rem_euclid(std::f64::consts::TAU);
876 (u, v)
877 }
878
879 pub fn to_nurbs(&self) -> Result<NurbsSurface, MathError> {
885 analytic_to_nurbs_sampled(
886 |u, v| self.evaluate(u, v),
887 (0.0, std::f64::consts::TAU),
888 (0.0, std::f64::consts::TAU),
889 )
890 }
891}
892
893#[derive(Debug, Clone)]
899pub struct RevolutionSurface {
900 origin: Point3,
901 axis: Vec3,
902 x_axis: Vec3,
903 y_axis: Vec3,
904 generatrix_radii: Vec<f64>,
906 generatrix_heights: Vec<f64>,
907}
908
909impl RevolutionSurface {
910 pub fn new(
917 origin: Point3,
918 axis: Vec3,
919 radii: Vec<f64>,
920 heights: Vec<f64>,
921 ) -> Result<Self, MathError> {
922 if radii.is_empty() || heights.is_empty() {
923 return Err(MathError::EmptyInput);
924 }
925 if radii.len() != heights.len() {
926 return Err(MathError::InvalidWeights {
927 expected: radii.len(),
928 got: heights.len(),
929 });
930 }
931 let f = Frame3::from_normal(origin, axis)?;
932 Ok(Self {
933 origin,
934 axis: f.z,
935 x_axis: f.x,
936 y_axis: f.y,
937 generatrix_radii: radii,
938 generatrix_heights: heights,
939 })
940 }
941
942 #[must_use]
945 #[allow(
946 clippy::cast_precision_loss,
947 clippy::cast_possible_truncation,
948 clippy::cast_sign_loss
949 )]
950 pub fn evaluate(&self, u: f64, v: f64) -> Point3 {
951 let num_pts = self.generatrix_radii.len();
952 let param = v.clamp(0.0, 1.0) * (num_pts - 1) as f64;
953 let idx = (param as usize).min(num_pts - 2);
954 let frac = param - idx as f64;
955
956 let r = frac.mul_add(
957 self.generatrix_radii[idx + 1] - self.generatrix_radii[idx],
958 self.generatrix_radii[idx],
959 );
960 let height = frac.mul_add(
961 self.generatrix_heights[idx + 1] - self.generatrix_heights[idx],
962 self.generatrix_heights[idx],
963 );
964
965 let (sin_u, cos_u) = u.sin_cos();
966 self.origin + self.x_axis * (r * cos_u) + self.y_axis * (r * sin_u) + self.axis * height
967 }
968
969 #[must_use]
971 pub const fn origin(&self) -> Point3 {
972 self.origin
973 }
974
975 #[must_use]
977 pub const fn axis(&self) -> Vec3 {
978 self.axis
979 }
980}
981
982fn analytic_to_nurbs_sampled(
993 surface_fn: impl Fn(f64, f64) -> Point3,
994 u_range: (f64, f64),
995 v_range: (f64, f64),
996) -> Result<NurbsSurface, MathError> {
997 let nu = 33;
1001 let nv = 9;
1002
1003 let mut cps = Vec::with_capacity(nu);
1004 let mut weights = Vec::with_capacity(nu);
1005
1006 #[allow(clippy::cast_precision_loss)]
1007 for iu in 0..nu {
1008 let u = u_range.0 + (u_range.1 - u_range.0) * (iu as f64 / (nu - 1) as f64);
1009 let mut row = Vec::with_capacity(nv);
1010 let mut w_row = Vec::with_capacity(nv);
1011 for iv in 0..nv {
1012 let v = v_range.0 + (v_range.1 - v_range.0) * (iv as f64 / (nv - 1) as f64);
1013 row.push(surface_fn(u, v));
1014 w_row.push(1.0);
1015 }
1016 cps.push(row);
1017 weights.push(w_row);
1018 }
1019
1020 let knots_u = uniform_clamped_knots(nu, 1);
1023 let knots_v = uniform_clamped_knots(nv, 1);
1024
1025 NurbsSurface::new(1, 1, knots_u, knots_v, cps, weights)
1026}
1027
1028#[allow(clippy::cast_precision_loss)]
1033fn uniform_clamped_knots(n: usize, degree: usize) -> Vec<f64> {
1034 let mut k = vec![0.0; degree + 1];
1035 for i in 1..n - degree {
1036 k.push(i as f64 / (n - degree) as f64);
1037 }
1038 k.extend(vec![1.0; degree + 1]);
1039 k
1040}
1041
1042#[cfg(test)]
1043#[allow(clippy::unwrap_used, clippy::expect_used)]
1044mod tests;