fj_core/operations/build/shell.rs
1use std::collections::BTreeMap;
2
3use fj_interop::ext::ArrayExt;
4use fj_math::Point;
5
6use crate::{
7 geometry::CurveBoundary,
8 objects::{Curve, Face, HalfEdge, Shell, Surface, Vertex},
9 operations::{
10 build::{BuildFace, BuildHalfEdge, BuildSurface, Polygon},
11 geometry::UpdateHalfEdgeGeometry,
12 insert::{Insert, IsInserted, IsInsertedNo, IsInsertedYes},
13 join::JoinCycle,
14 reverse::ReverseCurveCoordinateSystems,
15 update::{
16 UpdateCycle, UpdateFace, UpdateHalfEdge, UpdateRegion, UpdateShell,
17 },
18 },
19 Core,
20};
21
22/// Build a [`Shell`]
23///
24/// See [module-level documentation] for context.
25///
26/// [module-level documentation]: super
27pub trait BuildShell {
28 /// Build an empty shell
29 fn empty() -> Shell {
30 Shell::new([])
31 }
32
33 /// Build a polyhedron by specifying its vertices and indices
34 fn from_vertices_and_indices(
35 vertices: impl IntoIterator<Item = impl Into<Point<3>>>,
36 indices: impl IntoIterator<Item = [usize; 3]>,
37 core: &mut Core,
38 ) -> Shell {
39 let vertices = vertices
40 .into_iter()
41 .enumerate()
42 .map(|(index, position)| {
43 let vertex = Vertex::new().insert(core);
44 let position = position.into();
45
46 (index, (vertex, position))
47 })
48 .collect::<BTreeMap<_, _>>();
49
50 let mut curves = BTreeMap::new();
51
52 let faces = indices
53 .into_iter()
54 .map(|indices| {
55 let [(a, a_pos), (b, b_pos), (c, c_pos)] = indices
56 .map(|index| vertices.get(&index).expect("Invalid index"));
57
58 let (surface, _) = Surface::plane_from_points(
59 [a_pos, b_pos, c_pos].map(Clone::clone),
60 core,
61 );
62
63 let curves_and_boundaries =
64 [[a, b], [b, c], [c, a]].map(|vertices| {
65 let vertices = vertices.map(Clone::clone);
66 let vertices = CurveBoundary::<Vertex>::from(vertices);
67
68 curves
69 .get(&vertices.clone().reverse())
70 .cloned()
71 .unwrap_or_else(|| {
72 let curve = Curve::new().insert(core);
73 let boundary =
74 CurveBoundary::<Point<1>>::from([
75 [0.],
76 [1.],
77 ]);
78
79 curves.insert(
80 vertices,
81 (curve.clone(), boundary),
82 );
83
84 (curve, boundary.reverse())
85 })
86 });
87
88 let half_edges = {
89 let vertices = [a, b, c].map(Clone::clone);
90 let [a, b, c] = [[0., 0.], [1., 0.], [0., 1.]];
91 vertices
92 .zip_ext([[a, b], [b, c], [c, a]])
93 .zip_ext(curves_and_boundaries)
94 .map(|((vertex, positions), (curve, boundary))| {
95 let half_edge = HalfEdge::line_segment(
96 positions,
97 Some(boundary.reverse().inner),
98 core,
99 );
100 half_edge
101 .update_start_vertex(|_, _| vertex, core)
102 .update_curve(|_, _| curve, core)
103 .insert(core)
104 .set_path(
105 core.layers
106 .geometry
107 .of_half_edge(&half_edge)
108 .path,
109 &mut core.layers.geometry,
110 )
111 })
112 };
113
114 Face::unbound(surface, core).update_region(
115 |region, core| {
116 region.update_exterior(
117 |cycle, core| {
118 cycle.add_half_edges(half_edges, core)
119 },
120 core,
121 )
122 },
123 core,
124 )
125 })
126 .collect::<Vec<_>>();
127
128 Shell::empty().add_faces(faces, core)
129 }
130
131 /// Build a tetrahedron from the provided points
132 ///
133 /// Accepts 4 points, naturally. For the purposes of the following
134 /// discussion, let's call those `a`, `b`, `c`, and `d`, and assume that the
135 /// order they are listed in here matches the order they are provided in
136 /// within the array.
137 ///
138 /// Assumes that `a`, `b`, and `c` form a triangle in counter-clockwise
139 /// order, when arranging the viewpoint such that it is on the opposite side
140 /// of the triangle from `d`. If this assumption is met, the orientation of
141 /// all faces of the tetrahedron will be valid, meaning their
142 /// counter-clockwise sides are outside.
143 ///
144 /// # Implementation Note
145 ///
146 /// In principle, this method doesn't need to make assumptions about the
147 /// order of the points provided. It could, given some extra effort, just
148 /// build a correct tetrahedron, regardless of that order.
149 fn tetrahedron(
150 points: [impl Into<Point<3>>; 4],
151 core: &mut Core,
152 ) -> TetrahedronShell {
153 let [a, b, c, d] = points.map(Into::into);
154
155 let abc = Face::triangle([a, b, c], core);
156 let bad = Face::triangle([b, a, d], core).update_region(
157 |region, core| {
158 region.update_exterior(
159 |cycle, core| {
160 cycle
161 .update_half_edge(
162 cycle.half_edges().nth_circular(0),
163 |edge, core| {
164 [edge
165 .reverse_curve_coordinate_systems(core)]
166 },
167 core,
168 )
169 .join_to(
170 abc.face.region().exterior(),
171 0..=0,
172 0..=0,
173 core,
174 )
175 },
176 core,
177 )
178 },
179 core,
180 );
181 let dac = Face::triangle([d, a, c], core).update_region(
182 |region, core| {
183 region.update_exterior(
184 |cycle, core| {
185 cycle
186 .update_half_edge(
187 cycle.half_edges().nth_circular(1),
188 |edge, core| {
189 [edge
190 .reverse_curve_coordinate_systems(core)]
191 },
192 core,
193 )
194 .join_to(
195 abc.face.region().exterior(),
196 1..=1,
197 2..=2,
198 core,
199 )
200 .update_half_edge(
201 cycle.half_edges().nth_circular(0),
202 |edge, core| {
203 [edge
204 .reverse_curve_coordinate_systems(core)]
205 },
206 core,
207 )
208 .join_to(
209 bad.face.region().exterior(),
210 0..=0,
211 1..=1,
212 core,
213 )
214 },
215 core,
216 )
217 },
218 core,
219 );
220 let cbd = Face::triangle([c, b, d], core).update_region(
221 |region, core| {
222 region.update_exterior(
223 |cycle, core| {
224 cycle
225 .update_half_edge(
226 cycle.half_edges().nth_circular(0),
227 |edge, core| {
228 [edge
229 .reverse_curve_coordinate_systems(core)]
230 },
231 core,
232 )
233 .update_half_edge(
234 cycle.half_edges().nth_circular(1),
235 |edge, core| {
236 [edge
237 .reverse_curve_coordinate_systems(core)]
238 },
239 core,
240 )
241 .update_half_edge(
242 cycle.half_edges().nth_circular(2),
243 |edge, core| {
244 [edge
245 .reverse_curve_coordinate_systems(core)]
246 },
247 core,
248 )
249 .join_to(
250 abc.face.region().exterior(),
251 0..=0,
252 1..=1,
253 core,
254 )
255 .join_to(
256 bad.face.region().exterior(),
257 1..=1,
258 2..=2,
259 core,
260 )
261 .join_to(
262 dac.face.region().exterior(),
263 2..=2,
264 2..=2,
265 core,
266 )
267 },
268 core,
269 )
270 },
271 core,
272 );
273
274 let triangles =
275 [abc, bad, dac, cbd].map(|triangle| triangle.insert(core));
276 let shell =
277 Shell::new(triangles.iter().map(|triangle| triangle.face.clone()));
278
279 let [abc, bad, dac, cbd] = triangles;
280
281 TetrahedronShell {
282 shell,
283 abc,
284 bad,
285 dac,
286 cbd,
287 }
288 }
289}
290
291impl BuildShell for Shell {}
292
293/// A tetrahedron
294///
295/// A tetrahedron is constructed from 4 points and has 4 faces. For the purpose
296/// of naming the fields of this struct, the points are named `a`, `b`, `c`, and
297/// `d`, in the order in which they are passed.
298///
299/// Returned by [`BuildShell::tetrahedron`].
300pub struct TetrahedronShell<I: IsInserted = IsInsertedNo> {
301 /// The shell that forms the tetrahedron
302 pub shell: I::T<Shell>,
303
304 /// The face formed by the points `a`, `b`, and `c`.
305 pub abc: Polygon<3, IsInsertedYes>,
306
307 /// The face formed by the points `b`, `a`, and `d`.
308 pub bad: Polygon<3, IsInsertedYes>,
309
310 /// The face formed by the points `d`, `a`, and `c`.
311 pub dac: Polygon<3, IsInsertedYes>,
312
313 /// The face formed by the points `c`, `b`, and `d`.
314 pub cbd: Polygon<3, IsInsertedYes>,
315}