-
-
Notifications
You must be signed in to change notification settings - Fork 523
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
90a8036
commit 26c5460
Showing
8 changed files
with
374 additions
and
17 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains 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 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,94 @@ | ||
//! Collapse all nodes with all edges < distance | ||
use core::mem; | ||
|
||
use glam::DVec2; | ||
use petgraph::prelude::UnGraphMap; | ||
use rustc_hash::FxHashSet; | ||
|
||
use super::PointId; | ||
use super::VectorData; | ||
|
||
use super::IndexedVectorData; | ||
|
||
impl VectorData { | ||
/// Collapse all nodes with all edges < distance | ||
pub(crate) fn merge_by_distance(&mut self, distance: f64) { | ||
// treat self as an undirected graph with point = node, and segment = edge | ||
let mut data = IndexedVectorData::from_data(&*self); | ||
|
||
// Graph that will contain only short edges. References data graph | ||
let mut short_edges = UnGraphMap::new(); | ||
for seg_id in data.segments() { | ||
let length = data.segment_chord_length(seg_id); | ||
if length < distance { | ||
let [start, end] = data.segment_ends(seg_id); | ||
let start = data.point_graph.node_weight(start).unwrap().id; | ||
let end = data.point_graph.node_weight(end).unwrap().id; | ||
|
||
short_edges.add_node(start); | ||
short_edges.add_node(end); | ||
short_edges.add_edge(start, end, seg_id); | ||
} | ||
} | ||
|
||
// Now group connected segments - all will be collapsed to a single point. | ||
// Note: there are a few algos for this - perhaps test empirically to find fastest | ||
let collapse: Vec<FxHashSet<PointId>> = petgraph::algo::tarjan_scc(&short_edges).into_iter().map(|connected| connected.into_iter().collect()).collect(); | ||
let average_position = collapse | ||
.iter() | ||
.map(|collapse_set| { | ||
let sum: DVec2 = collapse_set.iter().map(|&id| data.point_position(id)).sum(); | ||
sum / collapse_set.len() as f64 | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
// steal the point->offset mapping before we drop the indexes | ||
let point_to_offset = mem::replace(&mut data.point_to_offset, Default::default()); | ||
drop(data); | ||
|
||
// we collect all points up and delete them at the end, so that our indices aren't invalidated | ||
let mut points_to_delete = FxHashSet::default(); | ||
let mut segments_to_delete = FxHashSet::default(); | ||
for (mut collapse_set, average_pos) in collapse.into_iter().zip(average_position.into_iter()) { | ||
// remove any segments where both endpoints are in the collapse set | ||
segments_to_delete.extend(self.segment_domain.iter().filter_map(|(id, start_offset, end_offset, _)| { | ||
let start = self.point_domain.ids()[start_offset]; | ||
let end = self.point_domain.ids()[end_offset]; | ||
if collapse_set.contains(&start) && collapse_set.contains(&end) { | ||
Some(id) | ||
} else { | ||
None | ||
} | ||
})); | ||
|
||
// Delete all points but the first (arbitrary). Set that point's position to the | ||
// average of the points, update segments to use replace all points with collapsed | ||
// point. | ||
|
||
// Unwrap: set created from connected algo will not be empty | ||
let first_id = collapse_set.iter().copied().next().unwrap(); | ||
// `first_id` the point we will collapse to. | ||
collapse_set.remove(&first_id); | ||
let first_offset = point_to_offset[&first_id]; | ||
self.point_domain.positions[first_offset] = average_pos; | ||
|
||
// look for segments with ends in collapse_set and replace them with the point we are collapsing to | ||
for (_, start_offset, end_offset, ..) in self.segment_domain.iter_mut() { | ||
let start_id = self.point_domain.ids()[*start_offset]; | ||
let end_id = self.point_domain.ids()[*end_offset]; | ||
if collapse_set.contains(&start_id) { | ||
*start_offset = first_offset; | ||
} else if collapse_set.contains(&end_id) { | ||
*end_offset = first_offset; | ||
} | ||
} | ||
|
||
points_to_delete.extend(collapse_set) | ||
} | ||
self.segment_domain.retain(|id| !segments_to_delete.contains(id), usize::MAX); | ||
self.point_domain.retain(&mut self.segment_domain, |id| !points_to_delete.contains(id)); | ||
|
||
// TODO: don't forget about faces | ||
} | ||
} |
This file contains 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 |
---|---|---|
|
@@ -12,3 +12,5 @@ mod vector_nodes; | |
pub use vector_nodes::*; | ||
|
||
pub use bezier_rs; | ||
|
||
mod merge_by_distance; |
This file contains 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 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.