fj_core/algorithms/triangulate/
mod.rs

1//! Shape triangulation
2
3mod delaunay;
4mod polygon;
5
6use fj_interop::Mesh;
7use fj_math::Point;
8
9use crate::Core;
10
11use self::polygon::Polygon;
12
13use super::approx::{face::FaceApprox, Approx, Tolerance};
14
15/// Triangulate a shape
16pub trait Triangulate: Sized {
17    /// Triangulate the shape
18    fn triangulate(self, core: &mut Core) -> Mesh<Point<3>> {
19        let mut mesh = Mesh::new();
20        self.triangulate_into_mesh(&mut mesh, core);
21        mesh
22    }
23
24    /// Triangulate a partial shape into the provided mesh
25    ///
26    /// This is a low-level method, intended for implementation of
27    /// `Triangulate`. Most callers should prefer [`Triangulate::triangulate`].
28    fn triangulate_into_mesh(self, mesh: &mut Mesh<Point<3>>, core: &mut Core);
29}
30
31impl<T> Triangulate for (T, Tolerance)
32where
33    T: Approx,
34    T::Approximation: IntoIterator<Item = FaceApprox>,
35{
36    fn triangulate_into_mesh(self, mesh: &mut Mesh<Point<3>>, core: &mut Core) {
37        let (approx, tolerance) = self;
38
39        let approx = approx.approx(tolerance, core);
40
41        for approx in approx {
42            approx.triangulate_into_mesh(mesh, core);
43        }
44    }
45}
46
47impl Triangulate for FaceApprox {
48    fn triangulate_into_mesh(
49        self,
50        mesh: &mut Mesh<Point<3>>,
51        _core: &mut Core,
52    ) {
53        let face_as_polygon = Polygon::new()
54            .with_exterior(
55                self.exterior
56                    .points()
57                    .into_iter()
58                    .map(|point| point.local_form),
59            )
60            .with_interiors(self.interiors.iter().map(|interior| {
61                interior.points().into_iter().map(|point| point.local_form)
62            }));
63
64        let cycles = [self.exterior].into_iter().chain(self.interiors);
65        let mut triangles =
66            delaunay::triangulate(cycles, self.coord_handedness);
67        triangles.retain(|triangle| {
68            face_as_polygon
69                .contains_triangle(triangle.map(|point| point.point_surface))
70        });
71
72        let color = self.color.unwrap_or_default();
73
74        for triangle in triangles {
75            let points = triangle.map(|point| point.point_global);
76            mesh.push_triangle(points, color);
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use fj_interop::Mesh;
84    use fj_math::{Point, Scalar};
85
86    use crate::{
87        algorithms::approx::{Approx, Tolerance},
88        objects::{Cycle, Face},
89        operations::{
90            build::{BuildCycle, BuildFace},
91            update::{UpdateFace, UpdateRegion},
92        },
93        Core,
94    };
95
96    use super::Triangulate;
97
98    #[test]
99    fn simple() -> anyhow::Result<()> {
100        let mut core = Core::new();
101
102        let a = [0., 0.];
103        let b = [2., 0.];
104        let c = [2., 2.];
105        let d = [0., 1.];
106
107        let face =
108            Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core)
109                .update_region(
110                    |region, core| {
111                        region.update_exterior(
112                            |_, core| Cycle::polygon([a, b, c, d], core),
113                            core,
114                        )
115                    },
116                    &mut core,
117                );
118
119        let a = Point::from(a).to_xyz();
120        let b = Point::from(b).to_xyz();
121        let c = Point::from(c).to_xyz();
122        let d = Point::from(d).to_xyz();
123
124        let triangles = triangulate(face, &mut core)?;
125
126        assert!(triangles.contains_triangle([a, b, d]));
127        assert!(triangles.contains_triangle([b, c, d]));
128        assert!(!triangles.contains_triangle([a, b, c]));
129        assert!(!triangles.contains_triangle([a, c, d]));
130
131        Ok(())
132    }
133
134    #[test]
135    fn simple_hole() -> anyhow::Result<()> {
136        let mut core = Core::new();
137
138        let a = [0., 0.];
139        let b = [4., 0.];
140        let c = [4., 4.];
141        let d = [0., 4.];
142
143        let e = [1., 1.];
144        let f = [1., 2.];
145        let g = [3., 3.];
146        let h = [3., 1.];
147
148        let surface = core.layers.objects.surfaces.xy_plane();
149
150        let face = Face::unbound(surface.clone(), &mut core).update_region(
151            |region, core| {
152                region
153                    .update_exterior(
154                        |_, core| Cycle::polygon([a, b, c, d], core),
155                        core,
156                    )
157                    .add_interiors([Cycle::polygon([e, f, g, h], core)], core)
158            },
159            &mut core,
160        );
161
162        let triangles = triangulate(face, &mut core)?;
163
164        let a = core
165            .layers
166            .geometry
167            .of_surface(&surface)
168            .point_from_surface_coords(a);
169        let b = core
170            .layers
171            .geometry
172            .of_surface(&surface)
173            .point_from_surface_coords(b);
174        let e = core
175            .layers
176            .geometry
177            .of_surface(&surface)
178            .point_from_surface_coords(e);
179        let f = core
180            .layers
181            .geometry
182            .of_surface(&surface)
183            .point_from_surface_coords(f);
184        let g = core
185            .layers
186            .geometry
187            .of_surface(&surface)
188            .point_from_surface_coords(g);
189        let h = core
190            .layers
191            .geometry
192            .of_surface(&surface)
193            .point_from_surface_coords(h);
194
195        // Let's test that some correct triangles are present. We don't need to
196        // test them all.
197        //
198        // Please note that there are multiple valid triangulations of any given
199        // polygon. So if you change the input data above, or the algorithm, the
200        // following assertions might break.
201        //
202        // This limits the usefulness of this test. It would be better to have a
203        // smarter way of verifying the triangulation.
204        assert!(triangles.contains_triangle([a, b, e]));
205        assert!(triangles.contains_triangle([b, e, h]));
206
207        // Shouldn't contain any possible triangle from the hole.
208        assert!(!triangles.contains_triangle([e, f, g]));
209        assert!(!triangles.contains_triangle([e, g, h]));
210        assert!(!triangles.contains_triangle([e, f, h]));
211        assert!(!triangles.contains_triangle([f, g, h]));
212
213        Ok(())
214    }
215
216    #[test]
217    fn sharp_concave_shape() -> anyhow::Result<()> {
218        let mut core = Core::new();
219
220        //   e       c
221        //   |\     /|
222        //   \ \   / b
223        //    \ \ / /
224        //     \ d /
225        //      \a/
226
227        // Naive Delaunay triangulation will create a triangle (c, d, e), which
228        // is not part of the polygon. The higher-level triangulation will
229        // filter that out, but it will result in missing triangles.
230
231        let a = [1., 0.];
232        let b = [2., 8.];
233        let c = [2., 9.];
234        let d = [1., 1.];
235        let e = [0., 9.];
236
237        let surface = core.layers.objects.surfaces.xy_plane();
238
239        let face = Face::unbound(surface.clone(), &mut core).update_region(
240            |region, core| {
241                region.update_exterior(
242                    |_, core| Cycle::polygon([a, b, c, d, e], core),
243                    core,
244                )
245            },
246            &mut core,
247        );
248
249        let triangles = triangulate(face, &mut core)?;
250
251        let a = core
252            .layers
253            .geometry
254            .of_surface(&surface)
255            .point_from_surface_coords(a);
256        let b = core
257            .layers
258            .geometry
259            .of_surface(&surface)
260            .point_from_surface_coords(b);
261        let c = core
262            .layers
263            .geometry
264            .of_surface(&surface)
265            .point_from_surface_coords(c);
266        let d = core
267            .layers
268            .geometry
269            .of_surface(&surface)
270            .point_from_surface_coords(d);
271        let e = core
272            .layers
273            .geometry
274            .of_surface(&surface)
275            .point_from_surface_coords(e);
276
277        assert!(triangles.contains_triangle([a, b, d]));
278        assert!(triangles.contains_triangle([a, d, e]));
279        assert!(triangles.contains_triangle([b, c, d]));
280
281        Ok(())
282    }
283
284    fn triangulate(
285        face: Face,
286        core: &mut Core,
287    ) -> anyhow::Result<Mesh<Point<3>>> {
288        let tolerance = Tolerance::from_scalar(Scalar::ONE)?;
289        Ok(face.approx(tolerance, core).triangulate(core))
290    }
291}