fj_kernel/operations/join/
cycle.rs

1use std::ops::RangeInclusive;
2
3use fj_math::Point;
4use itertools::Itertools;
5
6use crate::{
7    geometry::curve::Curve,
8    objects::{Cycle, HalfEdge},
9    operations::{BuildHalfEdge, Insert, UpdateCycle, UpdateHalfEdge},
10    services::Services,
11    storage::Handle,
12};
13
14/// Join a [`Cycle`] to another
15pub trait JoinCycle {
16    /// Create a cycle that is joined to the provided edges
17    fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self
18    where
19        Es: IntoIterator<Item = (Handle<HalfEdge>, Curve, [Point<1>; 2])>,
20        Es::IntoIter: Clone + ExactSizeIterator;
21
22    /// Join the cycle to another
23    ///
24    /// Joins the cycle to the other at the provided ranges. The ranges specify
25    /// the indices of the half-edges that are joined together.
26    ///
27    /// A modulo operation is applied to all indices before use, so in a cycle
28    /// of 3 half-edges, indices `0` and `3` refer to the same half-edge. This
29    /// allows for specifying a range that crosses the "seam" of the cycle.
30    ///
31    /// # Panics
32    ///
33    /// Panics, if the ranges have different lengths.
34    ///
35    /// # Implementation Note
36    ///
37    /// The use of the `RangeInclusive` type might be a bit limiting, as other
38    /// range types might be more convenient in a given use case. This
39    /// implementation was chosen for now, as it wasn't clear whether the
40    /// additional complexity of using `RangeBounds` would be worth it.
41    ///
42    /// A solution based on `SliceIndex` could theoretically be used, but that
43    /// trait is partially unstable. In addition, it's not clear how it could be
44    /// used generically, as it could yield a range (which can be iterated over)
45    /// or a single item (which can not). This is not a hard problem in
46    /// principle (a single item could just be an iterator of length 1), but I
47    /// don't see it how to address this in Rust in a reasonable way.
48    ///
49    /// Maybe a custom trait that is implemented for `usize` and all range types
50    /// would be the best solution.
51    fn join_to(
52        &self,
53        other: &Cycle,
54        range: RangeInclusive<usize>,
55        other_range: RangeInclusive<usize>,
56        services: &mut Services,
57    ) -> Self;
58}
59
60impl JoinCycle for Cycle {
61    fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self
62    where
63        Es: IntoIterator<Item = (Handle<HalfEdge>, Curve, [Point<1>; 2])>,
64        Es::IntoIter: Clone + ExactSizeIterator,
65    {
66        self.add_half_edges(edges.into_iter().circular_tuple_windows().map(
67            |((prev, _, _), (half_edge, curve, boundary))| {
68                HalfEdge::unjoined(curve, boundary, services)
69                    .replace_start_vertex(prev.start_vertex().clone())
70                    .replace_global_form(half_edge.global_form().clone())
71                    .insert(services)
72            },
73        ))
74    }
75
76    fn join_to(
77        &self,
78        other: &Cycle,
79        range: RangeInclusive<usize>,
80        range_other: RangeInclusive<usize>,
81        services: &mut Services,
82    ) -> Self {
83        assert_eq!(
84            range.end() - range.start(),
85            range_other.end() - range_other.start()
86        );
87
88        let mut cycle = self.clone();
89
90        for (index, index_other) in range.zip(range_other) {
91            let index = index % self.len();
92            let index_other = index_other % self.len();
93
94            let half_edge = self
95                .nth_half_edge(index)
96                .expect("Index must be valid, due to use of `%` above");
97            let half_edge_other = other
98                .nth_half_edge(index_other)
99                .expect("Index must be valid, due to use of `%` above");
100
101            let vertex_a = other
102                .half_edge_after(half_edge_other)
103                .expect("Expected other cycle to contain edge")
104                .start_vertex()
105                .clone();
106            let vertex_b = half_edge_other.start_vertex().clone();
107
108            let next_edge = cycle
109                .half_edge_after(half_edge)
110                .expect("Expected this cycle to contain edge");
111
112            let this_joined = half_edge
113                .replace_start_vertex(vertex_a)
114                .replace_global_form(half_edge_other.global_form().clone())
115                .insert(services);
116            let next_joined =
117                next_edge.replace_start_vertex(vertex_b).insert(services);
118
119            cycle = cycle
120                .replace_half_edge(half_edge, this_joined)
121                .replace_half_edge(next_edge, next_joined)
122        }
123
124        cycle
125    }
126}