mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Delete the vestigial region domain, reverting to naive logic for mesh region fill determination (#4463)
Delete the vestigial region domain, reverting the face fill decision to the bare branching test
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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<Graphic>` or `List<Vector>`.
|
||||
@@ -1277,7 +1277,7 @@ fn as_vector(_: impl Ctx, value: Item<Vector>) -> Item<Vector> {
|
||||
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<V: MapVectorItems + 'n + Send>(_: impl Ctx, #[implementations(Graphic, Vector)] points: Item<V>, #[default(true)] closed: Item<bool>) -> Item<V> {
|
||||
let closed = *closed.element();
|
||||
@@ -1298,8 +1298,6 @@ async fn points_to_polyline<V: MapVectorItems + 'n + Send>(_: 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<V: MapVectorItems + 'n + Send>(
|
||||
|
||||
/// 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<V: MapVectorItems + 'n + Send>(_: impl Ctx, #[implementations(Graphic, Vector)] source: Item<V>, connect_cells: Item<bool>) -> Item<V> {
|
||||
V::map_vector_items(source, |source| {
|
||||
@@ -1357,7 +1355,7 @@ async fn voronoi_cells<V: MapVectorItems + 'n + Send>(_: 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<V: MapVectorItems + 'n + Send>(_: impl Ctx, #[implementations(Graphic, Vector)] source: Item<V>, connect_cells: Item<bool>) -> Item<V> {
|
||||
V::map_vector_items(source, |source| {
|
||||
@@ -1378,17 +1376,15 @@ async fn triangulate<V: MapVectorItems + 'n + Send>(_: 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<Vec<DVec2>>, 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<Vec<DVec2
|
||||
}
|
||||
|
||||
let count = polygon.len();
|
||||
let mut first_segment = None;
|
||||
let mut last_segment = None;
|
||||
for i in 0..count {
|
||||
let start = base + i;
|
||||
let end = base + (i + 1) % count;
|
||||
let id = next_segment.next_id();
|
||||
first_segment.get_or_insert(id);
|
||||
last_segment = Some(id);
|
||||
segment_domain.push(id, start, end, BezierHandles::Linear);
|
||||
}
|
||||
|
||||
if let (Some(first), Some(last)) = (first_segment, last_segment) {
|
||||
region_domain.push(next_region.next_id(), first..=last);
|
||||
segment_domain.push(next_segment.next_id(), start, end, BezierHandles::Linear);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1458,7 +1445,6 @@ pub(crate) fn replace_with_polygons(vector: &mut Vector, polygons: Vec<Vec<DVec2
|
||||
|
||||
vector.point_domain = point_domain;
|
||||
vector.segment_domain = segment_domain;
|
||||
vector.region_domain = region_domain;
|
||||
}
|
||||
|
||||
/// The distance below which two mesh vertices are welded into one, scaled to the diagram's size so it tracks coordinate magnitude.
|
||||
@@ -2554,7 +2540,7 @@ async fn morph(
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes a subpath (list of manipulators) directly into a Vector's point, segment, and region domains,
|
||||
/// Pushes a subpath (list of manipulators) directly into a Vector's point and segment domains,
|
||||
/// bypassing the BezPath intermediate representation used by `append_bezpath`.
|
||||
fn push_manipulators_to_vector(vector: &mut Vector, manips: &[ManipulatorGroup], closed: bool, point_id: &mut PointId, segment_id: &mut SegmentId) {
|
||||
let Some(first) = manips.first() else { return };
|
||||
@@ -2562,28 +2548,20 @@ async fn morph(
|
||||
let first_point_index = vector.point_domain.ids().len();
|
||||
vector.point_domain.push_unchecked(point_id.next_id(), first.anchor);
|
||||
let mut prev_point_index = first_point_index;
|
||||
let mut first_segment_id = None;
|
||||
|
||||
for manip_window in manips.windows(2) {
|
||||
let point_index = vector.point_domain.ids().len();
|
||||
vector.point_domain.push_unchecked(point_id.next_id(), manip_window[1].anchor);
|
||||
|
||||
let handles = handles_from_manips(manip_window[0].out_handle, manip_window[1].in_handle);
|
||||
let seg_id = segment_id.next_id();
|
||||
first_segment_id.get_or_insert(seg_id);
|
||||
vector.segment_domain.push_unchecked(seg_id, prev_point_index, point_index, handles);
|
||||
vector.segment_domain.push_unchecked(segment_id.next_id(), prev_point_index, point_index, handles);
|
||||
|
||||
prev_point_index = point_index;
|
||||
}
|
||||
|
||||
if closed && manips.len() > 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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user