geop_core_geometry/nurb_surface/
mod.rs1mod convex_hull;
2mod curvature;
3mod evaluate;
4mod fit_pcurve;
5mod normal;
6mod project;
7mod reverse;
8mod split;
9mod translate;
10
11use std::fmt::Display;
12
13pub use project::clamp;
14
15use crate::aabb::compute_aabb;
16use geop_core_math::{
17 geop_error::{GeopError, GeopResult},
18 scalars::Scalar,
19 vector::Vector,
20};
21
22#[derive(Clone, Debug)]
29pub struct NurbSurface<S: Scalar, const D: usize> {
30 pub degree_u: usize,
31 pub degree_v: usize,
32 pub num_u: usize,
33 pub num_v: usize,
34 pub control_points: Vec<Vector<S, D>>,
35 pub knot_vector_u: Vec<S>,
36 pub knot_vector_v: Vec<S>,
37 pub(crate) aabb: [S; 3],
41}
42
43pub type NurbSurface3D<S> = NurbSurface<S, 4>;
45
46impl<S: Scalar, const D: usize> NurbSurface<S, D> {
47 pub fn try_new(
48 degree_u: usize,
49 degree_v: usize,
50 control_points: Vec<Vector<S, D>>,
51 knot_vector_u: Vec<S>,
52 knot_vector_v: Vec<S>,
53 ) -> GeopResult<Self> {
54 let len_u = knot_vector_u.len();
55 let len_v = knot_vector_v.len();
56
57 if len_u < degree_u + 2 {
58 return Err(GeopError::new(
59 "NurbSurface: knot_vector_u too short for the given degree",
60 ));
61 }
62 if len_v < degree_v + 2 {
63 return Err(GeopError::new(
64 "NurbSurface: knot_vector_v too short for the given degree",
65 ));
66 }
67
68 let num_u = len_u - degree_u - 1;
69 let num_v = len_v - degree_v - 1;
70
71 if control_points.len() != num_u * num_v {
72 return Err(GeopError::new(format!(
73 "NurbSurface: expected {} control points ({}×{}), got {}",
74 num_u * num_v,
75 num_u,
76 num_v,
77 control_points.len()
78 )));
79 }
80
81 for p in &control_points {
87 if p[D - 1].could_be_equal(S::ZERO) {
88 return Err(GeopError::new(&format!(
89 "NurbSurface::try_new: control point {p:?} has a weight that could be zero"
90 )));
91 }
92 }
93
94 let aabb = compute_aabb(&control_points);
95 Ok(Self {
96 degree_u,
97 degree_v,
98 num_u,
99 num_v,
100 control_points,
101 knot_vector_u,
102 knot_vector_v,
103 aabb,
104 })
105 }
106
107 pub fn recompute_aabb(&mut self) {
112 self.aabb = compute_aabb(&self.control_points);
113 }
114
115 pub fn domain_u(&self) -> (S, S) {
117 (
118 self.knot_vector_u[self.degree_u],
119 self.knot_vector_u[self.num_u],
120 )
121 }
122
123 pub fn domain_v(&self) -> (S, S) {
125 (
126 self.knot_vector_v[self.degree_v],
127 self.knot_vector_v[self.num_v],
128 )
129 }
130
131 pub fn num_u(&self) -> usize {
133 self.num_u
134 }
135
136 pub fn num_v(&self) -> usize {
138 self.num_v
139 }
140
141 pub fn is_everything(&self) -> bool {
151 self.num_u == 1
152 && self.num_v == 1
153 && !self.control_points[0][0].is_sharp()
154 && self.knot_vector_u.iter().all(|k| !k.is_sharp())
155 }
156
157 pub fn everything() -> Self {
163 let mut cp = Vector::<S, D>::everything();
164 cp[D - 1] = S::ONE;
165 NurbSurface {
166 degree_u: 0,
167 degree_v: 0,
168 num_u: 1,
169 num_v: 1,
170 control_points: vec![cp],
171 knot_vector_u: vec![S::ENTIRE, S::ENTIRE],
172 knot_vector_v: vec![S::ENTIRE, S::ENTIRE],
173 aabb: [S::ENTIRE; 3],
176 }
177 }
178}
179
180impl<S: Scalar> Display for NurbSurface3D<S> {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 let p00 = self
183 .evaluate(self.knot_vector_u[0], self.knot_vector_v[0])
184 .map(|p| p.to_string())
185 .unwrap_or_else(|_| "N/A".to_string());
186 let p01 = self
187 .evaluate(self.knot_vector_u[0], self.knot_vector_v[self.num_v])
188 .map(|p| p.to_string())
189 .unwrap_or_else(|_| "N/A".to_string());
190 let p10 = self
191 .evaluate(self.knot_vector_u[self.num_u], self.knot_vector_v[0])
192 .map(|p| p.to_string())
193 .unwrap_or_else(|_| "N/A".to_string());
194 let p11 = self
195 .evaluate(
196 self.knot_vector_u[self.num_u],
197 self.knot_vector_v[self.num_v],
198 )
199 .map(|p| p.to_string())
200 .unwrap_or_else(|_| "N/A".to_string());
201
202 write!(
203 f,
204 "NurbSurface((0, 0) -> {}, (1, 0) -> {}, (1, 1) -> {}, (0, 1) -> {})",
205 p00, p10, p11, p01
206 )
207 }
208}