|
| 1 | +use super::connectivity::Connectivity; |
| 2 | +use super::misc::HandleId; |
| 3 | +use super::path::{CurveType, Path, curve_type_at}; |
| 4 | +use super::vector_attributes::{PointId, SegmentId}; |
| 5 | +use core_types::list::{ATTR_ID, List}; |
| 6 | +use glam::DVec2; |
| 7 | + |
| 8 | +/// A segment's place in the weave: the span of one path between two consecutive anchors. |
| 9 | +/// Segments are never stored, so this is how one is named. |
| 10 | +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] |
| 11 | +pub struct Span { |
| 12 | + pub path: u32, |
| 13 | + pub index: u32, |
| 14 | +} |
| 15 | + |
| 16 | +impl Span { |
| 17 | + /// The reference index where this span's starting anchor sits. |
| 18 | + pub fn start_reference(&self, curve_type: CurveType) -> usize { |
| 19 | + self.index as usize * curve_type.stride() |
| 20 | + } |
| 21 | + |
| 22 | + /// The reference index where this span's ending anchor sits. |
| 23 | + pub fn end_reference(&self, curve_type: CurveType) -> usize { |
| 24 | + self.start_reference(curve_type) + curve_type.stride() |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +/// Every span of every path, in path order. |
| 29 | +pub fn spans(paths: &List<Path>) -> impl Iterator<Item = Span> + '_ { |
| 30 | + paths.iter_element_values().enumerate().flat_map(|(path_index, path)| { |
| 31 | + let curve_type = curve_type_at(paths, path_index); |
| 32 | + (0..path.segment_count(curve_type) as u32).map(move |index| Span { path: path_index as u32, index }) |
| 33 | + }) |
| 34 | +} |
| 35 | + |
| 36 | +/// The stable ID naming a span, stored on the anchor reference the span departs from. A reference cell left unnamed |
| 37 | +/// holds `SegmentId::ZERO`, which sequential IDs never take. |
| 38 | +pub fn segment_id_of(paths: &List<Path>, span: Span) -> Option<SegmentId> { |
| 39 | + let path = paths.element(span.path as usize)?; |
| 40 | + let reference = span.start_reference(curve_type_at(paths, span.path as usize)); |
| 41 | + |
| 42 | + path.0.attribute::<SegmentId>(ATTR_ID, reference).copied() |
| 43 | +} |
| 44 | + |
| 45 | +/// The span a segment ID names, found by scanning every span in O(references). |
| 46 | +pub fn span_of_segment_id(paths: &List<Path>, segment: SegmentId) -> Option<Span> { |
| 47 | + spans(paths).find(|&span| segment_id_of(paths, span) == Some(segment)) |
| 48 | +} |
| 49 | + |
| 50 | +/// The pool points a span runs between. |
| 51 | +pub fn span_endpoints(paths: &List<Path>, span: Span) -> Option<(u32, u32)> { |
| 52 | + let path = paths.element(span.path as usize)?; |
| 53 | + let curve_type = curve_type_at(paths, span.path as usize); |
| 54 | + let start = path.0.element(span.start_reference(curve_type))?; |
| 55 | + let end = path.0.element(span.end_reference(curve_type))?; |
| 56 | + |
| 57 | + Some((*start, *end)) |
| 58 | +} |
| 59 | + |
| 60 | +/// The pool point a handle of the given span occupies, absent on a linear span that has no handles. |
| 61 | +pub fn handle_point(paths: &List<Path>, span: Span, primary: bool) -> Option<u32> { |
| 62 | + let path = paths.element(span.path as usize)?; |
| 63 | + let curve_type = curve_type_at(paths, span.path as usize); |
| 64 | + if curve_type == CurveType::Linear { |
| 65 | + return None; |
| 66 | + } |
| 67 | + |
| 68 | + // A quadratic span's single handle answers to either side |
| 69 | + let offset = if primary || curve_type == CurveType::Quadratic { 1 } else { 2 }; |
| 70 | + |
| 71 | + path.0.element(span.start_reference(curve_type) + offset).copied() |
| 72 | +} |
| 73 | + |
| 74 | +/// The stable ID of a pool point. |
| 75 | +pub fn point_id_at(pool: &List<DVec2>, index: u32) -> Option<PointId> { |
| 76 | + pool.attribute::<PointId>(ATTR_ID, index as usize).copied() |
| 77 | +} |
| 78 | + |
| 79 | +/// The pool index a point ID names. |
| 80 | +pub fn index_of_point_id(pool: &List<DVec2>, point: PointId) -> Option<u32> { |
| 81 | + pool.iter_attribute_values::<PointId>(ATTR_ID)?.position(|&id| id == point).map(|index| index as u32) |
| 82 | +} |
| 83 | + |
| 84 | +/// The spans meeting at a pool point, each paired with whether the point is where that span ends. |
| 85 | +pub fn spans_at(pool_index: u32, paths: &List<Path>, connectivity: &Connectivity) -> Vec<(Span, bool)> { |
| 86 | + let mut found = Vec::new(); |
| 87 | + |
| 88 | + for reference in connectivity.references(pool_index) { |
| 89 | + let path_index = reference.path as usize; |
| 90 | + let Some(path) = paths.element(path_index) else { continue }; |
| 91 | + |
| 92 | + let curve_type = curve_type_at(paths, path_index); |
| 93 | + if !path.is_anchor_reference(reference.index as usize, curve_type) { |
| 94 | + continue; |
| 95 | + } |
| 96 | + |
| 97 | + // An anchor departs the span starting at it and arrives on the one before it |
| 98 | + let anchor = reference.index as usize / curve_type.stride(); |
| 99 | + if anchor < path.segment_count(curve_type) { |
| 100 | + found.push(( |
| 101 | + Span { |
| 102 | + path: reference.path, |
| 103 | + index: anchor as u32, |
| 104 | + }, |
| 105 | + false, |
| 106 | + )); |
| 107 | + } |
| 108 | + if let Some(previous) = anchor.checked_sub(1) { |
| 109 | + found.push(( |
| 110 | + Span { |
| 111 | + path: reference.path, |
| 112 | + index: previous as u32, |
| 113 | + }, |
| 114 | + true, |
| 115 | + )); |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + found |
| 120 | +} |
| 121 | + |
| 122 | +/// The segments departing the given pool point. |
| 123 | +pub fn start_connected(pool_index: u32, paths: &List<Path>, connectivity: &Connectivity) -> Vec<SegmentId> { |
| 124 | + spans_at(pool_index, paths, connectivity) |
| 125 | + .into_iter() |
| 126 | + .filter(|&(_, arriving)| !arriving) |
| 127 | + .filter_map(|(span, _)| segment_id_of(paths, span)) |
| 128 | + .collect() |
| 129 | +} |
| 130 | + |
| 131 | +/// The segments arriving at the given pool point. |
| 132 | +pub fn end_connected(pool_index: u32, paths: &List<Path>, connectivity: &Connectivity) -> Vec<SegmentId> { |
| 133 | + spans_at(pool_index, paths, connectivity) |
| 134 | + .into_iter() |
| 135 | + .filter(|&(_, arriving)| arriving) |
| 136 | + .filter_map(|(span, _)| segment_id_of(paths, span)) |
| 137 | + .collect() |
| 138 | +} |
| 139 | + |
| 140 | +/// The handles of every segment meeting the given pool point, naming the side that faces it. |
| 141 | +pub fn all_connected(pool_index: u32, paths: &List<Path>, connectivity: &Connectivity) -> Vec<HandleId> { |
| 142 | + spans_at(pool_index, paths, connectivity) |
| 143 | + .into_iter() |
| 144 | + .filter_map(|(span, arriving)| { |
| 145 | + let segment = segment_id_of(paths, span)?; |
| 146 | + Some(if arriving { HandleId::end(segment) } else { HandleId::primary(segment) }) |
| 147 | + }) |
| 148 | + .collect() |
| 149 | +} |
| 150 | + |
| 151 | +#[cfg(test)] |
| 152 | +mod tests { |
| 153 | + use super::*; |
| 154 | + use core_types::list::{ATTR_CURVE_TYPE, Item}; |
| 155 | + |
| 156 | + fn pool(count: u32) -> List<DVec2> { |
| 157 | + (0..count) |
| 158 | + .map(|index| Item::new_from_element(DVec2::new(index as f64, 0.)).with_attribute(ATTR_ID, PointId::generate())) |
| 159 | + .collect() |
| 160 | + } |
| 161 | + |
| 162 | + fn paths_of(specs: &[(CurveType, &[u32])]) -> List<Path> { |
| 163 | + let mut paths = List::new(); |
| 164 | + |
| 165 | + for &(curve_type, references) in specs { |
| 166 | + let mut path = Path(references.iter().map(|&reference| Item::new_from_element(reference)).collect()); |
| 167 | + |
| 168 | + // Name each span on the anchor it departs from |
| 169 | + for span in 0..path.segment_count(curve_type) { |
| 170 | + path.0.set_attribute(ATTR_ID, span * curve_type.stride(), SegmentId::generate()); |
| 171 | + } |
| 172 | + |
| 173 | + paths.push(Item::new_from_element(path).with_attribute(ATTR_CURVE_TYPE, curve_type)); |
| 174 | + } |
| 175 | + |
| 176 | + paths |
| 177 | + } |
| 178 | + |
| 179 | + #[test] |
| 180 | + fn spans_name_every_segment() { |
| 181 | + let paths = paths_of(&[(CurveType::Linear, &[0, 1, 2]), (CurveType::Cubic, &[2, 3, 4, 5])]); |
| 182 | + |
| 183 | + let all: Vec<_> = spans(&paths).collect(); |
| 184 | + assert_eq!(all.len(), 3); |
| 185 | + assert_eq!(all[2], Span { path: 1, index: 0 }); |
| 186 | + |
| 187 | + for span in all { |
| 188 | + let id = segment_id_of(&paths, span).expect("every span was named"); |
| 189 | + assert_eq!(span_of_segment_id(&paths, id), Some(span)); |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + #[test] |
| 194 | + fn spans_resolve_their_endpoints_and_handles() { |
| 195 | + let paths = paths_of(&[(CurveType::Cubic, &[0, 1, 2, 3])]); |
| 196 | + let span = Span { path: 0, index: 0 }; |
| 197 | + |
| 198 | + assert_eq!(span_endpoints(&paths, span), Some((0, 3))); |
| 199 | + assert_eq!(handle_point(&paths, span, true), Some(1)); |
| 200 | + assert_eq!(handle_point(&paths, span, false), Some(2)); |
| 201 | + |
| 202 | + let linear = paths_of(&[(CurveType::Linear, &[0, 1])]); |
| 203 | + assert_eq!(handle_point(&linear, Span { path: 0, index: 0 }, true), None); |
| 204 | + } |
| 205 | + |
| 206 | + // A welded anchor reports the segments on both sides, each from the path that owns it |
| 207 | + #[test] |
| 208 | + fn connectivity_by_segment_id() { |
| 209 | + let paths = paths_of(&[(CurveType::Linear, &[0, 1]), (CurveType::Linear, &[1, 2])]); |
| 210 | + let connectivity = Connectivity::build(3, &paths); |
| 211 | + |
| 212 | + assert_eq!(start_connected(0, &paths, &connectivity).len(), 1); |
| 213 | + assert!(end_connected(0, &paths, &connectivity).is_empty()); |
| 214 | + |
| 215 | + assert_eq!(start_connected(1, &paths, &connectivity).len(), 1); |
| 216 | + assert_eq!(end_connected(1, &paths, &connectivity).len(), 1); |
| 217 | + assert_eq!(all_connected(1, &paths, &connectivity).len(), 2); |
| 218 | + } |
| 219 | + |
| 220 | + #[test] |
| 221 | + fn point_ids_resolve_both_ways() { |
| 222 | + let pool = pool(4); |
| 223 | + |
| 224 | + for index in 0..4 { |
| 225 | + let id = point_id_at(&pool, index).expect("every point was named"); |
| 226 | + assert_eq!(index_of_point_id(&pool, id), Some(index)); |
| 227 | + } |
| 228 | + } |
| 229 | +} |
0 commit comments