Repository navigation
Add new vector pool storage types, not yet used #4732
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
f6ff968
Add the Path and CurveType primitives for the pool-based Vector format
Keavon 2885fd8
Resolve path segments and emit contours against the Vector point pool
Keavon 361f5c5
Read a vector's paths and their curve types off its holder item
Keavon e172a36
Weave bezpath contours into pool points and stride-uniform paths
Keavon 9cc2114
Index which paths meet at each pool point for topology queries
Keavon b0f9790
Assemble render contours by following welds across split paths
Keavon c0fcd0a
Address segments as path spans while keeping stable point and segment…
Keavon 19c2e4b
Give the 'flatten_add' test its own node ID counter so it can't flake
Keavon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
node-graph/libraries/vector-types/src/vector/addressing.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| use super::connectivity::Connectivity; | ||
| use super::misc::HandleId; | ||
| use super::path::{CurveType, Path, curve_type_at}; | ||
| use super::vector_attributes::{PointId, SegmentId}; | ||
| use core_types::list::{ATTR_ID, List}; | ||
| use glam::DVec2; | ||
|
|
||
| /// A segment's place in the weave: the span of one path between two consecutive anchors. | ||
| /// Segments are never stored, so this is how one is named. | ||
| #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] | ||
| pub struct Span { | ||
| pub path: u32, | ||
| pub index: u32, | ||
| } | ||
|
|
||
| impl Span { | ||
| /// The reference index where this span's starting anchor sits. | ||
| pub fn start_reference(&self, curve_type: CurveType) -> usize { | ||
| self.index as usize * curve_type.stride() | ||
| } | ||
|
|
||
| /// The reference index where this span's ending anchor sits. | ||
| pub fn end_reference(&self, curve_type: CurveType) -> usize { | ||
| self.start_reference(curve_type) + curve_type.stride() | ||
| } | ||
| } | ||
|
|
||
| /// Every span of every path, in path order. | ||
| pub fn spans(paths: &List<Path>) -> impl Iterator<Item = Span> + '_ { | ||
| paths.iter_element_values().enumerate().flat_map(|(path_index, path)| { | ||
| let curve_type = curve_type_at(paths, path_index); | ||
| (0..path.segment_count(curve_type) as u32).map(move |index| Span { path: path_index as u32, index }) | ||
| }) | ||
| } | ||
|
|
||
| /// The stable ID naming a span, stored on the anchor reference the span departs from. A reference cell left unnamed | ||
| /// holds `SegmentId::ZERO`, which sequential IDs never take. | ||
| pub fn segment_id_of(paths: &List<Path>, span: Span) -> Option<SegmentId> { | ||
| let path = paths.element(span.path as usize)?; | ||
| let reference = span.start_reference(curve_type_at(paths, span.path as usize)); | ||
|
|
||
| path.0.attribute::<SegmentId>(ATTR_ID, reference).copied() | ||
| } | ||
|
|
||
| /// The span a segment ID names, found by scanning every span in O(references). | ||
| pub fn span_of_segment_id(paths: &List<Path>, segment: SegmentId) -> Option<Span> { | ||
| spans(paths).find(|&span| segment_id_of(paths, span) == Some(segment)) | ||
| } | ||
|
|
||
| /// The pool points a span runs between. | ||
| pub fn span_endpoints(paths: &List<Path>, span: Span) -> Option<(u32, u32)> { | ||
| let path = paths.element(span.path as usize)?; | ||
| let curve_type = curve_type_at(paths, span.path as usize); | ||
| let start = path.0.element(span.start_reference(curve_type))?; | ||
| let end = path.0.element(span.end_reference(curve_type))?; | ||
|
|
||
| Some((*start, *end)) | ||
| } | ||
|
|
||
| /// The pool point a handle of the given span occupies, absent on a linear span that has no handles. | ||
| pub fn handle_point(paths: &List<Path>, span: Span, primary: bool) -> Option<u32> { | ||
| let path = paths.element(span.path as usize)?; | ||
| let curve_type = curve_type_at(paths, span.path as usize); | ||
| if curve_type == CurveType::Linear { | ||
| return None; | ||
| } | ||
|
|
||
| // A quadratic span's single handle answers to either side | ||
| let offset = if primary || curve_type == CurveType::Quadratic { 1 } else { 2 }; | ||
|
|
||
| path.0.element(span.start_reference(curve_type) + offset).copied() | ||
| } | ||
|
|
||
| /// The stable ID of a pool point. | ||
| pub fn point_id_at(pool: &List<DVec2>, index: u32) -> Option<PointId> { | ||
| pool.attribute::<PointId>(ATTR_ID, index as usize).copied() | ||
| } | ||
|
|
||
| /// The pool index a point ID names. | ||
| pub fn index_of_point_id(pool: &List<DVec2>, point: PointId) -> Option<u32> { | ||
| pool.iter_attribute_values::<PointId>(ATTR_ID)?.position(|&id| id == point).map(|index| index as u32) | ||
| } | ||
|
|
||
| /// The spans meeting at a pool point, each paired with whether the point is where that span ends. | ||
| pub fn spans_at(pool_index: u32, paths: &List<Path>, connectivity: &Connectivity) -> Vec<(Span, bool)> { | ||
| let mut found = Vec::new(); | ||
|
|
||
| for reference in connectivity.references(pool_index) { | ||
| let path_index = reference.path as usize; | ||
| let Some(path) = paths.element(path_index) else { continue }; | ||
|
|
||
| let curve_type = curve_type_at(paths, path_index); | ||
| if !path.is_anchor_reference(reference.index as usize, curve_type) { | ||
| continue; | ||
| } | ||
|
|
||
| // An anchor departs the span starting at it and arrives on the one before it | ||
| let anchor = reference.index as usize / curve_type.stride(); | ||
| if anchor < path.segment_count(curve_type) { | ||
| found.push(( | ||
| Span { | ||
| path: reference.path, | ||
| index: anchor as u32, | ||
| }, | ||
| false, | ||
| )); | ||
| } | ||
| if let Some(previous) = anchor.checked_sub(1) { | ||
| found.push(( | ||
| Span { | ||
| path: reference.path, | ||
| index: previous as u32, | ||
| }, | ||
| true, | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| found | ||
| } | ||
|
|
||
| /// The segments departing the given pool point. | ||
| pub fn start_connected(pool_index: u32, paths: &List<Path>, connectivity: &Connectivity) -> Vec<SegmentId> { | ||
| spans_at(pool_index, paths, connectivity) | ||
| .into_iter() | ||
| .filter(|&(_, arriving)| !arriving) | ||
| .filter_map(|(span, _)| segment_id_of(paths, span)) | ||
| .collect() | ||
| } | ||
|
|
||
| /// The segments arriving at the given pool point. | ||
| pub fn end_connected(pool_index: u32, paths: &List<Path>, connectivity: &Connectivity) -> Vec<SegmentId> { | ||
| spans_at(pool_index, paths, connectivity) | ||
| .into_iter() | ||
| .filter(|&(_, arriving)| arriving) | ||
| .filter_map(|(span, _)| segment_id_of(paths, span)) | ||
| .collect() | ||
| } | ||
|
|
||
| /// The handles of every segment meeting the given pool point, naming the side that faces it. | ||
| pub fn all_connected(pool_index: u32, paths: &List<Path>, connectivity: &Connectivity) -> Vec<HandleId> { | ||
| spans_at(pool_index, paths, connectivity) | ||
| .into_iter() | ||
| .filter_map(|(span, arriving)| { | ||
| let segment = segment_id_of(paths, span)?; | ||
| Some(if arriving { HandleId::end(segment) } else { HandleId::primary(segment) }) | ||
| }) | ||
| .collect() | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use core_types::list::{ATTR_CURVE_TYPE, Item}; | ||
|
|
||
| fn pool(count: u32) -> List<DVec2> { | ||
| (0..count) | ||
| .map(|index| Item::new_from_element(DVec2::new(index as f64, 0.)).with_attribute(ATTR_ID, PointId::generate())) | ||
| .collect() | ||
| } | ||
|
|
||
| fn paths_of(specs: &[(CurveType, &[u32])]) -> List<Path> { | ||
| let mut paths = List::new(); | ||
|
|
||
| for &(curve_type, references) in specs { | ||
| let mut path = Path(references.iter().map(|&reference| Item::new_from_element(reference)).collect()); | ||
|
|
||
| // Name each span on the anchor it departs from | ||
| for span in 0..path.segment_count(curve_type) { | ||
| path.0.set_attribute(ATTR_ID, span * curve_type.stride(), SegmentId::generate()); | ||
| } | ||
|
|
||
| paths.push(Item::new_from_element(path).with_attribute(ATTR_CURVE_TYPE, curve_type)); | ||
| } | ||
|
|
||
| paths | ||
| } | ||
|
|
||
| #[test] | ||
| fn spans_name_every_segment() { | ||
| let paths = paths_of(&[(CurveType::Linear, &[0, 1, 2]), (CurveType::Cubic, &[2, 3, 4, 5])]); | ||
|
|
||
| let all: Vec<_> = spans(&paths).collect(); | ||
| assert_eq!(all.len(), 3); | ||
| assert_eq!(all[2], Span { path: 1, index: 0 }); | ||
|
|
||
| for span in all { | ||
| let id = segment_id_of(&paths, span).expect("every span was named"); | ||
| assert_eq!(span_of_segment_id(&paths, id), Some(span)); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn spans_resolve_their_endpoints_and_handles() { | ||
| let paths = paths_of(&[(CurveType::Cubic, &[0, 1, 2, 3])]); | ||
| let span = Span { path: 0, index: 0 }; | ||
|
|
||
| assert_eq!(span_endpoints(&paths, span), Some((0, 3))); | ||
| assert_eq!(handle_point(&paths, span, true), Some(1)); | ||
| assert_eq!(handle_point(&paths, span, false), Some(2)); | ||
|
|
||
| let linear = paths_of(&[(CurveType::Linear, &[0, 1])]); | ||
| assert_eq!(handle_point(&linear, Span { path: 0, index: 0 }, true), None); | ||
| } | ||
|
|
||
| // A welded anchor reports the segments on both sides, each from the path that owns it | ||
| #[test] | ||
| fn connectivity_by_segment_id() { | ||
| let paths = paths_of(&[(CurveType::Linear, &[0, 1]), (CurveType::Linear, &[1, 2])]); | ||
| let connectivity = Connectivity::build(3, &paths); | ||
|
|
||
| assert_eq!(start_connected(0, &paths, &connectivity).len(), 1); | ||
| assert!(end_connected(0, &paths, &connectivity).is_empty()); | ||
|
|
||
| assert_eq!(start_connected(1, &paths, &connectivity).len(), 1); | ||
| assert_eq!(end_connected(1, &paths, &connectivity).len(), 1); | ||
| assert_eq!(all_connected(1, &paths, &connectivity).len(), 2); | ||
| } | ||
|
|
||
| #[test] | ||
| fn point_ids_resolve_both_ways() { | ||
| let pool = pool(4); | ||
|
|
||
| for index in 0..4 { | ||
| let id = point_id_at(&pool, index).expect("every point was named"); | ||
| assert_eq!(index_of_point_id(&pool, id), Some(index)); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.