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}