mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 07:28:12 +08:00
Implement index datastructures
This commit is contained in:
@@ -8,6 +8,12 @@ use std::collections::HashMap;
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::zip;
|
use std::iter::zip;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Hash)]
|
||||||
|
struct PointSegmentConnections {
|
||||||
|
start_segments: Vec<usize>,
|
||||||
|
end_segments: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).
|
/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).
|
||||||
macro_rules! create_ids {
|
macro_rules! create_ids {
|
||||||
($($id:ident),*) => {
|
($($id:ident),*) => {
|
||||||
@@ -58,8 +64,8 @@ impl Hasher for NoHash {
|
|||||||
fn finish(&self) -> u64 {
|
fn finish(&self) -> u64 {
|
||||||
self.0.unwrap()
|
self.0.unwrap()
|
||||||
}
|
}
|
||||||
fn write(&mut self, _bytes: &[u8]) {
|
fn write(&mut self, bytes: &[u8]) {
|
||||||
unimplemented!()
|
self.0 = Some(u64::from_ne_bytes(bytes.try_into().unwrap_or_default()));
|
||||||
}
|
}
|
||||||
fn write_u64(&mut self, i: u64) {
|
fn write_u64(&mut self, i: u64) {
|
||||||
debug_assert!(self.0.is_none());
|
debug_assert!(self.0.is_none());
|
||||||
@@ -79,11 +85,33 @@ impl std::hash::BuildHasher for NoHashBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(from = "PointDomainSerde")]
|
||||||
/// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes.
|
/// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes.
|
||||||
pub struct PointDomain {
|
pub struct PointDomain {
|
||||||
id: Vec<PointId>,
|
id: Vec<PointId>,
|
||||||
#[serde(alias = "positions")]
|
#[serde(alias = "positions")]
|
||||||
pub(crate) position: Vec<DVec2>,
|
pub(crate) position: Vec<DVec2>,
|
||||||
|
#[serde(skip)]
|
||||||
|
id_to_index: HashMap<PointId, usize, NoHashBuilder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct PointDomainSerde {
|
||||||
|
id: Vec<PointId>,
|
||||||
|
#[serde(alias = "positions")]
|
||||||
|
position: Vec<DVec2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PointDomainSerde> for PointDomain {
|
||||||
|
fn from(serde_data: PointDomainSerde) -> Self {
|
||||||
|
let mut point_domain = Self {
|
||||||
|
id: serde_data.id,
|
||||||
|
position: serde_data.position,
|
||||||
|
id_to_index: HashMap::with_hasher(NoHashBuilder),
|
||||||
|
};
|
||||||
|
point_domain.rebuild_reverse_map();
|
||||||
|
point_domain
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for PointDomain {
|
impl Hash for PointDomain {
|
||||||
@@ -95,12 +123,24 @@ impl Hash for PointDomain {
|
|||||||
|
|
||||||
impl PointDomain {
|
impl PointDomain {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self { id: Vec::new(), position: Vec::new() }
|
Self {
|
||||||
|
id: Vec::new(),
|
||||||
|
position: Vec::new(),
|
||||||
|
id_to_index: HashMap::with_hasher(NoHashBuilder),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.id.clear();
|
self.id.clear();
|
||||||
self.position.clear();
|
self.position.clear();
|
||||||
|
self.id_to_index.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild_reverse_map(&mut self) {
|
||||||
|
self.id_to_index.clear();
|
||||||
|
for (index, &id) in self.id.iter().enumerate() {
|
||||||
|
self.id_to_index.insert(id, index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retain(&mut self, segment_domain: &mut SegmentDomain, f: impl Fn(&PointId) -> bool) {
|
pub fn retain(&mut self, segment_domain: &mut SegmentDomain, f: impl Fn(&PointId) -> bool) {
|
||||||
@@ -125,20 +165,27 @@ impl PointDomain {
|
|||||||
segment_domain.end_point.iter_mut().for_each(update_index);
|
segment_domain.end_point.iter_mut().for_each(update_index);
|
||||||
|
|
||||||
self.id.retain(f);
|
self.id.retain(f);
|
||||||
|
|
||||||
|
// Rebuild the reverse map with new indices
|
||||||
|
self.rebuild_reverse_map();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, id: PointId, position: DVec2) {
|
pub fn push(&mut self, id: PointId, position: DVec2) {
|
||||||
if self.id.contains(&id) {
|
if self.id_to_index.contains_key(&id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let index = self.id.len();
|
||||||
self.id.push(id);
|
self.id.push(id);
|
||||||
self.position.push(position);
|
self.position.push(position);
|
||||||
|
self.id_to_index.insert(id, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_unchecked(&mut self, id: PointId, position: DVec2) {
|
pub fn push_unchecked(&mut self, id: PointId, position: DVec2) {
|
||||||
|
let index = self.id.len();
|
||||||
self.id.push(id);
|
self.id.push(id);
|
||||||
self.position.push(position);
|
self.position.push(position);
|
||||||
|
self.id_to_index.insert(id, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn positions(&self) -> &[DVec2] {
|
pub fn positions(&self) -> &[DVec2] {
|
||||||
@@ -171,16 +218,25 @@ impl PointDomain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn resolve_id(&self, id: PointId) -> Option<usize> {
|
pub(crate) fn resolve_id(&self, id: PointId) -> Option<usize> {
|
||||||
self.id.iter().position(|&check_id| check_id == id)
|
self.id_to_index.get(&id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) {
|
pub fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) {
|
||||||
|
let start_index = self.id.len();
|
||||||
self.id.extend(other.id.iter().map(|id| *id_map.point_map.get(id).unwrap_or(id)));
|
self.id.extend(other.id.iter().map(|id| *id_map.point_map.get(id).unwrap_or(id)));
|
||||||
self.position.extend(other.position.iter().map(|&pos| transform.transform_point2(pos)));
|
self.position.extend(other.position.iter().map(|&pos| transform.transform_point2(pos)));
|
||||||
|
|
||||||
|
// Add new entries to reverse map
|
||||||
|
for (offset, &id) in self.id.iter().skip(start_index).enumerate() {
|
||||||
|
self.id_to_index.insert(id, start_index + offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_ids(&mut self, id_map: &IdMap) {
|
pub fn map_ids(&mut self, id_map: &IdMap) {
|
||||||
self.id.iter_mut().for_each(|id| *id = *id_map.point_map.get(id).unwrap_or(id));
|
self.id.iter_mut().for_each(|id| *id = *id_map.point_map.get(id).unwrap_or(id));
|
||||||
|
|
||||||
|
// Rebuild reverse map with new IDs
|
||||||
|
self.rebuild_reverse_map();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, transform: DAffine2) {
|
pub fn transform(&mut self, transform: DAffine2) {
|
||||||
@@ -203,7 +259,8 @@ impl PointDomain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(from = "SegmentDomainSerde")]
|
||||||
/// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes.
|
/// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes.
|
||||||
pub struct SegmentDomain {
|
pub struct SegmentDomain {
|
||||||
#[serde(alias = "ids")]
|
#[serde(alias = "ids")]
|
||||||
@@ -212,6 +269,54 @@ pub struct SegmentDomain {
|
|||||||
end_point: Vec<usize>,
|
end_point: Vec<usize>,
|
||||||
handles: Vec<BezierHandles>,
|
handles: Vec<BezierHandles>,
|
||||||
stroke: Vec<StrokeId>,
|
stroke: Vec<StrokeId>,
|
||||||
|
#[serde(skip)]
|
||||||
|
point_to_segments: HashMap<usize, PointSegmentConnections, NoHashBuilder>,
|
||||||
|
#[serde(skip)]
|
||||||
|
id_to_index: HashMap<SegmentId, usize, NoHashBuilder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for SegmentDomain {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id && self.start_point == other.start_point && self.end_point == other.end_point && self.handles == other.handles && self.stroke == other.stroke
|
||||||
|
// Note: point_to_segments is derived from other fields, so we don't compare it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for SegmentDomain {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
self.start_point.hash(state);
|
||||||
|
self.end_point.hash(state);
|
||||||
|
self.handles.hash(state);
|
||||||
|
self.stroke.hash(state);
|
||||||
|
// Note: point_to_segments is derived from other fields, so we don't hash it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct SegmentDomainSerde {
|
||||||
|
#[serde(alias = "ids")]
|
||||||
|
id: Vec<SegmentId>,
|
||||||
|
start_point: Vec<usize>,
|
||||||
|
end_point: Vec<usize>,
|
||||||
|
handles: Vec<BezierHandles>,
|
||||||
|
stroke: Vec<StrokeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SegmentDomainSerde> for SegmentDomain {
|
||||||
|
fn from(serde_data: SegmentDomainSerde) -> Self {
|
||||||
|
let mut segment_domain = Self {
|
||||||
|
id: serde_data.id,
|
||||||
|
start_point: serde_data.start_point,
|
||||||
|
end_point: serde_data.end_point,
|
||||||
|
handles: serde_data.handles,
|
||||||
|
stroke: serde_data.stroke,
|
||||||
|
point_to_segments: HashMap::with_hasher(NoHashBuilder),
|
||||||
|
id_to_index: HashMap::with_hasher(NoHashBuilder),
|
||||||
|
};
|
||||||
|
segment_domain.rebuild_point_mappings();
|
||||||
|
segment_domain
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SegmentDomain {
|
impl SegmentDomain {
|
||||||
@@ -222,6 +327,8 @@ impl SegmentDomain {
|
|||||||
end_point: Vec::new(),
|
end_point: Vec::new(),
|
||||||
handles: Vec::new(),
|
handles: Vec::new(),
|
||||||
stroke: Vec::new(),
|
stroke: Vec::new(),
|
||||||
|
point_to_segments: HashMap::with_hasher(NoHashBuilder),
|
||||||
|
id_to_index: HashMap::with_hasher(NoHashBuilder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +338,18 @@ impl SegmentDomain {
|
|||||||
self.end_point.clear();
|
self.end_point.clear();
|
||||||
self.handles.clear();
|
self.handles.clear();
|
||||||
self.stroke.clear();
|
self.stroke.clear();
|
||||||
|
self.point_to_segments.clear();
|
||||||
|
self.id_to_index.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild_point_mappings(&mut self) {
|
||||||
|
self.point_to_segments.clear();
|
||||||
|
self.id_to_index.clear();
|
||||||
|
for (segment_index, (&start, &end)) in self.start_point.iter().zip(&self.end_point).enumerate() {
|
||||||
|
self.point_to_segments.entry(start).or_default().start_segments.push(segment_index);
|
||||||
|
self.point_to_segments.entry(end).or_default().end_segments.push(segment_index);
|
||||||
|
self.id_to_index.insert(self.id[segment_index], segment_index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool, points_length: usize) {
|
pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool, points_length: usize) {
|
||||||
@@ -274,6 +393,9 @@ impl SegmentDomain {
|
|||||||
f(id)
|
f(id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Rebuild point mappings after retain operation
|
||||||
|
self.rebuild_point_mappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ids(&self) -> &[SegmentId] {
|
pub fn ids(&self) -> &[SegmentId] {
|
||||||
@@ -310,15 +432,21 @@ impl SegmentDomain {
|
|||||||
|
|
||||||
pub fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) {
|
pub fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
if self.id.contains(&id) {
|
if self.id_to_index.contains_key(&id) {
|
||||||
warn!("Tried to push an existing point to a point domain");
|
warn!("Tried to push an existing segment to a segment domain");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let segment_index = self.id.len();
|
||||||
self.id.push(id);
|
self.id.push(id);
|
||||||
self.start_point.push(start);
|
self.start_point.push(start);
|
||||||
self.end_point.push(end);
|
self.end_point.push(end);
|
||||||
self.handles.push(handles);
|
self.handles.push(handles);
|
||||||
self.stroke.push(stroke);
|
self.stroke.push(stroke);
|
||||||
|
|
||||||
|
// Update mappings
|
||||||
|
self.point_to_segments.entry(start).or_default().start_segments.push(segment_index);
|
||||||
|
self.point_to_segments.entry(end).or_default().end_segments.push(segment_index);
|
||||||
|
self.id_to_index.insert(id, segment_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> {
|
pub(crate) fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> {
|
||||||
@@ -363,19 +491,18 @@ impl SegmentDomain {
|
|||||||
|
|
||||||
/// Gets all points connected to the current one but not including the current one.
|
/// Gets all points connected to the current one but not including the current one.
|
||||||
pub(crate) fn connected_points(&self, current: usize) -> impl Iterator<Item = usize> + '_ {
|
pub(crate) fn connected_points(&self, current: usize) -> impl Iterator<Item = usize> + '_ {
|
||||||
self.start_point.iter().zip(&self.end_point).filter_map(move |(&a, &b)| match (a == current, b == current) {
|
self.point_to_segments.get(¤t)
|
||||||
(true, false) => Some(b),
|
.map(|connections| {
|
||||||
(false, true) => Some(a),
|
connections.start_segments.iter().map(move |&segment_index| self.end_point[segment_index])
|
||||||
_ => None,
|
.chain(connections.end_segments.iter().map(move |&segment_index| self.start_point[segment_index]))
|
||||||
})
|
})
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get index from ID by linear search. Takes `O(n)` time.
|
/// Get index from ID by HashMap lookup. Takes `O(1)` time.
|
||||||
fn id_to_index(&self, id: SegmentId) -> Option<usize> {
|
fn id_to_index(&self, id: SegmentId) -> Option<usize> {
|
||||||
debug_assert_eq!(self.id.len(), self.handles.len());
|
self.id_to_index.get(&id).copied()
|
||||||
debug_assert_eq!(self.id.len(), self.start_point.len());
|
|
||||||
debug_assert_eq!(self.id.len(), self.end_point.len());
|
|
||||||
self.id.iter().position(|&check_id| check_id == id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_range(&self, range: &std::ops::RangeInclusive<SegmentId>) -> Option<std::ops::RangeInclusive<usize>> {
|
fn resolve_range(&self, range: &std::ops::RangeInclusive<SegmentId>) -> Option<std::ops::RangeInclusive<usize>> {
|
||||||
@@ -394,10 +521,15 @@ impl SegmentDomain {
|
|||||||
self.end_point.extend(other.end_point.iter().map(|&index| id_map.point_offset + index));
|
self.end_point.extend(other.end_point.iter().map(|&index| id_map.point_offset + index));
|
||||||
self.handles.extend(other.handles.iter().map(|handles| handles.apply_transformation(|p| transform.transform_point2(p))));
|
self.handles.extend(other.handles.iter().map(|handles| handles.apply_transformation(|p| transform.transform_point2(p))));
|
||||||
self.stroke.extend(&other.stroke);
|
self.stroke.extend(&other.stroke);
|
||||||
|
|
||||||
|
// Rebuild point mappings after concatenation
|
||||||
|
self.rebuild_point_mappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_ids(&mut self, id_map: &IdMap) {
|
pub fn map_ids(&mut self, id_map: &IdMap) {
|
||||||
self.id.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id));
|
self.id.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id));
|
||||||
|
|
||||||
|
// Point mappings don't need rebuilding since only segment IDs changed, not point connections
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, transform: DAffine2) {
|
pub fn transform(&mut self, transform: DAffine2) {
|
||||||
@@ -408,12 +540,22 @@ impl SegmentDomain {
|
|||||||
|
|
||||||
/// Enumerate all segments that start at the point.
|
/// Enumerate all segments that start at the point.
|
||||||
pub(crate) fn start_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {
|
pub(crate) fn start_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {
|
||||||
self.start_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
|
self.point_to_segments
|
||||||
|
.get(&point)
|
||||||
|
.map(|connections| connections.start_segments.as_slice())
|
||||||
|
.unwrap_or(&[])
|
||||||
|
.iter()
|
||||||
|
.map(move |&segment_index| self.id[segment_index])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerate all segments that end at the point.
|
/// Enumerate all segments that end at the point.
|
||||||
pub(crate) fn end_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {
|
pub(crate) fn end_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {
|
||||||
self.end_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
|
self.point_to_segments
|
||||||
|
.get(&point)
|
||||||
|
.map(|connections| connections.end_segments.as_slice())
|
||||||
|
.unwrap_or(&[])
|
||||||
|
.iter()
|
||||||
|
.map(move |&segment_index| self.id[segment_index])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.
|
/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.
|
||||||
@@ -423,12 +565,18 @@ impl SegmentDomain {
|
|||||||
|
|
||||||
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
|
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
|
||||||
pub(crate) fn connected_count(&self, point: usize) -> usize {
|
pub(crate) fn connected_count(&self, point: usize) -> usize {
|
||||||
self.all_connected(point).count()
|
self.point_to_segments
|
||||||
|
.get(&point)
|
||||||
|
.map(|connections| connections.start_segments.len() + connections.end_segments.len())
|
||||||
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
|
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
|
||||||
pub(crate) fn any_connected(&self, point: usize) -> bool {
|
pub(crate) fn any_connected(&self, point: usize) -> bool {
|
||||||
self.all_connected(point).next().is_some()
|
self.point_to_segments
|
||||||
|
.get(&point)
|
||||||
|
.map(|connections| !connections.start_segments.is_empty() || !connections.end_segments.is_empty())
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterates over segments in the domain.
|
/// Iterates over segments in the domain.
|
||||||
|
|||||||
Reference in New Issue
Block a user