parry3d_f64/transformation/convex_hull3/
initial_mesh.rs

1use super::{ConvexHullError, TriangleFacet};
2use crate::math::{MatExt, Vector2, Vector3};
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;
9
10#[derive(Debug)]
11pub enum InitialMesh {
12    Facets(Vec<TriangleFacet>),
13    ResultMesh(Vec<Vector3>, Vec<[u32; 3]>),
14}
15
16fn build_degenerate_mesh_point(point: Vector3) -> (Vec<Vector3>, Vec<[u32; 3]>) {
17    let ta = [0u32; 3];
18    let tb = [0u32; 3];
19
20    (vec![point], vec![ta, tb])
21}
22
23fn build_degenerate_mesh_segment(
24    dir: Vector3,
25    points: &[Vector3],
26) -> (Vec<Vector3>, Vec<[u32; 3]>) {
27    let a = utils::point_cloud_support_point(dir, points);
28    let b = utils::point_cloud_support_point(-dir, points);
29
30    let ta = [0u32, 1, 0];
31    let tb = [1u32, 0, 0];
32
33    (vec![a, b], vec![ta, tb])
34}
35
36pub fn try_get_initial_mesh(
37    original_points: &[Vector3],
38    normalized_points: &mut [Vector3],
39    undecidable: &mut Vec<usize>,
40) -> Result<InitialMesh, ConvexHullError> {
41    /*
42     * Compute the eigenvectors to see if the input data live on a subspace.
43     */
44    let eigvec;
45    let eigval;
46
47    #[cfg(not(feature = "improved_fixed_point_support"))]
48    {
49        let cov_mat = utils::cov(normalized_points);
50        let eig = cov_mat.symmetric_eigen();
51        eigvec = eig.eigenvectors;
52        eigval = eig.eigenvalues;
53    }
54
55    #[cfg(feature = "improved_fixed_point_support")]
56    {
57        use crate::math::{Matrix3, MatrixExt};
58        eigvec = Matrix3::identity();
59        eigval = Vector3::splat(1.0);
60    }
61
62    let mut eigpairs = [
63        (eigvec.x_axis, eigval[0]),
64        (eigvec.y_axis, eigval[1]),
65        (eigvec.z_axis, 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(Vector2::new(point.dot(axis1), point.dot(axis2)))
114            }
115
116            // … and compute the 2d convex hull.
117            let idx = transformation::convex_hull2_idx(&subspace_points[..]);
118
119            // Finalize the result, triangulating the polyline.
120            let npoints = idx.len();
121            let coords = idx.into_iter().map(|i| original_points[i]).collect();
122            let mut triangles = Vec::with_capacity(npoints + npoints - 4);
123
124            for id in 1u32..npoints as u32 - 1 {
125                triangles.push([0, id, id + 1]);
126            }
127
128            // NOTE: We use a different starting point for the triangulation
129            // of the bottom faces in order to avoid bad topology where
130            // and edge would end be being shared by more than two triangles.
131            for id in 0u32..npoints as u32 - 2 {
132                let a = npoints as u32 - 1;
133                triangles.push([a, id + 1, id]);
134            }
135
136            Ok(InitialMesh::ResultMesh(coords, triangles))
137        }
138        3 => {
139            // The hull is a polyhedron.
140            // Find a initial triangle lying on the principal halfspace…
141            let center = utils::center(normalized_points);
142
143            // Get the maximum absolute value from the eigenvalues
144            let max_eigval = {
145                let ax = eigval.x.abs();
146                let ay = eigval.y.abs();
147                let az = eigval.z.abs();
148                ax.max(ay).max(az)
149            };
150
151            for point in normalized_points.iter_mut() {
152                *point = Vector3::from((*point - center) / max_eigval);
153            }
154
155            let p1 = support_point_id(eigpairs[0].0, normalized_points)
156                .ok_or(ConvexHullError::MissingSupportPoint)?;
157            let p2 = support_point_id(-eigpairs[0].0, normalized_points)
158                .ok_or(ConvexHullError::MissingSupportPoint)?;
159
160            let mut max_area = 0.0;
161            let mut p3 = usize::MAX;
162
163            for (i, point) in normalized_points.iter().enumerate() {
164                let area =
165                    Triangle::new(normalized_points[p1], normalized_points[p2], *point).area();
166
167                if area > max_area {
168                    max_area = area;
169                    p3 = i;
170                }
171            }
172
173            if p3 == usize::MAX {
174                Err(ConvexHullError::InternalError("no triangle found."))
175            } else {
176                // Build two facets with opposite normals
177                let mut f1 = TriangleFacet::new(p1, p2, p3, normalized_points);
178                let mut f2 = TriangleFacet::new(p2, p1, p3, normalized_points);
179
180                // Link the facets together
181                f1.set_facets_adjascency(1, 1, 1, 0, 2, 1);
182                f2.set_facets_adjascency(0, 0, 0, 0, 2, 1);
183
184                let mut facets = vec![f1, f2];
185
186                // … and attribute visible points to each one of them.
187                // TODO: refactor this with the two others.
188                for point in 0..normalized_points.len() {
189                    if normalized_points[point] == normalized_points[p1]
190                        || normalized_points[point] == normalized_points[p2]
191                        || normalized_points[point] == normalized_points[p3]
192                    {
193                        continue;
194                    }
195
196                    let mut furthest = usize::MAX;
197                    let mut furthest_dist = 0.0;
198
199                    for (i, curr_facet) in facets.iter().enumerate() {
200                        if curr_facet.can_see_point(point, normalized_points) {
201                            let distance = curr_facet.distance_to_point(point, normalized_points);
202
203                            if distance > furthest_dist {
204                                furthest = i;
205                                furthest_dist = distance;
206                            }
207                        }
208                    }
209
210                    if furthest != usize::MAX {
211                        facets[furthest].add_visible_point(point, normalized_points);
212                    } else {
213                        undecidable.push(point);
214                    }
215
216                    // If none of the facet can be seen from the point, it is naturally deleted.
217                }
218
219                super::check_facet_links(0, &facets[..]);
220                super::check_facet_links(1, &facets[..]);
221
222                Ok(InitialMesh::Facets(facets))
223            }
224        }
225        _ => Err(ConvexHullError::Unreachable),
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    #[test]
232    #[cfg(feature = "f32")]
233    fn try_get_initial_mesh_should_fail_for_missing_support_points() {
234        use super::*;
235        use crate::math::Vector;
236        use crate::transformation::try_convex_hull;
237
238        let point_cloud = vec![
239            Vector::new(103.05024, 303.44974, 106.125),
240            Vector::new(103.21692, 303.44974, 106.125015),
241            Vector::new(104.16538, 303.44974, 106.125),
242            Vector::new(106.55025, 303.44974, 106.125),
243            Vector::new(106.55043, 303.44974, 106.125),
244        ];
245        let result = try_convex_hull(&point_cloud);
246        assert!(result.is_ok());
247    }
248}