geop_core_geometry/nurb_curve/
within.rs1use geop_core_math::{
9 geop_error::{GeopError, GeopResult},
10 polygon::Convex,
11 scalars::Scalar,
12 solvers::subdivide::{Order, Searched, Split, subdivide},
13 vector::Vector2,
14};
15
16use super::{NurbCurve, dehomogenize};
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct Within {
24 pub wholly: bool,
26 pub touches: bool,
28}
29
30impl NurbCurve<3> {
31 pub fn within(
37 &self,
38 region: &Convex,
39 min_size: Scalar,
40 max_nodes: usize,
41 ) -> GeopResult<Within> {
42 let (mut inside, mut outside) = (false, false);
43 let pieces = self.bezier_pieces()?;
44 let searched = subdivide(pieces, Order::DepthFirst, max_nodes, |piece, _| {
45 if inside && outside {
46 return Ok(Split::Done);
47 }
48 let points: Vec<Vector2> = dehomogenize(&piece.control_points);
49 let clear = (region.sides.iter()).any(|(n, c)| {
51 (points.iter()).all(|p| n.prod_dot(p).sub(*c).definitely_greater(Scalar::ZERO))
52 });
53 if clear {
54 outside = true;
55 return Ok(Split::Done);
56 }
57 if points.iter().all(|p| region.could_contain(p)) {
58 inside = true;
59 return Ok(Split::Done);
60 }
61 for end in [points.first(), points.last()].into_iter().flatten() {
63 match region.could_contain(end) {
64 true => inside = true,
65 false => outside = true,
66 }
67 }
68 let [x, y, _] = *piece.aabb();
69 if !x.width().max(y.width()).definitely_greater(min_size) {
70 inside = true;
72 return Ok(Split::Done);
73 }
74 let (left, right) = piece.split_mid()?;
75 Ok::<_, GeopError>(Split::Halves(left, right))
76 })?;
77 if let Searched::Exhausted { pending } = searched {
78 return Err(GeopError::new(format!(
79 "NurbCurve::within: exhausted max_nodes={max_nodes} with {pending} pieces left, \
80 min_size={min_size:?}, region={region:?}"
81 )));
82 }
83 Ok(Within {
84 wholly: !outside,
85 touches: inside,
86 })
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::nurb_curve::NurbCurve2D;
94
95 fn v(x: f64, y: f64) -> Vector2 {
96 Vector2::from_array([Scalar::from_f64(x), Scalar::from_f64(y)])
97 }
98
99 fn square(x0: f64, y0: f64, x1: f64, y1: f64) -> Convex {
100 Convex::new(&[v(x0, y0), v(x1, y0), v(x1, y1), v(x0, y1)]).unwrap()
101 }
102
103 fn within(curve: &NurbCurve2D, region: &Convex) -> Within {
104 curve
105 .within(region, Scalar::from_f64(1e-9), 10_000)
106 .unwrap()
107 }
108
109 fn quarter() -> NurbCurve2D {
111 let w = Scalar::ONE.div(Scalar::TWO).unwrap().sqrt().unwrap();
112 NurbCurve2D::arc(v(1.0, 0.0), v(1.0, 1.0), v(0.0, 1.0), w).unwrap()
113 }
114
115 #[test]
116 fn a_line_inside_across_and_clear() {
117 let line = NurbCurve2D::line(v(1.0, 1.0), v(2.0, 2.0)).unwrap();
118 let both = Within {
119 wholly: true,
120 touches: true,
121 };
122 assert_eq!(within(&line, &square(0.0, 0.0, 3.0, 3.0)), both);
123 let across = within(&line, &square(1.5, 0.0, 3.0, 3.0));
124 assert!(!across.wholly && across.touches);
125 let through = NurbCurve2D::line(v(-1.0, 0.5), v(4.0, 0.5)).unwrap();
127 let crossed = within(&through, &square(0.0, 0.0, 3.0, 3.0));
128 assert!(!crossed.wholly && crossed.touches);
129 let clear = within(&line, &square(5.0, 5.0, 6.0, 6.0));
130 assert!(!clear.wholly && !clear.touches);
131 }
132
133 #[test]
137 fn an_arc_is_judged_by_itself_not_its_control_points() {
138 let arc = quarter();
139 let corner = within(&arc, &square(0.8, 0.8, 1.2, 1.2));
140 assert!(!corner.touches, "{corner:?}");
141 let holds = within(&arc, &square(-0.01, -0.01, 1.01, 1.01));
142 assert!(holds.wholly && holds.touches, "{holds:?}");
143 let bulge = within(&arc, &square(0.6, 0.6, 0.72, 0.72));
146 assert!(bulge.touches && !bulge.wholly, "{bulge:?}");
147 let near = within(&arc, &square(0.72, 0.72, 0.9, 0.9));
148 assert!(!near.touches, "{near:?}");
149 }
150}