diff --git a/iOverlay/Cargo.toml b/iOverlay/Cargo.toml index f48e6420..5dce7d66 100644 --- a/iOverlay/Cargo.toml +++ b/iOverlay/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "i_overlay" -version = "7.0.1" +version = "7.0.2" authors = ["Nail Sharipov "] edition = "2024" rust-version = "1.88" @@ -15,7 +15,7 @@ categories = ["algorithms", "graphics", "science::geo", "mathematics", "no-std"] i_float = { version = "^3.0.0" } i_shape = { version = "^3.0.0" } i_tree = { version = "^0.19.0" } -i_key_sort = { version = "^0.10.1" } +i_key_sort = { version = "^0.10.3" } #i_float = { path = "../../iFloat"} #i_shape = { path = "../../iShape"} diff --git a/iOverlay/src/bind/solver.rs b/iOverlay/src/bind/solver.rs index 90e793ce..5ad69e62 100644 --- a/iOverlay/src/bind/solver.rs +++ b/iOverlay/src/bind/solver.rs @@ -1,10 +1,11 @@ use crate::bind::segment::{ContourIndex, IdSegment, IdSegments}; -use crate::geom::v_segment::VSegment; +use crate::geom::v_segment::{BottomSegment, VSegment}; use crate::util::log::Int; use alloc::vec; use alloc::vec::Vec; use core::cmp::Ordering; use i_float::int::number::int::IntNumber; +use i_float::int::point::IntPoint; use i_key_sort::sort::key::SortKey; use i_key_sort::sort::two_keys_cmp::TwoKeysAndCmpSort; use i_shape::int::path::IntPath; @@ -198,26 +199,39 @@ impl JoinHoles for Vec> { pub(crate) trait LeftBottomSegment { fn left_bottom_segment(&self) -> VSegment; + fn left_bottom_segment_from(&self, a: IntPoint) -> VSegment; } impl LeftBottomSegment for IntContour { fn left_bottom_segment(&self) -> VSegment { - let mut index = 0; let mut a = *self.first().unwrap(); - for (i, &p) in self.iter().enumerate().skip(1) { + for &p in self.iter().skip(1) { if p < a { a = p; - index = i; } } + + self.left_bottom_segment_from(a) + } + + fn left_bottom_segment_from(&self, a: IntPoint) -> VSegment { let n = self.len(); - let b0 = self[(index + 1) % n]; - let b1 = self[(index + n - 1) % n]; + let mut result: Option> = None; - let s0 = VSegment { a, b: b0 }; - let s1 = VSegment { a, b: b1 }; + for (i, &p) in self.iter().enumerate() { + if p != a { + continue; + } + + // Self-touching contours can visit the left-bottom point several times. + // Check every incident edge at that point and keep the lowest anchor edge. + let b0 = self[(i + 1) % n]; + let b1 = self[(i + n - 1) % n]; + result.update_if_under(VSegment { a, b: b0 }); + result.update_if_under(VSegment { a, b: b1 }); + } - if s0.is_under_segment(&s1) { s0 } else { s1 } + result.unwrap_or(VSegment { a, b: a }) } } diff --git a/iOverlay/src/core/extract.rs b/iOverlay/src/core/extract.rs index 112b3af4..e5c3ace7 100644 --- a/iOverlay/src/core/extract.rs +++ b/iOverlay/src/core/extract.rs @@ -6,7 +6,6 @@ use crate::core::link::OverlayLink; use crate::core::link::OverlayLinkFilter; use crate::core::nearest_vector::NearestVector; use crate::core::overlay::ContourDirection; -use crate::geom::v_segment::VSegment; use crate::i_shape::flat::buffer::FlatContoursBuffer; use alloc::vec; use alloc::vec::Vec; @@ -158,17 +157,9 @@ where let contour = buffer.points.as_slice().to_vec(); if is_hole { - let mut v_segment = if clockwise { - VSegment { - a: contour[1], - b: contour[2], - } - } else { - VSegment { - a: contour[0], - b: contour[contour.len() - 1], - } - }; + 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 { diff --git a/iOverlay/src/core/extract_ogc.rs b/iOverlay/src/core/extract_ogc.rs index 6033861d..d4dcda86 100644 --- a/iOverlay/src/core/extract_ogc.rs +++ b/iOverlay/src/core/extract_ogc.rs @@ -6,7 +6,6 @@ use crate::core::extract::{ use crate::core::graph::OverlayGraph; use crate::core::overlay::ContourDirection; use crate::core::overlay_rule::OverlayRule; -use crate::geom::v_segment::VSegment; use alloc::vec; use alloc::vec::Vec; use i_float::int::number::int::IntNumber; @@ -147,17 +146,8 @@ where } let contour = buffer.points.as_slice().to_vec(); - let mut v_segment = if is_main_dir_cw { - VSegment { - a: contour[1], - b: contour[2], - } - } else { - VSegment { - a: contour[0], - b: contour[contour.len() - 1], - } - }; + 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(); diff --git a/iOverlay/src/geom/v_segment.rs b/iOverlay/src/geom/v_segment.rs index ee3b2450..f8449d9a 100644 --- a/iOverlay/src/geom/v_segment.rs +++ b/iOverlay/src/geom/v_segment.rs @@ -50,6 +50,23 @@ impl VSegment { } } +pub(crate) trait BottomSegment { + fn update_if_under(&mut self, segment: VSegment); +} + +impl BottomSegment for Option> { + #[inline(always)] + fn update_if_under(&mut self, segment: VSegment) { + if let Some(best) = self { + if segment.is_under_segment(&best) { + *best = segment + } + } else { + *self = Some(segment); + } + } +} + impl From> for VSegment { #[inline(always)] fn from(seg: XSegment) -> Self { diff --git a/iOverlay/src/vector/extract.rs b/iOverlay/src/vector/extract.rs index 903dd735..ef7f7053 100644 --- a/iOverlay/src/vector/extract.rs +++ b/iOverlay/src/vector/extract.rs @@ -6,7 +6,7 @@ use crate::core::graph::OverlayGraph; use crate::core::link::{OverlayLink, OverlayLinkFilter}; use crate::core::overlay::ContourDirection; use crate::core::overlay_rule::OverlayRule; -use crate::geom::v_segment::VSegment; +use crate::geom::v_segment::{BottomSegment, VSegment}; use crate::segm::segment::SegmentFill; use crate::vector::edge::{DataVectorEdge, DataVectorPath, DataVectorShape}; use crate::vector::simplify::VectorSimplify; @@ -92,17 +92,9 @@ where } if is_hole { - let mut v_segment = if clockwise { - VSegment { - a: contour[1].a, - b: contour[2].a, - } - } else { - VSegment { - a: contour[0].a, - b: contour[contour.len() - 1].a, - } - }; + let left_bottom = if clockwise { contour[1].a } else { contour[0].a }; + let mut v_segment = most_left_bottom_from(&contour, left_bottom); + if is_modified { let most_left = most_left_bottom(&contour); if most_left != v_segment { @@ -308,22 +300,35 @@ where #[inline] fn most_left_bottom(path: &DataVectorPath) -> VSegment { - let mut index = 0; let mut a = path[0].a; - for (i, e) in path.iter().enumerate().skip(1) { + for e in path.iter().skip(1) { if e.a < a { a = e.a; - index = i; } } + + most_left_bottom_from(path, a) +} + +#[inline] +fn most_left_bottom_from(path: &DataVectorPath, a: IntPoint) -> VSegment { let n = path.len(); - let b0 = path[index].b; - let b1 = path[(index + n - 1) % n].a; + let mut result: Option> = None; - let s0 = VSegment { a, b: b0 }; - let s1 = VSegment { a, b: b1 }; + for (i, edge) in path.iter().enumerate() { + if edge.a != a { + continue; + } + + // Self-touching contours can visit the left-bottom point several times. + // Check every incident edge at that point and keep the lowest anchor edge. + let b0 = edge.b; + let b1 = path[(i + n - 1) % n].a; + result.update_if_under(VSegment { a, b: b0 }); + result.update_if_under(VSegment { a, b: b1 }); + } - if s0.is_under_segment(&s1) { s0 } else { s1 } + result.unwrap_or(VSegment { a, b: a }) } #[inline] diff --git a/iOverlay/tests/crash_tests.rs b/iOverlay/tests/crash_tests.rs index b4b17202..ae04ba35 100644 --- a/iOverlay/tests/crash_tests.rs +++ b/iOverlay/tests/crash_tests.rs @@ -5,8 +5,9 @@ mod tests { use i_float::int::point::IntPoint; use i_key_sort::sort::key::SortKey; use i_overlay::core::fill_rule::FillRule; - use i_overlay::core::overlay::{Overlay, ShapeType}; + use i_overlay::core::overlay::{IntOverlayOptions, Overlay, ShapeType}; use i_overlay::core::overlay_rule::OverlayRule; + use i_overlay::core::simplify::Simplify; use i_overlay::core::solver::{Precision, Solver, Strategy}; use i_overlay::float::overlay::{FloatOverlay, OverlayOptions}; use i_shape::base::data::{Path, Shape}; @@ -179,4 +180,17 @@ mod tests { let _ = overlay.overlay(OverlayRule::Subject, FillRule::NonZero); } + + #[test] + fn test_06() { + let shape = int_shape![ + [[0, 0], [8, 0], [8, 8], [0, 8]], + [[2, 2], [2, 6], [6, 6], [6, 2], [2, 2], [5, 3], [3, 5]], + [[10, 0], [12, 0], [12, 2], [10, 2]], + ]; + + let result = shape.simplify(FillRule::NonZero, IntOverlayOptions::default()); + + assert_eq!(result.len(), 2); + } }