Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion iOverlay/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "i_overlay"
version = "8.1.1"
version = "8.1.2"
authors = ["Nail Sharipov <nailxsharipov@gmail.com>"]
edition = "2024"
rust-version = "1.88"
Expand Down
27 changes: 20 additions & 7 deletions iOverlay/src/bind/segment.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::geom::v_segment::VSegment;
use crate::vector::edge::{DataVectorEdge, DataVectorPath};
use crate::vector::edge::DataVectorPath;
use alloc::vec::Vec;
use i_float::int::number::int::IntNumber;
use i_float::int::point::IntPoint;
Expand Down Expand Up @@ -125,24 +125,37 @@ impl<I: IntNumber, D> IdSegments<I> for DataVectorPath<I, D> {
x_max: I,
clockwise: bool,
) {
fn inner<'a, D: 'a, I: IntNumber + 'a, It: Iterator<Item = &'a DataVectorEdge<I, D>>>(
fn inner<I: IntNumber, It: Iterator<Item = (IntPoint<I>, IntPoint<I>)>>(
iter: It,
buffer: &mut Vec<IdSegment<I>>,
id_data: ContourIndex,
x_min: I,
x_max: I,
) {
for vec in iter {
if vec.a.x < vec.b.x && x_min < vec.b.x && vec.a.x <= x_max {
buffer.push(IdSegment::<I>::new(id_data, vec.a, vec.b));
for (a, b) in iter {
if a.x < b.x && x_min < b.x && a.x <= x_max {
buffer.push(IdSegment::<I>::new(id_data, a, b));
}
}
}

if clockwise {
inner(self.iter(), buffer, id_data, x_min, x_max);
// Reversing edge order does not reverse their endpoints.
inner(
self.iter().map(|edge| (edge.b, edge.a)),
buffer,
id_data,
x_min,
x_max,
);
} else {
inner(self.iter().rev(), buffer, id_data, x_min, x_max);
inner(
self.iter().map(|edge| (edge.a, edge.b)),
buffer,
id_data,
x_min,
x_max,
);
}
}
}
67 changes: 31 additions & 36 deletions iOverlay/src/core/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ where
&self,
overlay_rule: OverlayRule,
buffer: &mut BooleanExtractionBuffer<I>,
) -> IntShapes<I> {
let mut shapes = self.extract_shapes_with_collinear(overlay_rule, buffer);
if !self.options.preserve_output_collinear {
shapes.simplify_contour();
}
shapes
}

// Keep shared vertices until every binding step has finished.
fn extract_shapes_with_collinear(
&self,
overlay_rule: OverlayRule,
buffer: &mut BooleanExtractionBuffer<I>,
) -> IntShapes<I> {
self.links
.filter_by_overlay_into(overlay_rule, &mut buffer.visited);
Expand All @@ -87,8 +100,8 @@ where
buffer: &mut BooleanExtractionBuffer<I>,
) -> FlatShapeHierarchy<I> {
let clockwise = self.options.output_direction == ContourDirection::Clockwise;
let shapes = self.extract_shapes(overlay_rule, buffer);
FlatShapeHierarchy::from_shapes(shapes, clockwise)
let shapes = self.extract_shapes_with_collinear(overlay_rule, buffer);
FlatShapeHierarchy::from_shapes(shapes, clockwise, self.options.preserve_output_collinear)
}

/// Extracts the flat contours from the overlay graph based on the specified overlay rule.
Expand Down Expand Up @@ -130,7 +143,6 @@ where
.reserve(buffer.visited.len().saturating_sub(buffer.points.len()));

let mut link_index = 0;
let mut anchors_already_sorted = true;
while link_index < buffer.visited.len() {
if buffer.visited.is_visited(link_index) {
link_index += 1;
Expand Down Expand Up @@ -160,10 +172,7 @@ where
&mut buffer.visited,
&mut buffer.points,
);
let (is_valid, is_modified) = buffer.points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
);
let is_valid = buffer.points.validate(self.options.min_output_area);

if !is_valid {
link_index += 1;
Expand All @@ -174,15 +183,7 @@ where

if is_hole {
let left_bottom = if clockwise { contour[1] } else { contour[0] };
let mut v_segment = contour.left_bottom_segment_from(left_bottom);

if is_modified {
let most_left = contour.left_bottom_segment();
if most_left != v_segment {
v_segment = most_left;
anchors_already_sorted = false;
}
};
let v_segment = contour.left_bottom_segment_from(left_bottom);

debug_assert!(v_segment == contour.left_bottom_segment());
let id_data = ContourIndex::new_hole(holes.len());
Expand All @@ -193,10 +194,6 @@ where
}
}

if !anchors_already_sorted {
anchors.sort_unstable_by_key(|s0| s0.v_segment.a);
}

shapes.join_sorted_holes(holes, anchors, clockwise);

shapes
Expand Down Expand Up @@ -277,16 +274,21 @@ where
&mut buffer.visited,
&mut buffer.points,
);
let (is_valid, _) = buffer.points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
);
let is_valid = buffer.points.validate(self.options.min_output_area);

if !is_valid {
link_index += 1;
continue;
}

// Flat output has no binding step. Simplify only when exporting
// the contour, and discard contours that collapse during cleanup.
if !self.options.preserve_output_collinear {
buffer.points.simplify_contour();
if buffer.points.len() < 3 {
continue;
}
}
output.add_contour(buffer.points.as_slice());
}
}
Expand Down Expand Up @@ -321,31 +323,24 @@ impl<I: IntNumber> StartPathData<I> {
}

pub(crate) trait GraphContour<I: IntNumber> {
fn validate(&mut self, min_output_area: I::WideUInt, preserve_output_collinear: bool) -> (bool, bool);
fn validate(&mut self, min_output_area: I::WideUInt) -> bool;
fn push_node_and_get_other<D>(&mut self, link: &OverlayLink<I, D>, node_id: usize) -> usize;
}

impl<I: IntNumber> GraphContour<I> for IntContour<I> {
#[inline]
fn validate(&mut self, min_output_area: I::WideUInt, preserve_output_collinear: bool) -> (bool, bool) {
let is_modified = if !preserve_output_collinear {
self.simplify_contour()
} else {
false
};

fn validate(&mut self, min_output_area: I::WideUInt) -> bool {
if self.len() < 3 {
return (false, is_modified);
return false;
}

if min_output_area == I::WideUInt::ZERO {
return (true, is_modified);
return true;
}
let area = self.unsafe_area();
let abs_area = area.unsigned_abs() >> 1;
let is_valid = abs_area >= min_output_area;

(is_valid, is_modified)
abs_area >= min_output_area
}

#[inline]
Expand Down
39 changes: 10 additions & 29 deletions iOverlay/src/core/extract_ogc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ where

let mut holes = Vec::with_capacity(hole_count_hint);
let mut anchors = Vec::with_capacity(hole_count_hint);
let mut anchors_already_sorted = true;
link_index = 0;

while link_index < buffer.visited.len() {
Expand Down Expand Up @@ -134,10 +133,7 @@ where
&mut buffer.points,
);

let (is_valid, is_modified) = buffer.points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
);
let is_valid = buffer.points.validate(self.options.min_output_area);

if !is_valid {
link_index += 1;
Expand All @@ -146,26 +142,14 @@ where
let contour = buffer.points.as_slice().to_vec();

let left_bottom = if is_main_dir_cw { contour[1] } else { contour[0] };
let mut v_segment = contour.left_bottom_segment_from(left_bottom);

if is_modified {
let most_left = contour.left_bottom_segment();
if most_left != v_segment {
v_segment = most_left;
anchors_already_sorted = false;
}
};
let v_segment = contour.left_bottom_segment_from(left_bottom);

debug_assert!(v_segment == contour.left_bottom_segment());
let id_data = ContourIndex::new_hole(holes.len());
anchors.push(IdSegment::with_segment(id_data, v_segment));
holes.push(contour);
}

if !anchors_already_sorted {
anchors.sort_unstable_by_key(|s0| s0.v_segment.a);
}

shapes.join_sorted_holes(holes, anchors, is_main_dir_cw);
}

Expand Down Expand Up @@ -224,6 +208,7 @@ where

// First, mark all edges that belong to the contour.

let mut start_link_id = start_data.link_id;
let mut end_link_id = start_data.link_id;

global_visited.visit_edge(link_id, VisitState::HullVisited);
Expand Down Expand Up @@ -263,6 +248,7 @@ where
link.a.id
};
end_link_id = end_link_id.max(link_id);
start_link_id = start_link_id.min(link_id);
contour_visited.visit_edge(link_id, VisitState::Unvisited);
global_visited.visit_edge(link_id, VisitState::HullVisited);
original_contour_len += 1;
Expand All @@ -280,10 +266,7 @@ where
points,
);

let (is_valid, _) = points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
);
let is_valid = points.validate(self.options.min_output_area);

let contour_len = points.len();

Expand All @@ -298,7 +281,8 @@ where

if contour_len < original_contour_len {
// contour has self touches
let mut link_index = start_data.link_id;
let mut link_index = start_link_id;

while link_index <= end_link_id {
if contour_visited.is_visited(link_index) {
link_index += 1;
Expand All @@ -318,21 +302,18 @@ where

// Self-touch splits can only produce holes inside this contour.

let hole_start_data = StartPathData::new(clockwise, link, left_top_link);
let hole_start_data = StartPathData::new(!clockwise, link, left_top_link);
self.find_contour(
&hole_start_data,
clockwise,
!clockwise,
VisitState::HoleVisited,
contour_visited,
points,
);

// Hole have to belong to this shape.
if let Some(shape) = shape.as_mut() {
let (is_valid, _) = points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
);
let is_valid = points.validate(self.options.min_output_area);

if !is_valid {
link_index += 1;
Expand Down
21 changes: 20 additions & 1 deletion iOverlay/src/core/hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use i_key_sort::sort::two_keys_cmp::TwoKeysAndCmpSort;
use i_shape::flat::buffer::FlatShapesBuffer;
use i_shape::int::count::PointsCount;
use i_shape::int::shape::IntShapes;
use i_shape::int::simple::Simplify;
use i_tree::Expiration;

/// A direct relationship between a hole contour and a shape nested inside it.
Expand Down Expand Up @@ -44,8 +45,26 @@ impl<I> FlatShapeHierarchy<I>
where
I: IntNumber + Expiration + SortKey,
{
pub(crate) fn from_shapes(shapes: IntShapes<I>, clockwise: bool) -> Self {
pub(crate) fn from_shapes(
mut shapes: IntShapes<I>,
clockwise: bool,
preserve_output_collinear: bool,
) -> Self {
let links = Self::bind_links(&shapes, clockwise);
if !preserve_output_collinear {
// Extracted contours have non-zero area. Removing collinear
// vertices preserves that area, so no contour or shape disappears
// and all indices in links remain valid.
for shape in &mut shapes {
for contour in shape {
contour.simplify_contour();
debug_assert!(
contour.len() >= 3,
"non-zero-area contour collapsed during simplification"
);
}
}
}
let shapes = Self::flatten(shapes);

Self { shapes, links }
Expand Down
Loading
Loading