geop_core_geometry/nurb_curve/
convex_hull.rs1use geop_core_math::{
2 convex_hull::ConvexHull,
3 geop_error::{GeopError, GeopResult},
4 scalars::Scalar,
5 vector::Vector,
6};
7
8use super::NurbCurve;
9
10pub(crate) fn dehomogenize<S: Scalar, const D: usize, const C: usize>(
20 control_points: &[Vector<S, D>],
21) -> GeopResult<Vec<Vector<S, C>>> {
22 let mut points = Vec::with_capacity(control_points.len());
23 for p in control_points {
24 let w = p[D - 1];
25 if w.could_be_equal(S::ZERO) {
26 return Err(GeopError::new(
27 "convex_hull: control point has zero or near-zero weight",
28 ));
29 }
30 let inv_w = S::ONE.div(w)?;
31 let mut pt = Vector::<S, C>::zero();
32 for c in 0..C {
33 pt[c] = p[c].mul(inv_w);
34 }
35 points.push(pt);
36 }
37 Ok(points)
38}
39
40fn hull_size<S: Scalar, const N: usize>(hull: &ConvexHull<S, N>) -> S {
41 hull.points[hull.points.len() - 1]
42 .sub(&hull.points[0])
43 .norm()
44}
45
46pub trait HasConvexHull<S: Scalar, const C: usize> {
53 fn convex_hull(&self) -> GeopResult<ConvexHull<S, C>>;
54 fn size(&self) -> GeopResult<S>;
55}
56
57impl<S: Scalar> NurbCurve<S, 4> {
58 pub fn convex_hull(&self) -> GeopResult<ConvexHull<S, 3>> {
63 Ok(ConvexHull::new(dehomogenize(&self.control_points)?))
64 }
65
66 pub fn size(&self) -> GeopResult<S> {
69 Ok(hull_size(&self.convex_hull()?))
70 }
71}
72
73impl<S: Scalar> HasConvexHull<S, 3> for NurbCurve<S, 4> {
74 fn convex_hull(&self) -> GeopResult<ConvexHull<S, 3>> {
75 self.convex_hull()
76 }
77 fn size(&self) -> GeopResult<S> {
78 self.size()
79 }
80}
81
82impl<S: Scalar> NurbCurve<S, 3> {
83 pub fn convex_hull(&self) -> GeopResult<ConvexHull<S, 2>> {
88 Ok(ConvexHull::new(dehomogenize(&self.control_points)?))
89 }
90
91 pub fn size(&self) -> GeopResult<S> {
94 Ok(hull_size(&self.convex_hull()?))
95 }
96}
97
98impl<S: Scalar> HasConvexHull<S, 2> for NurbCurve<S, 3> {
99 fn convex_hull(&self) -> GeopResult<ConvexHull<S, 2>> {
100 self.convex_hull()
101 }
102 fn size(&self) -> GeopResult<S> {
103 self.size()
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use crate::nurb_curve::NurbCurve;
110 use geop_core_math::for_all_scalars;
111 use geop_core_math::{scalars::Scalar, vector::Vector4};
112
113 fn pt<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
114 Vector4::from_array([
115 S::from_f64(x),
116 S::from_f64(y),
117 S::from_f64(z),
118 S::from_f64(w),
119 ])
120 }
121
122 fn check_hull_contains_curve_points<S: Scalar>() {
123 let f = S::from_f64;
124 let c = NurbCurve::try_new(
125 2,
126 vec![pt(0., 0., 0., 1.), pt(0.5, 1., 0., 1.), pt(1., 0., 0., 1.)],
127 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
128 )
129 .unwrap();
130 let hull = c.convex_hull().unwrap();
131 let p = c.evaluate(S::from_f64(0.5)).unwrap();
132 assert!(hull.could_contain(&p));
133 }
134 #[test]
135 fn hull_contains_curve_points() {
136 for_all_scalars!(check_hull_contains_curve_points);
137 }
138
139 fn check_hull_points_match_dehomogenized_control_points<S: Scalar>() {
140 let f = S::from_f64;
141 let c = NurbCurve::try_new(
142 2,
143 vec![pt(0., 0., 0., 1.), pt(0.5, 2., 0., 2.), pt(1., 0., 0., 1.)],
144 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
145 )
146 .unwrap();
147 let hull = c.convex_hull().unwrap();
148 assert!(hull.points[0][0].could_be_equal(S::ZERO));
149 assert!(hull.points[1][0].could_be_equal(S::from_f64(0.25)));
151 assert!(hull.points[1][1].could_be_equal(S::ONE));
152 assert!(hull.points[2][0].could_be_equal(S::ONE));
153 }
154 #[test]
155 fn hull_points_match_dehomogenized_control_points() {
156 for_all_scalars!(check_hull_points_match_dehomogenized_control_points);
157 }
158
159 fn check_hull_excludes_far_point<S: Scalar>() {
160 let f = S::from_f64;
161 let c = NurbCurve::try_new(
162 1,
163 vec![pt(0., 0., 0., 1.), pt(1., 0., 0., 1.)],
164 vec![f(0.), f(0.), f(1.), f(1.)],
165 )
166 .unwrap();
167 let hull = c.convex_hull().unwrap();
168 assert!(
169 hull.definitely_not_contains(&geop_core_math::vector::Vector3::from_array([
170 f(0.5),
171 f(5.),
172 f(0.),
173 ]))
174 );
175 }
176 #[test]
177 fn hull_excludes_far_point() {
178 for_all_scalars!(check_hull_excludes_far_point);
179 }
180
181 fn check_zero_weight_returns_err<S: Scalar>() {
182 let f = S::from_f64;
183 let result = NurbCurve::<S, 4>::try_new(
187 1,
188 vec![pt(0., 0., 0., 0.), pt(1., 0., 0., 1.)],
189 vec![f(0.), f(0.), f(1.), f(1.)],
190 );
191 assert!(result.is_err());
192 }
193 #[test]
194 fn zero_weight_returns_err() {
195 for_all_scalars!(check_zero_weight_returns_err);
196 }
197
198 fn check_2d_hull_contains_pcurve_points<S: Scalar>() {
199 let f = S::from_f64;
200 let c: crate::nurb_curve::NurbCurve2D<S> = NurbCurve::try_new(
201 1,
202 vec![
203 geop_core_math::vector::Vector3::from_array([f(0.), f(0.), f(1.)]),
204 geop_core_math::vector::Vector3::from_array([f(1.), f(1.), f(1.)]),
205 ],
206 vec![f(0.), f(0.), f(1.), f(1.)],
207 )
208 .unwrap();
209 let hull = c.convex_hull().unwrap();
210 assert!(
211 hull.could_contain(&geop_core_math::vector::Vector2::from_array([
212 f(0.5),
213 f(0.5)
214 ]))
215 );
216 assert!(
217 hull.definitely_not_contains(&geop_core_math::vector::Vector2::from_array([
218 f(5.),
219 f(5.)
220 ]))
221 );
222 }
223 #[test]
224 fn hull_2d_contains_pcurve_points() {
225 for_all_scalars!(check_2d_hull_contains_pcurve_points);
226 }
227}