1use std::collections::VecDeque;
14
15use crate::{
16 aabb::aabb_could_contain,
17 fat_line::{Plan, carried_width, clip_tensor, converged, extent, greville_abscissae, plan},
18 knot_insertion::{is_clamped_at, pinned_clamped_end},
19 nurb_curve::NurbCurve,
20 nurb_surface::NurbSurface,
21};
22use geop_core_math::{
23 geop_error::{GeopError, GeopResult},
24 scalars::Scalar,
25 vector::Vector3,
26};
27
28use super::curve::curve_could_contain;
29
30pub(crate) fn surface_extents<S: Scalar>(patch: &NurbSurface<S, 4>) -> [f64; 2] {
34 let (nu, nv) = (patch.num_u, patch.num_v);
35 let cp = &patch.control_points;
36 [
37 extent((0..nv).map(|j| (0..nu).map(move |i| cp[i * nv + j]))),
38 extent(cp.chunks(nv).map(|row| row.iter().copied())),
39 ]
40}
41
42pub(crate) fn weights_positive<S: Scalar>(patch: &NurbSurface<S, 4>) -> bool {
46 patch
47 .control_points
48 .iter()
49 .all(|q| q[3].definitely_greater(S::ZERO))
50}
51
52fn clip<S: Scalar>(patch: &NurbSurface<S, 4>, point: &Vector3<S>) -> GeopResult<Option<(S, S)>> {
58 let (u0, u1) = patch.domain_u();
59 let (v0, v1) = patch.domain_v();
60 let mut hats = [u0.union(u1), v0.union(v1)];
61 if !weights_positive(patch) {
62 return Ok(Some((hats[0], hats[1])));
63 }
64 let (nu, nv) = (patch.num_u, patch.num_v);
65 let greville = [
66 greville_abscissae(&patch.knot_vector_u, patch.degree_u, nu)?,
67 greville_abscissae(&patch.knot_vector_v, patch.degree_v, nv)?,
68 ];
69 let mut d = Vec::with_capacity(nu * nv);
70 for k in 0..3 {
71 d.clear();
74 d.extend(
75 patch
76 .control_points
77 .iter()
78 .map(|q| q[k].sub(point[k].mul(q[3]))),
79 );
80 if !clip_tensor(&d, &[nu, nv], &greville, &mut hats) {
81 return Ok(None);
82 }
83 }
84 Ok(Some((hats[0], hats[1])))
85}
86
87pub(crate) fn boundary_curve<S: Scalar>(
92 patch: &NurbSurface<S, 4>,
93 u_fixed: bool,
94 first: bool,
95) -> Option<NurbCurve<S, 4>> {
96 let (nu, nv) = (patch.num_u, patch.num_v);
97 if u_fixed {
98 if !is_clamped_at(&patch.knot_vector_u, patch.degree_u, first) {
99 return None;
100 }
101 let i = if first { 0 } else { nu - 1 };
102 let row = patch.control_points[i * nv..(i + 1) * nv].to_vec();
103 NurbCurve::try_new(patch.degree_v, row, patch.knot_vector_v.clone()).ok()
104 } else {
105 if !is_clamped_at(&patch.knot_vector_v, patch.degree_v, first) {
106 return None;
107 }
108 let j = if first { 0 } else { nv - 1 };
109 let column = (0..nu).map(|i| patch.control_points[i * nv + j]).collect();
110 NurbCurve::try_new(patch.degree_u, column, patch.knot_vector_u.clone()).ok()
111 }
112}
113
114pub(crate) fn collapsed_boundary<S: Scalar>(
119 patch: &NurbSurface<S, 4>,
120 u_hat: S,
121 v_hat: S,
122) -> Option<(NurbCurve<S, 4>, bool)> {
123 let (nu, nv) = (patch.num_u, patch.num_v);
124 if let Some(first) = pinned_clamped_end(u_hat, &patch.knot_vector_u, nu, patch.degree_u) {
125 return boundary_curve(patch, true, first).map(|c| (c, true));
126 }
127 if let Some(first) = pinned_clamped_end(v_hat, &patch.knot_vector_v, nv, patch.degree_v) {
128 return boundary_curve(patch, false, first).map(|c| (c, false));
129 }
130 None
131}
132
133pub fn surface_could_contain<S: Scalar>(
156 surface: &NurbSurface<S, 4>,
157 point: &Vector3<S>,
158 max_nodes: usize,
159 min_subdivision_size: S,
160) -> GeopResult<Option<(S, S)>> {
161 let mut queue: VecDeque<NurbSurface<S, 4>> = VecDeque::new();
162 queue.push_back(surface.clone());
163
164 let mut explored = 0usize;
165 let mut solution: Option<(S, S)> = None;
166 let mut report = |(u, v): (S, S)| {
167 solution = Some(match solution {
168 Some((su, sv)) => (su.union(u), sv.union(v)),
169 None => (u, v),
170 });
171 };
172
173 while let Some(patch) = queue.pop_front() {
174 if explored >= max_nodes {
175 return Err(GeopError::new(format!(
176 "surface_could_contain (clipping): exhausted max_nodes={max_nodes} with {} \
177 patches pending; the result would be incomplete",
178 queue.len() + 1
179 )));
180 }
181 explored += 1;
182
183 if !aabb_could_contain(&patch.aabb, point) {
184 continue;
185 }
186 let Some((u_hat, v_hat)) = clip(&patch, point)? else {
187 continue;
188 };
189
190 let sizes = surface_extents(&patch);
191 let point_width = (0..3)
193 .map(|k| point[k].width().to_f64())
194 .fold(0.0, f64::max);
195 let carried = carried_width(&patch.control_points).max(point_width);
196 if converged(&sizes, carried, min_subdivision_size) {
197 match patch.convex_hull() {
198 Ok(hull) if !hull.could_contain(point) => {}
199 _ => report((u_hat, v_hat)),
200 }
201 continue;
202 }
203
204 if let Some((boundary, along_v)) = collapsed_boundary(&patch, u_hat, v_hat) {
205 let budget = max_nodes - explored;
206 if let Some(t) = curve_could_contain(&boundary, point, budget, min_subdivision_size)? {
207 let (fixed, free) = if along_v {
208 (u_hat, v_hat)
209 } else {
210 (v_hat, u_hat)
211 };
212 if t.could_be_equal(free) {
213 let free = free.intersect(t);
214 report(if along_v {
215 (fixed, free)
216 } else {
217 (free, fixed)
218 });
219 }
220 }
221 continue;
222 }
223
224 let ranges = [patch.domain_u(), patch.domain_v()];
225 let order = match plan(&[u_hat, v_hat], &ranges, &sizes, min_subdivision_size)? {
226 Plan::Restrict(b) => match patch.sub_surface(b[0], b[1]) {
227 Ok(restricted) => {
228 queue.push_back(restricted);
229 continue;
230 }
231 Err(_) => vec![0, 1],
232 },
233 Plan::Bisect(order) => order,
234 };
235 let halves = order.iter().find_map(|&dir| {
236 if dir == 0 {
237 patch.split_u_mid()
238 } else {
239 patch.split_v_mid()
240 }
241 .ok()
242 });
243 if let Some((left, right)) = halves {
244 queue.push_back(left);
245 queue.push_back(right);
246 continue;
247 }
248 report((u_hat, v_hat));
250 }
251
252 Ok(solution)
253}
254
255#[cfg(test)]
256mod tests {
257 use super::surface_could_contain;
258 use crate::nurb_surface::NurbSurface;
259 use geop_core_math::for_all_scalars;
260 use geop_core_math::{
261 scalars::Scalar,
262 vector::{Vector3, Vector4},
263 };
264
265 const MAX: usize = 2000;
266 const EPS: f64 = 1e-4;
267
268 fn pt<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
270 Vector4::from_array([
271 S::from_f64(x * w),
272 S::from_f64(y * w),
273 S::from_f64(z * w),
274 S::from_f64(w),
275 ])
276 }
277
278 fn v3<S: Scalar>(x: f64, y: f64, z: f64) -> Vector3<S> {
279 Vector3::from_array([S::from_f64(x), S::from_f64(y), S::from_f64(z)])
280 }
281
282 fn knots<S: Scalar>(k: &[f64]) -> Vec<S> {
283 k.iter().map(|&x| S::from_f64(x)).collect()
284 }
285
286 fn lifted<S: Scalar>() -> NurbSurface<S, 4> {
288 NurbSurface::try_new(
289 1,
290 1,
291 vec![
292 pt(0., 0., 0., 1.),
293 pt(0., 1., 0., 1.),
294 pt(1., 0., 0., 1.),
295 pt(1., 1., 1., 1.),
296 ],
297 knots(&[0., 0., 1., 1.]),
298 knots(&[0., 0., 1., 1.]),
299 )
300 .unwrap()
301 }
302
303 fn quarter_cylinder<S: Scalar>() -> NurbSurface<S, 4> {
305 let w = std::f64::consts::FRAC_1_SQRT_2;
306 let circle = [(1., 0., 1.), (1., 1., w), (0., 1., 1.)];
307 let cps = circle
308 .iter()
309 .flat_map(|&(x, y, wi)| [pt(x, y, 0., wi), pt(x, y, 1., wi)])
310 .collect();
311 NurbSurface::try_new(
312 2,
313 1,
314 cps,
315 knots(&[0., 0., 0., 1., 1., 1.]),
316 knots(&[0., 0., 1., 1.]),
317 )
318 .unwrap()
319 }
320
321 fn sphere_octant<S: Scalar>() -> NurbSurface<S, 4> {
323 let w = std::f64::consts::FRAC_1_SQRT_2;
324 let circle = [(1., 0., 1.), (1., 1., w), (0., 1., 1.)];
325 let meridian = [(1., 0., 1.), (1., 1., w), (0., 1., 1.)];
326 let cps = circle
327 .iter()
328 .flat_map(|&(x, y, wu)| {
329 meridian
330 .iter()
331 .map(move |&(r, z, wv)| pt(x * r, y * r, z, wu * wv))
332 })
333 .collect();
334 let k = knots(&[0., 0., 0., 1., 1., 1.]);
335 NurbSurface::try_new(2, 2, cps, k.clone(), k).unwrap()
336 }
337
338 fn wavy<S: Scalar>() -> NurbSurface<S, 4> {
340 let cps = (0..5)
341 .flat_map(|i| {
342 (0..5).map(move |j| {
343 let z = ((i * 3 + j * 5) % 7) as f64 / 7.0 - 0.5;
344 pt(i as f64, j as f64, z, 1.0)
345 })
346 })
347 .collect();
348 let k = knots(&[0., 0., 0., 0., 0.4, 1., 1., 1., 1.]);
349 NurbSurface::try_new(3, 3, cps, k.clone(), k).unwrap()
350 }
351
352 fn assert_contains_at<S: Scalar>(s: &NurbSurface<S, 4>, u: f64, v: f64) {
354 let (u, v) = (S::from_f64(u), S::from_f64(v));
355 let p = s.evaluate(u, v).unwrap();
356 let (ru, rv) = surface_could_contain(s, &p, MAX, S::from_f64(EPS))
357 .unwrap()
358 .unwrap_or_else(|| panic!("point at ({u:?}, {v:?}) not found"));
359 assert!(
360 ru.could_be_equal(u) && rv.could_be_equal(v),
361 "({ru:?}, {rv:?}) misses ({u:?}, {v:?})"
362 );
363 }
364
365 fn check_contains_grid<S: Scalar>() {
366 for s in [lifted::<S>(), quarter_cylinder(), sphere_octant(), wavy()] {
367 for u in [0., 0.3, 0.4, 0.75, 1.] {
368 for v in [0., 0.2, 0.4, 0.5, 1.] {
369 assert_contains_at(&s, u, v);
370 }
371 }
372 }
373 }
374 #[test]
375 fn contains_grid() {
376 for_all_scalars!(check_contains_grid);
377 }
378
379 fn check_pole_keeps_every_u<S: Scalar>() {
381 let (u, v) = surface_could_contain(
382 &sphere_octant::<S>(),
383 &v3(0., 0., 1.),
384 MAX,
385 S::from_f64(EPS),
386 )
387 .unwrap()
388 .unwrap();
389 assert!(
390 u.could_be_equal(S::ZERO) && u.could_be_equal(S::ONE),
391 "{u:?}"
392 );
393 assert!(v.could_be_equal(S::ONE), "{v:?}");
394 }
395 #[test]
396 fn pole_keeps_every_u() {
397 for_all_scalars!(check_pole_keeps_every_u);
398 }
399
400 fn check_misses_off_surface_points<S: Scalar>() {
401 let miss = |s: &NurbSurface<S, 4>, p: Vector3<S>| {
402 surface_could_contain(s, &p, MAX, S::from_f64(EPS))
403 .unwrap()
404 .is_none()
405 };
406 assert!(miss(&lifted(), v3(0.5, 0.5, 5.)));
407 assert!(miss(&lifted(), v3(0.5, 0.5, 0.3)));
408 assert!(miss(&quarter_cylinder(), v3(0.8, 0.5, 0.5)));
410 assert!(miss(&sphere_octant(), v3(0.5, 0.5, 0.5)));
411 assert!(miss(&wavy(), v3(2., 2., 3.)));
412 }
413 #[test]
414 fn misses_off_surface_points() {
415 for_all_scalars!(check_misses_off_surface_points);
416 }
417
418 fn check_zero_budget_is_an_error<S: Scalar>() {
421 let s = lifted::<S>();
422 assert!(surface_could_contain(&s, &v3(0.5, 0.5, 5.), 0, S::from_f64(EPS)).is_err());
423 }
424 #[test]
425 fn zero_budget_is_an_error() {
426 for_all_scalars!(check_zero_budget_is_an_error);
427 }
428
429 fn check_result_is_tight<S: Scalar>() {
431 let s = wavy::<S>();
432 let p = s.evaluate(S::from_f64(0.3), S::from_f64(0.7)).unwrap();
433 let (u, v) = surface_could_contain(&s, &p, MAX, S::from_f64(EPS))
434 .unwrap()
435 .unwrap();
436 assert!(u.width().definitely_less(S::from_f64(EPS)), "{u:?}");
437 assert!(v.width().definitely_less(S::from_f64(EPS)), "{v:?}");
438 }
439 #[test]
440 fn result_is_tight() {
441 for_all_scalars!(check_result_is_tight);
442 }
443}