diff --git a/examples/overlay_editor/Cargo.toml b/examples/overlay_editor/Cargo.toml index 7d2e313..ef6cd6e 100644 --- a/examples/overlay_editor/Cargo.toml +++ b/examples/overlay_editor/Cargo.toml @@ -26,10 +26,10 @@ log = "0.4.22" console_log = "^1.0.0" console_error_panic_hook = "^0" -i_mesh = "^0.3.0" -i_triangle = { version = "^0.35.0", features = ["serde"] } +#i_mesh = "^0.4.0" +#i_triangle = { version = "^0.35.0", features = ["serde"] } -#i_triangle = { path = "../../../../iShape/iTriangle/iTriangle", default-features = true, features = ["serde"] } -#i_mesh = { path = "../../../../iShape/iMesh/iMesh" } +i_triangle = { path = "../../../../iShape/iTriangle/iTriangle", default-features = true, features = ["serde"] } +i_mesh = { path = "../../../../iShape/iMesh/iMesh" } #ICED_BACKEND=wgpu cargo r -r diff --git a/examples/overlay_editor/src/app/boolean/content.rs b/examples/overlay_editor/src/app/boolean/content.rs index 9919c43..c75803f 100644 --- a/examples/overlay_editor/src/app/boolean/content.rs +++ b/examples/overlay_editor/src/app/boolean/content.rs @@ -39,7 +39,7 @@ pub(crate) enum BooleanMessage { } impl EditorApp { - fn boolean_sidebar(&self) -> Column { + fn boolean_sidebar(&self) -> Column<'_, AppMessage> { let count = self.app_resource.boolean.count; let mut column = Column::new().push( Space::new() diff --git a/examples/overlay_editor/src/app/main.rs b/examples/overlay_editor/src/app/main.rs index 326b709..cc3c5ab 100644 --- a/examples/overlay_editor/src/app/main.rs +++ b/examples/overlay_editor/src/app/main.rs @@ -6,7 +6,6 @@ use crate::app::string::content::StringMessage; use crate::app::string::content::StringState; use crate::app::stroke::content::StrokeMessage; use crate::app::stroke::content::StrokeState; -use iced::event::Event as MainEvent; use iced::keyboard::key::Named; use iced::keyboard::Key; use iced::widget::{rule, Space}; @@ -64,7 +63,6 @@ pub(crate) enum AppMessage { String(StringMessage), Stroke(StrokeMessage), Outline(OutlineMessage), - EventOccurred(MainEvent), NextTest, PrevTest, } @@ -111,7 +109,6 @@ impl EditorApp { MainAction::Stroke => self.stroke_prev_test(), MainAction::Outline => self.outline_prev_test(), } - _ => {} } Task::none() diff --git a/examples/overlay_editor/src/app/stroke/content.rs b/examples/overlay_editor/src/app/stroke/content.rs index ec71743..b9f903a 100644 --- a/examples/overlay_editor/src/app/stroke/content.rs +++ b/examples/overlay_editor/src/app/stroke/content.rs @@ -13,6 +13,7 @@ use i_triangle::i_overlay::i_float::int::rect::IntRect; use iced::widget::{scrollable, Button, Column, Container, Row, Space, Text}; use iced::{Alignment, Length, Padding, Size, Vector}; use std::collections::HashMap; +use std::rc::Rc; pub(crate) struct StrokeState { pub(crate) test: usize, @@ -307,7 +308,7 @@ impl StrokeState { [ 3.0, 0.0], [-1.0, 2.0], ]; - style = style.start_cap(LineCap::Custom(points)) + style = style.start_cap(LineCap::Custom(Rc::from(points))) } } @@ -326,7 +327,7 @@ impl StrokeState { [ 3.0, 0.0], [-1.0, 2.0], ]; - style = style.end_cap(LineCap::Custom(points)) + style = style.end_cap(LineCap::Custom(Rc::from(points))) } } diff --git a/examples/overlay_editor/src/draw/shape.rs b/examples/overlay_editor/src/draw/shape.rs index 8447ff2..970a136 100644 --- a/examples/overlay_editor/src/draw/shape.rs +++ b/examples/overlay_editor/src/draw/shape.rs @@ -71,7 +71,7 @@ impl ShapeWidget { let validation = Validation::with_fill_rule(fill_rule.unwrap_or_default()); let triangulation = IntTriangulator::new(shapes.points_count(), validation, Default::default()) - .triangulate_shapes(shapes, false); + .triangulate_shapes(shapes); Self::fill_mesh_for_triangulation(triangulation, camera, offset, color) } @@ -91,7 +91,7 @@ impl ShapeWidget { let validation = Validation::with_fill_rule(fill_rule.unwrap_or_default()); let triangulation = IntTriangulator::new(paths.points_count(), validation, Default::default()) - .triangulate_shape(paths, false); + .triangulate_shape(paths); // let triangulation = paths.triangulate().into_triangulation(); diff --git a/iOverlay/Cargo.toml b/iOverlay/Cargo.toml index fefb081..0391d0a 100644 --- a/iOverlay/Cargo.toml +++ b/iOverlay/Cargo.toml @@ -34,7 +34,7 @@ allow_multithreading = ["dep:rayon"] [dev-dependencies] serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" -rand = { version = "~0.9", features = ["alloc"] } +rand = { version = "~0.10", features = ["alloc"] } #i_float = { path = "../../iFloat", features = ["serde"] } #i_shape = { path = "../../iShape", features = ["serde"] } i_float = { version = "~1.16.0", features = ["serde"] } diff --git a/iOverlay/src/build/boolean.rs b/iOverlay/src/build/boolean.rs index 8cc136e..c6b3a92 100644 --- a/iOverlay/src/build/boolean.rs +++ b/iOverlay/src/build/boolean.rs @@ -274,19 +274,6 @@ impl BooleanFillFilter for SegmentFill { } impl OverlayLinkFilter for [OverlayLink] { - #[inline] - fn filter_by_overlay(&self, overlay_rule: OverlayRule) -> Vec { - match overlay_rule { - OverlayRule::Subject => filter_subject(self), - OverlayRule::Clip => filter_clip(self), - OverlayRule::Intersect => filter_intersect(self), - OverlayRule::Union => filter_union(self), - OverlayRule::Difference => filter_difference(self), - OverlayRule::Xor => filter_xor(self), - OverlayRule::InverseDifference => filter_inverse_difference(self), - } - } - #[inline] fn filter_by_overlay_into(&self, overlay_rule: OverlayRule, buffer: &mut Vec) { match overlay_rule { @@ -301,62 +288,6 @@ impl OverlayLinkFilter for [OverlayLink] { } } -#[inline] -fn filter_subject(links: &[OverlayLink]) -> Vec { - links - .iter() - .map(|link| VisitState::new(!link.fill.is_subject())) - .collect() -} - -#[inline] -fn filter_clip(links: &[OverlayLink]) -> Vec { - links - .iter() - .map(|link| VisitState::new(!link.fill.is_clip())) - .collect() -} - -#[inline] -fn filter_intersect(links: &[OverlayLink]) -> Vec { - links - .iter() - .map(|link| VisitState::new(!link.fill.is_intersect())) - .collect() -} - -#[inline] -fn filter_union(links: &[OverlayLink]) -> Vec { - links - .iter() - .map(|link| VisitState::new(!link.fill.is_union())) - .collect() -} - -#[inline] -fn filter_difference(links: &[OverlayLink]) -> Vec { - links - .iter() - .map(|link| VisitState::new(!link.fill.is_difference())) - .collect() -} - -#[inline] -fn filter_inverse_difference(links: &[OverlayLink]) -> Vec { - links - .iter() - .map(|link| VisitState::new(!link.fill.is_inverse_difference())) - .collect() -} - -#[inline] -fn filter_xor(links: &[OverlayLink]) -> Vec { - links - .iter() - .map(|link| VisitState::new(!link.fill.is_xor())) - .collect() -} - #[inline] fn filter_subject_into(links: &[OverlayLink], buffer: &mut Vec) { buffer.clear(); diff --git a/iOverlay/src/build/mod.rs b/iOverlay/src/build/mod.rs index c757ecb..994e989 100644 --- a/iOverlay/src/build/mod.rs +++ b/iOverlay/src/build/mod.rs @@ -1,7 +1,7 @@ pub(crate) mod boolean; pub(crate) mod builder; mod graph; -mod offset; +pub(crate) mod offset; pub(crate) mod string; pub(crate) mod sweep; mod util; diff --git a/iOverlay/src/build/offset.rs b/iOverlay/src/build/offset.rs index 6b1b2e9..32369d5 100644 --- a/iOverlay/src/build/offset.rs +++ b/iOverlay/src/build/offset.rs @@ -9,12 +9,12 @@ use crate::segm::segment::{Segment, SegmentFill}; impl GraphBuilder { #[inline] - pub(crate) fn build_offset( + pub(crate) fn build_offset>( &mut self, solver: &Solver, segments: &[Segment], ) -> OffsetGraph<'_> { - self.build_fills_with_strategy::(solver, segments); + self.build_fills_with_strategy::(solver, segments); self.build_links_all(segments); self.offset_graph(solver) } @@ -29,10 +29,11 @@ impl GraphBuilder { } } -struct SubjectOffsetStrategy; +pub(crate) struct PositiveSubjectOffsetStrategy; +pub(crate) struct NegativeSubjectOffsetStrategy; const BOLD_BIT: usize = 2; -impl FillStrategy for SubjectOffsetStrategy { +impl FillStrategy for PositiveSubjectOffsetStrategy { #[inline(always)] fn add_and_fill(this: ShapeCountOffset, bot: ShapeCountOffset) -> (ShapeCountOffset, SegmentFill) { let top_subj = bot.subj + this.subj; @@ -53,6 +54,27 @@ impl FillStrategy for SubjectOffsetStrategy { } } +impl FillStrategy for NegativeSubjectOffsetStrategy { + #[inline(always)] + fn add_and_fill(this: ShapeCountOffset, bot: ShapeCountOffset) -> (ShapeCountOffset, SegmentFill) { + let top_subj = bot.subj + this.subj; + let bot_subj = bot.subj; + + let subj_top = (top_subj < 0) as SegmentFill; + let subj_bot = (bot_subj < 0) as SegmentFill; + + let bold = this.bold as SegmentFill; + + let fill = subj_top | (subj_bot << 1) | (bold << BOLD_BIT); + let top = ShapeCountOffset { + subj: top_subj, + bold: false, + }; // bold not need + + (top, fill) + } +} + impl OverlayLink { #[inline(always)] pub(crate) fn is_bold(&self) -> bool { diff --git a/iOverlay/src/core/extract.rs b/iOverlay/src/core/extract.rs index a0d0cc9..5e343f4 100644 --- a/iOverlay/src/core/extract.rs +++ b/iOverlay/src/core/extract.rs @@ -183,7 +183,7 @@ impl OverlayGraph<'_> { start_data: &StartPathData, clockwise: bool, visited_state: VisitState, - visited: &mut Vec, + visited: &mut [VisitState], points: &mut Vec, ) { let mut link_id = start_data.link_id; @@ -196,7 +196,7 @@ impl OverlayGraph<'_> { // Find a closed tour while node_id != last_node_id { - link_id = GraphUtil::next_link(self.links, self.nodes, link_id, node_id, clockwise, &visited); + link_id = GraphUtil::next_link(self.links, self.nodes, link_id, node_id, clockwise, visited); let link = unsafe { // Safety: `link_id` is always derived from a previous in-bounds index or diff --git a/iOverlay/src/core/extract_ogc.rs b/iOverlay/src/core/extract_ogc.rs index 2ed0f22..07de181 100644 --- a/iOverlay/src/core/extract_ogc.rs +++ b/iOverlay/src/core/extract_ogc.rs @@ -216,8 +216,8 @@ impl OverlayGraph<'_> { &self, start_data: &StartPathData, clockwise: bool, - global_visited: &mut Vec, - contour_visited: &mut Vec, + global_visited: &mut [VisitState], + contour_visited: &mut [VisitState], points: &mut Vec, ) -> Option { let mut link_id = start_data.link_id; @@ -266,7 +266,7 @@ impl OverlayGraph<'_> { points.reserve_capacity(original_contour_len); self.find_contour( - &start_data, + start_data, !clockwise, VisitState::HullVisited, contour_visited, diff --git a/iOverlay/src/core/link.rs b/iOverlay/src/core/link.rs index ca11646..bd67256 100644 --- a/iOverlay/src/core/link.rs +++ b/iOverlay/src/core/link.rs @@ -29,6 +29,5 @@ impl OverlayLink { } pub(crate) trait OverlayLinkFilter { - fn filter_by_overlay(&self, fill_rule: OverlayRule) -> Vec; fn filter_by_overlay_into(&self, overlay_rule: OverlayRule, buffer: &mut Vec); } diff --git a/iOverlay/src/mesh/extract.rs b/iOverlay/src/mesh/extract.rs index 8d8dff0..ca8aa49 100644 --- a/iOverlay/src/mesh/extract.rs +++ b/iOverlay/src/mesh/extract.rs @@ -1,6 +1,8 @@ use crate::bind::segment::{ContourIndex, IdSegment}; use crate::bind::solver::{JoinHoles, LeftBottomSegment}; -use crate::core::extract::{GraphContour, GraphUtil, StartPathData, Visit, VisitState}; +use crate::core::extract::{ + BooleanExtractionBuffer, GraphContour, GraphUtil, StartPathData, Visit, VisitState, +}; use crate::core::link::OverlayLinkFilter; use crate::core::overlay::ContourDirection; use crate::core::overlay_rule::OverlayRule; @@ -9,38 +11,60 @@ use crate::mesh::graph::OffsetGraph; use crate::segm::segment::SUBJ_TOP; use alloc::vec; use alloc::vec::Vec; -use i_shape::int::shape::{IntContour, IntShapes}; +use i_float::int::point::IntPoint; +use i_shape::flat::buffer::FlatContoursBuffer; +use i_shape::int::shape::IntShapes; +use i_shape::util::reserve::Reserve; impl OffsetGraph<'_> { - pub(crate) fn extract_offset(&self, main_direction: ContourDirection, min_area: u64) -> IntShapes { - let visited = self.links.filter_by_overlay(OverlayRule::Subject); - self.extract_offset_shapes(visited, main_direction, min_area) + pub(crate) fn extract_offset( + &self, + main_direction: ContourDirection, + min_area: u64, + buffer: &mut BooleanExtractionBuffer, + ) -> IntShapes { + self.links + .filter_by_overlay_into(OverlayRule::Subject, &mut buffer.visited); + self.extract_offset_shapes(main_direction, min_area, buffer) + } + + #[inline] + pub(crate) fn extract_contours_into( + &self, + main_direction: ContourDirection, + min_area: u64, + buffer: &mut BooleanExtractionBuffer, + output: &mut FlatContoursBuffer, + ) { + self.links + .filter_by_overlay_into(OverlayRule::Subject, &mut buffer.visited); + self.extract_contours(main_direction, min_area, buffer, output); } fn extract_offset_shapes( &self, - filter: Vec, main_direction: ContourDirection, min_area: u64, + buffer: &mut BooleanExtractionBuffer, ) -> IntShapes { let clockwise = main_direction == ContourDirection::Clockwise; - let mut buffer = filter; - let visited = buffer.as_mut_slice(); + let len = buffer.visited.len(); + buffer.points.reserve_capacity(len); + let mut shapes = Vec::new(); let mut holes = Vec::new(); let mut anchors = Vec::new(); let mut link_index = 0; let mut is_all_anchors_sorted = true; - while link_index < visited.len() { - if visited.is_visited(link_index) { + while link_index < len { + if buffer.visited.is_visited(link_index) { link_index += 1; continue; } - let left_top_link = unsafe { - // SAFETY: `link_index` walks 0..buffer.visited.len(), and buffer.visited.len() <= self.links.len(). - GraphUtil::find_left_top_link(self.links, self.nodes, link_index, visited) + // Safety: `link_index` walks 0..buffer.visited.len(), and buffer.visited.len() <= self.links.len(). + GraphUtil::find_left_top_link(self.links, self.nodes, link_index, &buffer.visited) }; let link = unsafe { @@ -54,19 +78,28 @@ impl OffsetGraph<'_> { let start_data = StartPathData::new(direction, link, left_top_link); - let mut contour = self.get_fill_contour(&start_data, direction, &mut bold, visited); + self.find_contour( + &start_data, + direction, + &mut bold, + &mut buffer.visited, + &mut buffer.points, + ); + if !bold { link_index += 1; continue; } - let (is_valid, is_modified) = contour.validate(min_area, true); + let (is_valid, is_modified) = buffer.points.validate(min_area, true); if !is_valid { link_index += 1; continue; } + let contour = buffer.points.to_vec(); + if is_hole { let mut v_segment = if clockwise { VSegment { @@ -105,38 +138,95 @@ impl OffsetGraph<'_> { shapes } - fn get_fill_contour( + fn extract_contours( + &self, + main_direction: ContourDirection, + min_area: u64, + buffer: &mut BooleanExtractionBuffer, + output: &mut FlatContoursBuffer, + ) { + let clockwise = main_direction == ContourDirection::Clockwise; + let len = buffer.visited.len(); + buffer.points.reserve_capacity(len); + output.clear_and_reserve(len, 4); + + let mut link_index = 0; + while link_index < len { + if buffer.visited.is_visited(link_index) { + link_index += 1; + continue; + } + + let left_top_link = unsafe { + // Safety: `link_index` walks 0..buffer.visited.len(), and buffer.visited.len() <= self.links.len(). + GraphUtil::find_left_top_link(self.links, self.nodes, link_index, &buffer.visited) + }; + + let link = unsafe { + // Safety: `left_top_link` originates from `find_left_top_link`, which only returns + // indices in 0..self.links.len(), so this lookup cannot go out of bounds. + self.links.get_unchecked(left_top_link) + }; + let is_hole = link.fill & SUBJ_TOP == SUBJ_TOP; + let mut bold = link.is_bold(); + let direction = is_hole == clockwise; + + let start_data = StartPathData::new(direction, link, left_top_link); + + self.find_contour( + &start_data, + direction, + &mut bold, + &mut buffer.visited, + &mut buffer.points, + ); + + if !bold { + link_index += 1; + continue; + } + + let (is_valid, _) = buffer.points.validate(min_area, true); + + if !is_valid { + link_index += 1; + continue; + } + + output.add_contour(buffer.points.as_slice()); + } + } + + fn find_contour( &self, start_data: &StartPathData, clockwise: bool, bold: &mut bool, visited: &mut [VisitState], - ) -> IntContour { + points: &mut Vec, + ) { let mut link_id = start_data.link_id; let mut node_id = start_data.node_id; let last_node_id = start_data.last_node_id; visited.visit(link_id); - let mut contour = IntContour::new(); - contour.push(start_data.begin); + points.clear(); + points.push(start_data.begin); // Find a closed tour while node_id != last_node_id { link_id = GraphUtil::next_link(self.links, self.nodes, link_id, node_id, clockwise, visited); let link = unsafe { - // SAFETY: `link_id` is always derived from a previous in-bounds index or + // Safety: `link_id` is always derived from a previous in-bounds index or // from `find_left_top_link`, so it remains in `0..self.links.len()`. self.links.get_unchecked(link_id) }; *bold = *bold || link.is_bold(); - - node_id = contour.push_node_and_get_other(link, node_id); + node_id = points.push_node_and_get_other(link, node_id); visited.visit(link_id); } - - contour } } diff --git a/iOverlay/src/mesh/outline/builder_join.rs b/iOverlay/src/mesh/outline/builder_join.rs index 0cbb44a..1835150 100644 --- a/iOverlay/src/mesh/outline/builder_join.rs +++ b/iOverlay/src/mesh/outline/builder_join.rs @@ -26,27 +26,29 @@ pub(super) struct BevelJoinBuilder; impl BevelJoinBuilder { #[inline] - fn join_weak>( + fn join>( s0: &Section, s1: &Section, adapter: &FloatPointAdapter, segments: &mut Vec>, ) { - Self::add_weak_segment(&s0.b_top, &s1.a_top, adapter, segments); - } - - #[inline] - fn add_weak_segment>( - a: &P, - b: &P, - adapter: &FloatPointAdapter, - segments: &mut Vec>, - ) { - let ia = adapter.float_to_int(a); - let ib = adapter.float_to_int(b); - if ia != ib { - segments.push(Segment::weak_subject_ab(ia, ib)); + let b0 = adapter.float_to_int(&s0.b_top); + let a1 = adapter.float_to_int(&s1.a_top); + if b0 == a1 { + return; } + let a0 = adapter.float_to_int(&s0.a_top); + let b1 = adapter.float_to_int(&s1.b_top); + + let a0b0 = b0 - a0; + let a1b1 = b1 - a1; + let b0a1 = a1 - b0; + + let a0b0_x_a1b1 = a0b0.cross_product(a1b1); + let a0b0_x_b0a1 = a0b0.cross_product(b0a1); + let bold = (a0b0_x_a1b1 >= 0) == (a0b0_x_b0a1 >= 0); + + segments.push(Segment::subject_ab(b0, a1, bold)); } } @@ -59,7 +61,7 @@ impl> JoinBuilder for BevelJoin adapter: &FloatPointAdapter, segments: &mut Vec>, ) { - Self::join_weak(s0, s1, adapter, segments); + Self::join(s0, s1, adapter, segments); } #[inline] @@ -118,7 +120,7 @@ impl> JoinBuilder for MiterJoin let cross_product = FloatPointMath::cross_product(&s0.dir, &s1.dir); let turn = cross_product >= T::from_float(0.0); if turn == self.expand { - BevelJoinBuilder::join_weak(s0, s1, adapter, segments); + BevelJoinBuilder::join(s0, s1, adapter, segments); return; } @@ -130,7 +132,7 @@ impl> JoinBuilder for MiterJoin let sq_len = ia.sqr_distance(ib); if sq_len < 4 { - BevelJoinBuilder::join_weak(s0, s1, adapter, segments); + BevelJoinBuilder::join(s0, s1, adapter, segments); return; } @@ -225,13 +227,13 @@ impl> JoinBuilder for RoundJoin let cross_product = FloatPointMath::cross_product(&s0.dir, &s1.dir); let turn = cross_product >= T::from_float(0.0); if turn == self.expand { - BevelJoinBuilder::join_weak(s0, s1, adapter, segments); + BevelJoinBuilder::join(s0, s1, adapter, segments); return; } let dot_product = FloatPointMath::dot_product(&s0.dir, &s1.dir); if self.limit_dot_product < dot_product { - BevelJoinBuilder::join_weak(s0, s1, adapter, segments); + BevelJoinBuilder::join(s0, s1, adapter, segments); return; } diff --git a/iOverlay/src/mesh/outline/offset.rs b/iOverlay/src/mesh/outline/offset.rs index 14ddab6..214a0a4 100644 --- a/iOverlay/src/mesh/outline/offset.rs +++ b/iOverlay/src/mesh/outline/offset.rs @@ -1,3 +1,5 @@ +use crate::build::offset::{NegativeSubjectOffsetStrategy, PositiveSubjectOffsetStrategy}; +use crate::core::extract::BooleanExtractionBuffer; use crate::core::fill_rule::FillRule; use crate::core::overlay::{ContourDirection, Overlay, ShapeType}; use crate::core::overlay_rule::OverlayRule; @@ -13,6 +15,7 @@ use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; use i_float::float::rect::FloatRect; use i_shape::base::data::Shapes; +use i_shape::flat::buffer::FlatContoursBuffer; use i_shape::float::adapter::ShapesToFloat; use i_shape::float::despike::DeSpikeContour; use i_shape::float::int_area::IntArea; @@ -137,7 +140,7 @@ impl + 'static, T: FloatNumber + 'static> OutlineSolv let join = style.join.clone().normalize(); let outer_builder = OutlineBuilder::new(-style.outer_offset, &join); - let inner_builder = OutlineBuilder::new(style.inner_offset, &join); + let inner_builder = OutlineBuilder::new(-style.inner_offset, &join); let outer_radius = style.outer_offset; let inner_radius = style.inner_offset; @@ -195,20 +198,24 @@ impl + 'static, T: FloatNumber + 'static> OutlineSolv self.outer_builder.build(path, &self.adapter, &mut segments); OffsetOverlay::with_segments(segments) - .build_graph_view_with_solver(Default::default()) - .map(|graph| graph.extract_offset(options.output_direction, int_min_area)) + .build_graph_view_with_solver::(Default::default()) + .map(|graph| { + graph.extract_offset(options.output_direction, int_min_area, &mut Default::default()) + }) .unwrap_or_default() } else { let total_capacity = self.outer_builder.capacity(self.points_count); - let mut overlay = Overlay::new_custom( total_capacity, options.int_with_adapter(&self.adapter), Default::default(), ); + let mut offset_overlay = OffsetOverlay::new(128); let mut segments = Vec::new(); + let mut extraction_buffer = BooleanExtractionBuffer::default(); + let mut flat_buffer = FlatContoursBuffer::default(); for path in source.iter_paths() { let area = path.unsafe_int_area(&self.adapter); @@ -217,7 +224,7 @@ impl + 'static, T: FloatNumber + 'static> OutlineSolv continue; } - if area < 0 { + let (offset_graph, direction) = if area < 0 { let capacity = self.outer_builder.capacity(path.len()); let additional = capacity.saturating_sub(segments.capacity()); if additional > 0 { @@ -230,42 +237,30 @@ impl + 'static, T: FloatNumber + 'static> OutlineSolv offset_overlay.clear(); offset_overlay.add_segments(&segments); - let shapes = offset_overlay - .build_graph_view_with_solver(Default::default()) - .map(|graph| graph.extract_offset(ContourDirection::CounterClockwise, 0)) - .unwrap_or_default(); - - overlay.add_shapes(&shapes, ShapeType::Subject); + let graph = offset_overlay + .build_graph_view_with_solver::(Default::default()); + (graph, ContourDirection::CounterClockwise) } else { - let mut inverted = Vec::with_capacity(path.len()); - for p in path.iter().rev() { - inverted.push(*p); - } - - let capacity = self.inner_builder.capacity(inverted.len()); + let capacity = self.inner_builder.capacity(path.len()); let additional = capacity.saturating_sub(segments.capacity()); if additional > 0 { segments.reserve(additional); } segments.clear(); - self.inner_builder.build(&inverted, &self.adapter, &mut segments); + self.inner_builder.build(path, &self.adapter, &mut segments); offset_overlay.clear(); offset_overlay.add_segments(&segments); - let mut shapes = offset_overlay - .build_graph_view_with_solver(Default::default()) - .map(|graph| graph.extract_offset(ContourDirection::CounterClockwise, 0)) - .unwrap_or_default(); - - for shape in shapes.iter_mut() { - for path in shape.iter_mut() { - path.reverse(); - } - } + let graph = offset_overlay + .build_graph_view_with_solver::(Default::default()); + (graph, ContourDirection::Clockwise) + }; - overlay.add_shapes(&shapes, ShapeType::Subject); + if let Some(graph) = offset_graph { + graph.extract_contours_into(direction, 0, &mut extraction_buffer, &mut flat_buffer); + overlay.add_flat_buffer(&flat_buffer, ShapeType::Subject); } } @@ -357,7 +352,7 @@ mod tests { let path = [[-5.0, -5.0f32], [5.0, -5.0], [5.0, 5.0], [-5.0, 5.0]]; let style = OutlineStyle::new(10.0); - let shapes = path.outline(&style); + let shapes = path.outline_fixed_scale(&style, 10.0).unwrap(); assert_eq!(shapes.len(), 1); @@ -369,7 +364,19 @@ mod tests { } #[test] - fn test_square_offset() { + fn test_square_round_offset() { + let path = [[-5.0, -5.0f32], [5.0, -5.0], [5.0, 5.0], [-5.0, 5.0]]; + + let angle = PI / 3.0f32; + let style = OutlineStyle::new(10.0).line_join(LineJoin::Round(angle)); + + let shapes = path.outline(&style); + + assert_eq!(shapes.len(), 1); + } + + #[test] + fn test_square_negative_offset() { let path = [[-5.0, -5.0f32], [5.0, -5.0], [5.0, 5.0], [-5.0, 5.0]]; let style = OutlineStyle::new(-20.0); @@ -378,6 +385,18 @@ mod tests { assert_eq!(shapes.len(), 0); } + #[test] + fn test_square_negative_round_offset() { + let path = [[-5.0, -5.0f32], [5.0, -5.0], [5.0, 5.0], [-5.0, 5.0]]; + + let angle = PI / 3.0f32; + let style = OutlineStyle::new(-20.0).line_join(LineJoin::Round(angle)); + + let shapes = path.outline(&style); + + assert_eq!(shapes.len(), 0); + } + #[test] fn test_rhombus_miter() { let path = [[-10.0, 0.0], [0.0, -10.0], [10.0, 0.0], [0.0, 10.0]]; @@ -397,10 +416,12 @@ mod tests { ]; let style = OutlineStyle::new(1.0).line_join(LineJoin::Bevel); - let shapes = window.outline(&style); + let shapes = window.outline_fixed_scale(&style, 10.0).unwrap(); assert_eq!(shapes.len(), 1); assert_eq!(shapes[0].len(), 2); + assert_eq!(shapes[0][0].len(), 8); + assert_eq!(shapes[0][1].len(), 4); } // [[[[300.0, 300.0], [500.0, 300.0], [500.0, 500.0], [300.0, 500.0]]]] @@ -486,4 +507,215 @@ mod tests { assert!(shape[0].len() < 1_000); }; } + + #[test] + fn test_real_case_0() { + let main = vec![ + [411162.0470393328, 5848155.806033095], + [411162.3299983172, 5848152.285037002], + [411162.44901687186, 5848149.446047744], + [411167.5609553484, 5848148.9709500875], + [411175.2629817156, 5848147.891970595], + [411186.7560237078, 5848146.501955947], + [411203.86503249686, 5848144.432009658], + [411214.44804030936, 5848143.314944228], + [411221.0470393328, 5848142.421999892], + [411227.85697585624, 5848141.499026259], + [411233.74100905936, 5848140.505007705], + [411238.4249690203, 5848139.349978408], + [411242.85697585624, 5848138.25305458], + [411249.1400569109, 5848136.395022353], + [411256.6129573015, 5848134.406008681], + [411262.81803542655, 5848132.916018447], + [411275.2460139422, 5848129.93298622], + [411284.6999934344, 5848127.662966689], + [411292.3739436297, 5848125.3869657125], + [411295.41703445, 5848123.3430204], + [411297.0079768328, 5848121.340945205], + [411297.43900710624, 5848119.1510037985], + [411293.11698562186, 5848105.54602333], + [411287.24100905936, 5848076.412966689], + [411286.6709407, 5848062.798953017], + [411286.98099929374, 5848053.410037002], + [411288.3879817156, 5848038.451052627], + [411294.0620539813, 5848006.396975478], + [411294.9409602312, 5847995.477053603], + [411295.2140315203, 5847988.534060439], + [411296.3359797625, 5847983.056033095], + [411297.8600276141, 5847976.624026259], + [411297.86698562186, 5847976.590945205], + [411298.2679865984, 5847974.952029189], + [411301.5709651141, 5847965.958010634], + [411303.7980158953, 5847955.29602333], + [411305.12797195, 5847948.927004775], + [411307.15897780936, 5847937.4279813375], + [411307.711956325, 5847934.313967666], + [411310.8889582781, 5847916.500979384], + [411311.8309748797, 5847911.5959500875], + [411312.3839533953, 5847898.51098915], + [411311.64603835624, 5847891.3459500875], + [411308.97904616874, 5847878.494997939], + [411303.793987575, 5847862.650027236], + [411301.5509455828, 5847857.5080594625], + [411297.4499934344, 5847849.958010634], + [411294.81303054374, 5847846.331057509], + [411281.3550227312, 5847828.88598915], + [411261.0709651141, 5847805.2369412985], + [411259.2100032, 5847804.3619412985], + [411258.0150569109, 5847803.80005165], + [411254.8910334734, 5847803.62805458], + [411251.86503249686, 5847805.3430204], + [411249.4499934344, 5847802.375002822], + [411248.2519953875, 5847800.202029189], + [411248.1909602312, 5847794.432009658], + [411253.23197585624, 5847785.8170194235], + [411255.7970393328, 5847788.224978408], + [411257.6870539813, 5847789.534060439], + [411259.8690608172, 5847790.333010634], + [411262.0520442156, 5847790.332034072], + [411268.8910334734, 5847788.8420438375], + [411269.4320490984, 5847790.333010634], + [411270.6329768328, 5847793.6369657125], + [411269.1820490984, 5847794.332034072], + [411267.65397292655, 5847796.224001845], + [411266.9259455828, 5847798.990969619], + [411267.6709407, 5847800.479006728], + [411268.7440608172, 5847802.625979384], + [411281.10795241874, 5847816.8850125875], + [411283.4420588641, 5847819.822024306], + [411294.9740412859, 5847834.3430204], + [411304.0699885515, 5847846.6369657125], + [411307.27103835624, 5847852.748049697], + [411309.8900569109, 5847857.912966689], + [411311.78300124686, 5847864.460940322], + [411313.6019709734, 5847869.698977431], + [411314.9850276141, 5847872.171999892], + [411317.53104812186, 5847875.446047744], + [411320.7970393328, 5847877.480959853], + [411325.86600905936, 5847879.2180204], + [411335.5499690203, 5847882.012942275], + [411368.4549983172, 5847890.26098915], + [411387.668987575, 5847895.4010037985], + [411397.7240412859, 5847898.576052627], + [411405.50297195, 5847902.333010634], + [411411.0599787859, 5847905.931033095], + [411418.5199397234, 5847911.750979384], + [411434.2660334734, 5847926.797976455], + [411436.82304030936, 5847934.838015517], + [411437.5780451922, 5847936.113039931], + [411434.0089533953, 5847946.812991103], + [411431.19804030936, 5847949.901980361], + [411411.5659602312, 5847985.880984267], + [411407.6529963641, 5847993.0510282125], + [411404.77994948905, 5848000.453982314], + [411402.93803054374, 5848007.354983291], + [411399.39701491874, 5848029.913943252], + [411392.9289973406, 5848080.029055556], + [411390.43998366874, 5848099.298953017], + [411388.8789485125, 5848106.744021377], + [411386.1429865984, 5848113.750979384], + [411383.2870295672, 5848120.22595497], + [411379.1269953875, 5848126.2580594625], + [411373.0499690203, 5848132.165041884], + [411368.44901687186, 5848135.741946181], + [411362.3199885515, 5848138.749026259], + [411354.7980158953, 5848141.105959853], + [411345.8729670672, 5848143.8990506735], + [411334.5969660906, 5848146.394045791], + [411322.7279475359, 5848149.957034072], + [411321.0050471453, 5848151.457034072], + [411319.7229426531, 5848152.791995009], + [411319.23698073905, 5848154.2740506735], + [411319.336956325, 5848156.656008681], + [411339.95194655936, 5848207.255007705], + [411351.7620051531, 5848236.444949111], + [411364.9020198015, 5848268.477053603], + [411376.5170100359, 5848297.156008681], + [411377.7340510515, 5848300.22595497], + [411395.8690608172, 5848345.97595497], + [411411.2689631609, 5848381.8459500875], + [411413.1310237078, 5848382.543948134], + [411405.27994948905, 5848384.8010282125], + [411332.1410334734, 5848206.078005752], + [411309.5890315203, 5848150.895998916], + [411307.8690608172, 5848147.239993056], + [411305.3419612078, 5848144.284060439], + [411301.6329768328, 5848141.729983291], + [411296.9740412859, 5848139.974978408], + [411293.77494460624, 5848139.145022353], + [411290.1350520281, 5848139.240969619], + [411276.68998366874, 5848140.473025283], + [411274.44901687186, 5848140.531008681], + [411266.961956325, 5848144.207034072], + [411247.6239436297, 5848146.937014541], + [411246.2670100359, 5848147.2369412985], + [411240.0699885515, 5848148.041018447], + [411234.7219660906, 5848150.973025283], + [411224.2479670672, 5848149.892947158], + [411223.6759455828, 5848148.834963759], + [411222.2269709734, 5848148.297976455], + [411213.2560237078, 5848149.380984267], + [411189.6649592547, 5848152.199953994], + [411162.0470393328, 5848155.806033095], + ]; + + let hole = vec![ + [411294.2500422625, 5848072.3189725485], + [411373.9180110125, 5848124.016970595], + [411377.22904616874, 5848118.990969619], + [411393.0859797625, 5848020.979983291], + [411394.7030451922, 5848005.639040908], + [411397.4359553484, 5848003.376955947], + [411431.1029475359, 5847937.537966689], + [411431.2639582781, 5847933.187991103], + [411314.8390315203, 5848005.308962783], + [411314.5590022234, 5848009.708010634], + [411309.3459895281, 5848009.447024306], + [411305.3719905047, 5848010.244997939], + [411294.2500422625, 5848072.3189725485], + ]; + + let shape = vec![main, hole]; + + let angle = 10.0f64 / (core::f64::consts::PI / 2.0f64); + let style = OutlineStyle::new(600.0).line_join(LineJoin::Round(angle)); + + if let Some(shape) = shape.outline(&style).first() { + assert!(shape[0].len() < 1_000); + }; + } + + #[test] + fn test_real_case_0_simplified() { + let main = vec![ + [410_000.0, 5847_000.0], + [413_000.0, 5847_000.0], + [413_000.0, 5850_000.0], + [410_000.0, 5850_000.0], + ]; + + let hole = vec![ + [411_294.2500422625, 5848_072.3189725485], + [411_373.9180110125, 5848_124.016970595], + [411_377.22904616874, 5848_118.990969619], + [411_393.0859797625, 5848_020.979983291], + [411_394.7030451922, 5848_005.639040908], + [411_397.4359553484, 5848_003.376955947], + [411_431.1029475359, 5847_937.537966689], + [411_431.2639582781, 5847_933.187991103], + [411_314.8390315203, 5848_005.308962783], + [411_314.5590022234, 5848_009.708010634], + [411_309.3459895281, 5848_009.447024306], + [411_305.3719905047, 5848_010.244997939], + ]; + + let shape = vec![main, hole]; + + let angle = 10.0f64 / (core::f64::consts::PI / 2.0f64); + let style = OutlineStyle::new(600.0).line_join(LineJoin::Round(angle)); + + if let Some(shape) = shape.outline(&style).first() { + assert!(shape[0].len() < 1_000); + }; + } } diff --git a/iOverlay/src/mesh/overlay.rs b/iOverlay/src/mesh/overlay.rs index 39149c6..bc31e7f 100644 --- a/iOverlay/src/mesh/overlay.rs +++ b/iOverlay/src/mesh/overlay.rs @@ -1,4 +1,5 @@ use crate::build::builder::GraphBuilder; +use crate::build::sweep::FillStrategy; use crate::core::graph::OverlayNode; use crate::core::solver::Solver; use crate::mesh::graph::OffsetGraph; @@ -43,12 +44,15 @@ impl OffsetOverlay { } #[inline] - pub fn build_graph_view_with_solver(&mut self, solver: Solver) -> Option> { + pub fn build_graph_view_with_solver>( + &mut self, + solver: Solver, + ) -> Option> { self.split_solver.split_segments(&mut self.segments, &solver); if self.segments.is_empty() { return None; } - let graph = self.graph_builder.build_offset(&solver, &self.segments); + let graph = self.graph_builder.build_offset::(&solver, &self.segments); Some(graph) } diff --git a/iOverlay/src/mesh/stroke/offset.rs b/iOverlay/src/mesh/stroke/offset.rs index 69d931a..a0f7538 100644 --- a/iOverlay/src/mesh/stroke/offset.rs +++ b/iOverlay/src/mesh/stroke/offset.rs @@ -1,3 +1,4 @@ +use crate::build::offset::PositiveSubjectOffsetStrategy; use crate::float::overlay::OverlayOptions; use crate::float::scale::FixedScaleOverlayError; use crate::i_shape::source::resource::ShapeResource; @@ -202,8 +203,8 @@ impl, T: 'static + FloatNumber> StrokeSolve let min_area = self.adapter.sqr_float_to_int(options.min_output_area); let shapes = OffsetOverlay::with_segments(segments) - .build_graph_view_with_solver(Default::default()) - .map(|graph| graph.extract_offset(options.output_direction, min_area)) + .build_graph_view_with_solver::(Default::default()) + .map(|graph| graph.extract_offset(options.output_direction, min_area, &mut Default::default())) .unwrap_or_default(); let mut float = shapes.to_float(&self.adapter); diff --git a/iOverlay/src/mesh/subject.rs b/iOverlay/src/mesh/subject.rs index 22467f3..0378785 100644 --- a/iOverlay/src/mesh/subject.rs +++ b/iOverlay/src/mesh/subject.rs @@ -18,21 +18,18 @@ impl Segment { } } } - + #[inline] - pub(crate) fn weak_subject_ab(p0: IntPoint, p1: IntPoint) -> Self { + pub(crate) fn subject_ab(p0: IntPoint, p1: IntPoint, bold: bool) -> Self { if p0 < p1 { Self { x_segment: XSegment { a: p0, b: p1 }, - count: ShapeCountOffset { subj: 1, bold: false }, + count: ShapeCountOffset { subj: 1, bold }, } } else { Self { x_segment: XSegment { a: p1, b: p0 }, - count: ShapeCountOffset { - subj: -1, - bold: false, - }, + count: ShapeCountOffset { subj: -1, bold }, } } } diff --git a/iOverlay/src/split/grid_layout.rs b/iOverlay/src/split/grid_layout.rs index 7e7fcfa..fc47f1c 100644 --- a/iOverlay/src/split/grid_layout.rs +++ b/iOverlay/src/split/grid_layout.rs @@ -300,7 +300,7 @@ mod tests { use i_float::int::point::IntPoint; use i_float::int::rect::IntRect; use i_float::triangle::Triangle; - use rand::Rng; + use rand::RngExt; #[test] fn test_0() { diff --git a/iOverlay/tests/dynamic_tests.rs b/iOverlay/tests/dynamic_tests.rs index baf6b20..d49ec16 100644 --- a/iOverlay/tests/dynamic_tests.rs +++ b/iOverlay/tests/dynamic_tests.rs @@ -9,7 +9,7 @@ mod tests { use i_shape::base::data::Path; use i_shape::int::path::IntPath; use i_shape::int::shape::IntShape; - use rand::Rng; + use rand::RngExt; use std::f64::consts::PI; const SOLVERS: [Solver; 3] = [Solver::LIST, Solver::TREE, Solver::AUTO]; diff --git a/iOverlay/tests/float_overlay_tests.rs b/iOverlay/tests/float_overlay_tests.rs index 0d484eb..41d4ad9 100644 --- a/iOverlay/tests/float_overlay_tests.rs +++ b/iOverlay/tests/float_overlay_tests.rs @@ -9,7 +9,7 @@ mod tests { use i_overlay::float::overlay::{FloatOverlay, OverlayOptions}; use i_overlay::float::slice::FloatSlice; use i_overlay::string::clip::ClipRule; - use rand::Rng; + use rand::RngExt; #[derive(Clone, Copy)] struct FPoint { diff --git a/iOverlay/tests/slice_tests.rs b/iOverlay/tests/slice_tests.rs index 9c432eb..22e1cb6 100644 --- a/iOverlay/tests/slice_tests.rs +++ b/iOverlay/tests/slice_tests.rs @@ -5,7 +5,7 @@ mod tests { use i_overlay::string::line::IntLine; use i_overlay::string::slice::IntSlice; use i_shape::int::path::IntPath; - use rand::Rng; + use rand::RngExt; #[test] fn test_miss_slice() {