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
use std::ops::RangeInclusive;
use fj_math::Point;
use itertools::Itertools;
use crate::{
geometry::{CurveBoundary, SurfacePath},
objects::{Cycle, HalfEdge},
operations::{
build::BuildHalfEdge,
insert::Insert,
update::{UpdateCycle, UpdateHalfEdge},
},
services::Services,
storage::Handle,
};
/// 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, services: &mut Services) -> 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>,
services: &mut Services,
) -> Self;
}
impl JoinCycle for Cycle {
fn add_joined_edges<Es>(&self, edges: Es, services: &mut Services) -> Self
where
Es: IntoIterator<
Item = (Handle<HalfEdge>, SurfacePath, CurveBoundary<Point<1>>),
>,
Es::IntoIter: Clone + ExactSizeIterator,
{
self.add_half_edges(edges.into_iter().circular_tuple_windows().map(
|((prev_half_edge, _, _), (half_edge, curve, boundary))| {
HalfEdge::unjoined(curve, boundary, services)
.update_curve(|_| half_edge.curve().clone())
.update_start_vertex(|_| {
prev_half_edge.start_vertex().clone()
})
.insert(services)
},
))
}
fn join_to(
&self,
other: &Cycle,
range: RangeInclusive<usize>,
range_other: RangeInclusive<usize>,
services: &mut Services,
) -> 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),
|edge| {
edge.update_curve(|_| edge_other.curve().clone())
.update_start_vertex(|_| {
other
.half_edges()
.nth_circular(index_other + 1)
.start_vertex()
.clone()
})
.insert(services)
},
)
.update_half_edge(
self.half_edges().nth_circular(index + 1),
|edge| {
edge.update_start_vertex(|_| {
edge_other.start_vertex().clone()
})
.insert(services)
},
)
},
)
}
}