diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message.rs index cf35abca53..9efc67eba8 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message.rs @@ -39,6 +39,5 @@ pub enum VectorTableTab { #[default] Points, Segments, - Regions, Handles, } diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index 07c80ab549..9220ef5028 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -647,7 +647,7 @@ impl TableItemLayout for Vector { ) } fn value_page(&self, data: &mut LayoutData) -> Vec { - let table_tab_entries = [VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Regions, VectorTableTab::Handles] + let table_tab_entries = [VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Handles] .into_iter() .map(|tab| { RadioEntryData::new(format!("{tab:?}")) @@ -694,17 +694,6 @@ impl TableItemLayout for Vector { ] })); } - VectorTableTab::Regions => { - table_rows.push(column_headings(&["", "segment_range"])); - table_rows.extend(self.region_domain.iter().map(|(id, segment_range)| { - vec![ - TextLabel::new(format!("{}", id.inner())).narrow(true).widget_instance(), - TextLabel::new(format!("Segment {} – Segment {}", segment_range.start().inner(), segment_range.end().inner())) - .narrow(true) - .widget_instance(), - ] - })); - } } vec![LayoutGroup::row(table_tabs), LayoutGroup::table(table_rows, false)] diff --git a/node-graph/libraries/graphic-types/src/lib.rs b/node-graph/libraries/graphic-types/src/lib.rs index 27f338482a..9e5b18c6e8 100644 --- a/node-graph/libraries/graphic-types/src/lib.rs +++ b/node-graph/libraries/graphic-types/src/lib.rs @@ -24,7 +24,7 @@ pub mod migrations { use core_types::Color; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; - use vector_types::vector::{PointDomain, RegionDomain, SegmentDomain, misc::HandleId, style::Stroke}; + use vector_types::vector::{PointDomain, SegmentDomain, misc::HandleId, style::Stroke}; use vector_types::{GradientRamp, Vector, vector}; #[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)] @@ -111,7 +111,6 @@ pub mod migrations { pub colinear_manipulators: Vec<[HandleId; 2]>, pub point_domain: PointDomain, pub segment_domain: SegmentDomain, - pub region_domain: RegionDomain, } #[derive(serde::Deserialize)] @@ -142,7 +141,6 @@ pub mod migrations { colinear_manipulators: old.colinear_manipulators, point_domain: old.point_domain, segment_domain: old.segment_domain, - region_domain: old.region_domain, }), VectorFormat::Vector(vector) => Some(vector), VectorFormat::List(list) => list.element.into_iter().next(), diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index b7b1939577..959465e09f 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -1848,7 +1848,6 @@ fn render_vector_item_to_vello( }; }; - // Branching vectors without regions (e.g. mesh grids) need face-by-face fill rendering. let use_face_fill = element.use_face_fill(); let do_fill = |scene: &mut Scene, context: &mut RenderContext| { if use_face_fill { diff --git a/node-graph/libraries/vector-types/src/vector/algorithms/merge_by_distance.rs b/node-graph/libraries/vector-types/src/vector/algorithms/merge_by_distance.rs index 63271baa9d..e4cf8f25a5 100644 --- a/node-graph/libraries/vector-types/src/vector/algorithms/merge_by_distance.rs +++ b/node-graph/libraries/vector-types/src/vector/algorithms/merge_by_distance.rs @@ -94,10 +94,6 @@ impl MergeByDistanceExt for Vector { points_to_delete.extend(collapse_set) } - // Remove faces whose start or end segments are removed - // TODO: Adjust faces and only delete if all (or all but one) segments are removed - self.region_domain - .retain_with_region(|_, segment_range| segments_to_delete.contains(segment_range.start()) || segments_to_delete.contains(segment_range.end())); 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)); } diff --git a/node-graph/libraries/vector-types/src/vector/vector_attributes.rs b/node-graph/libraries/vector-types/src/vector/vector_attributes.rs index a4cd04a03e..f87d238d82 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_attributes.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_attributes.rs @@ -48,7 +48,7 @@ macro_rules! create_ids { }; } -create_ids! { PointId, SegmentId, RegionId } +create_ids! { PointId, SegmentId } /// A no-op hasher that allows writing u64s (the id type). #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -550,110 +550,6 @@ impl SegmentDomain { } } -#[derive(Clone, Debug, Default, PartialEq, Hash, graphene_hash::CacheHash, DynAny)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -/// Stores data which is per-region. A region is an enclosed area composed of a range of segments from the -/// [`SegmentDomain`]. In future this will be extendable at runtime with custom attributes. -pub struct RegionDomain { - #[cfg_attr(feature = "serde", serde(alias = "ids"))] - id: Vec, - segment_range: Vec>, -} - -impl RegionDomain { - pub const fn new() -> Self { - Self { - id: Vec::new(), - segment_range: Vec::new(), - } - } - - #[inline(always)] - pub fn reserve(&mut self, additional: usize) { - self.id.reserve(additional); - self.segment_range.reserve(additional); - } - - pub(crate) fn retain(&mut self, f: impl Fn(&RegionId) -> bool) { - let mut keep = self.id.iter().map(&f); - self.segment_range.retain(|_| keep.next().unwrap_or_default()); - self.id.retain(&f); - } - - /// Like [`Self::retain`] but also gives the function access to the segment range. - /// - /// Note that this function requires an allocation that `retain` avoids. - pub(crate) fn retain_with_region(&mut self, f: impl Fn(&RegionId, &std::ops::RangeInclusive) -> bool) { - let keep = self.id.iter().zip(self.segment_range.iter()).map(|(id, range)| f(id, range)).collect::>(); - let mut iter = keep.iter().copied(); - self.segment_range.retain(|_| iter.next().unwrap()); - let mut iter = keep.iter().copied(); - self.id.retain(|_| iter.next().unwrap()); - } - - pub fn push(&mut self, id: RegionId, segment_range: std::ops::RangeInclusive) { - #[cfg(debug_assertions)] - if self.id.contains(&id) { - warn!("Tried to push a duplicate region to a region domain"); - return; - } - - self.push_unchecked(id, segment_range); - } - - #[inline(always)] - pub fn push_unchecked(&mut self, id: RegionId, segment_range: std::ops::RangeInclusive) { - self.id.push(id); - self.segment_range.push(segment_range); - } - - fn _resolve_id(&self, id: RegionId) -> Option { - self.id.iter().position(|&check_id| check_id == id) - } - - pub fn next_id(&self) -> RegionId { - self.id.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO) - } - - pub(crate) fn segment_range_mut(&mut self) -> impl Iterator)> { - self.id.iter().copied().zip(self.segment_range.iter_mut()) - } - - pub fn ids(&self) -> &[RegionId] { - &self.id - } - - pub(crate) fn segment_range(&self) -> &[std::ops::RangeInclusive] { - &self.segment_range - } - - pub(crate) fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) { - self.id.extend(other.id.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id))); - self.segment_range.extend( - other - .segment_range - .iter() - .map(|range| *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end())), - ); - } - - pub(crate) fn map_ids(&mut self, id_map: &IdMap) { - self.id.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id)); - self.segment_range - .iter_mut() - .for_each(|range| *range = *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end())); - } - - /// Iterates over regions in the domain. - /// - /// Tuple is: (id, segment_range) - pub fn iter(&self) -> impl Iterator)> + '_ { - let ids = self.id.iter().copied(); - let segment_range = self.segment_range.iter().cloned(); - zip(ids, segment_range) - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct HalfEdge { pub id: SegmentId, @@ -1022,18 +918,15 @@ impl Vector { pub fn vector_new_ids_from_hash(&mut self, node_id: u64) { let point_map = self.point_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::>(); let segment_map = self.segment_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::>(); - let region_map = self.region_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::>(); let id_map = IdMap { point_offset: self.point_domain.ids().len(), point_map, segment_map, - region_map, }; self.point_domain.map_ids(&id_map); self.segment_domain.map_ids(&id_map); - self.region_domain.map_ids(&id_map); } pub fn is_branching(&self) -> bool { @@ -1048,17 +941,10 @@ impl Vector { false } - fn has_regions(&self) -> bool { - !self.region_domain.id.is_empty() - } - - /// Determines if face-by-face fill rendering should be used. - /// Branching vectors without regions (e.g. mesh grids) need face-by-face fill rendering. - /// Branching vectors with regions (e.g. boolean operation results) use even-odd fill - /// on the main stroke path instead, since face decomposition can't determine which - /// bounded faces should vs. shouldn't be filled in boolean results. + /// Determines if face-by-face fill rendering should be used. Branching vectors are meshes, whose + /// bounded faces are found and filled individually rather than filling the stroke path directly. pub fn use_face_fill(&self) -> bool { - self.is_branching() && !self.has_regions() + self.is_branching() } pub fn construct_faces(&self) -> FaceIterator<'_> { @@ -1265,5 +1151,4 @@ pub(crate) struct IdMap { pub point_offset: usize, pub point_map: HashMap, pub segment_map: HashMap, - pub region_map: HashMap, } diff --git a/node-graph/libraries/vector-types/src/vector/vector_modification.rs b/node-graph/libraries/vector-types/src/vector/vector_modification.rs index 3a9aa1ba55..fc7f6f498a 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_modification.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_modification.rs @@ -247,50 +247,12 @@ impl SegmentModification { } } -/// Represents a procedural change to the [`RegionDomain`] in [`Vector`]. -#[derive(Clone, Debug, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub(crate) struct RegionModification { - add: Vec, - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashset"))] - remove: HashSet, - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap"))] - segment_range: HashMap>, -} - -impl RegionModification { - /// Apply this modification to the specified [`RegionDomain`]. - pub fn apply(&self, region_domain: &mut RegionDomain) { - region_domain.retain(|id| !self.remove.contains(id)); - - for (id, segment_range) in region_domain.segment_range_mut() { - let Some(new) = self.segment_range.get(&id) else { continue }; - *segment_range = new.clone(); // Range inclusive is not copy - } - - for &add_id in &self.add { - let Some(segment_range) = self.segment_range.get(&add_id) else { continue }; - region_domain.push(add_id, segment_range.clone()); - } - } - - /// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`]. - pub fn create_from_vector(vector: &Vector) -> Self { - Self { - add: vector.region_domain.ids().to_vec(), - remove: HashSet::new(), - segment_range: vector.region_domain.ids().iter().copied().zip(vector.region_domain.segment_range().iter().cloned()).collect(), - } - } -} - /// Represents a procedural change to the [`Vector`]. #[derive(Clone, Debug, Default, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct VectorModification { points: PointModification, segments: SegmentModification, - regions: RegionModification, #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashset"))] add_g1_continuous: HashSet<[HandleId; 2]>, #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_hashset"))] @@ -323,7 +285,6 @@ pub enum VectorModificationType { struct ModificationCategoryCounts { points: [usize; 3], segments: [usize; 3], - regions: [usize; 3], smooth_handles: [usize; 3], } @@ -331,7 +292,7 @@ impl ModificationCategoryCounts { /// Returns the `[added, removed, modified]` totals across all categories. fn totals(&self) -> [usize; 3] { let mut totals = [0; 3]; - for [a, r, m] in [self.points, self.segments, self.regions, self.smooth_handles] { + for [a, r, m] in [self.points, self.segments, self.smooth_handles] { totals[0] += a; totals[1] += r; totals[2] += m; @@ -341,7 +302,7 @@ impl ModificationCategoryCounts { /// Iterates over each named category and its `[added, removed, modified]` counts. fn iter_categories(&self) -> impl Iterator { - [("Points", self.points), ("Segments", self.segments), ("Regions", self.regions), ("Smooth Handles", self.smooth_handles)].into_iter() + [("Points", self.points), ("Segments", self.segments), ("Smooth Handles", self.smooth_handles)].into_iter() } } @@ -351,7 +312,6 @@ impl VectorModification { // Build sets of added IDs so we can distinguish true modifications from initial values stored for newly added items let add_points: HashSet<_> = self.points.add.iter().copied().collect(); let add_segments: HashSet<_> = self.segments.add.iter().copied().collect(); - let add_regions: HashSet<_> = self.regions.add.iter().copied().collect(); let point_modifications = self.points.delta.keys().filter(|id| !add_points.contains(id)).count(); @@ -363,15 +323,9 @@ impl VectorModification { modified_segments.extend(self.segments.handle_primary.keys().filter(not_added_segment)); modified_segments.extend(self.segments.handle_end.keys().filter(not_added_segment)); - // Count unique modified region IDs across all field maps - let mut modified_regions: HashSet<&RegionId> = HashSet::with_capacity(self.regions.segment_range.len()); - let not_added_region = |id: &&RegionId| !add_regions.contains(id); - modified_regions.extend(self.regions.segment_range.keys().filter(not_added_region)); - ModificationCategoryCounts { points: [self.points.add.len(), self.points.remove.len(), point_modifications], segments: [self.segments.add.len(), self.segments.remove.len(), modified_segments.len()], - regions: [self.regions.add.len(), self.regions.remove.len(), modified_regions.len()], smooth_handles: [self.add_g1_continuous.len(), self.remove_g1_continuous.len(), 0], } } @@ -423,7 +377,6 @@ impl VectorModification { pub fn apply(&self, vector: &mut Vector) { self.points.apply(&mut vector.point_domain, &mut vector.segment_domain); self.segments.apply(&mut vector.segment_domain, &vector.point_domain); - self.regions.apply(&mut vector.region_domain); let valid = |val: &[HandleId; 2]| vector.segment_domain.ids().contains(&val[0].segment) && vector.segment_domain.ids().contains(&val[1].segment); vector @@ -497,7 +450,6 @@ impl VectorModification { Self { points: PointModification::create_from_vector(vector), segments: SegmentModification::create_from_vector(vector), - regions: RegionModification::create_from_vector(vector), add_g1_continuous: vector.colinear_manipulators.iter().copied().collect(), remove_g1_continuous: HashSet::new(), } @@ -622,8 +574,6 @@ pub(crate) struct AppendBezpath<'a> { last_point: Option, first_point_index: Option, last_point_index: Option, - first_segment_id: Option, - last_segment_id: Option, point_id: PointId, segment_id: SegmentId, vector: &'a mut Vector, @@ -636,8 +586,6 @@ impl<'a> AppendBezpath<'a> { last_point: None, first_point_index: None, last_point_index: None, - first_segment_id: None, - last_segment_id: None, point_id: vector.point_domain.next_id(), segment_id: vector.segment_domain.next_id(), vector, @@ -664,13 +612,6 @@ impl<'a> AppendBezpath<'a> { self.vector .segment_domain .push(next_segment_id, self.last_point_index.unwrap(), self.first_point_index.unwrap(), handle); - - // Create a new region. - let next_region_id = self.vector.region_domain.next_id(); - let first_segment_id = self.first_segment_id.unwrap_or(next_segment_id); - let last_segment_id = next_segment_id; - - self.vector.region_domain.push(next_region_id, first_segment_id..=last_segment_id); } fn append_segment(&mut self, end_point: Point, handle: BezierHandles) { @@ -687,9 +628,6 @@ impl<'a> AppendBezpath<'a> { // Update the states. self.last_point = Some(end_point); self.last_point_index = Some(next_point_index); - - self.first_segment_id = Some(self.first_segment_id.unwrap_or(next_segment_id)); - self.last_segment_id = Some(next_segment_id); } fn append_first_point(&mut self, point: Point) { @@ -710,8 +648,6 @@ impl<'a> AppendBezpath<'a> { self.last_point = None; self.first_point_index = None; self.last_point_index = None; - self.first_segment_id = None; - self.last_segment_id = None; } pub fn append_bezpath(vector: &'a mut Vector, bezpath: BezPath) { diff --git a/node-graph/libraries/vector-types/src/vector/vector_types.rs b/node-graph/libraries/vector-types/src/vector/vector_types.rs index 1879593003..034a2c28f3 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_types.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_types.rs @@ -21,7 +21,6 @@ pub struct Vector { pub point_domain: PointDomain, pub segment_domain: SegmentDomain, - pub region_domain: RegionDomain, } unsafe impl StaticType for Vector { type Static = Self; @@ -33,7 +32,6 @@ impl Default for Vector { colinear_manipulators: Vec::new(), point_domain: PointDomain::new(), segment_domain: SegmentDomain::new(), - region_domain: RegionDomain::new(), } } } @@ -42,7 +40,6 @@ impl graphene_hash::CacheHash for Vector { fn cache_hash(&self, state: &mut H) { self.point_domain.cache_hash(state); self.segment_domain.cache_hash(state); - self.region_domain.cache_hash(state); self.colinear_manipulators.cache_hash(state); } } @@ -82,7 +79,6 @@ impl Vector { (Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle }, (Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end }, }; - let [mut first_seg, mut last_seg] = [None, None]; let mut segment_id = self.segment_domain.next_id(); let mut last_point = None; let mut first_point = None; @@ -108,24 +104,14 @@ impl Vector { self.point_domain.push(end, pair[1].anchor); let id = segment_id.next_id(); - first_seg = Some(first_seg.unwrap_or(id)); - last_seg = Some(id); self.segment_domain.push(id, start, end_index, handles(&pair[0], &pair[1])); last_point = Some(end_index); } - if closed { - if let (Some(last), Some(first), Some(first_id), Some(last_id)) = (manipulator_groups.last(), manipulator_groups.first(), first_point, last_point) { - let id = segment_id.next_id(); - first_seg = Some(first_seg.unwrap_or(id)); - last_seg = Some(id); - self.segment_domain.push(id, last_id, first_id, handles(last, first)); - } - - if let [Some(first_seg), Some(last_seg)] = [first_seg, last_seg] { - self.region_domain.push(self.region_domain.next_id(), first_seg..=last_seg); - } + if closed && let (Some(last), Some(first), Some(first_id), Some(last_id)) = (manipulator_groups.last(), manipulator_groups.first(), first_point, last_point) { + let id = segment_id.next_id(); + self.segment_domain.push(id, last_id, first_id, handles(last, first)); } } @@ -450,24 +436,14 @@ impl Vector { .map(|&old| (old, old.generate_from_hash(collision_hash_seed))) .collect::>(); - let region_map = additional - .region_domain - .ids() - .iter() - .filter(|id| self.region_domain.ids().contains(id)) - .map(|&old| (old, old.generate_from_hash(collision_hash_seed))) - .collect::>(); - let id_map = IdMap { point_offset: self.point_domain.ids().len(), point_map, segment_map, - region_map, }; self.point_domain.concat(&additional.point_domain, transform_of_additional, &id_map); self.segment_domain.concat(&additional.segment_domain, transform_of_additional, &id_map); - self.region_domain.concat(&additional.region_domain, transform_of_additional, &id_map); self.colinear_manipulators.extend(additional.colinear_manipulators.iter().copied()); } diff --git a/node-graph/nodes/gstd/src/lib.rs b/node-graph/nodes/gstd/src/lib.rs index ecff4295dc..158110a7cd 100644 --- a/node-graph/nodes/gstd/src/lib.rs +++ b/node-graph/nodes/gstd/src/lib.rs @@ -32,7 +32,7 @@ pub mod vector { pub use vector_types::vector::algorithms; pub use vector_types::vector::click_target; pub use vector_types::vector::misc::HandleId; - pub use vector_types::vector::{PointId, RegionId, SegmentId}; + pub use vector_types::vector::{PointId, SegmentId}; pub use vector_types::vector::{deserialize_hashmap, serialize_hashmap, serialize_hashmap_as_sorted_object}; // Re-export HandleExt trait and NoHashBuilder diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 8cfc1e31ea..a77b5c98eb 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -430,10 +430,10 @@ mod tests { #[test] fn grid_disconnected_cells_test() { - // A 3x3 rectangular grid has a 2x2 arrangement of cells, each its own closed quad subpath with a fillable region. + // A 3x3 rectangular grid has a 2x2 arrangement of cells, each its own closed quad subpath. let grid = grid((), (), item(GridType::Rectangular), item(10.), item(3_u32), item(3_u32), item((30., 30.).into()), item(false)); let vector = grid.element(); - assert_eq!(vector.region_domain.ids().len(), 4); + assert_eq!(vector.stroke_manipulator_groups().filter(|(_, closed)| *closed).count(), 4); assert_eq!(vector.point_domain.ids().len(), 4 * 4); assert_eq!(vector.segment_domain.ids().len(), 4 * 4); diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 80154fc083..c8f55d90fc 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -34,7 +34,7 @@ use vector_types::vector::misc::{ bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, handles_to_segment, is_linear, point_to_dvec2, segment_to_handles, }; use vector_types::vector::style::{DashPattern, Gradient, GradientSettings, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; -use vector_types::vector::{PointDomain, PointId, RegionDomain, RegionId, SegmentDomain, SegmentId, VectorExt}; +use vector_types::vector::{PointDomain, PointId, SegmentDomain, SegmentId, VectorExt}; /// Implemented for `List` types that contain vector items reachable via mutable access. /// Used by the whole-collection Assign Colors node so it can apply to either `List` or `List`. @@ -1277,7 +1277,7 @@ fn as_vector(_: impl Ctx, value: Item) -> Item { value } -/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist. +/// Creates a polyline from a series of vector points, replacing any existing segments that may already exist. #[node_macro::node(category("Vector"), name("Points to Polyline"), path(core_types::vector))] async fn points_to_polyline(_: impl Ctx, #[implementations(Graphic, Vector)] points: Item, #[default(true)] closed: Item) -> Item { let closed = *closed.element(); @@ -1298,8 +1298,6 @@ async fn points_to_polyline(_: impl Ctx, #[implem if closed && points_count != 2 { segment_domain.push(next_id.next_id(), points_count - 1, 0, BezierHandles::Linear); - - vector.region_domain.push(RegionId::generate(), segment_domain.ids()[0]..=*segment_domain.ids().last().unwrap()); } } @@ -1337,7 +1335,7 @@ async fn relax_points( /// Builds a Voronoi diagram from the anchor points. Each point claims the region of space closest to it, and those regions tessellate the plane. Cells around the outside are clipped to the convex hull of the points so the diagram stays finite. /// -/// When Connect Cells is off, every cell becomes its own closed, fillable subpath. When on, the cells share their common points and segments, forming a single connected mesh with no fillable regions. +/// When Connect Cells is off, every cell becomes its own closed, fillable subpath. When on, the cells share their common points and segments, forming a single connected mesh. #[node_macro::node(category("Vector"), path(core_types::vector))] async fn voronoi_cells(_: impl Ctx, #[implementations(Graphic, Vector)] source: Item, connect_cells: Item) -> Item { V::map_vector_items(source, |source| { @@ -1357,7 +1355,7 @@ async fn voronoi_cells(_: impl Ctx, #[implementat /// Builds a Delaunay triangulation connecting the anchor points. It is the geometric dual of the **Voronoi** node: a mesh of triangles in which no point lies inside any triangle's circumscribed circle. /// -/// When Connect Cells is off, every triangle becomes its own closed, fillable subpath. When on, the triangles share their common points and segments, forming a single connected mesh with no fillable regions. +/// When Connect Cells is off, every triangle becomes its own closed, fillable subpath. When on, the triangles share their common points and segments, forming a single connected mesh. #[node_macro::node(category("Vector"), path(core_types::vector))] async fn triangulate(_: impl Ctx, #[implementations(Graphic, Vector)] source: Item, connect_cells: Item) -> Item { V::map_vector_items(source, |source| { @@ -1378,17 +1376,15 @@ async fn triangulate(_: impl Ctx, #[implementatio }) } -/// Replaces a vector's geometry (points, segments, and regions) with the given closed polygons, preserving its style. +/// Replaces a vector's geometry (points and segments) with the given closed polygons, preserving its style. /// -/// Without `connect_cells`, each polygon becomes its own closed subpath with a fillable region. -/// With it, coincident vertices are welded and each shared edge is emitted once, producing a connected mesh with no regions. +/// Without `connect_cells`, each polygon becomes its own closed subpath. +/// With it, coincident vertices are welded and each shared edge is emitted once, producing a connected mesh. pub(crate) fn replace_with_polygons(vector: &mut Vector, polygons: Vec>, connect_cells: bool) { let mut point_domain = PointDomain::new(); let mut segment_domain = SegmentDomain::new(); - let mut region_domain = RegionDomain::new(); let mut next_point = PointId::ZERO; let mut next_segment = SegmentId::ZERO; - let mut next_region = RegionId::ZERO; if !connect_cells { for polygon in &polygons { @@ -1402,19 +1398,10 @@ pub(crate) fn replace_with_polygons(vector: &mut Vector, polygons: Vec 1 { let handles = handles_from_manips(manips.last().unwrap().out_handle, manips[0].in_handle); - let closing_seg_id = segment_id.next_id(); - first_segment_id.get_or_insert(closing_seg_id); - vector.segment_domain.push_unchecked(closing_seg_id, prev_point_index, first_point_index, handles); - - let region_id = vector.region_domain.next_id(); - vector.region_domain.push_unchecked(region_id, first_segment_id.unwrap()..=closing_seg_id); + vector.segment_domain.push_unchecked(segment_id.next_id(), prev_point_index, first_point_index, handles); } } @@ -2999,7 +2977,6 @@ async fn morph( // Pre-allocate domain storage based on total manipulator counts across all subpaths let mut total_points = 0; let mut total_segments = 0; - let mut total_regions = 0; for ((source_manips, source_closed), (target_manips, _)) in source_subpaths.iter().zip(target_subpaths.iter()) { if source_manips.is_empty() || target_manips.is_empty() { continue; @@ -3007,20 +2984,13 @@ async fn morph( let manip_count = source_manips.len().max(target_manips.len()); total_points += manip_count; total_segments += if *source_closed { manip_count } else { manip_count.saturating_sub(1) }; - if *source_closed { - total_regions += 1; - } } for (manips, closed) in extra_source.iter().chain(extra_target.iter()) { total_points += manips.len(); total_segments += if *closed { manips.len() } else { manips.len().saturating_sub(1) }; - if *closed { - total_regions += 1; - } } vector.point_domain.reserve(total_points); vector.segment_domain.reserve(total_segments); - vector.region_domain.reserve(total_regions); let mut point_id = PointId::ZERO; let mut segment_id = SegmentId::ZERO; @@ -3644,13 +3614,14 @@ mod test { } #[tokio::test] - async fn delaunay_disconnected_cells_make_one_region_per_triangle() { + async fn delaunay_disconnected_cells_make_one_subpath_per_triangle() { let result = super::triangulate((), vector_item_from_points(&SQUARE_WITH_CENTER), item(false)).await; let vector = result.element(); // The square plus its center tessellates into four triangles, each its own closed subpath. - assert_eq!(vector.region_domain.ids().len(), 4); + assert_eq!(vector.stroke_manipulator_groups().filter(|(_, closed)| *closed).count(), 4); assert_eq!(vector.segment_domain.ids().len(), 4 * 3); assert_eq!(vector.point_domain.ids().len(), 4 * 3); + assert!(!vector.use_face_fill()); } #[tokio::test] @@ -3688,20 +3659,20 @@ mod test { async fn delaunay_shared_mesh_welds_points_and_shares_edges() { let result = super::triangulate((), vector_item_from_points(&SQUARE_WITH_CENTER), item(true)).await; let vector = result.element(); - // The connected mesh reuses the five input points and shares edges, with no fillable regions. - assert_eq!(vector.region_domain.ids().len(), 0); + // The connected mesh reuses the five input points and shares edges, so it fills face by face. + assert!(vector.use_face_fill()); assert_eq!(vector.point_domain.ids().len(), 5); // Four hull edges plus four spokes to the center, each emitted once. assert_eq!(vector.segment_domain.ids().len(), 8); } #[tokio::test] - async fn voronoi_disconnected_cells_make_a_region_per_cell() { + async fn voronoi_disconnected_cells_make_a_subpath_per_cell() { let result = super::voronoi_cells((), vector_item_from_points(&SQUARE_WITH_CENTER), item(false)).await; let vector = result.element(); - let regions = vector.region_domain.ids().len(); - assert!(regions > 0, "expected at least one Voronoi region"); - // Every region is a closed subpath, so segments and points come in matched per-region loops. + let cells = vector.stroke_manipulator_groups().filter(|(_, closed)| *closed).count(); + assert!(cells > 0, "expected at least one Voronoi cell"); + // Every cell is a closed subpath, so segments and points come in matched per-cell loops. assert_eq!(vector.segment_domain.ids().len(), vector.point_domain.ids().len()); // Clipping to the convex hull keeps all cell vertices within the input bounds. @@ -3712,10 +3683,10 @@ mod test { } #[tokio::test] - async fn voronoi_shared_mesh_has_no_regions() { + async fn voronoi_shared_mesh_uses_face_fill() { let result = super::voronoi_cells((), vector_item_from_points(&SQUARE_WITH_CENTER), item(true)).await; let vector = result.element(); - assert_eq!(vector.region_domain.ids().len(), 0); + assert!(vector.use_face_fill()); assert!(vector.segment_domain.ids().len() > 0); }