1use geop_core_math::{
2 geop_error::{GeopError, GeopResult},
3 scalars::Scalar,
4 vector::Vector,
5};
6
7use super::NurbSurface;
8use crate::{aabb::compute_aabb, knot_insertion, nurb_curve::NurbCurve};
9
10#[derive(Clone, Copy)]
12enum Dir {
13 U,
14 V,
15}
16
17impl<const D: usize> NurbSurface<D> {
18 fn degree_in(&self, dir: Dir) -> usize {
19 match dir {
20 Dir::U => self.degree_u,
21 Dir::V => self.degree_v,
22 }
23 }
24
25 fn knots_in(&self, dir: Dir) -> &Vec<Scalar> {
26 match dir {
27 Dir::U => &self.knot_vector_u,
28 Dir::V => &self.knot_vector_v,
29 }
30 }
31
32 fn rows_along(&self, dir: Dir) -> Vec<Vec<Vector<D>>> {
36 let (nu, nv) = (self.num_u, self.num_v);
37 match dir {
38 Dir::U => (0..nv)
39 .map(|j| (0..nu).map(|i| self.control_points[i * nv + j]).collect())
40 .collect(),
41 Dir::V => self.control_points.chunks(nv).map(<[_]>::to_vec).collect(),
42 }
43 }
44
45 fn with_rows(&self, dir: Dir, knots: Vec<Scalar>, rows: Vec<Vec<Vector<D>>>) -> Self {
48 let along = rows[0].len();
49 let (num_u, num_v, control_points): (usize, usize, Vec<Vector<D>>) = match dir {
50 Dir::U => (
51 along,
52 rows.len(),
53 (0..along)
54 .flat_map(|i| rows.iter().map(move |col| col[i]))
55 .collect(),
56 ),
57 Dir::V => (rows.len(), along, rows.into_iter().flatten().collect()),
58 };
59 let (knot_vector_u, knot_vector_v) = match dir {
60 Dir::U => (knots, self.knot_vector_v.clone()),
61 Dir::V => (self.knot_vector_u.clone(), knots),
62 };
63 NurbSurface {
64 degree_u: self.degree_u,
65 degree_v: self.degree_v,
66 num_u,
67 num_v,
68 aabb: compute_aabb(&control_points),
69 control_points,
70 knot_vector_u,
71 knot_vector_v,
72 }
73 }
74
75 fn split_in(&self, dir: Dir, t: Scalar) -> GeopResult<(Self, Self)> {
76 let mut knots = self.knots_in(dir).clone();
77 let mut rows = self.rows_along(dir);
78 let (right_knots, right_rows) =
79 knot_insertion::split(&mut knots, &mut rows, self.degree_in(dir), t)?;
80 Ok((
81 self.with_rows(dir, knots, rows),
82 self.with_rows(dir, right_knots, right_rows),
83 ))
84 }
85
86 fn restrict_in(&self, dir: Dir, t0: Scalar, t1: Scalar) -> GeopResult<Self> {
87 let mut knots = self.knots_in(dir).clone();
88 let mut rows = self.rows_along(dir);
89 knot_insertion::restrict(&mut knots, &mut rows, self.degree_in(dir), t0, t1)?;
90 Ok(self.with_rows(dir, knots, rows))
91 }
92
93 pub fn split_u(&self, t: Scalar) -> GeopResult<(NurbSurface<D>, NurbSurface<D>)> {
106 self.split_in(Dir::U, t)
107 }
108
109 pub fn split_v(&self, t: Scalar) -> GeopResult<(NurbSurface<D>, NurbSurface<D>)> {
114 self.split_in(Dir::V, t)
115 }
116
117 fn split_in_mid(&self, dir: Dir) -> GeopResult<(Self, Self)> {
118 let knots = self.knots_in(dir);
119 let n = match dir {
120 Dir::U => self.num_u,
121 Dir::V => self.num_v,
122 };
123 let (t0, t1) = (knots[self.degree_in(dir)], knots[n]);
124 let mid = t0.add(t1).div(Scalar::TWO)?.sharpen();
128 self.split_in(dir, mid)
129 }
130
131 pub fn iso_curve(&self, u_fixed: bool, at: Scalar) -> GeopResult<NurbCurve<D>> {
139 let (dir, other) = if u_fixed {
140 (Dir::U, Dir::V)
141 } else {
142 (Dir::V, Dir::U)
143 };
144 let (knots, degree) = (self.knots_in(dir), self.degree_in(dir));
145 let n = self.rows_along(dir)[0].len();
146 let (lo, hi) = (knots[degree], knots[n]);
147 let ends = |first: bool| -> GeopResult<Vec<Vector<D>>> {
148 if !knot_insertion::is_clamped_at(knots, degree, first) {
149 return Err(GeopError::new(format!(
150 "NurbSurface::iso_curve: the domain is not clamped at {at:?}"
151 )));
152 }
153 let i = if first { 0 } else { n - 1 };
154 Ok(self.rows_along(dir).iter().map(|row| row[i]).collect())
155 };
156 let points = if at.is_exactly(lo) {
157 ends(true)?
158 } else if at.is_exactly(hi) {
159 ends(false)?
160 } else {
161 let (before, _) = self.split_in(dir, at)?;
162 let rows = before.rows_along(dir);
163 rows.iter().map(|row| row[row.len() - 1]).collect()
164 };
165 NurbCurve::try_new(self.degree_in(other), points, self.knots_in(other).clone())
166 }
167
168 pub fn split_u_mid(&self) -> GeopResult<(NurbSurface<D>, NurbSurface<D>)> {
170 self.split_in_mid(Dir::U)
171 }
172
173 pub fn split_v_mid(&self) -> GeopResult<(NurbSurface<D>, NurbSurface<D>)> {
175 self.split_in_mid(Dir::V)
176 }
177
178 pub(crate) fn refined(&self, pieces: [usize; 2]) -> GeopResult<Self> {
182 let mut out = self.clone();
183 for (dir, pieces) in [Dir::U, Dir::V].into_iter().zip(pieces) {
184 let degree = out.degree_in(dir);
185 let mut knots = out.knots_in(dir).clone();
186 let mut rows = out.rows_along(dir);
187 let breaks = crate::spline::breakpoints(&knots, degree, rows[0].len());
188 for pair in breaks.windows(2) {
189 for k in 1..pieces {
190 let f = Scalar::from_ratio(k as i64, pieces as i64)?;
191 knot_insertion::insert(
192 &mut knots,
193 &mut rows,
194 degree,
195 Scalar::fraction(pair[0], pair[1], f),
196 );
197 }
198 }
199 out = out.with_rows(dir, knots, rows);
200 }
201 Ok(out)
202 }
203
204 pub(crate) fn with_knots_u(&self, at: &[Scalar]) -> Self {
207 let mut knots = self.knot_vector_u.clone();
208 let mut rows = self.rows_along(Dir::U);
209 for &t in at {
210 knot_insertion::insert(&mut knots, &mut rows, self.degree_u, t);
211 }
212 self.with_rows(Dir::U, knots, rows)
213 }
214
215 pub fn sub_surface(
220 &self,
221 (u0, u1): (Scalar, Scalar),
222 (v0, v1): (Scalar, Scalar),
223 ) -> GeopResult<Self> {
224 self.restrict_in(Dir::U, u0, u1)?
225 .restrict_in(Dir::V, v0, v1)
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use crate::test_fixtures::bilinear;
232 use geop_core_math::scalars::Scalar;
233
234 #[test]
235 fn split_u_junction_matches() {
236 let orig = bilinear();
237 let (left, right) = orig.split_u(Scalar::from_f64(0.5)).unwrap();
238 for v_val in [0.0_f64, 0.5, 1.0] {
239 let v = Scalar::from_f64(v_val);
240 let o = orig.evaluate(Scalar::from_f64(0.5), v).unwrap();
241 let l = left.evaluate(Scalar::from_f64(0.5), v).unwrap();
242 let r = right.evaluate(Scalar::from_f64(0.5), v).unwrap();
243 for c in 0..3 {
244 assert!(
245 o[c].could_be_equal(l[c]),
246 "split_u left mismatch v={v_val} c={c}"
247 );
248 assert!(
249 o[c].could_be_equal(r[c]),
250 "split_u right mismatch v={v_val} c={c}"
251 );
252 }
253 }
254 }
255
256 #[test]
257 fn split_u_left_start_matches() {
258 let orig = bilinear();
259 let (left, _) = orig.split_u(Scalar::from_f64(0.5)).unwrap();
260 for v_val in [0.0_f64, 0.5, 1.0] {
261 let v = Scalar::from_f64(v_val);
262 let o = orig.evaluate(Scalar::ZERO, v).unwrap();
263 let l = left.evaluate(Scalar::ZERO, v).unwrap();
264 for c in 0..3 {
265 assert!(o[c].could_be_equal(l[c]), "c={c} v={v_val}");
266 }
267 }
268 }
269
270 #[test]
271 fn split_u_right_end_matches() {
272 let orig = bilinear();
273 let (_, right) = orig.split_u(Scalar::from_f64(0.5)).unwrap();
274 for v_val in [0.0_f64, 0.5, 1.0] {
275 let v = Scalar::from_f64(v_val);
276 let o = orig.evaluate(Scalar::ONE, v).unwrap();
277 let r = right.evaluate(Scalar::ONE, v).unwrap();
278 for c in 0..3 {
279 assert!(o[c].could_be_equal(r[c]), "c={c} v={v_val}");
280 }
281 }
282 }
283
284 #[test]
285 fn split_u_at_boundary_returns_err() {
286 let s = bilinear();
287 assert!(s.split_u(Scalar::ZERO).is_err());
288 assert!(s.split_u(Scalar::ONE).is_err());
289 }
290
291 #[test]
292 fn split_v_junction_matches() {
293 let orig = bilinear();
294 let (left, right) = orig.split_v(Scalar::from_f64(0.5)).unwrap();
295 for u_val in [0.0_f64, 0.5, 1.0] {
296 let u = Scalar::from_f64(u_val);
297 let o = orig.evaluate(u, Scalar::from_f64(0.5)).unwrap();
298 let l = left.evaluate(u, Scalar::from_f64(0.5)).unwrap();
299 let r = right.evaluate(u, Scalar::from_f64(0.5)).unwrap();
300 for c in 0..3 {
301 assert!(
302 o[c].could_be_equal(l[c]),
303 "split_v left mismatch u={u_val} c={c}"
304 );
305 assert!(
306 o[c].could_be_equal(r[c]),
307 "split_v right mismatch u={u_val} c={c}"
308 );
309 }
310 }
311 }
312
313 #[test]
314 fn split_v_left_start_matches() {
315 let orig = bilinear();
316 let (left, _) = orig.split_v(Scalar::from_f64(0.5)).unwrap();
317 for u_val in [0.0_f64, 0.5, 1.0] {
318 let u = Scalar::from_f64(u_val);
319 let o = orig.evaluate(u, Scalar::ZERO).unwrap();
320 let l = left.evaluate(u, Scalar::ZERO).unwrap();
321 for c in 0..3 {
322 assert!(o[c].could_be_equal(l[c]), "c={c} u={u_val}");
323 }
324 }
325 }
326
327 #[test]
328 fn split_v_right_end_matches() {
329 let orig = bilinear();
330 let (_, right) = orig.split_v(Scalar::from_f64(0.5)).unwrap();
331 for u_val in [0.0_f64, 0.5, 1.0] {
332 let u = Scalar::from_f64(u_val);
333 let o = orig.evaluate(u, Scalar::ONE).unwrap();
334 let r = right.evaluate(u, Scalar::ONE).unwrap();
335 for c in 0..3 {
336 assert!(o[c].could_be_equal(r[c]), "c={c} u={u_val}");
337 }
338 }
339 }
340
341 #[test]
342 fn split_v_at_boundary_returns_err() {
343 let s = bilinear();
344 assert!(s.split_v(Scalar::ZERO).is_err());
345 assert!(s.split_v(Scalar::ONE).is_err());
346 }
347
348 #[test]
349 fn split_u_domains() {
350 let orig = bilinear();
351 let (left, right) = orig.split_u(Scalar::from_f64(0.5)).unwrap();
352 let (lu_min, lu_max) = left.domain_u();
353 let (ru_min, ru_max) = right.domain_u();
354 assert!(lu_min.could_be_equal(Scalar::ZERO));
355 assert!(lu_max.could_be_equal(Scalar::from_f64(0.5)));
356 assert!(ru_min.could_be_equal(Scalar::from_f64(0.5)));
357 assert!(ru_max.could_be_equal(Scalar::ONE));
358 }
359
360 #[test]
361 fn split_v_domains() {
362 let orig = bilinear();
363 let (left, right) = orig.split_v(Scalar::from_f64(0.5)).unwrap();
364 let (lv_min, lv_max) = left.domain_v();
365 let (rv_min, rv_max) = right.domain_v();
366 assert!(lv_min.could_be_equal(Scalar::ZERO));
367 assert!(lv_max.could_be_equal(Scalar::from_f64(0.5)));
368 assert!(rv_min.could_be_equal(Scalar::from_f64(0.5)));
369 assert!(rv_max.could_be_equal(Scalar::ONE));
370 }
371}