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