fj_core/operations/join/
cycle.rs

1use std::ops::RangeInclusive;
2
3use fj_math::Point;
4use itertools::Itertools;
5
6use crate::{
7    geometry::{CurveBoundary, HalfEdgeGeometry, SurfacePath},
8    objects::{Cycle, HalfEdge},
9    operations::{
10        build::BuildHalfEdge,
11        geometry::UpdateHalfEdgeGeometry,
12        insert::Insert,
13        update::{UpdateCycle, UpdateHalfEdge},
14    },
15    storage::Handle,
16    Core,
17};
18
19/// Join a [`Cycle`] to another
20pub trait JoinCycle {
21    /// Add half-edges to the cycle that are joined to the provided ones
22    #[must_use]
23    fn add_joined_edges<Es>(&self, edges: Es, core: &mut Core) -> Self
24    where
25        Es: IntoIterator<
26            Item = (Handle<HalfEdge>, SurfacePath, CurveBoundary<Point<1>>),
27        >,
28        Es::IntoIter: Clone + ExactSizeIterator;
29
30    /// Join the cycle to another
31    ///
32    /// Joins the cycle to the other at the provided ranges. The ranges specify
33    /// the indices of the edges that are joined together.
34    ///
35    /// A modulo operation is applied to all indices before use, so in a cycle
36    /// of 3 edges, indices `0` and `3` refer to the same edge. This allows for
37    /// specifying a range that crosses the "seam" of the cycle.
38    ///
39    /// # Panics
40    ///
41    /// Panics, if the ranges have different lengths.
42    ///
43    /// # Assumptions
44    ///
45    /// This method makes some assumptions that need to be met, if the operation
46    /// is to result in a valid shape:
47    ///
48    /// - **The joined edges must be coincident.**
49    /// - **The locally defined curve coordinate systems of the edges must
50    ///   match.**
51    ///
52    /// If either of those assumptions are not met, this will result in a
53    /// validation error down the line.
54    ///
55    /// # Implementation Note
56    ///
57    /// The use of the `RangeInclusive` type might be a bit limiting, as other
58    /// range types might be more convenient in a given use case. This
59    /// implementation was chosen for now, as it wasn't clear whether the
60    /// additional complexity of using `RangeBounds` would be worth it.
61    ///
62    /// A solution based on `SliceIndex` could theoretically be used, but that
63    /// trait is partially unstable. In addition, it's not clear how it could be
64    /// used generically, as it could yield a range (which can be iterated over)
65    /// or a single item (which can not). This is not a hard problem in
66    /// principle (a single item could just be an iterator of length 1), but I
67    /// don't see it how to address this in Rust in a reasonable way.
68    ///
69    /// Maybe a custom trait that is implemented for `usize` and all range types
70    /// would be the best solution.
71    #[must_use]
72    fn join_to(
73        &self,
74        other: &Cycle,
75        range: RangeInclusive<usize>,
76        other_range: RangeInclusive<usize>,
77        core: &mut Core,
78    ) -> Self;
79}
80
81impl JoinCycle for Cycle {
82    fn add_joined_edges<Es>(&self, edges: Es, core: &mut Core) -> Self
83    where
84        Es: IntoIterator<
85            Item = (Handle<HalfEdge>, SurfacePath, CurveBoundary<Point<1>>),
86        >,
87        Es::IntoIter: Clone + ExactSizeIterator,
88    {
89        let half_edges = edges
90            .into_iter()
91            .circular_tuple_windows()
92            .map(|((prev_half_edge, _, _), (half_edge, path, boundary))| {
93                let half_edge = HalfEdge::unjoined(path, boundary, core)
94                    .update_curve(|_, _| half_edge.curve().clone(), core)
95                    .update_start_vertex(
96                        |_, _| prev_half_edge.start_vertex().clone(),
97                        core,
98                    )
99                    .insert(core);
100
101                core.layers.geometry.define_half_edge(
102                    half_edge.clone(),
103                    HalfEdgeGeometry { path },
104                );
105
106                half_edge
107            })
108            .collect::<Vec<_>>();
109        self.add_half_edges(half_edges, core)
110    }
111
112    fn join_to(
113        &self,
114        other: &Cycle,
115        range: RangeInclusive<usize>,
116        range_other: RangeInclusive<usize>,
117        core: &mut Core,
118    ) -> Self {
119        assert_eq!(
120            range.end() - range.start(),
121            range_other.end() - range_other.start(),
122            "Ranges have different lengths",
123        );
124
125        range.zip(range_other).fold(
126            self.clone(),
127            |cycle, (index, index_other)| {
128                let edge_other = other.half_edges().nth_circular(index_other);
129
130                cycle
131                    .update_half_edge(
132                        self.half_edges().nth_circular(index),
133                        |half_edge, core| {
134                            [half_edge
135                                .update_curve(
136                                    |_, _| edge_other.curve().clone(),
137                                    core,
138                                )
139                                .update_start_vertex(
140                                    |_, _| {
141                                        other
142                                            .half_edges()
143                                            .nth_circular(index_other + 1)
144                                            .start_vertex()
145                                            .clone()
146                                    },
147                                    core,
148                                )
149                                .insert(core)
150                                .set_path(
151                                    core.layers
152                                        .geometry
153                                        .of_half_edge(half_edge)
154                                        .path,
155                                    &mut core.layers.geometry,
156                                )]
157                        },
158                        core,
159                    )
160                    .update_half_edge(
161                        self.half_edges().nth_circular(index + 1),
162                        |half_edge, core| {
163                            [half_edge
164                                .update_start_vertex(
165                                    |_, _| edge_other.start_vertex().clone(),
166                                    core,
167                                )
168                                .insert(core)
169                                .set_path(
170                                    core.layers
171                                        .geometry
172                                        .of_half_edge(half_edge)
173                                        .path,
174                                    &mut core.layers.geometry,
175                                )]
176                        },
177                        core,
178                    )
179            },
180        )
181    }
182}