geop_core_geometry/nurb_surface/
convex_hull.rs1use geop_core_math::{
2 convex_hull::ConvexHull,
3 geop_error::{GeopError, GeopResult},
4 scalars::Scalar,
5 vector::Vector3,
6};
7
8use super::NurbSurface;
9
10impl<S: Scalar> NurbSurface<S, 4> {
11 pub fn convex_hull(&self) -> GeopResult<ConvexHull<S, 3>> {
17 let mut points: Vec<Vector3<S>> = Vec::with_capacity(self.control_points.len());
18 for p in &self.control_points {
19 let w = p[3];
20 if w.could_be_equal(S::ZERO) {
21 return Err(GeopError::new(
22 "convex_hull: control point has zero or near-zero weight",
23 ));
24 }
25 let inv_w = S::ONE.div(w)?;
26 let mut pt = Vector3::zero();
27 for c in 0..3 {
28 pt[c] = p[c].mul(inv_w);
29 }
30 points.push(pt);
31 }
32 Ok(ConvexHull::new(points))
33 }
34
35 fn extents(&self) -> GeopResult<(S, S)> {
52 let hull = self.convex_hull()?;
53 let (nu, nv) = (self.num_u(), self.num_v());
54 let (u0v0, u0vn, unv0, unvn) = (
55 hull.points[0],
56 hull.points[nv - 1],
57 hull.points[(nu - 1) * nv],
58 hull.points[(nu - 1) * nv + (nv - 1)],
59 );
60 let u_size_at_v0 = unv0.sub(&u0v0).norm();
61 let u_size_at_vmax = unvn.sub(&u0vn).norm();
62 let v_size_at_u0 = u0vn.sub(&u0v0).norm();
63 let v_size_at_umax = unvn.sub(&unv0).norm();
64 let u_size = if u_size_at_vmax.definitely_greater(u_size_at_v0) {
65 u_size_at_vmax
66 } else {
67 u_size_at_v0
68 };
69 let v_size = if v_size_at_umax.definitely_greater(v_size_at_u0) {
70 v_size_at_umax
71 } else {
72 v_size_at_u0
73 };
74 Ok((u_size, v_size))
75 }
76
77 pub fn size(&self) -> GeopResult<S> {
81 let (u_size, v_size) = self.extents()?;
82 Ok(if v_size.definitely_greater(u_size) {
83 v_size
84 } else {
85 u_size
86 })
87 }
88
89 pub fn split_mid(&self) -> GeopResult<(NurbSurface<S, 4>, NurbSurface<S, 4>)> {
92 let (u_size, v_size) = self.extents()?;
93 let along_u = u_size.definitely_greater(v_size);
94 if along_u {
95 self.split_u_mid()
96 } else {
97 self.split_v_mid()
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::super::NurbSurface;
105 use geop_core_math::for_all_scalars;
106 use geop_core_math::{
107 scalars::Scalar,
108 vector::{Vector3, Vector4},
109 };
110
111 fn pt<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
112 Vector4::from_array([
113 S::from_f64(x),
114 S::from_f64(y),
115 S::from_f64(z),
116 S::from_f64(w),
117 ])
118 }
119
120 fn v3<S: Scalar>(x: f64, y: f64, z: f64) -> Vector3<S> {
121 Vector3::from_array([S::from_f64(x), S::from_f64(y), S::from_f64(z)])
122 }
123
124 fn flat_patch<S: Scalar>() -> NurbSurface<S, 4> {
125 let f = S::from_f64;
126 NurbSurface::try_new(
127 1,
128 1,
129 vec![
130 pt(0., 0., 0., 1.),
131 pt(0., 1., 0., 1.),
132 pt(1., 0., 0., 1.),
133 pt(1., 1., 0., 1.),
134 ],
135 vec![f(0.), f(0.), f(1.), f(1.)],
136 vec![f(0.), f(0.), f(1.), f(1.)],
137 )
138 .unwrap()
139 }
140
141 fn lifted_patch<S: Scalar>() -> NurbSurface<S, 4> {
142 let f = S::from_f64;
143 NurbSurface::try_new(
144 1,
145 1,
146 vec![
147 pt(0., 0., 0., 1.),
148 pt(0., 1., 0., 1.),
149 pt(1., 0., 0., 1.),
150 pt(1., 1., 1., 1.),
151 ],
152 vec![f(0.), f(0.), f(1.), f(1.)],
153 vec![f(0.), f(0.), f(1.), f(1.)],
154 )
155 .unwrap()
156 }
157
158 fn check_flat_patch_contains_surface_points<S: Scalar>() {
159 let s = flat_patch::<S>();
160 let hull = s.convex_hull().unwrap();
161 let p = s.evaluate(S::from_f64(0.5), S::from_f64(0.5)).unwrap();
162 assert!(hull.could_contain(&p));
163 }
164 #[test]
165 fn flat_patch_contains_surface_points() {
166 for_all_scalars!(check_flat_patch_contains_surface_points);
167 }
168
169 fn check_flat_patch_excludes_elevated_point<S: Scalar>() {
170 let hull = flat_patch::<S>().convex_hull().unwrap();
171 assert!(hull.definitely_not_contains(&v3(0.5, 0.5, 5.)));
172 }
173 #[test]
174 fn flat_patch_excludes_elevated_point() {
175 for_all_scalars!(check_flat_patch_excludes_elevated_point);
176 }
177
178 fn check_flat_patch_excludes_point_outside_bounds<S: Scalar>() {
179 let hull = flat_patch::<S>().convex_hull().unwrap();
180 assert!(hull.definitely_not_contains(&v3(2., 0.5, 0.)));
181 }
182 #[test]
183 fn flat_patch_excludes_point_outside_bounds() {
184 for_all_scalars!(check_flat_patch_excludes_point_outside_bounds);
185 }
186
187 fn check_lifted_patch_contains_midheight_point<S: Scalar>() {
188 let hull = lifted_patch::<S>().convex_hull().unwrap();
189 assert!(hull.could_contain(&v3(0.5, 0.5, 0.25)));
191 }
192 #[test]
193 fn lifted_patch_contains_midheight_point() {
194 for_all_scalars!(check_lifted_patch_contains_midheight_point);
195 }
196
197 fn check_lifted_patch_excludes_far_above<S: Scalar>() {
198 let hull = lifted_patch::<S>().convex_hull().unwrap();
199 assert!(hull.definitely_not_contains(&v3(0.5, 0.5, 5.)));
200 }
201 #[test]
202 fn lifted_patch_excludes_far_above() {
203 for_all_scalars!(check_lifted_patch_excludes_far_above);
204 }
205
206 fn check_zero_weight_returns_err<S: Scalar>() {
207 let f = S::from_f64;
208 let result = NurbSurface::<S, 4>::try_new(
212 1,
213 1,
214 vec![
215 pt(0., 0., 0., 0.),
216 pt(0., 1., 0., 1.),
217 pt(1., 0., 0., 1.),
218 pt(1., 1., 0., 1.),
219 ],
220 vec![f(0.), f(0.), f(1.), f(1.)],
221 vec![f(0.), f(0.), f(1.), f(1.)],
222 );
223 assert!(result.is_err());
224 }
225 #[test]
226 fn zero_weight_returns_err() {
227 for_all_scalars!(check_zero_weight_returns_err);
228 }
229}