fj_core/operations/split/
face.rs

1use fj_interop::ext::ArrayExt;
2use fj_math::Point;
3use itertools::Itertools;
4
5use crate::{
6    objects::{Cycle, Face, HalfEdge, Shell},
7    operations::{
8        build::{BuildCycle, BuildHalfEdge},
9        derive::DeriveFrom,
10        geometry::UpdateHalfEdgeGeometry,
11        insert::Insert,
12        split::SplitEdge,
13        update::{
14            UpdateCycle, UpdateFace, UpdateHalfEdge, UpdateRegion, UpdateShell,
15        },
16    },
17    storage::Handle,
18    Core,
19};
20
21/// Split a face into two
22pub trait SplitFace: Sized {
23    /// Split the face into two
24    ///
25    /// The line that splits the face is defined by two points, each specified
26    /// in local coordinates of an edge.
27    ///
28    /// # Panics
29    ///
30    /// Panics, if the half-edges are not part of the boundary of the provided
31    /// face.
32    ///
33    /// # Implementation Note
34    ///
35    /// The way the split line is specified is rather inconvenient, and not very
36    /// flexible. This is an artifact of the current implementation, and more
37    /// flexible and convenient ways to split the face (like an arbitrary curve)
38    /// can be provided later.
39    #[must_use]
40    fn split_face(
41        &self,
42        face: &Handle<Face>,
43        line: [(&Handle<HalfEdge>, impl Into<Point<1>>); 2],
44        core: &mut Core,
45    ) -> (Self, [Handle<Face>; 2]);
46}
47
48impl SplitFace for Shell {
49    fn split_face(
50        &self,
51        face: &Handle<Face>,
52        line: [(&Handle<HalfEdge>, impl Into<Point<1>>); 2],
53        core: &mut Core,
54    ) -> (Self, [Handle<Face>; 2]) {
55        // The code below might assume that the half-edges that define the line
56        // are part of the face's exterior. Let's make that explicit here.
57        //
58        // This is actually the only time we're using `face` in this method, as
59        // it's going to get replaced with a new version as soon as we split the
60        // edges. We could probably do without it, but not taking it would
61        // probably make validating that both half-edges belong to the same face
62        // more difficult, as well as make the method signature less intuitive.
63        //
64        // Something to think about though!
65        {
66            let [(a, _), (b, _)] = line.each_ref_ext();
67
68            let exterior = face.region().exterior();
69
70            assert!(exterior.half_edges().contains(a));
71            assert!(exterior.half_edges().contains(b));
72        }
73
74        let mut self_ = self.clone();
75
76        let [[a, b], [c, d]] = line.map(|(half_edge, point)| {
77            let (shell, [[a, b], _]) = self_.split_edge(half_edge, point, core);
78            self_ = shell;
79            [a, b]
80        });
81
82        // The original face doesn't exist in the updated shell, as it's been
83        // replaced by a new version due to the edge splitting. Let's find the
84        // face that replaced it.
85        let mut updated_face_after_split_edges = None;
86        for f in self_.faces() {
87            let half_edges = f.region().exterior().half_edges();
88
89            if half_edges.contains(&a)
90                && half_edges.contains(&b)
91                && half_edges.contains(&c)
92                && half_edges.contains(&d)
93            {
94                assert!(
95                    updated_face_after_split_edges.is_none(),
96                    "There should never be two faces that share half-edges"
97                );
98                updated_face_after_split_edges = Some(f);
99            }
100        }
101        let updated_face_after_split_edges = updated_face_after_split_edges
102            .expect("Updated shell must contain updated face");
103
104        // Build the edge that's going to divide the new faces.
105        let dividing_half_edge_a_to_d = {
106            let half_edge = HalfEdge::line_segment(
107                [b.start_position(), d.start_position()],
108                None,
109                core,
110            );
111            half_edge
112                .update_start_vertex(|_, _| b.start_vertex().clone(), core)
113                .insert(core)
114                .set_path(
115                    core.layers.geometry.of_half_edge(&half_edge).path,
116                    &mut core.layers.geometry,
117                )
118        };
119        let dividing_half_edge_c_to_b = HalfEdge::from_sibling(
120            &dividing_half_edge_a_to_d,
121            d.start_vertex().clone(),
122            core,
123        );
124
125        let mut half_edges_of_face_starting_at_b =
126            updated_face_after_split_edges
127                .region()
128                .exterior()
129                .half_edges()
130                .iter()
131                .cloned()
132                .cycle()
133                .skip_while(|half_edge| half_edge != &b);
134
135        let half_edges_b_to_c_inclusive = half_edges_of_face_starting_at_b
136            .take_while_ref(|half_edge| half_edge != &d);
137        let split_face_a = updated_face_after_split_edges
138            .update_region(
139                |region, core| {
140                    region.update_exterior(
141                        |_, core| {
142                            Cycle::empty()
143                                .add_half_edges(
144                                    half_edges_b_to_c_inclusive,
145                                    core,
146                                )
147                                .add_half_edges(
148                                    [dividing_half_edge_c_to_b],
149                                    core,
150                                )
151                        },
152                        core,
153                    )
154                },
155                core,
156            )
157            .insert(core)
158            .derive_from(updated_face_after_split_edges, core);
159
160        // The previous operation has moved the iterator along.
161        let half_edges_of_face_starting_at_d = half_edges_of_face_starting_at_b;
162
163        let half_edges_d_to_a_inclusive = half_edges_of_face_starting_at_d
164            .take_while(|half_edge| half_edge != &b);
165        let split_face_b = updated_face_after_split_edges
166            .update_region(
167                |region, core| {
168                    region.update_exterior(
169                        |_, core| {
170                            Cycle::empty()
171                                .add_half_edges(
172                                    half_edges_d_to_a_inclusive,
173                                    core,
174                                )
175                                .add_half_edges(
176                                    [dividing_half_edge_a_to_d],
177                                    core,
178                                )
179                        },
180                        core,
181                    )
182                },
183                core,
184            )
185            .insert(core)
186            .derive_from(updated_face_after_split_edges, core);
187
188        let faces = [split_face_a, split_face_b];
189        let self_ = self_.update_face(
190            updated_face_after_split_edges,
191            |_, _| faces.clone(),
192            core,
193        );
194
195        (self_, faces)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use fj_interop::Color;
202
203    use crate::{
204        objects::Shell,
205        operations::{
206            build::BuildShell,
207            presentation::{GetColor, SetColor},
208            split::SplitFace,
209        },
210        Core,
211    };
212
213    #[test]
214    fn split_face_should_keep_color() {
215        let mut core = Core::new();
216
217        let tetrahedron = Shell::tetrahedron(
218            [[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
219            &mut core,
220        );
221        let triangle = tetrahedron.abc;
222
223        let color = Color::default();
224        triangle.face.region().set_color(color, &mut core);
225
226        let split_line = [
227            (&triangle.half_edges[0], [0.5]),
228            (&triangle.half_edges[1], [0.5]),
229        ];
230        let (_shell, [face_a, face_b]) =
231            tetrahedron
232                .shell
233                .split_face(&triangle.face, split_line, &mut core);
234
235        assert_eq!(face_a.region().get_color(&mut core), Some(color));
236        assert_eq!(face_b.region().get_color(&mut core), Some(color));
237    }
238}