parry3d_f64/transformation/convex_hull3/
initial_mesh.rs

1use super::{ConvexHullError, TriangleFacet};
2use crate::math::Real;
3use crate::shape::Triangle;
4use crate::transformation;
5use crate::transformation::convex_hull_utils::support_point_id;
6use crate::utils;
7use alloc::{vec, vec::Vec};
8use core::cmp::Ordering;
9use na::{Point2, Point3, Vector3};
10
11#[derive(Debug)]
12pub enum InitialMesh {
13    Facets(Vec<TriangleFacet>),
14    ResultMesh(Vec<Point3<Real>>, Vec<[u32; 3]>),
15}
16
17fn build_degenerate_mesh_point(point: Point3<Real>) -> (Vec<Point3<Real>>, Vec<[u32; 3]>) {
18    let ta = [0u32; 3];
19    let tb = [0u32; 3];
20
21    (vec![point], vec![ta, tb])
22}
23
24fn build_degenerate_mesh_segment(
25    dir: &Vector3<Real>,
26    points: &[Point3<Real>],
27) -> (Vec<Point3<Real>>, Vec<[u32; 3]>) {
28    let a = utils::point_cloud_support_point(dir, points);
29    let b = utils::point_cloud_support_point(&-*dir, points);
30
31    let ta = [0u32, 1, 0];
32    let tb = [1u32, 0, 0];
33
34    (vec![a, b], vec![ta, tb])
35}
36
37pub fn try_get_initial_mesh(
38    original_points: &[Point3<Real>],
39    normalized_points: &mut [Point3<Real>],
40    undecidable: &mut Vec<usize>,
41) -> Result<InitialMesh, ConvexHullError> {
42    /*
43     * Compute the eigenvectors to see if the input data live on a subspace.
44     */
45    let eigvec;
46    let eigval;
47
48    #[cfg(not(feature = "improved_fixed_point_support"))]
49    {
50        let cov_mat = utils::cov(normalized_points);
51        let eig = cov_mat.symmetric_eigen();
52        eigvec = eig.eigenvectors;
53        eigval = eig.eigenvalues;
54    }
55
56    #[cfg(feature = "improved_fixed_point_support")]
57    {
58        eigvec = Matrix3::identity();
59        eigval = Vector3::repeat(1.0);
60    }
61
62    let mut eigpairs = [
63        (eigvec.column(0).into_owned(), eigval[0]),
64        (eigvec.column(1).into_owned(), eigval[1]),
65        (eigvec.column(2).into_owned(), eigval[2]),
66    ];
67
68    /*
69     * Sort in decreasing order wrt. eigenvalues.
70     */
71    eigpairs.sort_by(|a, b| {
72        if a.1 > b.1 {
73            Ordering::Less // `Less` and `Greater` are reversed.
74        } else if a.1 < b.1 {
75            Ordering::Greater
76        } else {
77            Ordering::Equal
78        }
79    });
80
81    /*
82     * Count the dimension the data lives in.
83     */
84    let mut dimension = 0;
85    while dimension < 3 {
86        if relative_eq!(eigpairs[dimension].1, 0.0, epsilon = 1.0e-7) {
87            break;
88        }
89
90        dimension += 1;
91    }
92
93    match dimension {
94        0 => {
95            // The hull is a point.
96            let (vtx, idx) = build_degenerate_mesh_point(original_points[0]);
97            Ok(InitialMesh::ResultMesh(vtx, idx))
98        }
99        1 => {
100            // The hull is a segment.
101            let (vtx, idx) = build_degenerate_mesh_segment(&eigpairs[0].0, original_points);
102            Ok(InitialMesh::ResultMesh(vtx, idx))
103        }
104        2 => {
105            // The hull is a triangle.
106            // Project into the principal halfspace…
107            let axis1 = &eigpairs[0].0;
108            let axis2 = &eigpairs[1].0;
109
110            let mut subspace_points = Vec::with_capacity(normalized_points.len());
111
112            for point in normalized_points.iter() {
113                subspace_points.push(Point2::new(
114                    point.coords.dot(axis1),
115                    point.coords.dot(axis2),
116                ))
117            }
118
119            // … and compute the 2d convex hull.
120            let idx = transformation::convex_hull2_idx(&subspace_points[..]);
121
122            // Finalize the result, triangulating the polyline.
123            let npoints = idx.len();
124            let coords = idx.into_iter().map(|i| original_points[i]).collect();
125            let mut triangles = Vec::with_capacity(npoints + npoints - 4);
126
127            for id in 1u32..npoints as u32 - 1 {
128                triangles.push([0, id, id + 1]);
129            }
130
131            // NOTE: We use a different starting point for the triangulation
132            // of the bottom faces in order to avoid bad topology where
133            // and edge would end be being shared by more than two triangles.
134            for id in 0u32..npoints as u32 - 2 {
135                let a = npoints as u32 - 1;
136                triangles.push([a, id + 1, id]);
137            }
138
139            Ok(InitialMesh::ResultMesh(coords, triangles))
140        }
141        3 => {
142            // The hull is a polyhedron.
143            // Find a initial triangle lying on the principal halfspace…
144            let center = utils::center(normalized_points);
145
146            for point in normalized_points.iter_mut() {
147                *point = Point3::from((*point - center) / eigval.amax());
148            }
149
150            let p1 = support_point_id(&eigpairs[0].0, normalized_points)
151                .ok_or(ConvexHullError::MissingSupportPoint)?;
152            let p2 = support_point_id(&-eigpairs[0].0, normalized_points)
153                .ok_or(ConvexHullError::MissingSupportPoint)?;
154
155            let mut max_area = 0.0;
156            let mut p3 = usize::MAX;
157
158            for (i, point) in normalized_points.iter().enumerate() {
159                let area =
160                    Triangle::new(normalized_points[p1], normalized_points[p2], *point).area();
161
162                if area > max_area {
163                    max_area = area;
164                    p3 = i;
165                }
166            }
167
168            if p3 == usize::MAX {
169                Err(ConvexHullError::InternalError("no triangle found."))
170            } else {
171                // Build two facets with opposite normals
172                let mut f1 = TriangleFacet::new(p1, p2, p3, normalized_points);
173                let mut f2 = TriangleFacet::new(p2, p1, p3, normalized_points);
174
175                // Link the facets together
176                f1.set_facets_adjascency(1, 1, 1, 0, 2, 1);
177                f2.set_facets_adjascency(0, 0, 0, 0, 2, 1);
178
179                let mut facets = vec![f1, f2];
180
181                // … and attribute visible points to each one of them.
182                // TODO: refactor this with the two others.
183                for point in 0..normalized_points.len() {
184                    if normalized_points[point] == normalized_points[p1]
185                        || normalized_points[point] == normalized_points[p2]
186                        || normalized_points[point] == normalized_points[p3]
187                    {
188                        continue;
189                    }
190
191                    let mut furthest = usize::MAX;
192                    let mut furthest_dist = 0.0;
193
194                    for (i, curr_facet) in facets.iter().enumerate() {
195                        if curr_facet.can_see_point(point, normalized_points) {
196                            let distance = curr_facet.distance_to_point(point, normalized_points);
197
198                            if distance > furthest_dist {
199                                furthest = i;
200                                furthest_dist = distance;
201                            }
202                        }
203                    }
204
205                    if furthest != usize::MAX {
206                        facets[furthest].add_visible_point(point, normalized_points);
207                    } else {
208                        undecidable.push(point);
209                    }
210
211                    // If none of the facet can be seen from the point, it is naturally deleted.
212                }
213
214                super::check_facet_links(0, &facets[..]);
215                super::check_facet_links(1, &facets[..]);
216
217                Ok(InitialMesh::Facets(facets))
218            }
219        }
220        _ => Err(ConvexHullError::Unreachable),
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    #[test]
227    #[cfg(feature = "f32")]
228    // TODO: ideally we would want this test to actually fail (i.e. we want the
229    // convex hull calculation to succeed in this case). Though right now almost-coplanar
230    // points can result in a failure of the algorithm. So we are testing here that the
231    // error is correctly reported (instead of panicking internally).
232    fn try_get_initial_mesh_should_fail_for_missing_support_points() {
233        use super::*;
234        use crate::transformation::try_convex_hull;
235        use na::Point3;
236
237        let point_cloud = vec![
238            Point3::new(103.05024, 303.44974, 106.125),
239            Point3::new(103.21692, 303.44974, 106.125015),
240            Point3::new(104.16538, 303.44974, 106.125),
241            Point3::new(106.55025, 303.44974, 106.125),
242            Point3::new(106.55043, 303.44974, 106.125),
243        ];
244        let result = try_convex_hull(&point_cloud);
245        assert_eq!(ConvexHullError::MissingSupportPoint, result.unwrap_err());
246    }
247}