diff --git a/examples/overlay_editor/src/app/boolean/content.rs b/examples/overlay_editor/src/app/boolean/content.rs index c75803f4..4ce07cd7 100644 --- a/examples/overlay_editor/src/app/boolean/content.rs +++ b/examples/overlay_editor/src/app/boolean/content.rs @@ -1,20 +1,20 @@ -use std::collections::HashMap; -use i_triangle::i_overlay::core::overlay::Overlay; -use i_triangle::i_overlay::i_shape::int::count::PointsCount; -use i_triangle::i_overlay::i_float::int::rect::IntRect; -use iced::widget::scrollable; -use iced::{Alignment, Length, Padding, Size, Vector}; -use iced::widget::{Button, Column, Container, Row, Space, Text}; -use crate::app::design; use crate::app::boolean::control::ModeOption; use crate::app::boolean::workspace::WorkspaceState; +use crate::app::design; use crate::app::fill_option::FillOption; -use crate::app::main::{EditorApp, AppMessage}; +use crate::app::main::{AppMessage, EditorApp}; use crate::app::solver_option::SolverOption; -use crate::geom::camera::Camera; use crate::data::boolean::BooleanResource; +use crate::geom::camera::Camera; use crate::point_editor::point::PathsToEditorPoints; use crate::point_editor::widget::PointEditUpdate; +use i_triangle::i_overlay::core::overlay::Overlay; +use i_triangle::i_overlay::i_float::int::rect::IntRect; +use i_triangle::i_overlay::i_shape::int::count::PointsCount; +use iced::widget::scrollable; +use iced::widget::{Button, Column, Container, Row, Space, Text}; +use iced::{Alignment, Length, Padding, Size, Vector}; +use std::collections::HashMap; pub(crate) struct BooleanState { pub(crate) test: usize, @@ -41,11 +41,8 @@ pub(crate) enum BooleanMessage { impl EditorApp { fn boolean_sidebar(&self) -> Column<'_, AppMessage> { let count = self.app_resource.boolean.count; - let mut column = Column::new().push( - Space::new() - .width(Length::Fill) - .height(Length::Fixed(2.0)), - ); + let mut column = + Column::new().push(Space::new().width(Length::Fill).height(Length::Fixed(2.0))); for index in 0..count { let is_selected = self.state.boolean.test == index; @@ -53,13 +50,22 @@ impl EditorApp { Container::new( Button::new( Text::new(format!("test_{}", index)) - .style(if is_selected { design::style_sidebar_text_selected } else { design::style_sidebar_text }) - .size(14) + .style(if is_selected { + design::style_sidebar_text_selected + } else { + design::style_sidebar_text + }) + .size(14), ) - .width(Length::Fill) - .on_press(AppMessage::Bool(BooleanMessage::TestSelected(index))) - .style(if is_selected { design::style_sidebar_button_selected } else { design::style_sidebar_button }) - ).padding(self.design.action_padding()) + .width(Length::Fill) + .on_press(AppMessage::Bool(BooleanMessage::TestSelected(index))) + .style(if is_selected { + design::style_sidebar_button_selected + } else { + design::style_sidebar_button + }), + ) + .padding(self.design.action_padding()), ); } @@ -75,14 +81,15 @@ impl EditorApp { .height(Length::Shrink) .align_x(Alignment::Start) .padding(Padding::new(0.0).right(8)) - .style(design::style_sidebar_background) - ).direction(scrollable::Direction::Vertical( + .style(design::style_sidebar_background), + ) + .direction(scrollable::Direction::Vertical( scrollable::Scrollbar::new() .width(4) .margin(0) .scroller_width(4) .anchor(scrollable::Anchor::Start), - )) + )), ) .push(self.boolean_workspace()) } @@ -101,7 +108,9 @@ impl EditorApp { } fn boolean_set_test(&mut self, index: usize) { - self.state.boolean.load_test(index, &mut self.app_resource.boolean); + self.state + .boolean + .load_test(index, &mut self.app_resource.boolean); self.state.boolean.update_solution(); } @@ -174,7 +183,8 @@ impl BooleanState { let editor_points = &mut self.workspace.points; if editor_points.is_empty() { - editor_points.reserve(test.clip_paths.points_count() + test.subj_paths.points_count()) + editor_points + .reserve(test.clip_paths.points_count() + test.subj_paths.points_count()) } else { editor_points.clear(); } @@ -204,10 +214,11 @@ impl BooleanState { let clip = &self.workspace.clip; let fill_rule = self.fill.fill_rule(); match self.mode { - ModeOption::Edit => {}, + ModeOption::Edit => {} ModeOption::Debug => { - self.workspace.vectors = Overlay::with_contours(subj, clip).build_separate_vectors(fill_rule); - }, + self.workspace.vectors = + Overlay::with_contours(subj, clip).build_separate_vectors(fill_rule); + } _ => { let overlay_rule = self.mode.overlay_rule().unwrap(); let solution = Overlay::with_contours(subj, clip).overlay(overlay_rule, fill_rule); diff --git a/examples/overlay_editor/src/app/boolean/control.rs b/examples/overlay_editor/src/app/boolean/control.rs index ba7c5109..1809c34d 100644 --- a/examples/overlay_editor/src/app/boolean/control.rs +++ b/examples/overlay_editor/src/app/boolean/control.rs @@ -1,66 +1,75 @@ -use i_triangle::i_overlay::core::overlay_rule::OverlayRule; use crate::app::boolean::content::BooleanMessage; use crate::app::fill_option::FillOption; -use crate::app::main::{EditorApp, AppMessage}; +use crate::app::main::{AppMessage, EditorApp}; use crate::app::solver_option::SolverOption; +use i_triangle::i_overlay::core::overlay_rule::OverlayRule; +use iced::widget::{pick_list, Column, Container, Row, Space, Text}; use iced::{Alignment, Length}; -use iced::widget::{Column, Container, pick_list, Row, Space, Text}; impl EditorApp { pub(crate) fn boolean_control(&self) -> Column<'_, AppMessage> { - let solver_pick_list = - Row::new() - .push(Text::new("Solver:") + let solver_pick_list = Row::new() + .push( + Text::new("Solver:") .width(Length::Fixed(90.0)) .height(Length::Fill) - .align_y(Alignment::Center)) - .push( - Container::new( - pick_list( - &SolverOption::ALL[..], - Some(self.state.boolean.solver), - on_select_solver, - ).width(Length::Fixed(160.0)) + .align_y(Alignment::Center), + ) + .push( + Container::new( + pick_list( + &SolverOption::ALL[..], + Some(self.state.boolean.solver), + on_select_solver, ) - .height(Length::Fill) - .align_y(Alignment::Center) - ).height(Length::Fixed(40.0)); + .width(Length::Fixed(160.0)), + ) + .height(Length::Fill) + .align_y(Alignment::Center), + ) + .height(Length::Fixed(40.0)); - let fill_pick_list = - Row::new() - .push(Text::new("Fill Rule:") + let fill_pick_list = Row::new() + .push( + Text::new("Fill Rule:") .width(Length::Fixed(90.0)) .height(Length::Fill) - .align_y(Alignment::Center)) - .push( - Container::new( - pick_list( - &FillOption::ALL[..], - Some(self.state.boolean.fill), - on_select_fill, - ).width(Length::Fixed(160.0)) + .align_y(Alignment::Center), + ) + .push( + Container::new( + pick_list( + &FillOption::ALL[..], + Some(self.state.boolean.fill), + on_select_fill, ) - .height(Length::Fill) - .align_y(Alignment::Center) - ).height(Length::Fixed(40.0)); + .width(Length::Fixed(160.0)), + ) + .height(Length::Fill) + .align_y(Alignment::Center), + ) + .height(Length::Fixed(40.0)); - let mode_pick_list = - Row::new() - .push(Text::new("Mode:") + let mode_pick_list = Row::new() + .push( + Text::new("Mode:") .width(Length::Fixed(90.0)) .height(Length::Fill) - .align_y(Alignment::Center)) - .push( - Container::new( - pick_list( - &ModeOption::ALL[..], - Some(self.state.boolean.mode), - on_select_mode, - ).width(Length::Fixed(160.0)) + .align_y(Alignment::Center), + ) + .push( + Container::new( + pick_list( + &ModeOption::ALL[..], + Some(self.state.boolean.mode), + on_select_mode, ) - .height(Length::Fill) - .align_y(Alignment::Center) - ).height(Length::Fixed(40.0)); + .width(Length::Fixed(160.0)), + ) + .height(Length::Fill) + .align_y(Alignment::Center), + ) + .height(Length::Fixed(40.0)); Column::new() .push(solver_pick_list) @@ -127,7 +136,7 @@ impl ModeOption { ModeOption::Difference => Some(OverlayRule::Difference), ModeOption::InverseDifference => Some(OverlayRule::InverseDifference), ModeOption::Xor => Some(OverlayRule::Xor), - _ => None + _ => None, } } } diff --git a/examples/overlay_editor/src/app/boolean/mod.rs b/examples/overlay_editor/src/app/boolean/mod.rs index 06876bd4..2ac0d54b 100644 --- a/examples/overlay_editor/src/app/boolean/mod.rs +++ b/examples/overlay_editor/src/app/boolean/mod.rs @@ -1,3 +1,3 @@ pub(crate) mod content; +mod control; mod workspace; -mod control; \ No newline at end of file diff --git a/examples/overlay_editor/src/app/boolean/workspace.rs b/examples/overlay_editor/src/app/boolean/workspace.rs index 6e72cc0d..27720ae5 100644 --- a/examples/overlay_editor/src/app/boolean/workspace.rs +++ b/examples/overlay_editor/src/app/boolean/workspace.rs @@ -1,19 +1,23 @@ +use crate::app::boolean::content::BooleanMessage; +use crate::app::boolean::control::ModeOption; +use crate::app::design::{style_sheet_background, Design}; +use crate::app::main::{AppMessage, EditorApp}; use crate::draw::shape::ShapeWidget; +use crate::draw::vectors::VectorsWidget; use crate::geom::camera::Camera; -use crate::sheet::widget::SheetWidget; use crate::point_editor::point::EditorPoint; use crate::point_editor::widget::{PointEditUpdate, PointsEditorWidget}; -use crate::app::boolean::content::BooleanMessage; -use crate::app::design::{style_sheet_background, Design}; -use crate::app::main::{EditorApp, AppMessage}; -use i_triangle::i_overlay::i_shape::int::count::IntShapes; -use i_triangle::i_overlay::i_shape::int::path::IntPaths; -use i_triangle::i_overlay::vector::edge::VectorEdge; -use iced::widget::Stack; +use crate::sheet::widget::SheetWidget; +use i_triangle::i_overlay::i_shape::int::count::IntShapes as RawIntShapes; +use i_triangle::i_overlay::i_shape::int::path::IntPaths as RawIntPaths; +use i_triangle::i_overlay::vector::edge::DataVectorEdge; use iced::widget::Container; +use iced::widget::Stack; use iced::{Length, Padding, Size, Vector}; -use crate::app::boolean::control::ModeOption; -use crate::draw::vectors::VectorsWidget; + +type IntPaths = RawIntPaths; +type IntShapes = RawIntShapes; +type VectorEdge = DataVectorEdge; pub(crate) struct WorkspaceState { pub(crate) camera: Camera, @@ -36,35 +40,37 @@ impl EditorApp { on_update_zoom, on_update_drag, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); if self.state.boolean.workspace.camera.is_not_empty() { match self.state.boolean.mode { ModeOption::Edit => { - stack = stack.push( - Container::new(ShapeWidget::with_paths( - &self.state.boolean.workspace.subj, - self.state.boolean.workspace.camera, - Some(self.state.boolean.fill.fill_rule()), - Some(Design::subject_color().scale_alpha(0.2)), - Some(Design::subject_color()), - 2.0, - )) + stack = stack + .push( + Container::new(ShapeWidget::with_paths( + &self.state.boolean.workspace.subj, + self.state.boolean.workspace.camera, + Some(self.state.boolean.fill.fill_rule()), + Some(Design::subject_color().scale_alpha(0.2)), + Some(Design::subject_color()), + 2.0, + )) .width(Length::Fill) - .height(Length::Fill) - ).push( - Container::new(ShapeWidget::with_paths( - &self.state.boolean.workspace.clip, - self.state.boolean.workspace.camera, - Some(self.state.boolean.fill.fill_rule()), - Some(Design::clip_color().scale_alpha(0.2)), - Some(Design::clip_color()), - 2.0, - )) + .height(Length::Fill), + ) + .push( + Container::new(ShapeWidget::with_paths( + &self.state.boolean.workspace.clip, + self.state.boolean.workspace.camera, + Some(self.state.boolean.fill.fill_rule()), + Some(Design::clip_color().scale_alpha(0.2)), + Some(Design::clip_color()), + 2.0, + )) .width(Length::Fill) - .height(Length::Fill) - ) + .height(Length::Fill), + ) } ModeOption::Debug => { stack = stack.push( @@ -76,34 +82,37 @@ impl EditorApp { Design::both_color(), 2.0, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ) } _ => { - stack = stack.push( - Container::new(ShapeWidget::with_paths( - &self.state.boolean.workspace.subj, - self.state.boolean.workspace.camera, - Some(self.state.boolean.fill.fill_rule()), - None, - Some(Design::subject_color()), - 1.0, - )) + stack = stack + .push( + Container::new(ShapeWidget::with_paths( + &self.state.boolean.workspace.subj, + self.state.boolean.workspace.camera, + Some(self.state.boolean.fill.fill_rule()), + None, + Some(Design::subject_color()), + 1.0, + )) .width(Length::Fill) - .height(Length::Fill) - ).push( - Container::new(ShapeWidget::with_paths( - &self.state.boolean.workspace.clip, - self.state.boolean.workspace.camera, - Some(self.state.boolean.fill.fill_rule()), - None, - Some(Design::clip_color()), - 1.0, - )) + .height(Length::Fill), + ) + .push( + Container::new(ShapeWidget::with_paths( + &self.state.boolean.workspace.clip, + self.state.boolean.workspace.camera, + Some(self.state.boolean.fill.fill_rule()), + None, + Some(Design::clip_color()), + 1.0, + )) .width(Length::Fill) - .height(Length::Fill) - ).push( + .height(Length::Fill), + ) + .push( Container::new(ShapeWidget::with_shapes( &self.state.boolean.workspace.solution, self.state.boolean.workspace.camera, @@ -112,21 +121,23 @@ impl EditorApp { Some(Design::solution_color()), 2.0, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ) } } stack = stack.push( - Container::new(PointsEditorWidget::new( - &self.state.boolean.workspace.points, - self.state.boolean.workspace.camera, - on_update_point) + Container::new( + PointsEditorWidget::new( + &self.state.boolean.workspace.points, + self.state.boolean.workspace.camera, + on_update_point, + ) .set_drag_color(Design::accent_color()) - .set_hover_color(Design::negative_color()) + .set_hover_color(Design::negative_color()), ) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); } @@ -134,10 +145,10 @@ impl EditorApp { Container::new(self.boolean_control()) .width(Length::Shrink) .height(Length::Shrink) - .padding(Padding::new(8.0)) + .padding(Padding::new(8.0)), ) }) - .style(style_sheet_background) + .style(style_sheet_background) } pub(super) fn boolean_update_point(&mut self, update: PointEditUpdate) { @@ -171,6 +182,13 @@ fn on_update_drag(drag: Vector) -> AppMessage { impl Default for WorkspaceState { fn default() -> Self { - WorkspaceState { camera: Camera::empty(), subj: vec![], clip: vec![], solution: vec![], points: vec![], vectors: vec![] } + WorkspaceState { + camera: Camera::empty(), + subj: vec![], + clip: vec![], + solution: vec![], + points: vec![], + vectors: vec![], + } } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/app/design.rs b/examples/overlay_editor/src/app/design.rs index a2b6f7ae..ca2d249d 100644 --- a/examples/overlay_editor/src/app/design.rs +++ b/examples/overlay_editor/src/app/design.rs @@ -2,8 +2,7 @@ use iced::widget::button; use iced::widget::container; use iced::widget::rule; use iced::widget::text; -use iced::{Background, border, Color, Padding, Theme}; - +use iced::{border, Background, Color, Padding, Theme}; pub(super) struct Design { pub(super) action_separator: f32, @@ -40,7 +39,7 @@ impl Design { pub(super) fn new() -> Self { Self { - action_separator: 3.0 + action_separator: 3.0, } } @@ -77,7 +76,10 @@ pub(super) fn style_sidebar_button(theme: &Theme, status: button::Status) -> but } } -pub(super) fn style_sidebar_button_selected(theme: &Theme, status: button::Status) -> button::Style { +pub(super) fn style_sidebar_button_selected( + theme: &Theme, + status: button::Status, +) -> button::Style { let palette = theme.extended_palette(); let base = button::Style { background: Some(Background::Color(palette.primary.strong.color)), @@ -102,19 +104,26 @@ pub(super) fn style_sidebar_button_selected(theme: &Theme, status: button::Statu pub(super) fn style_sidebar_text(theme: &Theme) -> text::Style { let palette = theme.palette(); text::Style { - color: Some(palette.text.scale_alpha(0.7)) + color: Some(palette.text.scale_alpha(0.7)), } } pub(super) fn style_sidebar_text_selected(theme: &Theme) -> text::Style { let palette = theme.palette(); text::Style { - color: Some(palette.text) + color: Some(palette.text), } } pub(super) fn style_sidebar_background(theme: &Theme) -> container::Style { - container::Style::default().background(theme.extended_palette().background.weak.color.scale_alpha(0.1)) + container::Style::default().background( + theme + .extended_palette() + .background + .weak + .color + .scale_alpha(0.1), + ) } pub(super) fn style_separator(theme: &Theme) -> rule::Style { diff --git a/examples/overlay_editor/src/app/fill_option.rs b/examples/overlay_editor/src/app/fill_option.rs index 8f6ad8a0..4ed4076c 100644 --- a/examples/overlay_editor/src/app/fill_option.rs +++ b/examples/overlay_editor/src/app/fill_option.rs @@ -40,4 +40,4 @@ impl std::fmt::Display for FillOption { } ) } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/app/main.rs b/examples/overlay_editor/src/app/main.rs index cc3c5ab9..f48296db 100644 --- a/examples/overlay_editor/src/app/main.rs +++ b/examples/overlay_editor/src/app/main.rs @@ -98,17 +98,17 @@ impl EditorApp { AppMessage::Stroke(msg) => self.stroke_update(msg), AppMessage::Outline(msg) => self.outline_update(msg), AppMessage::NextTest => match self.state.selected_action { - MainAction::Boolean => self.boolean_next_test(), - MainAction::String => self.string_next_test(), - MainAction::Stroke => self.stroke_next_test(), - MainAction::Outline => self.outline_next_test(), - }, + MainAction::Boolean => self.boolean_next_test(), + MainAction::String => self.string_next_test(), + MainAction::Stroke => self.stroke_next_test(), + MainAction::Outline => self.outline_next_test(), + }, AppMessage::PrevTest => match self.state.selected_action { MainAction::Boolean => self.boolean_prev_test(), MainAction::String => self.string_prev_test(), MainAction::Stroke => self.stroke_prev_test(), MainAction::Outline => self.outline_prev_test(), - } + }, } Task::none() @@ -167,11 +167,7 @@ impl EditorApp { fn main_navigation(&self) -> Column<'_, AppMessage> { self.main_actions.iter().fold( - Column::new().push( - Space::new() - .width(Length::Fill) - .height(Length::Fixed(2.0)), - ), + Column::new().push(Space::new().width(Length::Fill).height(Length::Fixed(2.0))), |column, item| { let is_selected = self.state.selected_action.eq(item); column.push( diff --git a/examples/overlay_editor/src/app/mod.rs b/examples/overlay_editor/src/app/mod.rs index 0e234c0d..18af7d16 100644 --- a/examples/overlay_editor/src/app/mod.rs +++ b/examples/overlay_editor/src/app/mod.rs @@ -1,8 +1,8 @@ -pub mod main; -mod design; mod boolean; -mod string; +mod design; mod fill_option; +pub mod main; +mod outline; mod solver_option; +mod string; mod stroke; -mod outline; \ No newline at end of file diff --git a/examples/overlay_editor/src/app/outline/content.rs b/examples/overlay_editor/src/app/outline/content.rs index f7c068a7..3032493f 100644 --- a/examples/overlay_editor/src/app/outline/content.rs +++ b/examples/overlay_editor/src/app/outline/content.rs @@ -6,10 +6,10 @@ use crate::data::outline::OutlineResource; use crate::geom::camera::Camera; use crate::point_editor::point::PathsToEditorPoints; use crate::point_editor::widget::PointEditUpdate; -use i_triangle::i_overlay::mesh::style::{LineJoin, OutlineStyle}; use i_triangle::i_overlay::i_float::int::point::IntPoint; use i_triangle::i_overlay::i_float::int::rect::IntRect; use i_triangle::i_overlay::mesh::outline::offset::OutlineOffset; +use i_triangle::i_overlay::mesh::style::{LineJoin, OutlineStyle}; use iced::widget::{scrollable, Button, Column, Container, Row, Space, Text}; use iced::{Alignment, Length, Padding, Size, Vector}; use std::collections::HashMap; @@ -41,11 +41,8 @@ pub(crate) enum OutlineMessage { impl EditorApp { fn outline_sidebar(&self) -> Column<'_, AppMessage> { let count = self.app_resource.outline.count; - let mut column = Column::new().push( - Space::new() - .width(Length::Fill) - .height(Length::Fixed(2.0)), - ); + let mut column = + Column::new().push(Space::new().width(Length::Fill).height(Length::Fixed(2.0))); for index in 0..count { let is_selected = self.state.outline.test == index; column = column.push( @@ -99,8 +96,12 @@ impl EditorApp { pub(crate) fn outline_update(&mut self, message: OutlineMessage) { match message { OutlineMessage::TestSelected(index) => self.outline_set_test(index), - OutlineMessage::OuterOffsetValueUpdated(value) => self.outline_update_outer_offset(value), - OutlineMessage::InnerOffsetValueUpdated(value) => self.outline_update_inner_offset(value), + OutlineMessage::OuterOffsetValueUpdated(value) => { + self.outline_update_outer_offset(value) + } + OutlineMessage::InnerOffsetValueUpdated(value) => { + self.outline_update_inner_offset(value) + } OutlineMessage::JoinSelected(join) => self.outline_update_join(join), OutlineMessage::JoinValueUpdated(value) => self.outline_update_join_value(value), OutlineMessage::PointEdited(update) => self.outline_update_point(update), @@ -247,7 +248,7 @@ impl OutlineState { JoinOption::Miter => { let ratio = 0.03 * self.join_value as f32; style = style.line_join(LineJoin::Miter(ratio)) - }, + } JoinOption::Round => { let ratio = 0.015 * self.join_value as f32; style = style.line_join(LineJoin::Round(ratio)) diff --git a/examples/overlay_editor/src/app/outline/control.rs b/examples/overlay_editor/src/app/outline/control.rs index 367f4ca5..622a9cf9 100644 --- a/examples/overlay_editor/src/app/outline/control.rs +++ b/examples/overlay_editor/src/app/outline/control.rs @@ -40,11 +40,16 @@ impl EditorApp { ) .push( Container::new( - slider(-50.0f32..=50.0f32, self.state.outline.outer_offset, on_update_outer_offset).step(0.01f32) + slider( + -50.0f32..=50.0f32, + self.state.outline.outer_offset, + on_update_outer_offset, + ) + .step(0.01f32), ) - .width(410) - .height(Length::Fill) - .align_y(Alignment::Center), + .width(410) + .height(Length::Fill) + .align_y(Alignment::Center), ) .height(Length::Fixed(40.0)); let inner_offset_list = Row::new() @@ -56,11 +61,16 @@ impl EditorApp { ) .push( Container::new( - slider(-50.0f32..=50.0f32, self.state.outline.inner_offset, on_update_inner_offset).step(0.01f32) + slider( + -50.0f32..=50.0f32, + self.state.outline.inner_offset, + on_update_inner_offset, + ) + .step(0.01f32), ) - .width(410) - .height(Length::Fill) - .align_y(Alignment::Center), + .width(410) + .height(Length::Fill) + .align_y(Alignment::Center), ) .height(Length::Fixed(40.0)); @@ -96,7 +106,6 @@ impl EditorApp { .width(250) .height(Length::Fill) .align_y(Alignment::Center), - ); } diff --git a/examples/overlay_editor/src/app/outline/mod.rs b/examples/overlay_editor/src/app/outline/mod.rs index 06876bd4..2ac0d54b 100644 --- a/examples/overlay_editor/src/app/outline/mod.rs +++ b/examples/overlay_editor/src/app/outline/mod.rs @@ -1,3 +1,3 @@ pub(crate) mod content; +mod control; mod workspace; -mod control; \ No newline at end of file diff --git a/examples/overlay_editor/src/app/outline/workspace.rs b/examples/overlay_editor/src/app/outline/workspace.rs index 8cf04f0e..7bac2501 100644 --- a/examples/overlay_editor/src/app/outline/workspace.rs +++ b/examples/overlay_editor/src/app/outline/workspace.rs @@ -1,17 +1,18 @@ -use i_triangle::i_overlay::core::fill_rule::FillRule; +use crate::app::design::{style_sheet_background, Design}; +use crate::app::main::{AppMessage, EditorApp}; +use crate::app::outline::content::OutlineMessage; use crate::draw::shape::ShapeWidget; use crate::geom::camera::Camera; -use crate::sheet::widget::SheetWidget; use crate::point_editor::point::EditorPoint; use crate::point_editor::widget::{PointEditUpdate, PointsEditorWidget}; -use crate::app::design::{style_sheet_background, Design}; -use crate::app::main::{EditorApp, AppMessage}; -use crate::app::outline::content::OutlineMessage; -use i_triangle::i_overlay::i_shape::int::path::IntPaths; -use iced::widget::Stack; +use crate::sheet::widget::SheetWidget; +use i_triangle::i_overlay::core::fill_rule::FillRule; +use i_triangle::i_overlay::i_shape::int::path::IntPaths as RawIntPaths; use iced::widget::Container; +use iced::widget::Stack; use iced::{Length, Padding, Size, Vector}; +type IntPaths = RawIntPaths; pub(crate) struct WorkspaceState { pub(crate) camera: Camera, @@ -33,8 +34,8 @@ impl EditorApp { on_update_zoom, on_update_drag, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); if self.state.outline.workspace.camera.is_not_empty() { @@ -47,8 +48,8 @@ impl EditorApp { Some(Design::solution_color()), 2.0, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); stack = stack.push( Container::new(ShapeWidget::with_paths( @@ -59,19 +60,21 @@ impl EditorApp { Some(Design::subject_color()), 1.0, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); stack = stack.push( - Container::new(PointsEditorWidget::new( - &self.state.outline.workspace.points, - self.state.outline.workspace.camera, - on_update_point) + Container::new( + PointsEditorWidget::new( + &self.state.outline.workspace.points, + self.state.outline.workspace.camera, + on_update_point, + ) .set_drag_color(Design::accent_color()) - .set_hover_color(Design::negative_color()) + .set_hover_color(Design::negative_color()), ) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); } @@ -79,10 +82,10 @@ impl EditorApp { Container::new(self.outline_control()) .width(Length::Shrink) .height(Length::Shrink) - .padding(Padding::new(8.0)) + .padding(Padding::new(8.0)), ) }) - .style(style_sheet_background) + .style(style_sheet_background) } pub(super) fn outline_update_point(&mut self, update: PointEditUpdate) { @@ -116,6 +119,12 @@ fn on_update_drag(drag: Vector) -> AppMessage { impl Default for WorkspaceState { fn default() -> Self { - WorkspaceState { scale: 1.0, camera: Camera::empty(), outline_input: vec![], outline_output: vec![], points: vec![] } + WorkspaceState { + scale: 1.0, + camera: Camera::empty(), + outline_input: vec![], + outline_output: vec![], + points: vec![], + } } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/app/solver_option.rs b/examples/overlay_editor/src/app/solver_option.rs index c325c0e7..e3ec72af 100644 --- a/examples/overlay_editor/src/app/solver_option.rs +++ b/examples/overlay_editor/src/app/solver_option.rs @@ -10,7 +10,7 @@ impl SolverOption { pub(crate) const ALL: [SolverOption; 3] = [ SolverOption::Auto, SolverOption::Average, - SolverOption::Precise + SolverOption::Precise, ]; } @@ -26,4 +26,4 @@ impl std::fmt::Display for SolverOption { } ) } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/app/string/content.rs b/examples/overlay_editor/src/app/string/content.rs index 187b1ea6..1d475d2c 100644 --- a/examples/overlay_editor/src/app/string/content.rs +++ b/examples/overlay_editor/src/app/string/content.rs @@ -1,21 +1,21 @@ -use std::collections::HashMap; -use i_triangle::i_overlay::i_shape::int::count::PointsCount; -use i_triangle::i_overlay::i_float::int::rect::IntRect; -use i_triangle::i_overlay::string::clip::{ClipRule, IntClip}; -use i_triangle::i_overlay::string::slice::IntSlice; -use iced::widget::scrollable; -use iced::{Alignment, Length, Padding, Size, Vector}; -use iced::widget::{Button, Column, Container, Row, Space, Text}; use crate::app::design; -use crate::app::string::control::ModeOption; -use crate::app::string::workspace::{Solution, WorkspaceState}; use crate::app::fill_option::FillOption; -use crate::app::main::{EditorApp, AppMessage}; +use crate::app::main::{AppMessage, EditorApp}; use crate::app::solver_option::SolverOption; -use crate::geom::camera::Camera; +use crate::app::string::control::ModeOption; +use crate::app::string::workspace::{Solution, WorkspaceState}; use crate::data::string::StringResource; +use crate::geom::camera::Camera; use crate::point_editor::point::PathsToEditorPoints; use crate::point_editor::widget::PointEditUpdate; +use i_triangle::i_overlay::i_float::int::rect::IntRect; +use i_triangle::i_overlay::i_shape::int::count::PointsCount; +use i_triangle::i_overlay::string::clip::{ClipRule, IntClip}; +use i_triangle::i_overlay::string::slice::IntSlice; +use iced::widget::scrollable; +use iced::widget::{Button, Column, Container, Row, Space, Text}; +use iced::{Alignment, Length, Padding, Size, Vector}; +use std::collections::HashMap; pub(crate) struct StringState { pub(crate) test: usize, @@ -42,24 +42,30 @@ pub(crate) enum StringMessage { impl EditorApp { fn string_sidebar(&self) -> Column<'_, AppMessage> { let count = self.app_resource.string.count; - let mut column = Column::new().push( - Space::new() - .width(Length::Fill) - .height(Length::Fixed(2.0)), - ); + let mut column = + Column::new().push(Space::new().width(Length::Fill).height(Length::Fixed(2.0))); for index in 0..count { let is_selected = self.state.string.test == index; column = column.push( Container::new( Button::new( Text::new(format!("test_{}", index)) - .style(if is_selected { design::style_sidebar_text_selected } else { design::style_sidebar_text }) - .size(14) + .style(if is_selected { + design::style_sidebar_text_selected + } else { + design::style_sidebar_text + }) + .size(14), ) - .width(Length::Fill) - .on_press(AppMessage::String(StringMessage::TestSelected(index))) - .style(if is_selected { design::style_sidebar_button_selected } else { design::style_sidebar_button }) - ).padding(self.design.action_padding()) + .width(Length::Fill) + .on_press(AppMessage::String(StringMessage::TestSelected(index))) + .style(if is_selected { + design::style_sidebar_button_selected + } else { + design::style_sidebar_button + }), + ) + .padding(self.design.action_padding()), ); } @@ -75,14 +81,15 @@ impl EditorApp { .height(Length::Shrink) .align_x(Alignment::Start) .padding(Padding::new(0.0).right(8)) - .style(design::style_sidebar_background) - ).direction(scrollable::Direction::Vertical( + .style(design::style_sidebar_background), + ) + .direction(scrollable::Direction::Vertical( scrollable::Scrollbar::new() .width(4) .margin(0) .scroller_width(4) .anchor(scrollable::Anchor::Start), - )) + )), ) .push(self.string_workspace()) } @@ -101,7 +108,9 @@ impl EditorApp { } fn string_set_test(&mut self, index: usize) { - self.state.string.set_test(index, &mut self.app_resource.string); + self.state + .string + .set_test(index, &mut self.app_resource.string); self.state.string.update_solution(); } @@ -215,11 +224,25 @@ impl StringState { self.workspace.solution = Solution::Shapes(slice); } ModeOption::ClipDirect => { - let clip = body.clip_paths(string, fill_rule, ClipRule { invert: false, boundary_included: false }); + let clip = body.clip_paths( + string, + fill_rule, + ClipRule { + invert: false, + boundary_included: false, + }, + ); self.workspace.solution = Solution::Paths(clip); } ModeOption::ClipInvert => { - let clip = body.clip_paths(string, fill_rule, ClipRule { invert: true, boundary_included: false }); + let clip = body.clip_paths( + string, + fill_rule, + ClipRule { + invert: true, + boundary_included: false, + }, + ); self.workspace.solution = Solution::Paths(clip); } } diff --git a/examples/overlay_editor/src/app/string/control.rs b/examples/overlay_editor/src/app/string/control.rs index 4a5960c5..35026592 100644 --- a/examples/overlay_editor/src/app/string/control.rs +++ b/examples/overlay_editor/src/app/string/control.rs @@ -1,65 +1,74 @@ -use crate::app::string::content::StringMessage; use crate::app::fill_option::FillOption; -use crate::app::main::{EditorApp, AppMessage}; +use crate::app::main::{AppMessage, EditorApp}; use crate::app::solver_option::SolverOption; +use crate::app::string::content::StringMessage; +use iced::widget::{pick_list, Column, Container, Row, Space, Text}; use iced::{Alignment, Length}; -use iced::widget::{Column, Container, pick_list, Row, Space, Text}; impl EditorApp { pub(crate) fn string_control(&self) -> Column<'_, AppMessage> { - let solver_pick_list = - Row::new() - .push(Text::new("Solver:") + let solver_pick_list = Row::new() + .push( + Text::new("Solver:") .width(Length::Fixed(90.0)) .height(Length::Fill) - .align_y(Alignment::Center)) - .push( - Container::new( - pick_list( - &SolverOption::ALL[..], - Some(self.state.string.solver), - on_select_solver, - ).width(Length::Fixed(160.0)) + .align_y(Alignment::Center), + ) + .push( + Container::new( + pick_list( + &SolverOption::ALL[..], + Some(self.state.string.solver), + on_select_solver, ) - .height(Length::Fill) - .align_y(Alignment::Center) - ).height(Length::Fixed(40.0)); + .width(Length::Fixed(160.0)), + ) + .height(Length::Fill) + .align_y(Alignment::Center), + ) + .height(Length::Fixed(40.0)); - let fill_pick_list = - Row::new() - .push(Text::new("Fill Rule:") + let fill_pick_list = Row::new() + .push( + Text::new("Fill Rule:") .width(Length::Fixed(90.0)) .height(Length::Fill) - .align_y(Alignment::Center)) - .push( - Container::new( - pick_list( - &FillOption::ALL[..], - Some(self.state.string.fill), - on_select_fill, - ).width(Length::Fixed(160.0)) + .align_y(Alignment::Center), + ) + .push( + Container::new( + pick_list( + &FillOption::ALL[..], + Some(self.state.string.fill), + on_select_fill, ) - .height(Length::Fill) - .align_y(Alignment::Center) - ).height(Length::Fixed(40.0)); + .width(Length::Fixed(160.0)), + ) + .height(Length::Fill) + .align_y(Alignment::Center), + ) + .height(Length::Fixed(40.0)); - let mode_pick_list = - Row::new() - .push(Text::new("Mode:") + let mode_pick_list = Row::new() + .push( + Text::new("Mode:") .width(Length::Fixed(90.0)) .height(Length::Fill) - .align_y(Alignment::Center)) - .push( - Container::new( - pick_list( - &ModeOption::ALL[..], - Some(self.state.string.mode), - on_select_mode, - ).width(Length::Fixed(160.0)) + .align_y(Alignment::Center), + ) + .push( + Container::new( + pick_list( + &ModeOption::ALL[..], + Some(self.state.string.mode), + on_select_mode, ) - .height(Length::Fill) - .align_y(Alignment::Center) - ).height(Length::Fixed(40.0)); + .width(Length::Fixed(160.0)), + ) + .height(Length::Fill) + .align_y(Alignment::Center), + ) + .height(Length::Fixed(40.0)); Column::new() .push(solver_pick_list) diff --git a/examples/overlay_editor/src/app/string/mod.rs b/examples/overlay_editor/src/app/string/mod.rs index 06876bd4..2ac0d54b 100644 --- a/examples/overlay_editor/src/app/string/mod.rs +++ b/examples/overlay_editor/src/app/string/mod.rs @@ -1,3 +1,3 @@ pub(crate) mod content; +mod control; mod workspace; -mod control; \ No newline at end of file diff --git a/examples/overlay_editor/src/app/string/workspace.rs b/examples/overlay_editor/src/app/string/workspace.rs index ecadfc00..977db253 100644 --- a/examples/overlay_editor/src/app/string/workspace.rs +++ b/examples/overlay_editor/src/app/string/workspace.rs @@ -1,19 +1,22 @@ +use crate::app::design::{style_sheet_background, Design}; +use crate::app::main::{AppMessage, EditorApp}; +use crate::app::string::content::StringMessage; +use crate::app::string::control::ModeOption; use crate::draw::path::PathWidget; use crate::draw::shape::ShapeWidget; +use crate::draw::varicolored::VaricoloredWidget; use crate::geom::camera::Camera; -use crate::sheet::widget::SheetWidget; use crate::point_editor::point::EditorPoint; use crate::point_editor::widget::{PointEditUpdate, PointsEditorWidget}; -use crate::app::string::content::StringMessage; -use crate::app::design::{style_sheet_background, Design}; -use crate::app::main::{EditorApp, AppMessage}; -use i_triangle::i_overlay::i_shape::int::count::IntShapes; -use i_triangle::i_overlay::i_shape::int::path::IntPaths; -use iced::widget::Stack; +use crate::sheet::widget::SheetWidget; +use i_triangle::i_overlay::i_shape::int::count::IntShapes as RawIntShapes; +use i_triangle::i_overlay::i_shape::int::path::IntPaths as RawIntPaths; use iced::widget::Container; +use iced::widget::Stack; use iced::{Length, Padding, Size, Vector}; -use crate::app::string::control::ModeOption; -use crate::draw::varicolored::VaricoloredWidget; + +type IntPaths = RawIntPaths; +type IntShapes = RawIntShapes; pub(crate) enum Solution { Shapes(IntShapes), @@ -21,7 +24,6 @@ pub(crate) enum Solution { None, } - pub(crate) struct WorkspaceState { pub(crate) camera: Camera, pub(crate) body: IntPaths, @@ -42,8 +44,8 @@ impl EditorApp { on_update_zoom, on_update_drag, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); if self.state.string.workspace.camera.is_not_empty() { @@ -56,8 +58,8 @@ impl EditorApp { self.state.string.workspace.camera, 2.0, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); } stack = stack.push( @@ -68,33 +70,35 @@ impl EditorApp { 2.0, true, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ) } ModeOption::ClipDirect | ModeOption::ClipInvert => { - stack = stack.push( - Container::new(ShapeWidget::with_paths( - &self.state.string.workspace.body, - self.state.string.workspace.camera, - Some(self.state.string.fill.fill_rule()), - Some(Design::clip_color().scale_alpha(0.3)), - Some(Design::clip_color()), - 2.0, - )) + stack = stack + .push( + Container::new(ShapeWidget::with_paths( + &self.state.string.workspace.body, + self.state.string.workspace.camera, + Some(self.state.string.fill.fill_rule()), + Some(Design::clip_color().scale_alpha(0.3)), + Some(Design::clip_color()), + 2.0, + )) .width(Length::Fill) - .height(Length::Fill) - ).push( - Container::new(PathWidget::with_paths( - &self.state.string.workspace.string, - self.state.string.workspace.camera, - Design::negative_color(), - 2.0, - true, - )) + .height(Length::Fill), + ) + .push( + Container::new(PathWidget::with_paths( + &self.state.string.workspace.string, + self.state.string.workspace.camera, + Design::negative_color(), + 2.0, + true, + )) .width(Length::Fill) - .height(Length::Fill) - ); + .height(Length::Fill), + ); if let Solution::Paths(paths) = &self.state.string.workspace.solution { stack = stack.push( Container::new(PathWidget::with_paths( @@ -104,46 +108,50 @@ impl EditorApp { 2.0, true, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); } } _ => { - stack = stack.push( - Container::new(ShapeWidget::with_paths( - &self.state.string.workspace.body, - self.state.string.workspace.camera, - Some(self.state.string.fill.fill_rule()), - Some(Design::subject_color().scale_alpha(0.2)), - Some(Design::subject_color()), - 2.0, - )) + stack = stack + .push( + Container::new(ShapeWidget::with_paths( + &self.state.string.workspace.body, + self.state.string.workspace.camera, + Some(self.state.string.fill.fill_rule()), + Some(Design::subject_color().scale_alpha(0.2)), + Some(Design::subject_color()), + 2.0, + )) .width(Length::Fill) - .height(Length::Fill) - ).push( - Container::new(PathWidget::with_paths( - &self.state.string.workspace.string, - self.state.string.workspace.camera, - Design::negative_color(), - 2.0, - true, - )) + .height(Length::Fill), + ) + .push( + Container::new(PathWidget::with_paths( + &self.state.string.workspace.string, + self.state.string.workspace.camera, + Design::negative_color(), + 2.0, + true, + )) .width(Length::Fill) - .height(Length::Fill) - ) + .height(Length::Fill), + ) } } stack = stack.push( - Container::new(PointsEditorWidget::new( - &self.state.string.workspace.points, - self.state.string.workspace.camera, - on_update_point) + Container::new( + PointsEditorWidget::new( + &self.state.string.workspace.points, + self.state.string.workspace.camera, + on_update_point, + ) .set_drag_color(Design::accent_color()) - .set_hover_color(Design::negative_color()) + .set_hover_color(Design::negative_color()), ) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); } @@ -151,10 +159,10 @@ impl EditorApp { Container::new(self.string_control()) .width(Length::Shrink) .height(Length::Shrink) - .padding(Padding::new(8.0)) + .padding(Padding::new(8.0)), ) }) - .style(style_sheet_background) + .style(style_sheet_background) } pub(super) fn string_update_point(&mut self, update: PointEditUpdate) { @@ -188,6 +196,12 @@ fn on_update_drag(drag: Vector) -> AppMessage { impl Default for WorkspaceState { fn default() -> Self { - WorkspaceState { camera: Camera::empty(), body: vec![], string: vec![], solution: Solution::None, points: vec![] } + WorkspaceState { + camera: Camera::empty(), + body: vec![], + string: vec![], + solution: Solution::None, + points: vec![], + } } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/app/stroke/content.rs b/examples/overlay_editor/src/app/stroke/content.rs index b9f903a5..0d1fd498 100644 --- a/examples/overlay_editor/src/app/stroke/content.rs +++ b/examples/overlay_editor/src/app/stroke/content.rs @@ -6,10 +6,10 @@ use crate::data::stroke::StrokeResource; use crate::geom::camera::Camera; use crate::point_editor::point::PathsToEditorPoints; use crate::point_editor::widget::PointEditUpdate; -use i_triangle::i_overlay::mesh::stroke::offset::StrokeOffset; -use i_triangle::i_overlay::mesh::style::{LineCap, LineJoin, StrokeStyle}; use i_triangle::i_overlay::i_float::int::point::IntPoint; use i_triangle::i_overlay::i_float::int::rect::IntRect; +use i_triangle::i_overlay::mesh::stroke::offset::StrokeOffset; +use i_triangle::i_overlay::mesh::style::{LineCap, LineJoin, StrokeStyle}; use iced::widget::{scrollable, Button, Column, Container, Row, Space, Text}; use iced::{Alignment, Length, Padding, Size, Vector}; use std::collections::HashMap; @@ -50,11 +50,8 @@ pub(crate) enum StrokeMessage { impl EditorApp { fn stroke_sidebar(&self) -> Column<'_, AppMessage> { let count = self.app_resource.stroke.count; - let mut column = Column::new().push( - Space::new() - .width(Length::Fill) - .height(Length::Fixed(2.0)), - ); + let mut column = + Column::new().push(Space::new().width(Length::Fill).height(Length::Fixed(2.0))); for index in 0..count { let is_selected = self.state.stroke.test == index; column = column.push( @@ -285,7 +282,7 @@ impl StrokeState { let ratio = 0.03 * self.join_value as f32; println!("ratio: {}", ratio); style = style.line_join(LineJoin::Miter(ratio)) - }, + } JoinOption::Round => { let ratio = 0.015 * self.join_value as f32; style = style.line_join(LineJoin::Round(ratio)) @@ -294,39 +291,27 @@ impl StrokeState { } match self.start_cap { - CapOption::Butt => { - style = style.start_cap(LineCap::Butt) - }, + CapOption::Butt => style = style.start_cap(LineCap::Butt), CapOption::Round => { let ratio = 0.015 * self.start_cap_value as f32; style = style.start_cap(LineCap::Round(ratio)) } CapOption::Square => style = style.start_cap(LineCap::Square), CapOption::Arrow => { - let points = vec![ - [-1.0, -2.0], - [ 3.0, 0.0], - [-1.0, 2.0], - ]; + let points = vec![[-1.0, -2.0], [3.0, 0.0], [-1.0, 2.0]]; style = style.start_cap(LineCap::Custom(Rc::from(points))) } } match self.end_cap { - CapOption::Butt => { - style = style.end_cap(LineCap::Butt) - }, + CapOption::Butt => style = style.end_cap(LineCap::Butt), CapOption::Round => { let ratio = 0.015 * self.end_cap_value as f32; style = style.end_cap(LineCap::Round(ratio)) } CapOption::Square => style = style.end_cap(LineCap::Square), CapOption::Arrow => { - let points = vec![ - [-1.0, -2.0], - [ 3.0, 0.0], - [-1.0, 2.0], - ]; + let points = vec![[-1.0, -2.0], [3.0, 0.0], [-1.0, 2.0]]; style = style.end_cap(LineCap::Custom(Rc::from(points))) } } diff --git a/examples/overlay_editor/src/app/stroke/control.rs b/examples/overlay_editor/src/app/stroke/control.rs index 1fc05ba8..db5b86b3 100644 --- a/examples/overlay_editor/src/app/stroke/control.rs +++ b/examples/overlay_editor/src/app/stroke/control.rs @@ -13,7 +13,12 @@ pub(crate) enum CapOption { } impl CapOption { - const ALL: [CapOption; 4] = [CapOption::Butt, CapOption::Round, CapOption::Square, CapOption::Arrow]; + const ALL: [CapOption; 4] = [ + CapOption::Butt, + CapOption::Round, + CapOption::Square, + CapOption::Arrow, + ]; } impl std::fmt::Display for CapOption { @@ -68,11 +73,12 @@ impl EditorApp { ) .push( Container::new( - slider(0.1f32..=10.0f32, self.state.stroke.width, on_update_width).step(0.01f32) + slider(0.1f32..=10.0f32, self.state.stroke.width, on_update_width) + .step(0.01f32), ) - .width(160) - .height(Length::Fill) - .align_y(Alignment::Center), + .width(160) + .height(Length::Fill) + .align_y(Alignment::Center), ) .height(Length::Fixed(40.0)); @@ -98,9 +104,13 @@ impl EditorApp { .height(Length::Fixed(40.0)); if self.state.stroke.start_cap == CapOption::Round { - let slider = slider(1..=100, self.state.stroke.start_cap_value, on_update_start_cap_value) - .default(50) - .shift_step(5); + let slider = slider( + 1..=100, + self.state.stroke.start_cap_value, + on_update_start_cap_value, + ) + .default(50) + .shift_step(5); start_cap_pick_list = start_cap_pick_list.push( Container::new(slider) @@ -125,17 +135,21 @@ impl EditorApp { Some(self.state.stroke.end_cap), on_select_end_cap, ) - .width(Length::Fixed(160.0)), + .width(Length::Fixed(160.0)), ) - .height(Length::Fill) - .align_y(Alignment::Center), + .height(Length::Fill) + .align_y(Alignment::Center), ) .height(Length::Fixed(40.0)); if self.state.stroke.end_cap == CapOption::Round { - let slider = slider(1..=100, self.state.stroke.end_cap_value, on_update_end_cap_value) - .default(50) - .shift_step(5); + let slider = slider( + 1..=100, + self.state.stroke.end_cap_value, + on_update_end_cap_value, + ) + .default(50) + .shift_step(5); end_cap_pick_list = end_cap_pick_list.push( Container::new(slider) @@ -178,7 +192,6 @@ impl EditorApp { .width(250) .height(Length::Fill) .align_y(Alignment::Center), - ); } diff --git a/examples/overlay_editor/src/app/stroke/mod.rs b/examples/overlay_editor/src/app/stroke/mod.rs index 06876bd4..2ac0d54b 100644 --- a/examples/overlay_editor/src/app/stroke/mod.rs +++ b/examples/overlay_editor/src/app/stroke/mod.rs @@ -1,3 +1,3 @@ pub(crate) mod content; +mod control; mod workspace; -mod control; \ No newline at end of file diff --git a/examples/overlay_editor/src/app/stroke/workspace.rs b/examples/overlay_editor/src/app/stroke/workspace.rs index 0364756f..2abedd8b 100644 --- a/examples/overlay_editor/src/app/stroke/workspace.rs +++ b/examples/overlay_editor/src/app/stroke/workspace.rs @@ -1,18 +1,19 @@ -use i_triangle::i_overlay::core::fill_rule::FillRule; +use crate::app::design::{style_sheet_background, Design}; +use crate::app::main::{AppMessage, EditorApp}; +use crate::app::stroke::content::StrokeMessage; use crate::draw::path::PathWidget; use crate::draw::shape::ShapeWidget; use crate::geom::camera::Camera; -use crate::sheet::widget::SheetWidget; use crate::point_editor::point::EditorPoint; use crate::point_editor::widget::{PointEditUpdate, PointsEditorWidget}; -use crate::app::design::{style_sheet_background, Design}; -use crate::app::main::{EditorApp, AppMessage}; -use crate::app::stroke::content::StrokeMessage; -use i_triangle::i_overlay::i_shape::int::path::IntPaths; -use iced::widget::Stack; +use crate::sheet::widget::SheetWidget; +use i_triangle::i_overlay::core::fill_rule::FillRule; +use i_triangle::i_overlay::i_shape::int::path::IntPaths as RawIntPaths; use iced::widget::Container; +use iced::widget::Stack; use iced::{Length, Padding, Size, Vector}; +type IntPaths = RawIntPaths; pub(crate) struct WorkspaceState { pub(crate) camera: Camera, @@ -34,8 +35,8 @@ impl EditorApp { on_update_zoom, on_update_drag, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); if self.state.stroke.workspace.camera.is_not_empty() { @@ -50,8 +51,8 @@ impl EditorApp { Some(Design::solution_color()), 2.0, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); } stack = stack.push( @@ -62,19 +63,21 @@ impl EditorApp { 1.0, false, )) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); stack = stack.push( - Container::new(PointsEditorWidget::new( - &self.state.stroke.workspace.points, - self.state.stroke.workspace.camera, - on_update_point) + Container::new( + PointsEditorWidget::new( + &self.state.stroke.workspace.points, + self.state.stroke.workspace.camera, + on_update_point, + ) .set_drag_color(Design::accent_color()) - .set_hover_color(Design::negative_color()) + .set_hover_color(Design::negative_color()), ) - .width(Length::Fill) - .height(Length::Fill) + .width(Length::Fill) + .height(Length::Fill), ); } @@ -82,10 +85,10 @@ impl EditorApp { Container::new(self.stroke_control()) .width(Length::Shrink) .height(Length::Shrink) - .padding(Padding::new(8.0)) + .padding(Padding::new(8.0)), ) }) - .style(style_sheet_background) + .style(style_sheet_background) } pub(super) fn stroke_update_point(&mut self, update: PointEditUpdate) { @@ -119,6 +122,12 @@ fn on_update_drag(drag: Vector) -> AppMessage { impl Default for WorkspaceState { fn default() -> Self { - WorkspaceState { scale: 1.0, camera: Camera::empty(), stroke_input: vec![], stroke_output: vec![], points: vec![] } + WorkspaceState { + scale: 1.0, + camera: Camera::empty(), + stroke_input: vec![], + stroke_output: vec![], + points: vec![], + } } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/data/boolean.rs b/examples/overlay_editor/src/data/boolean.rs index 366c84ba..16b21653 100644 --- a/examples/overlay_editor/src/data/boolean.rs +++ b/examples/overlay_editor/src/data/boolean.rs @@ -1,7 +1,9 @@ +use i_triangle::i_overlay::i_shape::int::path::IntPath as RawIntPath; +use serde::Deserialize; use std::collections::HashMap; use std::path::PathBuf; -use i_triangle::i_overlay::i_shape::int::path::IntPath; -use serde::Deserialize; + +type IntPath = RawIntPath; #[derive(Debug, Clone, Deserialize)] pub(crate) struct BooleanTest { @@ -18,9 +20,7 @@ impl BooleanTest { path_buf.push(file_name); let data = match std::fs::read_to_string(path_buf.as_path()) { - Ok(data) => { - data - } + Ok(data) => data, Err(e) => { eprintln!("{:?}", e); return None; @@ -40,20 +40,18 @@ impl BooleanTest { fn tests_count(folder: &str) -> usize { let folder_path = PathBuf::from(folder); match std::fs::read_dir(folder_path) { - Ok(entries) => { - entries - .filter_map(|entry| { - entry.ok().and_then(|e| { - let path = e.path(); - if path.extension()?.to_str()? == "json" { - Some(()) - } else { - None - } - }) + Ok(entries) => entries + .filter_map(|entry| { + entry.ok().and_then(|e| { + let path = e.path(); + if path.extension()?.to_str()? == "json" { + Some(()) + } else { + None + } }) - .count() - } + }) + .count(), Err(e) => { eprintln!("Failed to read directory: {}", e); 0 @@ -65,15 +63,18 @@ impl BooleanTest { pub(crate) struct BooleanResource { folder: Option, pub(crate) count: usize, - pub(crate) tests: HashMap + pub(crate) tests: HashMap, } impl BooleanResource { - #[cfg(not(target_arch = "wasm32"))] pub(crate) fn with_path(folder: &str) -> Self { let count = BooleanTest::tests_count(folder); - Self { count, folder: Some(folder.to_string()), tests: Default::default() } + Self { + count, + folder: Some(folder.to_string()), + tests: Default::default(), + } } #[cfg(target_arch = "wasm32")] @@ -101,10 +102,14 @@ impl BooleanResource { return None; } if let Some(test) = self.tests.get(&index) { - return Some(test.clone()) + return Some(test.clone()); } - let folder = if let Some(folder) = &self.folder { folder } else { return None; }; + let folder = if let Some(folder) = &self.folder { + folder + } else { + return None; + }; let test = BooleanTest::load(index, folder.as_str())?; self.tests.insert(index, test.clone()); diff --git a/examples/overlay_editor/src/data/mod.rs b/examples/overlay_editor/src/data/mod.rs index 5df821ac..becad71f 100644 --- a/examples/overlay_editor/src/data/mod.rs +++ b/examples/overlay_editor/src/data/mod.rs @@ -1,5 +1,5 @@ -pub mod resource; pub(crate) mod boolean; +pub(crate) mod outline; +pub mod resource; pub(crate) mod string; pub(crate) mod stroke; -pub(crate) mod outline; \ No newline at end of file diff --git a/examples/overlay_editor/src/data/outline.rs b/examples/overlay_editor/src/data/outline.rs index 5bdac54a..f91b4b32 100644 --- a/examples/overlay_editor/src/data/outline.rs +++ b/examples/overlay_editor/src/data/outline.rs @@ -1,7 +1,7 @@ +use i_triangle::i_overlay::i_shape::base::data::Paths; +use serde::Deserialize; use std::collections::HashMap; use std::path::PathBuf; -use serde::Deserialize; -use i_triangle::i_overlay::i_shape::base::data::Paths; #[derive(Debug, Clone, Deserialize)] pub(crate) struct OutlineTest { @@ -16,9 +16,7 @@ impl OutlineTest { path_buf.push(file_name); let data = match std::fs::read_to_string(path_buf.as_path()) { - Ok(data) => { - data - } + Ok(data) => data, Err(e) => { eprintln!("{:?}", e); return None; @@ -38,20 +36,18 @@ impl OutlineTest { fn tests_count(folder: &str) -> usize { let folder_path = PathBuf::from(folder); match std::fs::read_dir(folder_path) { - Ok(entries) => { - entries - .filter_map(|entry| { - entry.ok().and_then(|e| { - let path = e.path(); - if path.extension()?.to_str()? == "json" { - Some(()) - } else { - None - } - }) + Ok(entries) => entries + .filter_map(|entry| { + entry.ok().and_then(|e| { + let path = e.path(); + if path.extension()?.to_str()? == "json" { + Some(()) + } else { + None + } }) - .count() - } + }) + .count(), Err(e) => { eprintln!("Failed to read directory: {}", e); 0 @@ -63,15 +59,18 @@ impl OutlineTest { pub(crate) struct OutlineResource { folder: Option, pub(crate) count: usize, - pub(crate) tests: HashMap + pub(crate) tests: HashMap, } impl OutlineResource { - #[cfg(not(target_arch = "wasm32"))] pub(crate) fn with_path(folder: &str) -> Self { let count = OutlineTest::tests_count(folder); - Self { count, folder: Some(folder.to_string()), tests: Default::default() } + Self { + count, + folder: Some(folder.to_string()), + tests: Default::default(), + } } #[cfg(target_arch = "wasm32")] @@ -99,14 +98,18 @@ impl OutlineResource { return None; } if let Some(test) = self.tests.get(&index) { - return Some(test.clone()) + return Some(test.clone()); } - let folder = if let Some(folder) = &self.folder { folder } else { return None; }; + let folder = if let Some(folder) = &self.folder { + folder + } else { + return None; + }; let test = OutlineTest::load(index, folder.as_str())?; self.tests.insert(index, test.clone()); Some(test) } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/data/resource.rs b/examples/overlay_editor/src/data/resource.rs index c5af9564..7ecae58b 100644 --- a/examples/overlay_editor/src/data/resource.rs +++ b/examples/overlay_editor/src/data/resource.rs @@ -1,7 +1,7 @@ -use crate::data::string::StringResource; use crate::data::boolean::BooleanResource; -use crate::data::stroke::StrokeResource; use crate::data::outline::OutlineResource; +use crate::data::string::StringResource; +use crate::data::stroke::StrokeResource; pub struct AppResource { pub(crate) boolean: BooleanResource, @@ -22,7 +22,12 @@ impl AppResource { } #[cfg(target_arch = "wasm32")] - pub fn with_content(boolean: &String, string: &String, stroke: &String, outline: &String) -> Self { + pub fn with_content( + boolean: &String, + string: &String, + stroke: &String, + outline: &String, + ) -> Self { Self { boolean: BooleanResource::with_content(boolean), string: StringResource::with_content(string), @@ -30,4 +35,4 @@ impl AppResource { outline: OutlineResource::with_content(outline), } } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/data/string.rs b/examples/overlay_editor/src/data/string.rs index 5518cc13..71a75f4f 100644 --- a/examples/overlay_editor/src/data/string.rs +++ b/examples/overlay_editor/src/data/string.rs @@ -1,8 +1,11 @@ +use i_triangle::i_overlay::i_shape::int::path::IntPath as RawIntPath; +use i_triangle::i_overlay::i_shape::int::shape::IntContour as RawIntContour; +use serde::Deserialize; use std::collections::HashMap; use std::path::PathBuf; -use i_triangle::i_overlay::i_shape::int::path::IntPath; -use i_triangle::i_overlay::i_shape::int::shape::IntContour; -use serde::Deserialize; + +type IntContour = RawIntContour; +type IntPath = RawIntPath; #[derive(Debug, Clone, Deserialize)] pub(crate) struct StringTest { @@ -19,9 +22,7 @@ impl StringTest { path_buf.push(file_name); let data = match std::fs::read_to_string(path_buf.as_path()) { - Ok(data) => { - data - } + Ok(data) => data, Err(e) => { eprintln!("{:?}", e); return None; @@ -41,20 +42,18 @@ impl StringTest { fn tests_count(folder: &str) -> usize { let folder_path = PathBuf::from(folder); match std::fs::read_dir(folder_path) { - Ok(entries) => { - entries - .filter_map(|entry| { - entry.ok().and_then(|e| { - let path = e.path(); - if path.extension()?.to_str()? == "json" { - Some(()) - } else { - None - } - }) + Ok(entries) => entries + .filter_map(|entry| { + entry.ok().and_then(|e| { + let path = e.path(); + if path.extension()?.to_str()? == "json" { + Some(()) + } else { + None + } }) - .count() - } + }) + .count(), Err(e) => { eprintln!("Failed to read directory: {}", e); 0 @@ -66,15 +65,18 @@ impl StringTest { pub(crate) struct StringResource { folder: Option, pub(crate) count: usize, - pub(crate) tests: HashMap + pub(crate) tests: HashMap, } impl StringResource { - #[cfg(not(target_arch = "wasm32"))] pub(crate) fn with_path(folder: &str) -> Self { let count = StringTest::tests_count(folder); - Self { count, folder: Some(folder.to_string()), tests: Default::default() } + Self { + count, + folder: Some(folder.to_string()), + tests: Default::default(), + } } #[cfg(target_arch = "wasm32")] @@ -102,10 +104,14 @@ impl StringResource { return None; } if let Some(test) = self.tests.get(&index) { - return Some(test.clone()) + return Some(test.clone()); } - let folder = if let Some(folder) = &self.folder { folder } else { return None; }; + let folder = if let Some(folder) = &self.folder { + folder + } else { + return None; + }; let test = StringTest::load(index, folder.as_str())?; self.tests.insert(index, test.clone()); diff --git a/examples/overlay_editor/src/data/stroke.rs b/examples/overlay_editor/src/data/stroke.rs index f0b2b9a7..2e583aef 100644 --- a/examples/overlay_editor/src/data/stroke.rs +++ b/examples/overlay_editor/src/data/stroke.rs @@ -1,7 +1,7 @@ +use i_triangle::i_overlay::i_shape::base::data::Paths; +use serde::Deserialize; use std::collections::HashMap; use std::path::PathBuf; -use serde::Deserialize; -use i_triangle::i_overlay::i_shape::base::data::Paths; #[derive(Debug, Clone, Deserialize)] pub(crate) struct StrokeTest { @@ -16,9 +16,7 @@ impl StrokeTest { path_buf.push(file_name); let data = match std::fs::read_to_string(path_buf.as_path()) { - Ok(data) => { - data - } + Ok(data) => data, Err(e) => { eprintln!("{:?}", e); return None; @@ -38,20 +36,18 @@ impl StrokeTest { fn tests_count(folder: &str) -> usize { let folder_path = PathBuf::from(folder); match std::fs::read_dir(folder_path) { - Ok(entries) => { - entries - .filter_map(|entry| { - entry.ok().and_then(|e| { - let path = e.path(); - if path.extension()?.to_str()? == "json" { - Some(()) - } else { - None - } - }) + Ok(entries) => entries + .filter_map(|entry| { + entry.ok().and_then(|e| { + let path = e.path(); + if path.extension()?.to_str()? == "json" { + Some(()) + } else { + None + } }) - .count() - } + }) + .count(), Err(e) => { eprintln!("Failed to read directory: {}", e); 0 @@ -63,15 +59,18 @@ impl StrokeTest { pub(crate) struct StrokeResource { folder: Option, pub(crate) count: usize, - pub(crate) tests: HashMap + pub(crate) tests: HashMap, } impl StrokeResource { - #[cfg(not(target_arch = "wasm32"))] pub(crate) fn with_path(folder: &str) -> Self { let count = StrokeTest::tests_count(folder); - Self { count, folder: Some(folder.to_string()), tests: Default::default() } + Self { + count, + folder: Some(folder.to_string()), + tests: Default::default(), + } } #[cfg(target_arch = "wasm32")] @@ -99,14 +98,18 @@ impl StrokeResource { return None; } if let Some(test) = self.tests.get(&index) { - return Some(test.clone()) + return Some(test.clone()); } - let folder = if let Some(folder) = &self.folder { folder } else { return None; }; + let folder = if let Some(folder) = &self.folder { + folder + } else { + return None; + }; let test = StrokeTest::load(index, folder.as_str())?; self.tests.insert(index, test.clone()); Some(test) } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/draw/mod.rs b/examples/overlay_editor/src/draw/mod.rs index b7f47136..cb0e15ad 100644 --- a/examples/overlay_editor/src/draw/mod.rs +++ b/examples/overlay_editor/src/draw/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod shape; pub(crate) mod path; +pub(crate) mod shape; pub(crate) mod varicolored; -pub(crate) mod vectors; \ No newline at end of file +pub(crate) mod vectors; diff --git a/examples/overlay_editor/src/draw/path.rs b/examples/overlay_editor/src/draw/path.rs index b9384b00..b9efcfaf 100644 --- a/examples/overlay_editor/src/draw/path.rs +++ b/examples/overlay_editor/src/draw/path.rs @@ -1,35 +1,50 @@ +use crate::geom::camera::Camera; +use crate::geom::vector::VectorExt; use i_mesh::path::butt::ButtStrokeBuilder; use i_mesh::path::style::StrokeStyle; use i_triangle::float::builder::TriangulationBuilder; use i_triangle::float::triangulation::Triangulation; use i_triangle::i_overlay::i_float::float::point::FloatPoint; use i_triangle::i_overlay::i_float::int::point::IntPoint; -use i_triangle::i_overlay::i_shape::int::path::{IntPath, IntPaths}; +use i_triangle::i_overlay::i_shape::int::path::{IntPath as RawIntPath, IntPaths as RawIntPaths}; +use iced::advanced::graphics::color::pack; +use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; +use iced::advanced::graphics::Mesh; use iced::advanced::layout::{self, Layout}; use iced::advanced::renderer; use iced::advanced::widget::{Tree, Widget}; -use iced::{mouse, Color, Vector, Transformation}; +use iced::{mouse, Color, Transformation, Vector}; use iced::{Element, Length, Rectangle, Renderer, Size, Theme}; -use iced::advanced::graphics::color::pack; -use iced::advanced::graphics::Mesh; -use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; -use crate::geom::camera::Camera; -use crate::geom::vector::VectorExt; + +type IntPath = RawIntPath; +type IntPaths = RawIntPaths; pub(crate) struct PathWidget { stroke: Option, } impl PathWidget { - pub(crate) fn with_paths(paths: &IntPaths, camera: Camera, stroke_color: Color, stroke_width: f32, arrows: bool) -> Self { + pub(crate) fn with_paths( + paths: &IntPaths, + camera: Camera, + stroke_color: Color, + stroke_width: f32, + arrows: bool, + ) -> Self { let offset = Self::offset_for_paths(paths, camera); - let stroke = Self::stroke_mesh_for_paths(paths, camera, offset, stroke_color, stroke_width, arrows); - Self { - stroke, - } + let stroke = + Self::stroke_mesh_for_paths(paths, camera, offset, stroke_color, stroke_width, arrows); + Self { stroke } } - fn stroke_mesh_for_paths(paths: &IntPaths, camera: Camera, offset: Vector, color: Color, width: f32, arrows: bool) -> Option { + fn stroke_mesh_for_paths( + paths: &IntPaths, + camera: Camera, + offset: Vector, + color: Color, + width: f32, + arrows: bool, + ) -> Option { if paths.is_empty() { return None; } @@ -40,11 +55,7 @@ impl PathWidget { Self::append_path(&mut builder, camera, path, width, arrows); } - let s = if arrows { - 2.5 * width - } else { - 0.5 * width - }; + let s = if arrows { 2.5 * width } else { 0.5 * width }; let offset = Vector::new(offset.x - s, offset.y - s); @@ -53,14 +64,23 @@ impl PathWidget { Self::stroke_mesh_for_triangulation(triangulation, offset, color) } - fn stroke_mesh_for_triangulation(triangulation: Triangulation, usize>, offset: Vector, color: Color) -> Option { + fn stroke_mesh_for_triangulation( + triangulation: Triangulation, usize>, + offset: Vector, + color: Color, + ) -> Option { if triangulation.indices.is_empty() { return None; } let color_pack = pack(color); - let vertices = triangulation.points.iter().map(|&p| { - SolidVertex2D { position: [p.x - offset.x, p.y - offset.y], color: color_pack } - }).collect(); + let vertices = triangulation + .points + .iter() + .map(|&p| SolidVertex2D { + position: [p.x - offset.x, p.y - offset.y], + color: color_pack, + }) + .collect(); let indices = triangulation.indices.iter().map(|&i| i as u32).collect(); @@ -87,12 +107,21 @@ impl PathWidget { camera.int_world_to_view(IntPoint::new(min_x, max_y)) } - fn append_path(builder: &mut TriangulationBuilder, usize>, camera: Camera, path: &IntPath, width: f32, arrows: bool) { + fn append_path( + builder: &mut TriangulationBuilder, usize>, + camera: Camera, + path: &IntPath, + width: f32, + arrows: bool, + ) { let stroke_builder = ButtStrokeBuilder::new(StrokeStyle::with_width(width)); - let screen_path: Vec<_> = path.iter().map(|&p| { - let v = camera.int_world_to_view(p); - FloatPoint::new(v.x, v.y) - }).collect(); + let screen_path: Vec<_> = path + .iter() + .map(|&p| { + let v = camera.int_world_to_view(p); + FloatPoint::new(v.x, v.y) + }) + .collect(); let sub_triangulation = stroke_builder.build_open_path_mesh::(&screen_path); builder.append(sub_triangulation); @@ -111,7 +140,8 @@ impl PathWidget { let v0 = m0 + t0; let v1 = m0 + t1; - let arrow_triangulation = stroke_builder.build_open_path_mesh::(&[v0, m, v1]); + let arrow_triangulation = + stroke_builder.build_open_path_mesh::(&[v0, m, v1]); builder.append(arrow_triangulation); a = b; @@ -154,9 +184,7 @@ impl Widget for PathWidget { renderer.with_layer(bounds, |renderer| { let offset = Vector::point(layout.position()); if let Some(mesh) = &self.stroke { - renderer.with_translation(offset, |renderer| { - renderer.draw_mesh(mesh.clone()) - }); + renderer.with_translation(offset, |renderer| renderer.draw_mesh(mesh.clone())); } }); } diff --git a/examples/overlay_editor/src/draw/shape.rs b/examples/overlay_editor/src/draw/shape.rs index 7e3ff3a1..64ae6bce 100644 --- a/examples/overlay_editor/src/draw/shape.rs +++ b/examples/overlay_editor/src/draw/shape.rs @@ -8,8 +8,8 @@ use i_triangle::i_overlay::core::fill_rule::FillRule; use i_triangle::i_overlay::i_float::float::point::FloatPoint; use i_triangle::i_overlay::i_float::int::point::IntPoint; use i_triangle::i_overlay::i_shape::int::count::PointsCount; -use i_triangle::i_overlay::i_shape::int::path::IntPaths; -use i_triangle::i_overlay::i_shape::int::shape::IntShapes; +use i_triangle::i_overlay::i_shape::int::path::IntPaths as RawIntPaths; +use i_triangle::i_overlay::i_shape::int::shape::IntShapes as RawIntShapes; use i_triangle::int::triangulation::IntTriangulation; use i_triangle::int::triangulator::IntTriangulator; use i_triangle::int::validation::Validation; @@ -22,6 +22,9 @@ use iced::advanced::widget::{Tree, Widget}; use iced::{mouse, Color, Transformation, Vector}; use iced::{Element, Length, Rectangle, Renderer, Size, Theme}; +type IntPaths = RawIntPaths; +type IntShapes = RawIntShapes; + pub(crate) struct ShapeWidget { fill: Option, stroke: Option, @@ -99,7 +102,7 @@ impl ShapeWidget { } fn fill_mesh_for_triangulation( - triangulation: IntTriangulation, + triangulation: IntTriangulation, camera: Camera, offset: Vector, color: Color, @@ -154,8 +157,7 @@ impl ShapeWidget { }) .collect(); - let sub_triangulation = - stroke_builder.build_closed_path_mesh::(&world_path); + let sub_triangulation = stroke_builder.build_closed_path_mesh::(&world_path); builder.append(sub_triangulation); } } @@ -191,8 +193,7 @@ impl ShapeWidget { }) .collect(); - let sub_triangulation = - stroke_builder.build_closed_path_mesh::(&world_path); + let sub_triangulation = stroke_builder.build_closed_path_mesh::(&world_path); builder.append(sub_triangulation); } diff --git a/examples/overlay_editor/src/draw/varicolored.rs b/examples/overlay_editor/src/draw/varicolored.rs index aeea0a8d..191bac72 100644 --- a/examples/overlay_editor/src/draw/varicolored.rs +++ b/examples/overlay_editor/src/draw/varicolored.rs @@ -1,23 +1,26 @@ +use crate::geom::camera::Camera; +use crate::geom::vector::VectorExt; use i_mesh::path::butt::ButtStrokeBuilder; use i_mesh::path::style::StrokeStyle; use i_triangle::float::builder::TriangulationBuilder; use i_triangle::float::triangulation::Triangulation; use i_triangle::i_overlay::i_float::float::point::FloatPoint; use i_triangle::i_overlay::i_float::int::point::IntPoint; -use i_triangle::i_overlay::i_shape::int::path::IntPaths; -use i_triangle::i_overlay::i_shape::int::shape::IntShapes; +use i_triangle::i_overlay::i_shape::int::path::IntPaths as RawIntPaths; +use i_triangle::i_overlay::i_shape::int::shape::IntShapes as RawIntShapes; use i_triangle::int::triangulatable::IntTriangulatable; use i_triangle::int::triangulation::IntTriangulation; +use iced::advanced::graphics::color::pack; +use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; +use iced::advanced::graphics::Mesh; use iced::advanced::layout::{self, Layout}; use iced::advanced::renderer; use iced::advanced::widget::{Tree, Widget}; -use iced::{mouse, Color, Vector, Transformation}; +use iced::{mouse, Color, Transformation, Vector}; use iced::{Element, Length, Rectangle, Renderer, Size, Theme}; -use iced::advanced::graphics::color::pack; -use iced::advanced::graphics::Mesh; -use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; -use crate::geom::camera::Camera; -use crate::geom::vector::VectorExt; + +type IntPaths = RawIntPaths; +type IntShapes = RawIntShapes; pub(crate) struct VaricoloredWidget { fill: Vec, @@ -26,18 +29,18 @@ pub(crate) struct VaricoloredWidget { impl VaricoloredWidget { const SHAPE_COLOR_STORE: [[u8; 3]; 12] = [ - [255, 149, 0], // Orange - [88, 86, 214], // Purple - [255, 45, 85], // Pink - [90, 200, 250], // Blue - [76, 217, 100], // Green - [255, 204, 0], // Yellow - [142, 142, 147], // Gray - [255, 59, 48], // Red - [52, 199, 89], // Green - [0, 122, 255], // Blue - [175, 82, 222], // Indigo - [255, 214, 10], // Teal + [255, 149, 0], // Orange + [88, 86, 214], // Purple + [255, 45, 85], // Pink + [90, 200, 250], // Blue + [76, 217, 100], // Green + [255, 204, 0], // Yellow + [142, 142, 147], // Gray + [255, 59, 48], // Red + [52, 199, 89], // Green + [0, 122, 255], // Blue + [175, 82, 222], // Indigo + [255, 214, 10], // Teal ]; pub(crate) fn with_shapes(shapes: &IntShapes, camera: Camera, stroke_width: f32) -> Self { @@ -49,21 +52,27 @@ impl VaricoloredWidget { let data = Self::SHAPE_COLOR_STORE[index % Self::SHAPE_COLOR_STORE.len()]; let color = Color::from_rgb8(data[0], data[1], data[2]); - if let Some(mesh) = Self::fill_mesh_for_paths(shape, camera, offset, color.scale_alpha(0.2)) { + if let Some(mesh) = + Self::fill_mesh_for_paths(shape, camera, offset, color.scale_alpha(0.2)) + { fill.push(mesh); } - if let Some(mesh) = Self::stroke_mesh_for_paths(shape, camera, offset, color, stroke_width) { + if let Some(mesh) = + Self::stroke_mesh_for_paths(shape, camera, offset, color, stroke_width) + { stroke.push(mesh); } } - Self { - fill, - stroke, - } + Self { fill, stroke } } - fn fill_mesh_for_paths(paths: &IntPaths, camera: Camera, offset: Vector, color: Color) -> Option { + fn fill_mesh_for_paths( + paths: &IntPaths, + camera: Camera, + offset: Vector, + color: Color, + ) -> Option { if paths.is_empty() { return None; } @@ -72,15 +81,27 @@ impl VaricoloredWidget { Self::fill_mesh_for_triangulation(triangulation, camera, offset, color) } - fn fill_mesh_for_triangulation(triangulation: IntTriangulation, camera: Camera, offset: Vector, color: Color) -> Option { + fn fill_mesh_for_triangulation( + triangulation: IntTriangulation, + camera: Camera, + offset: Vector, + color: Color, + ) -> Option { if triangulation.indices.is_empty() { return None; } let color_pack = pack(color); - let vertices = triangulation.points.iter().map(|&p| { - let v = camera.int_world_to_view(p); - SolidVertex2D { position: [v.x - offset.x, v.y - offset.y], color: color_pack } - }).collect(); + let vertices = triangulation + .points + .iter() + .map(|&p| { + let v = camera.int_world_to_view(p); + SolidVertex2D { + position: [v.x - offset.x, v.y - offset.y], + color: color_pack, + } + }) + .collect(); let indices = triangulation.indices.iter().map(|&i| i as u32).collect(); @@ -91,7 +112,13 @@ impl VaricoloredWidget { }) } - fn stroke_mesh_for_paths(paths: &IntPaths, camera: Camera, offset: Vector, color: Color, width: f32) -> Option { + fn stroke_mesh_for_paths( + paths: &IntPaths, + camera: Camera, + offset: Vector, + color: Color, + width: f32, + ) -> Option { if paths.is_empty() { return None; } @@ -100,12 +127,16 @@ impl VaricoloredWidget { let mut builder = TriangulationBuilder::default(); for path in paths.iter() { - let world_path: Vec<_> = path.iter().map(|&p| { - let v = camera.int_world_to_view(p); - FloatPoint::new(v.x, v.y) - }).collect(); + let world_path: Vec<_> = path + .iter() + .map(|&p| { + let v = camera.int_world_to_view(p); + FloatPoint::new(v.x, v.y) + }) + .collect(); - let sub_triangulation: Triangulation, usize> = stroke_builder.build_closed_path_mesh(&world_path); + let sub_triangulation: Triangulation, usize> = + stroke_builder.build_closed_path_mesh(&world_path); builder.append(sub_triangulation); } @@ -118,14 +149,23 @@ impl VaricoloredWidget { Self::stroke_mesh_for_triangulation(triangulation, offset, color) } - fn stroke_mesh_for_triangulation(triangulation: Triangulation, usize>, offset: Vector, color: Color) -> Option { + fn stroke_mesh_for_triangulation( + triangulation: Triangulation, usize>, + offset: Vector, + color: Color, + ) -> Option { if triangulation.indices.is_empty() { return None; } let color_pack = pack(color); - let vertices = triangulation.points.iter().map(|&p| { - SolidVertex2D { position: [p.x - offset.x, p.y - offset.y], color: color_pack } - }).collect(); + let vertices = triangulation + .points + .iter() + .map(|&p| SolidVertex2D { + position: [p.x - offset.x, p.y - offset.y], + color: color_pack, + }) + .collect(); let indices = triangulation.indices.iter().map(|&i| i as u32).collect(); diff --git a/examples/overlay_editor/src/draw/vectors.rs b/examples/overlay_editor/src/draw/vectors.rs index 4cb5cd47..46b7a031 100644 --- a/examples/overlay_editor/src/draw/vectors.rs +++ b/examples/overlay_editor/src/draw/vectors.rs @@ -1,20 +1,24 @@ -use std::f32::consts::PI; +use crate::geom::camera::Camera; +use crate::geom::vector::VectorExt; use i_mesh::path::butt::ButtStrokeBuilder; use i_mesh::path::style::StrokeStyle; use i_triangle::float::triangulation::Triangulation; use i_triangle::i_overlay::i_float::float::point::FloatPoint; use i_triangle::i_overlay::i_float::int::point::IntPoint; -use i_triangle::i_overlay::vector::edge::{SideFill, SUBJ_LEFT, SUBJ_RIGHT, CLIP_LEFT, CLIP_RIGHT, VectorEdge}; +use i_triangle::i_overlay::vector::edge::{ + DataVectorEdge, SideFill, CLIP_LEFT, CLIP_RIGHT, SUBJ_LEFT, SUBJ_RIGHT, +}; +use iced::advanced::graphics::color::pack; +use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; +use iced::advanced::graphics::{color, Mesh}; use iced::advanced::layout::{self, Layout}; use iced::advanced::renderer; use iced::advanced::widget::{Tree, Widget}; -use iced::{mouse, Color, Vector, Transformation}; +use iced::{mouse, Color, Transformation, Vector}; use iced::{Element, Length, Rectangle, Renderer, Size, Theme}; -use iced::advanced::graphics::color::pack; -use iced::advanced::graphics::{color, Mesh}; -use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; -use crate::geom::camera::Camera; -use crate::geom::vector::VectorExt; +use std::f32::consts::PI; + +type VectorEdge = DataVectorEdge; struct ColorSchema { subj: color::Packed, @@ -49,19 +53,35 @@ impl ColorSchema { } fn subj_right(&self, fill: SideFill) -> color::Packed { - if fill & SUBJ_RIGHT != 0 { self.subj } else { self.subj_none } + if fill & SUBJ_RIGHT != 0 { + self.subj + } else { + self.subj_none + } } fn subj_left(&self, fill: SideFill) -> color::Packed { - if fill & SUBJ_LEFT != 0 { self.subj } else { self.subj_none } + if fill & SUBJ_LEFT != 0 { + self.subj + } else { + self.subj_none + } } fn clip_right(&self, fill: SideFill) -> color::Packed { - if fill & CLIP_RIGHT != 0 { self.clip } else { self.clip_none } + if fill & CLIP_RIGHT != 0 { + self.clip + } else { + self.clip_none + } } fn clip_left(&self, fill: SideFill) -> color::Packed { - if fill & CLIP_LEFT != 0 { self.clip } else { self.clip_none } + if fill & CLIP_LEFT != 0 { + self.clip + } else { + self.clip_none + } } } @@ -70,16 +90,27 @@ pub(crate) struct VectorsWidget { } impl VectorsWidget { - pub(crate) fn with_vectors(vectors: &[VectorEdge], camera: Camera, subj: Color, clip: Color, both: Color, stroke_width: f32) -> Self { + pub(crate) fn with_vectors( + vectors: &[VectorEdge], + camera: Camera, + subj: Color, + clip: Color, + both: Color, + stroke_width: f32, + ) -> Self { let schema = ColorSchema::new(subj, clip, both); let offset = Self::offset_for_vectors(vectors, camera); let stroke = Self::stroke_mesh_for_paths(vectors, camera, offset, schema, stroke_width); - Self { - stroke, - } + Self { stroke } } - fn stroke_mesh_for_paths(vectors: &[VectorEdge], camera: Camera, offset: Vector, schema: ColorSchema, width: f32) -> Option { + fn stroke_mesh_for_paths( + vectors: &[VectorEdge], + camera: Camera, + offset: Vector, + schema: ColorSchema, + width: f32, + ) -> Option { if vectors.is_empty() { return None; } @@ -120,13 +151,23 @@ impl VectorsWidget { camera.int_world_to_view(IntPoint::new(min_x, max_y)) } - fn append_vector(builder: &mut MeshBuilder, camera: Camera, vector: &VectorEdge, offset: Vector, schema: &ColorSchema, width: f32) { + fn append_vector( + builder: &mut MeshBuilder, + camera: Camera, + vector: &VectorEdge, + offset: Vector, + schema: &ColorSchema, + width: f32, + ) { let stroke_builder = ButtStrokeBuilder::new(StrokeStyle::with_width(width)); let path = [vector.a, vector.b]; - let screen_path: Vec<_> = path.iter().map(|&p| { - let v = camera.int_world_to_view(p); - FloatPoint::new(v.x - offset.x, v.y - offset.y) - }).collect(); + let screen_path: Vec<_> = path + .iter() + .map(|&p| { + let v = camera.int_world_to_view(p); + FloatPoint::new(v.x - offset.x, v.y - offset.y) + }) + .collect(); let segment_color = schema.color(vector.fill); @@ -166,10 +207,14 @@ impl VectorsWidget { let arrow_triangulation = stroke_builder.build_open_path_mesh(&[v0, b, v1]); builder.append(arrow_triangulation, segment_color); - } - fn append_circle(builder: &mut MeshBuilder, pos: FloatPoint, color: color::Packed, radius: f32) { + fn append_circle( + builder: &mut MeshBuilder, + pos: FloatPoint, + color: color::Packed, + radius: f32, + ) { let n = 8; let da = 2.0 * PI / n as f32; let mut a = 0.0f32; @@ -182,7 +227,7 @@ impl VectorsWidget { points.push(FloatPoint::new(x, y)); indices.extend(&[n, i, (i + 1) % n]); a += da; - }; + } points.push(pos); @@ -224,9 +269,7 @@ impl Widget for VectorsWidget { renderer.with_layer(bounds, |renderer| { let offset = Vector::point(layout.position()); if let Some(mesh) = &self.stroke { - renderer.with_translation(offset, |renderer| { - renderer.draw_mesh(mesh.clone()) - }); + renderer.with_translation(offset, |renderer| renderer.draw_mesh(mesh.clone())); } }); } @@ -251,17 +294,20 @@ impl MeshBuilder { } } - fn append(&mut self, triangulation: Triangulation, usize>, color: color::Packed) -> &mut Self { + fn append( + &mut self, + triangulation: Triangulation, usize>, + color: color::Packed, + ) -> &mut Self { let offset = self.vertices.len(); for p in triangulation.points.iter() { - self.vertices.push(SolidVertex2D { position: [p.x, p.y], color }); + self.vertices.push(SolidVertex2D { + position: [p.x, p.y], + color, + }); } - self.indices.extend( - triangulation - .indices - .iter() - .map(|&i| (i + offset) as u32), - ); + self.indices + .extend(triangulation.indices.iter().map(|&i| (i + offset) as u32)); self } diff --git a/examples/overlay_editor/src/geom/camera.rs b/examples/overlay_editor/src/geom/camera.rs index e93fab79..384a3a57 100644 --- a/examples/overlay_editor/src/geom/camera.rs +++ b/examples/overlay_editor/src/geom/camera.rs @@ -11,9 +11,13 @@ pub(crate) struct Camera { } impl Camera { - pub(crate) fn empty() -> Self { - Self { scale: 0.0, i_scale: 0.0, size: Size::ZERO, pos: Vector::new(0.0 ,0.0) } + Self { + scale: 0.0, + i_scale: 0.0, + size: Size::ZERO, + pos: Vector::new(0.0, 0.0), + } } pub(crate) fn is_empty(&self) -> bool { @@ -44,13 +48,24 @@ impl Camera { let y = 0.5 * (rect.min_y + rect.max_y) as f32; let pos = Vector::new(x, y); - Camera { scale, i_scale, size, pos } + Camera { + scale, + i_scale, + size, + pos, + } } #[inline] - pub(crate) fn world_to_screen(&self, view_left_top: Vector, world: IntPoint) -> Vector { - let x = self.scale * (world.x as f32 - self.pos.x) + view_left_top.x + 0.5 * self.size.width; - let y = self.scale * (self.pos.y - world.y as f32) + view_left_top.y + 0.5 * self.size.height; + pub(crate) fn world_to_screen( + &self, + view_left_top: Vector, + world: IntPoint, + ) -> Vector { + let x = + self.scale * (world.x as f32 - self.pos.x) + view_left_top.x + 0.5 * self.size.width; + let y = + self.scale * (self.pos.y - world.y as f32) + view_left_top.y + 0.5 * self.size.height; Vector { x, y } } @@ -79,4 +94,4 @@ impl Camera { let y = -view_distance.y * self.i_scale; Vector { x, y } } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/geom/mod.rs b/examples/overlay_editor/src/geom/mod.rs index 4978c296..c0ef811d 100644 --- a/examples/overlay_editor/src/geom/mod.rs +++ b/examples/overlay_editor/src/geom/mod.rs @@ -1,2 +1,2 @@ pub(crate) mod camera; -pub(crate) mod vector; \ No newline at end of file +pub(crate) mod vector; diff --git a/examples/overlay_editor/src/geom/vector.rs b/examples/overlay_editor/src/geom/vector.rs index f01b998c..4051b5ea 100644 --- a/examples/overlay_editor/src/geom/vector.rs +++ b/examples/overlay_editor/src/geom/vector.rs @@ -8,12 +8,12 @@ pub(crate) trait VectorExt { impl VectorExt for Vector { fn round(&self) -> IntPoint { - IntPoint::new( - self.x.round() as i32, - self.y.round() as i32, - ) + IntPoint::new(self.x.round() as i32, self.y.round() as i32) } fn point(point: Point) -> Self { - Self { x: point.x, y: point.y } + Self { + x: point.x, + y: point.y, + } } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/lib.rs b/examples/overlay_editor/src/lib.rs index 38aed26c..a72f900b 100644 --- a/examples/overlay_editor/src/lib.rs +++ b/examples/overlay_editor/src/lib.rs @@ -1,7 +1,7 @@ -pub mod web; -mod data; mod app; +mod data; mod draw; +mod geom; mod point_editor; mod sheet; -mod geom; \ No newline at end of file +pub mod web; diff --git a/examples/overlay_editor/src/main.rs b/examples/overlay_editor/src/main.rs index 741ff3ab..4cd68f8f 100644 --- a/examples/overlay_editor/src/main.rs +++ b/examples/overlay_editor/src/main.rs @@ -1,30 +1,27 @@ -mod data; mod app; +mod data; mod draw; +mod geom; mod point_editor; mod sheet; -mod geom; -use iced::application; use crate::app::main::EditorApp; use crate::data::resource::AppResource; +use iced::application; #[cfg(not(target_arch = "wasm32"))] fn main() -> iced::Result { run_desktop() } - - #[cfg(not(target_arch = "wasm32"))] fn run_desktop() -> iced::Result { - let app_initializer = move || { let app_resource = AppResource::with_paths( "../tests/boolean", "../tests/string", "../tests/stroke", - "../tests/outline" + "../tests/outline", ); let app = EditorApp::with_resource(app_resource); (app, iced::Task::none()) @@ -37,4 +34,3 @@ fn run_desktop() -> iced::Result { .subscription(EditorApp::subscription) .run() } - diff --git a/examples/overlay_editor/src/point_editor/mod.rs b/examples/overlay_editor/src/point_editor/mod.rs index 2a50377f..55ec49ce 100644 --- a/examples/overlay_editor/src/point_editor/mod.rs +++ b/examples/overlay_editor/src/point_editor/mod.rs @@ -1,3 +1,3 @@ -pub(crate) mod widget; +pub(crate) mod point; pub(crate) mod state; -pub(crate) mod point; \ No newline at end of file +pub(crate) mod widget; diff --git a/examples/overlay_editor/src/point_editor/point.rs b/examples/overlay_editor/src/point_editor/point.rs index 83cc0012..9540adf0 100644 --- a/examples/overlay_editor/src/point_editor/point.rs +++ b/examples/overlay_editor/src/point_editor/point.rs @@ -1,11 +1,13 @@ use i_triangle::i_overlay::i_float::int::point::IntPoint; -use i_triangle::i_overlay::i_shape::int::path::IntPaths; +use i_triangle::i_overlay::i_shape::int::path::IntPaths as RawIntPaths; + +type IntPaths = RawIntPaths; #[derive(Debug, Clone)] pub(crate) struct MultiIndex { pub(crate) point_index: usize, pub(crate) path_index: usize, - pub(crate) group_index: usize, // subj or clip + pub(crate) group_index: usize, // subj or clip } #[derive(Debug, Clone)] @@ -22,7 +24,11 @@ impl PathsToEditorPoints for IntPaths { fn feed_edit_points(&self, group_index: usize, edit_points: &mut Vec) { for (path_index, path) in self.iter().enumerate() { for (point_index, &pos) in path.iter().enumerate() { - let index = MultiIndex { point_index, path_index, group_index }; + let index = MultiIndex { + point_index, + path_index, + group_index, + }; edit_points.push(EditorPoint { pos, index }) } } diff --git a/examples/overlay_editor/src/point_editor/state.rs b/examples/overlay_editor/src/point_editor/state.rs index b5790076..83434033 100644 --- a/examples/overlay_editor/src/point_editor/state.rs +++ b/examples/overlay_editor/src/point_editor/state.rs @@ -1,12 +1,12 @@ use crate::point_editor::point::EditorPoint; -use iced::{Color, Rectangle, Transformation, Vector}; use iced::advanced::graphics::color::pack; +use iced::{Color, Rectangle, Transformation, Vector}; -use iced::advanced::graphics::Mesh; -use iced::advanced::graphics::mesh::{SolidVertex2D, Indexed}; -use crate::point_editor::widget::{PointEditUpdate, PointsEditorWidget}; use crate::geom::camera::Camera; use crate::geom::vector::VectorExt; +use crate::point_editor::widget::{PointEditUpdate, PointsEditorWidget}; +use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; +use iced::advanced::graphics::Mesh; #[derive(Clone)] pub(super) struct MeshCache { @@ -34,7 +34,13 @@ pub(crate) struct PointsEditorState { } impl PointsEditorState { - pub(crate) fn update_mesh(&mut self, r: f32, main_color: Color, hover_color: Color, drag_color: Color) { + pub(crate) fn update_mesh( + &mut self, + r: f32, + main_color: Color, + hover_color: Color, + drag_color: Color, + ) { let radius = if let Some(cache) = &self.mesh_cache { cache.radius } else { @@ -55,20 +61,56 @@ impl PointsEditorState { let hover_pack = pack(hover_color); let drag_pack = pack(drag_color); - main_vertices.push(SolidVertex2D { position: [0.0, r], color: main_pack }); - main_vertices.push(SolidVertex2D { position: [r, 2.0 * r], color: main_pack }); - main_vertices.push(SolidVertex2D { position: [2.0 * r, r], color: main_pack }); - main_vertices.push(SolidVertex2D { position: [r, 0.0], color: main_pack }); + main_vertices.push(SolidVertex2D { + position: [0.0, r], + color: main_pack, + }); + main_vertices.push(SolidVertex2D { + position: [r, 2.0 * r], + color: main_pack, + }); + main_vertices.push(SolidVertex2D { + position: [2.0 * r, r], + color: main_pack, + }); + main_vertices.push(SolidVertex2D { + position: [r, 0.0], + color: main_pack, + }); - hover_vertices.push(SolidVertex2D { position: [0.0, sr], color: hover_pack }); - hover_vertices.push(SolidVertex2D { position: [r, 2.0 * sr], color: hover_pack }); - hover_vertices.push(SolidVertex2D { position: [2.0 * sr, sr], color: hover_pack }); - hover_vertices.push(SolidVertex2D { position: [sr, 0.0], color: hover_pack }); + hover_vertices.push(SolidVertex2D { + position: [0.0, sr], + color: hover_pack, + }); + hover_vertices.push(SolidVertex2D { + position: [r, 2.0 * sr], + color: hover_pack, + }); + hover_vertices.push(SolidVertex2D { + position: [2.0 * sr, sr], + color: hover_pack, + }); + hover_vertices.push(SolidVertex2D { + position: [sr, 0.0], + color: hover_pack, + }); - drag_vertices.push(SolidVertex2D { position: [0.0, r], color: drag_pack }); - drag_vertices.push(SolidVertex2D { position: [r, 2.0 * r], color: drag_pack }); - drag_vertices.push(SolidVertex2D { position: [2.0 * r, r], color: drag_pack }); - drag_vertices.push(SolidVertex2D { position: [r, 0.0], color: drag_pack }); + drag_vertices.push(SolidVertex2D { + position: [0.0, r], + color: drag_pack, + }); + drag_vertices.push(SolidVertex2D { + position: [r, 2.0 * r], + color: drag_pack, + }); + drag_vertices.push(SolidVertex2D { + position: [2.0 * r, r], + color: drag_pack, + }); + drag_vertices.push(SolidVertex2D { + position: [r, 0.0], + color: drag_pack, + }); indices.push(0); indices.push(1); @@ -81,24 +123,37 @@ impl PointsEditorState { self.mesh_cache = Some(MeshCache { radius: r, main: Mesh::Solid { - buffers: Indexed { vertices: main_vertices, indices: indices.clone() }, + buffers: Indexed { + vertices: main_vertices, + indices: indices.clone(), + }, transformation: Transformation::IDENTITY, clip_bounds: Rectangle::INFINITE, }, hover: Mesh::Solid { - buffers: Indexed { vertices: hover_vertices, indices: indices.clone() }, + buffers: Indexed { + vertices: hover_vertices, + indices: indices.clone(), + }, transformation: Transformation::translate(r - sr, r - sr), clip_bounds: Rectangle::INFINITE, }, drag: Mesh::Solid { - buffers: Indexed { vertices: drag_vertices, indices }, + buffers: Indexed { + vertices: drag_vertices, + indices, + }, transformation: Transformation::IDENTITY, clip_bounds: Rectangle::INFINITE, }, }); } - pub(super) fn mouse_press(&mut self, widget: &PointsEditorWidget, cursor: Vector) -> bool { + pub(super) fn mouse_press( + &mut self, + widget: &PointsEditorWidget, + cursor: Vector, + ) -> bool { let mut min_ds = widget.hover_radius * widget.hover_radius; let mut min_index = usize::MAX; // println!("cursor: {:?}", &cursor); @@ -125,7 +180,11 @@ impl PointsEditorState { is_catch } - pub(super) fn mouse_release(&mut self, widget: &PointsEditorWidget, cursor: Vector) -> bool { + pub(super) fn mouse_release( + &mut self, + widget: &PointsEditorWidget, + cursor: Vector, + ) -> bool { if let SelectState::Drag(_) = &self.select { self.select = SelectState::None; self.mouse_hover(widget.camera, widget.hover_radius, widget.points, cursor); @@ -135,7 +194,11 @@ impl PointsEditorState { } } - pub(super) fn mouse_move(&mut self, widget: &PointsEditorWidget, cursor: Vector) -> Option { + pub(super) fn mouse_move( + &mut self, + widget: &PointsEditorWidget, + cursor: Vector, + ) -> Option { if let SelectState::Drag(drag) = &self.select { Self::mouse_drag(drag, widget.camera, widget.points, cursor) } else { @@ -144,7 +207,12 @@ impl PointsEditorState { } } - fn mouse_drag(drag: &Drag, camera: Camera, points: &[EditorPoint], cursor: Vector) -> Option { + fn mouse_drag( + drag: &Drag, + camera: Camera, + points: &[EditorPoint], + cursor: Vector, + ) -> Option { let translate = cursor - drag.start_float; let world_dist = camera.view_distance_to_world(translate).round(); let world_point = world_dist + drag.editor_point.pos; @@ -152,14 +220,23 @@ impl PointsEditorState { if world_point != real_point.pos { return Some(PointEditUpdate { index: drag.index, - point: EditorPoint { pos: world_point, index: drag.editor_point.index.clone() }, + point: EditorPoint { + pos: world_point, + index: drag.editor_point.index.clone(), + }, }); } None } - fn mouse_hover(&mut self, camera: Camera, radius: f32, points: &[EditorPoint], cursor: Vector) { + fn mouse_hover( + &mut self, + camera: Camera, + radius: f32, + points: &[EditorPoint], + cursor: Vector, + ) { let mut min_ds = radius * radius; let mut min_index = usize::MAX; for (i, p) in points.iter().enumerate() { @@ -192,4 +269,4 @@ impl Default for PointsEditorState { mesh_cache: None, } } -} \ No newline at end of file +} diff --git a/examples/overlay_editor/src/sheet/mod.rs b/examples/overlay_editor/src/sheet/mod.rs index 40f2d2f4..a73631c1 100644 --- a/examples/overlay_editor/src/sheet/mod.rs +++ b/examples/overlay_editor/src/sheet/mod.rs @@ -1,2 +1,2 @@ mod state; -pub(crate) mod widget; \ No newline at end of file +pub(crate) mod widget; diff --git a/examples/overlay_editor/src/sheet/state.rs b/examples/overlay_editor/src/sheet/state.rs index 1f3dabb2..d223d5b4 100644 --- a/examples/overlay_editor/src/sheet/state.rs +++ b/examples/overlay_editor/src/sheet/state.rs @@ -1,6 +1,6 @@ -use iced::{Size, Vector}; -use iced::mouse::ScrollDelta; use crate::geom::camera::Camera; +use iced::mouse::ScrollDelta; +use iced::{Size, Vector}; struct Drag { start_screen: Vector, @@ -18,14 +18,21 @@ pub(super) struct SheetState { impl SheetState { pub(super) fn mouse_press(&mut self, camera: Camera, view_cursor: Vector) { - self.drag_state = DragState::Drag(Drag { start_screen: view_cursor, start_world: camera.pos }); + self.drag_state = DragState::Drag(Drag { + start_screen: view_cursor, + start_world: camera.pos, + }); } pub(super) fn mouse_release(&mut self) { self.drag_state = DragState::None; } - pub(super) fn mouse_move(&mut self, camera: Camera, view_cursor: Vector) -> Option> { + pub(super) fn mouse_move( + &mut self, + camera: Camera, + view_cursor: Vector, + ) -> Option> { if let DragState::Drag(drag) = &self.drag_state { let translate = drag.start_screen - view_cursor; let world_dist = camera.view_distance_to_world(translate); @@ -39,9 +46,14 @@ impl SheetState { } } - pub(super) fn mouse_wheel_scrolled(&mut self, camera: Camera, viewport_size: Size, delta: ScrollDelta, view_cursor: Vector) -> Option { - if let ScrollDelta::Pixels { x: _ , y } = delta { - + pub(super) fn mouse_wheel_scrolled( + &mut self, + camera: Camera, + viewport_size: Size, + delta: ScrollDelta, + view_cursor: Vector, + ) -> Option { + if let ScrollDelta::Pixels { x: _, y } = delta { let s = 1.0 + y / viewport_size.height; let mut new_camera = camera; new_camera.set_scale(s * camera.scale); @@ -68,4 +80,3 @@ impl Default for SheetState { } } } - diff --git a/examples/overlay_editor/src/sheet/widget.rs b/examples/overlay_editor/src/sheet/widget.rs index 28073e3b..d63d7f89 100644 --- a/examples/overlay_editor/src/sheet/widget.rs +++ b/examples/overlay_editor/src/sheet/widget.rs @@ -1,15 +1,15 @@ use crate::geom::camera::Camera; use crate::sheet::state::SheetState; -use iced::{Color, Point, Transformation}; -use iced::advanced::widget::tree; +use iced::advanced::graphics::color::pack; +use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; +use iced::advanced::graphics::Mesh; use iced::advanced::layout::{self, Layout}; -use iced::advanced::{Clipboard, renderer, Shell}; +use iced::advanced::widget::tree; use iced::advanced::widget::{Tree, Widget}; -use iced::{Event, mouse}; +use iced::advanced::{renderer, Clipboard, Shell}; +use iced::{mouse, Event}; +use iced::{Color, Point, Transformation}; use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector}; -use iced::advanced::graphics::color::pack; -use iced::advanced::graphics::Mesh; -use iced::advanced::graphics::mesh::{SolidVertex2D, Indexed}; pub(crate) struct SheetWidget<'a, Message> { camera: Camera, @@ -47,10 +47,22 @@ impl<'a, Message: 'a> SheetWidget<'a, Message> { let mut vertices = Vec::with_capacity(4); let mut indices = Vec::with_capacity(6); - vertices.push(SolidVertex2D { position: [min_x, min_y], color: color_pack }); - vertices.push(SolidVertex2D { position: [min_x, max_y], color: color_pack }); - vertices.push(SolidVertex2D { position: [max_x, max_y], color: color_pack }); - vertices.push(SolidVertex2D { position: [max_x, min_y], color: color_pack }); + vertices.push(SolidVertex2D { + position: [min_x, min_y], + color: color_pack, + }); + vertices.push(SolidVertex2D { + position: [min_x, max_y], + color: color_pack, + }); + vertices.push(SolidVertex2D { + position: [max_x, max_y], + color: color_pack, + }); + vertices.push(SolidVertex2D { + position: [max_x, min_y], + color: color_pack, + }); indices.push(0); indices.push(1); @@ -145,7 +157,9 @@ impl Widget for SheetWidget<'_, Message> { let position = cursor.position().unwrap_or(Point::ORIGIN); if bounds.contains(position) { let cursor = position - bounds.position(); - if let Some(scale) = state.mouse_wheel_scrolled(self.camera, bounds.size(), *delta, cursor) { + if let Some(scale) = + state.mouse_wheel_scrolled(self.camera, bounds.size(), *delta, cursor) + { shell.publish((self.on_zoom)(scale)); shell.capture_event(); return; @@ -198,8 +212,12 @@ impl Widget for SheetWidget<'_, Message> { let round_world_max_x = world_max.x.trunc(); let round_world_max_y = world_max.y.trunc(); - let nfx = (round_world_max_x - round_world_min_x + 0.001).round().abs(); - let nfy = (round_world_max_y - round_world_min_y + 0.001).round().abs(); + let nfx = (round_world_max_x - round_world_min_x + 0.001) + .round() + .abs(); + let nfy = (round_world_max_y - round_world_min_y + 0.001) + .round() + .abs(); let nx = nfx as usize; let ny = nfy as usize; @@ -219,17 +237,13 @@ impl Widget for SheetWidget<'_, Message> { let mut position = Vector::new(round_view_min.x + rect.x, view_min.y + rect.y); for _ in 0..=nx { - renderer.with_translation(position, |renderer| { - renderer.draw_mesh(vr_mesh.clone()) - }); + renderer.with_translation(position, |renderer| renderer.draw_mesh(vr_mesh.clone())); position.x += dx; } let mut position = Vector::new(view_min.x + rect.x, round_view_min.y + rect.y); for _ in 0..=ny { - renderer.with_translation(position, |renderer| { - renderer.draw_mesh(hz_mesh.clone()) - }); + renderer.with_translation(position, |renderer| renderer.draw_mesh(hz_mesh.clone())); position.y += dy; } }); diff --git a/examples/overlay_editor/src/web.rs b/examples/overlay_editor/src/web.rs index 4b14e453..a819f6f4 100644 --- a/examples/overlay_editor/src/web.rs +++ b/examples/overlay_editor/src/web.rs @@ -2,7 +2,6 @@ use std::panic; use std::sync::Once; use wasm_bindgen::prelude::*; - #[wasm_bindgen] pub struct WebApp {} @@ -12,7 +11,9 @@ static INIT_LOGGER: Once = Once::new(); #[wasm_bindgen] impl WebApp { #[wasm_bindgen(constructor)] - pub fn create() -> Self { Self {} } + pub fn create() -> Self { + Self {} + } #[wasm_bindgen] pub fn start( @@ -22,8 +23,8 @@ impl WebApp { stroke_data: String, outline_data: String, ) { - use log::info; use iced::application; + use log::info; use crate::app::main::EditorApp; use crate::data::resource::AppResource; @@ -37,12 +38,8 @@ impl WebApp { let app_initializer = move || { info!("wasm init"); - let app_resource = AppResource::with_content( - &boolean_data, - &string_data, - &stroke_data, - &outline_data, - ); + let app_resource = + AppResource::with_content(&boolean_data, &string_data, &stroke_data, &outline_data); let app = EditorApp::with_resource(app_resource); (app, iced::Task::none()) @@ -58,4 +55,4 @@ impl WebApp { info!("wasm app run"); } -} \ No newline at end of file +} diff --git a/iOverlay/Cargo.toml b/iOverlay/Cargo.toml index 62ef955a..dd848bb4 100644 --- a/iOverlay/Cargo.toml +++ b/iOverlay/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "i_overlay" -version = "6.0.1" +version = "7.0.0" authors = ["Nail Sharipov "] edition = "2024" rust-version = "1.88" @@ -12,9 +12,9 @@ readme = "README.md" categories = ["algorithms", "graphics", "science::geo", "mathematics", "no-std"] [dependencies] -i_float = { version = "^2.0.0" } -i_shape = { version = "^2.0.0" } -i_tree = { version = "^0.18.0" } +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_float = { path = "../../iFloat"} @@ -38,5 +38,5 @@ serde_json = "^1.0" rand = { version = "~0.10", features = ["alloc"] } #i_float = { path = "../../iFloat", features = ["serde"] } #i_shape = { path = "../../iShape", features = ["serde"] } -i_float = { version = "^2.0.0", features = ["serde"] } -i_shape = { version = "^2.0.0", features = ["serde"] } +i_float = { version = "^3.0.0", features = ["serde"] } +i_shape = { version = "^3.0.0", features = ["serde"] } diff --git a/iOverlay/README.md b/iOverlay/README.md index 4d092f64..09503e39 100644 --- a/iOverlay/README.md +++ b/iOverlay/README.md @@ -23,6 +23,7 @@ iOverlay powers polygon boolean operations in [geo](https://github.com/georust/g - [Boolean Operations](#boolean-operations) - [Simple Example](#simple-example) - [Overlay Rules](#overlay-rules) + - [Edge Attributes and Provenance](#edge-attributes-and-provenance) - [Spatial Predicates](#spatial-predicates) - [Custom Point Type Support](#custom-point-type-support) - [Slicing & Clipping](#slicing--clipping) @@ -55,7 +56,7 @@ iOverlay powers polygon boolean operations in [geo](https://github.com/georust/g - **Simplification**: removes degenerate vertices and merges collinear edges. - **Buffering**: offsets paths and polygons. - **Fill Rules**: even-odd, non-zero, positive and negative. -- **Data Types**: Supports i32, f32, and f64 APIs. +- **Data Types**: Supports `i16`/`i32`/`i64` integer APIs and `f32`/`f64` floating-point APIs.   ## Demo @@ -69,9 +70,16 @@ iOverlay powers polygon boolean operations in [geo](https://github.com/georust/g   ## Performance -iOverlay is optimized for large and complex inputs while preserving robust geometry semantics. The benchmark report compares iOverlay to other polygon overlay engines with a focus on throughput and performance. +iOverlay supports: -See the detailed report: [Performance Comparison](https://ishape-rust.github.io/iShape-js/overlay/performance/performance.html) +- `i16`/`i32`/`i64` math solvers +- `on`/`off` multithreading feature + +Average relative time for iOverlay Rust solvers + +For bigger data sets, the math engine and multithreading mode have a larger impact on runtime. + +See the detailed reports: [Performance Comparison](https://ishape-rust.github.io/iShape-js/overlay/performance/performance.html) and [Rust Solver Benchmarks](https://ishape-rust.github.io/iShape-js/overlay/performance/rust_i_overlay.html)   ## Getting Started @@ -184,6 +192,78 @@ The `overlay` function returns a `Vec`: |---------|---------------|----------------------|----------------|--------------------|----------------| | AB | Union | Intersection | Difference | Inverse Difference | Exclusion | +  +### Edge Attributes and Provenance + +Use `EdgeOverlay` when the result boundary needs to keep data from the original input edges: source ids, layer ids, material ids, constraints, styles, or any other edge provenance. The regular `Overlay` API remains optimized for plain geometry; this API is opt-in and works with user-defined payloads. + +When an input edge is split by intersections, its data is copied by default. When coincident edges are merged, your `OverlayEdgeData` implementation decides what the resulting data means. A common policy is to keep identical values and mark conflicts as `Undefined`. + +```rust +use i_overlay::core::edge_data::{EdgeDataMerge, OverlayEdgeData}; +use i_overlay::core::edge_overlay::{EdgeOverlay, InputEdge}; +use i_overlay::core::fill_rule::FillRule; +use i_overlay::core::overlay::ShapeType; +use i_overlay::core::overlay_rule::OverlayRule; +use i_overlay::i_float::int::point::IntPoint; +use i_overlay::segm::boolean::ShapeCountBoolean; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum EdgeKind { + Red, + Green, + Undefined, +} + +impl OverlayEdgeData for EdgeKind { + fn merge(ctx: EdgeDataMerge) -> Self { + match (ctx.lhs_data, ctx.rhs_data) { + (EdgeKind::Red, EdgeKind::Red) => EdgeKind::Red, + (EdgeKind::Green, EdgeKind::Green) => EdgeKind::Green, + _ => EdgeKind::Undefined, + } + } +} + +let mut overlay = EdgeOverlay::new(8); + +let red_square = [ + [0, 0], [4, 0], [4, 4], [0, 4], +]; +let green_square = [ + [2, 0], [6, 0], [6, 4], [2, 4], +]; + +for edge in red_square.windows(2).map(|w| (w[0], w[1])) + .chain([(red_square[3], red_square[0])]) +{ + overlay.add_edge(InputEdge { + a: IntPoint::new(edge.0[0], edge.0[1]), + b: IntPoint::new(edge.1[0], edge.1[1]), + data: EdgeKind::Red, + }, ShapeType::Subject); +} + +for edge in green_square.windows(2).map(|w| (w[0], w[1])) + .chain([(green_square[3], green_square[0])]) +{ + overlay.add_edge(InputEdge { + a: IntPoint::new(edge.0[0], edge.0[1]), + b: IntPoint::new(edge.1[0], edge.1[1]), + data: EdgeKind::Green, + }, ShapeType::Clip); +} + +let shapes = overlay.build_vector_shapes(OverlayRule::Union, FillRule::NonZero); + +// The result is grouped as shapes -> contours -> edges. +// Each output edge contains geometry, fill, and the propagated user data. +assert_eq!(shapes.len(), 1); +assert!(shapes[0][0].iter().any(|edge| edge.data == EdgeKind::Undefined)); +``` + +This API currently targets integer boolean operations and exports the result as vector shapes. It keeps the same contour structure as the regular polygon API, but each contour item is an edge with propagated data. Collinear edges are simplified only when their data is equal, so attribute boundaries are preserved. +   ## Spatial Predicates @@ -534,7 +614,41 @@ If you need more control, use `FloatPointAdapter::with_scale` and `FloatOverlay: --- -### 4. How do I enable OGC-valid output? +### 4. How do I select the integer engine for float overlays? + +If you need control over the float-to-integer precision range, select the integer engine at +compile time with the `from_*` constructors. The supported engines are `i16`, `i32`, and `i64`. +The default engine is `i32`; use `i64` when the input bounds need a wider integer range. + +```rust +use i_overlay::core::fill_rule::FillRule; +use i_overlay::core::overlay_rule::OverlayRule; +use i_overlay::float::overlay::FloatOverlay; + +let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]]; +let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]]; + +let mut overlay = FloatOverlay::<[f64; 2], i64>::from_subj_and_clip(&subj, &clip); +let result = overlay.overlay(OverlayRule::Difference, FillRule::EvenOdd); +``` + +The sugar traits also use `i32` by default. Use the `*_as::` methods when you want to keep +the shorthand API and still select the integer engine explicitly: + +```rust +use i_overlay::core::fill_rule::FillRule; +use i_overlay::core::overlay_rule::OverlayRule; +use i_overlay::float::single::SingleFloatOverlay; + +let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]]; +let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]]; + +let result = subj.overlay_as::(&clip, OverlayRule::Difference, FillRule::EvenOdd); +``` + +--- + +### 5. How do I enable OGC-valid output? Set the `ogc` flag in `OverlayOptions`. diff --git a/iOverlay/readme/average_relative_time.svg b/iOverlay/readme/average_relative_time.svg new file mode 100644 index 00000000..56a16a1b --- /dev/null +++ b/iOverlay/readme/average_relative_time.svg @@ -0,0 +1,97 @@ + + Average Relative Time + Geometric mean relative time across benchmark rows where every solver has a result. i32 off is the baseline. + + + Average relative time, i32 off = 1.00x + + + 0.50 + + 0.75 + + 1.00 + + 1.25 + + + i16 off + + 0.92x + + i32 off + + 1.00x + + i64 off + + 1.30x + + i16 on + + 0.85x + + i32 on + + 0.86x + + i64 on + + 1.08x + + Lower is faster. Apple M4, 24 GB. + diff --git a/iOverlay/src/bind/segment.rs b/iOverlay/src/bind/segment.rs index b1a95de7..1ee7ed88 100644 --- a/iOverlay/src/bind/segment.rs +++ b/iOverlay/src/bind/segment.rs @@ -1,6 +1,7 @@ use crate::geom::v_segment::VSegment; -use crate::vector::edge::{VectorEdge, VectorPath}; +use crate::vector::edge::{DataVectorEdge, DataVectorPath}; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; use i_shape::int::path::IntPath; @@ -35,15 +36,15 @@ impl ContourIndex { } } -#[derive(Debug, Clone, Copy)] -pub(crate) struct IdSegment { +#[derive(Clone, Copy)] +pub(crate) struct IdSegment { pub(crate) contour_index: ContourIndex, - pub(crate) v_segment: VSegment, + pub(crate) v_segment: VSegment, } -impl IdSegment { +impl IdSegment { #[inline] - fn new(data: ContourIndex, a: IntPoint, b: IntPoint) -> Self { + fn new(data: ContourIndex, a: IntPoint, b: IntPoint) -> Self { Self { contour_index: data, v_segment: VSegment { a, b }, @@ -51,7 +52,7 @@ impl IdSegment { } #[inline] - pub(crate) fn with_segment(data: ContourIndex, v_segment: VSegment) -> Self { + pub(crate) fn with_segment(data: ContourIndex, v_segment: VSegment) -> Self { Self { contour_index: data, v_segment, @@ -59,45 +60,45 @@ impl IdSegment { } } -pub(crate) trait IdSegments { +pub(crate) trait IdSegments { fn append_id_segments( &self, - buffer: &mut Vec, + buffer: &mut Vec>, id_data: ContourIndex, - x_min: i32, - x_max: i32, + x_min: I, + x_max: I, clockwise: bool, ); } -impl IdSegments for IntPath { +impl IdSegments for IntPath { #[inline] fn append_id_segments( &self, - buffer: &mut Vec, + buffer: &mut Vec>, id_data: ContourIndex, - x_min: i32, - x_max: i32, + x_min: I, + x_max: I, clockwise: bool, ) { - fn inner>( - mut iter: I, - buffer: &mut Vec, + fn inner>>( + mut iter: It, + buffer: &mut Vec>, id_data: ContourIndex, - x_min: i32, - x_max: i32, + x_min: I, + x_max: I, ) { let first = iter.next().unwrap(); let mut b = first; for a in iter { if a.x < b.x && x_min < b.x && a.x <= x_max { - buffer.push(IdSegment::new(id_data, a, b)); + buffer.push(IdSegment::::new(id_data, a, b)); } b = a; } let a = first; if a.x < b.x && x_min < b.x && a.x <= x_max { - buffer.push(IdSegment::new(id_data, a, b)); + buffer.push(IdSegment::::new(id_data, a, b)); } } @@ -109,34 +110,34 @@ impl IdSegments for IntPath { } } -impl IdSegments for VectorPath { +impl IdSegments for DataVectorPath { #[inline] fn append_id_segments( &self, - buffer: &mut Vec, + buffer: &mut Vec>, id_data: ContourIndex, - x_min: i32, - x_max: i32, + x_min: I, + x_max: I, clockwise: bool, ) { - fn inner>( - iter: I, - buffer: &mut Vec, + fn inner<'a, D: 'a, I: IntNumber + 'a, It: Iterator>>( + iter: It, + buffer: &mut Vec>, id_data: ContourIndex, - x_min: i32, - x_max: i32, + 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::new(id_data, vec.a, vec.b)); + buffer.push(IdSegment::::new(id_data, vec.a, vec.b)); } } } if clockwise { - inner(self.iter().copied(), buffer, id_data, x_min, x_max); + inner(self.iter(), buffer, id_data, x_min, x_max); } else { - inner(self.iter().rev().copied(), buffer, id_data, x_min, x_max); + inner(self.iter().rev(), buffer, id_data, x_min, x_max); } } } diff --git a/iOverlay/src/bind/solver.rs b/iOverlay/src/bind/solver.rs index c10fbdf5..90e793ce 100644 --- a/iOverlay/src/bind/solver.rs +++ b/iOverlay/src/bind/solver.rs @@ -4,9 +4,12 @@ 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_key_sort::sort::key::SortKey; use i_key_sort::sort::two_keys_cmp::TwoKeysAndCmpSort; use i_shape::int::path::IntPath; use i_shape::int::shape::{IntContour, IntShape}; +use i_tree::Expiration; use i_tree::key::exp::KeyExpCollection; use i_tree::key::list::KeyExpList; use i_tree::key::tree::KeyExpTree; @@ -20,15 +23,18 @@ pub(crate) struct ShapeBinder; impl ShapeBinder { #[inline] - pub(crate) fn bind( + pub(crate) fn bind( shape_count: usize, - hole_segments: Vec, - segments: Vec, - ) -> BindSolution { + hole_segments: Vec>, + segments: Vec>, + ) -> BindSolution + where + I: IntNumber + Expiration, + { if shape_count < 32 { let capacity = segments.len().log2_sqrt().max(4) * 2; let list = KeyExpList::new(capacity); - Self::private_solve::>( + Self::private_solve::, I, ContourIndex>>( list, shape_count, hole_segments, @@ -37,7 +43,7 @@ impl ShapeBinder { } else { let capacity = segments.len().log2_sqrt().max(8); let list = KeyExpTree::new(capacity); - Self::private_solve::>( + Self::private_solve::, I, ContourIndex>>( list, shape_count, hole_segments, @@ -46,12 +52,16 @@ impl ShapeBinder { } } - fn private_solve>( + fn private_solve( mut scan_list: S, shape_count: usize, - anchors: Vec, - segments: Vec, - ) -> BindSolution { + anchors: Vec>, + segments: Vec>, + ) -> BindSolution + where + I: IntNumber + Expiration, + S: KeyExpCollection, I, ContourIndex>, + { let children_count = anchors.len(); let mut parent_for_child = { #[cfg(debug_assertions)] @@ -105,15 +115,15 @@ impl ShapeBinder { } } -pub(crate) trait JoinHoles { - fn join_unsorted_holes(&mut self, holes: Vec, clockwise: bool); - fn join_sorted_holes(&mut self, holes: Vec, anchors: Vec, clockwise: bool); - fn scan_join(&mut self, holes: Vec, hole_segments: Vec, clockwise: bool); +pub(crate) trait JoinHoles { + fn join_unsorted_holes(&mut self, holes: Vec>, clockwise: bool); + fn join_sorted_holes(&mut self, holes: Vec>, anchors: Vec>, clockwise: bool); + fn scan_join(&mut self, holes: Vec>, hole_segments: Vec>, clockwise: bool); } -impl JoinHoles for Vec { +impl JoinHoles for Vec> { #[inline] - fn join_unsorted_holes(&mut self, holes: Vec, clockwise: bool) { + fn join_unsorted_holes(&mut self, holes: Vec>, clockwise: bool) { if self.is_empty() || holes.is_empty() { return; } @@ -140,7 +150,7 @@ impl JoinHoles for Vec { } #[inline] - fn join_sorted_holes(&mut self, holes: Vec, anchors: Vec, clockwise: bool) { + fn join_sorted_holes(&mut self, holes: Vec>, anchors: Vec>, clockwise: bool) { if self.is_empty() || holes.is_empty() { return; } @@ -157,7 +167,7 @@ impl JoinHoles for Vec { self.scan_join(holes, anchors, clockwise); } - fn scan_join(&mut self, holes: Vec, hole_segments: Vec, clockwise: bool) { + fn scan_join(&mut self, holes: Vec>, hole_segments: Vec>, clockwise: bool) { let x_min = hole_segments[0].v_segment.a.x; let x_max = hole_segments[hole_segments.len() - 1].v_segment.a.x; @@ -186,12 +196,12 @@ impl JoinHoles for Vec { } } -pub(crate) trait LeftBottomSegment { - fn left_bottom_segment(&self) -> VSegment; +pub(crate) trait LeftBottomSegment { + fn left_bottom_segment(&self) -> VSegment; } -impl LeftBottomSegment for IntContour { - fn left_bottom_segment(&self) -> 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) { @@ -212,13 +222,13 @@ impl LeftBottomSegment for IntContour { } #[inline] -fn is_sorted(segments: &[IdSegment]) -> bool { +fn is_sorted(segments: &[IdSegment]) -> bool { segments .windows(2) .all(|slice| slice[0].v_segment.a <= slice[1].v_segment.a) } -impl IdSegment { +impl IdSegment { #[inline] fn cmp_by_a_then_by_angle(&self, other: &Self) -> Ordering { self.v_segment @@ -233,7 +243,7 @@ pub(crate) trait SortByAngle { fn add_sort_by_angle(&mut self); } -impl SortByAngle for [IdSegment] { +impl SortByAngle for [IdSegment] { #[inline] fn sort_by_a_then_by_angle(&mut self) { self.sort_by_two_keys_then_by( diff --git a/iOverlay/src/build/boolean.rs b/iOverlay/src/build/boolean.rs index c6b3a92f..01b8da54 100644 --- a/iOverlay/src/build/boolean.rs +++ b/iOverlay/src/build/boolean.rs @@ -2,6 +2,7 @@ use crate::build::builder::{GraphBuilder, InclusionFilterStrategy}; use crate::build::sweep::{ EvenOddStrategy, FillStrategy, NegativeStrategy, NonZeroStrategy, PositiveStrategy, }; +use crate::core::edge_data::OverlayEdgeData; use crate::core::extract::VisitState; use crate::core::fill_rule::FillRule; use crate::core::graph::OverlayGraph; @@ -18,17 +19,24 @@ use crate::segm::segment::{ }; use crate::segm::winding::WindingCount; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::util::reserve::Reserve; +use i_tree::Expiration; -impl GraphBuilder { +impl GraphBuilder +where + I: IntNumber + Expiration + SortKey, + D: OverlayEdgeData, +{ #[inline] pub(crate) fn build_boolean_all( &mut self, fill_rule: FillRule, - options: IntOverlayOptions, + options: IntOverlayOptions, solver: &Solver, - segments: &[Segment], - ) -> OverlayGraph<'_> { + segments: &[Segment], + ) -> OverlayGraph<'_, I, D> { self.build_boolean_fills(fill_rule, solver, segments); self.build_links_all(segments); self.boolean_graph(options, solver) @@ -39,10 +47,10 @@ impl GraphBuilder { &mut self, fill_rule: FillRule, overlay_rule: OverlayRule, - options: IntOverlayOptions, + options: IntOverlayOptions, solver: &Solver, - segments: &[Segment], - ) -> OverlayGraph<'_> { + segments: &[Segment], + ) -> OverlayGraph<'_, I, D> { self.build_boolean_fills(fill_rule, solver, segments); match overlay_rule { OverlayRule::Subject => self.build_links_by_filter::(segments), @@ -61,7 +69,7 @@ impl GraphBuilder { &mut self, fill_rule: FillRule, solver: &Solver, - segments: &[Segment], + segments: &[Segment], ) { match fill_rule { FillRule::EvenOdd => self.build_fills_with_strategy::(solver, segments), @@ -72,7 +80,11 @@ impl GraphBuilder { } #[inline] - fn boolean_graph(&mut self, options: IntOverlayOptions, solver: &Solver) -> OverlayGraph<'_> { + fn boolean_graph( + &mut self, + options: IntOverlayOptions, + solver: &Solver, + ) -> OverlayGraph<'_, I, D> { self.build_nodes_and_connect_links(solver); OverlayGraph { nodes: &self.nodes, @@ -273,7 +285,7 @@ impl BooleanFillFilter for SegmentFill { } } -impl OverlayLinkFilter for [OverlayLink] { +impl OverlayLinkFilter for [OverlayLink] { #[inline] fn filter_by_overlay_into(&self, overlay_rule: OverlayRule, buffer: &mut Vec) { match overlay_rule { @@ -289,7 +301,7 @@ impl OverlayLinkFilter for [OverlayLink] { } #[inline] -fn filter_subject_into(links: &[OverlayLink], buffer: &mut Vec) { +fn filter_subject_into(links: &[OverlayLink], buffer: &mut Vec) { buffer.clear(); buffer.reserve_capacity(links.len()); for link in links.iter() { @@ -298,7 +310,7 @@ fn filter_subject_into(links: &[OverlayLink], buffer: &mut Vec) { } #[inline] -fn filter_clip_into(links: &[OverlayLink], buffer: &mut Vec) { +fn filter_clip_into(links: &[OverlayLink], buffer: &mut Vec) { buffer.clear(); buffer.reserve_capacity(links.len()); for link in links.iter() { @@ -307,7 +319,7 @@ fn filter_clip_into(links: &[OverlayLink], buffer: &mut Vec) { } #[inline] -fn filter_intersect_into(links: &[OverlayLink], buffer: &mut Vec) { +fn filter_intersect_into(links: &[OverlayLink], buffer: &mut Vec) { buffer.clear(); buffer.reserve_capacity(links.len()); for link in links.iter() { @@ -316,7 +328,7 @@ fn filter_intersect_into(links: &[OverlayLink], buffer: &mut Vec) { } #[inline] -fn filter_union_into(links: &[OverlayLink], buffer: &mut Vec) { +fn filter_union_into(links: &[OverlayLink], buffer: &mut Vec) { buffer.clear(); buffer.reserve_capacity(links.len()); for link in links.iter() { @@ -325,7 +337,7 @@ fn filter_union_into(links: &[OverlayLink], buffer: &mut Vec) { } #[inline] -fn filter_difference_into(links: &[OverlayLink], buffer: &mut Vec) { +fn filter_difference_into(links: &[OverlayLink], buffer: &mut Vec) { buffer.clear(); buffer.reserve_capacity(links.len()); for link in links.iter() { @@ -334,7 +346,10 @@ fn filter_difference_into(links: &[OverlayLink], buffer: &mut Vec) { } #[inline] -fn filter_inverse_difference_into(links: &[OverlayLink], buffer: &mut Vec) { +fn filter_inverse_difference_into( + links: &[OverlayLink], + buffer: &mut Vec, +) { buffer.clear(); buffer.reserve_capacity(links.len()); for link in links.iter() { @@ -343,7 +358,7 @@ fn filter_inverse_difference_into(links: &[OverlayLink], buffer: &mut Vec) { +fn filter_xor_into(links: &[OverlayLink], buffer: &mut Vec) { buffer.clear(); buffer.reserve_capacity(links.len()); for link in links.iter() { diff --git a/iOverlay/src/build/builder.rs b/iOverlay/src/build/builder.rs index db12a52d..e6a211e9 100644 --- a/iOverlay/src/build/builder.rs +++ b/iOverlay/src/build/builder.rs @@ -1,4 +1,5 @@ use crate::build::sweep::{FillHandler, FillStrategy, SweepRunner}; +use crate::core::edge_data::OverlayEdgeData; use crate::core::link::OverlayLink; use crate::core::solver::Solver; use crate::geom::end::End; @@ -7,7 +8,9 @@ use crate::segm::segment::{NONE, Segment, SegmentFill}; use crate::segm::winding::WindingCount; use alloc::vec::Vec; use core::ops::ControlFlow; +use i_float::int::number::int::IntNumber; use i_shape::util::reserve::Reserve; +use i_tree::Expiration; pub(super) trait InclusionFilterStrategy { fn is_included(fill: SegmentFill) -> bool; @@ -24,11 +27,11 @@ impl<'a> StoreFillsHandler<'a> { } } -impl FillHandler for StoreFillsHandler<'_> { +impl FillHandler for StoreFillsHandler<'_> { type Output = (); #[inline(always)] - fn handle(&mut self, index: usize, _segment: &Segment, fill: SegmentFill) -> ControlFlow<()> { + fn handle(&mut self, index: usize, _segment: &Segment, fill: SegmentFill) -> ControlFlow<()> { // fills is pre-allocated to segments.len() and index is guaranteed // to be in range by the sweep algorithm unsafe { *self.fills.get_unchecked_mut(index) = fill }; @@ -43,15 +46,21 @@ pub(crate) trait GraphNode { fn with_indices(indices: &[usize]) -> Self; } -pub(crate) struct GraphBuilder { - sweep_runner: SweepRunner, - pub(super) links: Vec, +pub(crate) struct GraphBuilder { + sweep_runner: SweepRunner, + pub(super) links: Vec>, pub(super) nodes: Vec, pub(super) fills: Vec, - pub(super) ends: Vec, + pub(super) ends: Vec>, } -impl GraphBuilder { +impl GraphBuilder +where + C: WindingCount, + N: GraphNode, + I: IntNumber + Expiration, + D: OverlayEdgeData, +{ #[inline] pub(crate) fn new() -> Self { Self { @@ -67,15 +76,18 @@ impl GraphBuilder { pub(super) fn build_fills_with_strategy>( &mut self, solver: &Solver, - segments: &[Segment], + segments: &[Segment], ) { self.fills.resize(segments.len(), NONE); self.sweep_runner - .run::(solver, segments, StoreFillsHandler::new(&mut self.fills)); + .run::(solver, segments, StoreFillsHandler::new(&mut self.fills)); } #[inline] - pub(super) fn build_links_by_filter(&mut self, segments: &[Segment]) { + pub(super) fn build_links_by_filter( + &mut self, + segments: &[Segment], + ) { self.links.clear(); self.links.reserve_capacity(segments.len()); @@ -83,24 +95,26 @@ impl GraphBuilder { if !F::is_included(fill) { continue; } - self.links.push(OverlayLink::new( + self.links.push(OverlayLink::new_with_data( IdPoint::new(0, segment.x_segment.a), IdPoint::new(0, segment.x_segment.b), fill, + segment.data, )); } } #[inline] - pub(super) fn build_links_all(&mut self, segments: &[Segment]) { + pub(super) fn build_links_all(&mut self, segments: &[Segment]) { self.links.clear(); self.links.reserve_capacity(segments.len()); for (segment, &fill) in segments.iter().zip(&self.fills) { - self.links.push(OverlayLink::new( + self.links.push(OverlayLink::new_with_data( IdPoint::new(0, segment.x_segment.a), IdPoint::new(0, segment.x_segment.b), fill, + segment.data, )); } } diff --git a/iOverlay/src/build/graph.rs b/iOverlay/src/build/graph.rs index 0661b040..49749130 100644 --- a/iOverlay/src/build/graph.rs +++ b/iOverlay/src/build/graph.rs @@ -1,11 +1,21 @@ use crate::build::builder::{GraphBuilder, GraphNode}; +use crate::core::edge_data::OverlayEdgeData; use crate::core::solver::Solver; use crate::geom::end::End; use crate::segm::winding::WindingCount; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_key_sort::sort::two_keys::TwoKeysSort; +use i_tree::Expiration; -impl GraphBuilder { +impl GraphBuilder +where + I: IntNumber + Expiration + SortKey, + C: WindingCount, + N: GraphNode, + D: OverlayEdgeData, +{ pub(super) fn build_nodes_and_connect_links(&mut self, solver: &Solver) { let n = self.links.len(); if n == 0 { diff --git a/iOverlay/src/build/string.rs b/iOverlay/src/build/string.rs index 157637e1..ea039570 100644 --- a/iOverlay/src/build/string.rs +++ b/iOverlay/src/build/string.rs @@ -7,15 +7,21 @@ use crate::segm::string::ShapeCountString; use crate::string::clip::ClipRule; use crate::string::graph::StringGraph; use alloc::vec::Vec; - -impl GraphBuilder> { +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; +use i_tree::Expiration; + +impl GraphBuilder, I> +where + I: IntNumber + Expiration + SortKey, +{ #[inline] pub(crate) fn build_string_all( &mut self, fill_rule: FillRule, solver: &Solver, - segments: &[Segment], - ) -> StringGraph<'_> { + segments: &[Segment], + ) -> StringGraph<'_, I> { self.build_string_fills(fill_rule, solver, segments); self.build_links_all(segments); self.string_graph(solver) @@ -27,8 +33,8 @@ impl GraphBuilder> { fill_rule: FillRule, clip_rule: ClipRule, solver: &Solver, - segments: &[Segment], - ) -> StringGraph<'_> { + segments: &[Segment], + ) -> StringGraph<'_, I> { self.build_string_fills(fill_rule, solver, segments); match clip_rule { ClipRule { @@ -56,7 +62,7 @@ impl GraphBuilder> { &mut self, fill_rule: FillRule, solver: &Solver, - segments: &[Segment], + segments: &[Segment], ) { match fill_rule { FillRule::EvenOdd => self.build_fills_with_strategy::(solver, segments), @@ -67,7 +73,7 @@ impl GraphBuilder> { } #[inline] - fn string_graph(&mut self, solver: &Solver) -> StringGraph<'_> { + fn string_graph(&mut self, solver: &Solver) -> StringGraph<'_, I> { self.build_nodes_and_connect_links(solver); StringGraph { nodes: &self.nodes, diff --git a/iOverlay/src/build/sweep.rs b/iOverlay/src/build/sweep.rs index 0bf0286e..09d76049 100644 --- a/iOverlay/src/build/sweep.rs +++ b/iOverlay/src/build/sweep.rs @@ -7,7 +7,9 @@ use crate::segm::winding::WindingCount; use crate::util::log::Int; use alloc::vec::Vec; use core::ops::ControlFlow; +use i_float::int::number::int::IntNumber; use i_float::triangle::Triangle; +use i_tree::Expiration; use i_tree::key::exp::KeyExpCollection; use i_tree::key::list::KeyExpList; use i_tree::key::tree::KeyExpTree; @@ -16,19 +18,29 @@ pub(crate) trait FillStrategy { fn add_and_fill(this: C, bot: C) -> (C, SegmentFill); } -pub(crate) trait FillHandler { +pub(crate) trait FillHandler { type Output; - fn handle(&mut self, index: usize, segment: &Segment, fill: SegmentFill) -> ControlFlow; + fn handle( + &mut self, + index: usize, + segment: &Segment, + fill: SegmentFill, + ) -> ControlFlow; fn finalize(self) -> Self::Output; } #[inline] -fn sweep_with_handler(scan: &mut S, segments: &[Segment], mut handler: H) -> H::Output +fn sweep_with_handler( + scan: &mut S, + segments: &[Segment], + mut handler: H, +) -> H::Output where + I: IntNumber + Expiration, C: WindingCount, F: FillStrategy, - S: KeyExpCollection, - H: FillHandler, + S: KeyExpCollection, I, C>, + H: FillHandler, { let mut node = Vec::with_capacity(4); let n = segments.len(); @@ -52,7 +64,7 @@ where } if node.len() > 1 { - node.sort_by(|s0, s1| Triangle::clock_order_point(p, s1.point, s0.point)); + node.sort_by(|s0, s1| Triangle::clock_order(p, s1.point, s0.point)); } let mut sum_count = scan.first_less_or_equal_by(p.x, C::new(0, 0), |s| s.is_under_point_order(p)); @@ -77,12 +89,12 @@ where handler.finalize() } -pub(crate) struct SweepRunner { - list: Option>, - tree: Option>, +pub(crate) struct SweepRunner { + list: Option, I, C>>, + tree: Option, I, C>>, } -impl SweepRunner { +impl SweepRunner { #[inline] pub(crate) fn new() -> Self { Self { @@ -92,52 +104,59 @@ impl SweepRunner { } #[inline] - pub(crate) fn run(&mut self, solver: &Solver, segments: &[Segment], handler: H) -> H::Output + pub(crate) fn run( + &mut self, + solver: &Solver, + segments: &[Segment], + handler: H, + ) -> H::Output where F: FillStrategy, - H: FillHandler, + H: FillHandler, + D: Send, { let count = segments.len(); if solver.is_list_fill(segments) { let capacity = count.log2_sqrt().max(4) * 2; let mut list = self.take_scan_list(capacity); - let result = sweep_with_handler::(&mut list, segments, handler); + let result = sweep_with_handler::(&mut list, segments, handler); self.list = Some(list); result } else { let capacity = count.log2_sqrt().max(8); let mut tree = self.take_scan_tree(capacity); - let result = sweep_with_handler::(&mut tree, segments, handler); + let result = sweep_with_handler::(&mut tree, segments, handler); self.tree = Some(tree); result } } #[inline] - pub(crate) fn run_with_fill_rule( + pub(crate) fn run_with_fill_rule( &mut self, fill_rule: FillRule, solver: &Solver, - segments: &[Segment], + segments: &[Segment], handler: H, ) -> H::Output where - H: FillHandler, + H: FillHandler, + D: Send, EvenOddStrategy: FillStrategy, NonZeroStrategy: FillStrategy, PositiveStrategy: FillStrategy, NegativeStrategy: FillStrategy, { match fill_rule { - FillRule::EvenOdd => self.run::(solver, segments, handler), - FillRule::NonZero => self.run::(solver, segments, handler), - FillRule::Positive => self.run::(solver, segments, handler), - FillRule::Negative => self.run::(solver, segments, handler), + FillRule::EvenOdd => self.run::(solver, segments, handler), + FillRule::NonZero => self.run::(solver, segments, handler), + FillRule::Positive => self.run::(solver, segments, handler), + FillRule::Negative => self.run::(solver, segments, handler), } } #[inline] - fn take_scan_list(&mut self, capacity: usize) -> KeyExpList { + fn take_scan_list(&mut self, capacity: usize) -> KeyExpList, I, C> { if let Some(mut list) = self.list.take() { list.clear(); list.reserve_capacity(capacity); @@ -148,7 +167,7 @@ impl SweepRunner { } #[inline] - fn take_scan_tree(&mut self, capacity: usize) -> KeyExpTree { + fn take_scan_tree(&mut self, capacity: usize) -> KeyExpTree, I, C> { if let Some(mut tree) = self.tree.take() { tree.clear(); tree.reserve_capacity(capacity); diff --git a/iOverlay/src/build/util.rs b/iOverlay/src/build/util.rs index da6a302d..d7d9384e 100644 --- a/iOverlay/src/build/util.rs +++ b/iOverlay/src/build/util.rs @@ -1,14 +1,21 @@ use crate::build::builder::{GraphBuilder, GraphNode}; use crate::segm::winding::WindingCount; use alloc::vec::Vec; +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::TwoKeysSort; -impl GraphBuilder { +impl GraphBuilder +where + C: WindingCount, + N: GraphNode, + I: IntNumber + i_tree::Expiration + SortKey, +{ pub(crate) fn test_contour_for_loops( &mut self, - contour: &[IntPoint], - buffer: &mut Vec, + contour: &[IntPoint], + buffer: &mut Vec>, ) -> bool { let n = contour.len(); if n < 64 { diff --git a/iOverlay/src/core/divide.rs b/iOverlay/src/core/divide.rs index bd1191a2..1b61680b 100644 --- a/iOverlay/src/core/divide.rs +++ b/iOverlay/src/core/divide.rs @@ -1,18 +1,19 @@ use crate::geom::id_point::IdPoint; use alloc::vec; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; use i_shape::int::path::IntPath; use i_shape::int::shape::IntContour; -struct SubPath { +struct SubPath { last: usize, - node: IntPoint, - path: IntPath, + node: IntPoint, + path: IntPath, } -impl SubPath { - fn start(point: IdPoint) -> Self { +impl SubPath { + fn start(point: IdPoint) -> Self { Self { last: point.id + 1, node: point.point, @@ -20,22 +21,22 @@ impl SubPath { } } - fn join(&mut self, point: IdPoint, source: &IntContour) { + fn join(&mut self, point: IdPoint, source: &IntContour) { self.path.extend_from_slice(&source[self.last..point.id]); self.last = point.id; } - fn shift(&mut self, point: IdPoint) { + fn shift(&mut self, point: IdPoint) { self.last = point.id; } } -pub trait ContourDecomposition { - fn decompose_contours(&self) -> Option>; +pub trait ContourDecomposition { + fn decompose_contours(&self) -> Option>>; } -impl ContourDecomposition for IntContour { - fn decompose_contours(&self) -> Option> { +impl ContourDecomposition for IntContour { + fn decompose_contours(&self) -> Option>> { if self.len() < 3 { return None; } @@ -68,7 +69,7 @@ impl ContourDecomposition for IntContour { return None; } - anchors.sort_by(|p0, p1| p0.id.cmp(&p1.id)); + anchors.sort_by_key(|p0| p0.id); let mut contours = Vec::with_capacity((anchors.len() >> 1) + 1); @@ -77,10 +78,10 @@ impl ContourDecomposition for IntContour { let mut i = 0; while i < anchors.len() { let a = anchors[i]; - let mut sub_path: SubPath = if let Some(sub_path) = queue.pop() { + let mut sub_path: SubPath = if let Some(sub_path) = queue.pop() { sub_path } else { - queue.push(SubPath::start(a)); + queue.push(SubPath::::start(a)); i += 1; continue; }; @@ -91,17 +92,17 @@ impl ContourDecomposition for IntContour { if let Some(prev) = queue.last_mut() { prev.shift(a); } else { - queue.push(SubPath::start(a)); + queue.push(SubPath::::start(a)); } } else { sub_path.join(a, self); queue.push(sub_path); - queue.push(SubPath::start(a)); + queue.push(SubPath::::start(a)); } i += 1; } - let mut sub_path: SubPath = queue.pop().unwrap(); + let mut sub_path: SubPath = queue.pop().unwrap(); if sub_path.last < self.len() { sub_path.path.extend_from_slice(&self[sub_path.last..]); @@ -276,7 +277,7 @@ mod tests { } } - fn rotate(contour: &IntContour, s: usize) -> IntContour { + fn rotate(contour: &IntContour, s: usize) -> IntContour { contour .iter() .cycle() diff --git a/iOverlay/src/core/edge_data.rs b/iOverlay/src/core/edge_data.rs new file mode 100644 index 00000000..e0f2f6a0 --- /dev/null +++ b/iOverlay/src/core/edge_data.rs @@ -0,0 +1,45 @@ +use crate::segm::boolean::ShapeCountBoolean; +use i_float::int::number::int::IntNumber; +use i_float::int::point::IntPoint; + +pub trait OverlayEdgeData: Copy + PartialEq + Send + Sync { + type Store: Default; + + #[inline(always)] + fn reversed(self, _store: &mut Self::Store) -> Self { + self + } + + #[inline(always)] + fn split(self, _ctx: EdgeDataSplit, _store: &mut Self::Store) -> (Self, Self) { + (self, self) + } + + fn merge(ctx: EdgeDataMerge, store: &mut Self::Store) -> Self; +} + +#[derive(Debug, Clone)] +pub struct EdgeDataSplit { + pub a: IntPoint, + pub p: IntPoint, + pub b: IntPoint, +} + +#[derive(Debug, Clone)] +pub struct EdgeDataMerge { + pub lhs_data: D, + pub lhs_count: C, + pub rhs_data: D, + pub rhs_count: C, + pub out_count: C, +} + +impl OverlayEdgeData for () +where + C: Send + Sync, +{ + type Store = (); + + #[inline(always)] + fn merge(_: EdgeDataMerge, _: &mut Self::Store) -> Self {} +} diff --git a/iOverlay/src/core/edge_overlay.rs b/iOverlay/src/core/edge_overlay.rs new file mode 100644 index 00000000..342089bc --- /dev/null +++ b/iOverlay/src/core/edge_overlay.rs @@ -0,0 +1,142 @@ +use crate::build::builder::GraphBuilder; +use crate::core::edge_data::OverlayEdgeData; +use crate::core::extract::BooleanExtractionBuffer; +use crate::core::fill_rule::FillRule; +use crate::core::graph::OverlayNode; +use crate::core::overlay::{IntOverlayOptions, ShapeType}; +use crate::core::overlay_rule::OverlayRule; +use crate::core::solver::Solver; +use crate::segm::boolean::ShapeCountBoolean; +use crate::segm::segment::Segment; +use crate::split::solver::SplitSolver; +use crate::vector::edge::{DataVectorEdge, DataVectorShape}; +use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_float::int::point::IntPoint; +use i_key_sort::sort::key::SortKey; +use i_tree::{Expiration, LayoutNumber}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InputEdge { + pub a: IntPoint, + pub b: IntPoint, + pub data: D, +} + +pub struct EdgeOverlay { + pub solver: Solver, + pub options: IntOverlayOptions, + pub boolean_buffer: Option>, + data_store: D::Store, + segments: Vec>, + split_solver: SplitSolver, + graph_builder: GraphBuilder, +} + +impl EdgeOverlay +where + I: IntNumber + Expiration + LayoutNumber + SortKey, + D: OverlayEdgeData, +{ + pub fn new(capacity: usize) -> Self { + Self { + solver: Default::default(), + options: IntOverlayOptions::keep_output_points(), + boolean_buffer: None, + data_store: Default::default(), + segments: Vec::with_capacity(capacity), + split_solver: SplitSolver::new(), + graph_builder: GraphBuilder::::new(), + } + } + + pub fn add_edge(&mut self, edge: InputEdge, shape_type: ShapeType) { + if let Some(segment) = + Segment::try_ab_and_data(edge.a, edge.b, shape_type, edge.data, &mut self.data_store) + { + self.segments.push(segment); + } + } + + pub fn add_edges(&mut self, edges: It, shape_type: ShapeType) + where + It: IntoIterator>, + { + for edge in edges { + self.add_edge(edge, shape_type); + } + } + + pub fn data_store(&self) -> &D::Store { + &self.data_store + } + + pub fn data_store_mut(&mut self) -> &mut D::Store { + &mut self.data_store + } + + pub fn into_data_store(self) -> D::Store { + self.data_store + } + + pub fn build_vectors( + &mut self, + overlay_rule: OverlayRule, + fill_rule: FillRule, + ) -> Vec> { + self.split_solver + .split_segments_with_store(&mut self.segments, &self.solver, &mut self.data_store); + if self.segments.is_empty() { + return Vec::new(); + } + + self.graph_builder + .build_boolean_overlay( + fill_rule, + overlay_rule, + self.options, + &self.solver, + &self.segments, + ) + .extract_vectors_with_store(&mut self.data_store) + } + + pub fn build_vector_shapes( + &mut self, + overlay_rule: OverlayRule, + fill_rule: FillRule, + ) -> Vec> { + self.split_solver + .split_segments_with_store(&mut self.segments, &self.solver, &mut self.data_store); + if self.segments.is_empty() { + return Vec::new(); + } + + let mut buffer = self.boolean_buffer.take().unwrap_or_default(); + + let shapes = self + .graph_builder + .build_boolean_overlay( + fill_rule, + overlay_rule, + self.options, + &self.solver, + &self.segments, + ) + .extract_vector_shapes_with_store(overlay_rule, &mut buffer, &mut self.data_store); + + self.boolean_buffer = Some(buffer); + + shapes + } +} + +impl EdgeOverlay +where + I: IntNumber + Expiration + LayoutNumber + SortKey, + D: OverlayEdgeData, +{ + pub fn edges(&self) -> impl Iterator; 2]> + '_ { + self.segments.iter().map(|e| [e.x_segment.a, e.x_segment.b]) + } +} diff --git a/iOverlay/src/core/extract.rs b/iOverlay/src/core/extract.rs index 8db879e7..85d0c334 100644 --- a/iOverlay/src/core/extract.rs +++ b/iOverlay/src/core/extract.rs @@ -10,15 +10,20 @@ use crate::geom::v_segment::VSegment; use crate::i_shape::flat::buffer::FlatContoursBuffer; use alloc::vec; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_float::int::number::uint::UIntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; use i_float::triangle::Triangle; +use i_key_sort::sort::key::SortKey; use i_shape::int::path::ContourExtension; use i_shape::int::shape::{IntContour, IntShapes}; use i_shape::int::simple::Simplify; use i_shape::util::reserve::Reserve; +use i_tree::Expiration; #[repr(u8)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +#[derive(Copy, Clone, PartialEq, Default)] pub(crate) enum VisitState { #[default] Unvisited = 0, @@ -27,22 +32,34 @@ pub(crate) enum VisitState { HullVisited = 3, } -#[derive(Default)] -pub struct BooleanExtractionBuffer { - pub(crate) points: Vec, +pub struct BooleanExtractionBuffer { + pub(crate) points: Vec>, pub(crate) visited: Vec, pub(crate) contour_visited: Option>, } -impl OverlayGraph<'_> { +impl Default for BooleanExtractionBuffer { + fn default() -> Self { + Self { + points: Vec::new(), + visited: Vec::new(), + contour_visited: None, + } + } +} + +impl OverlayGraph<'_, I> +where + I: IntNumber + Expiration + SortKey, +{ /// Extracts shapes from the overlay graph based on the specified overlay rule. This method is used to retrieve the final geometric shapes after boolean operations have been applied. It's suitable for most use cases where the minimum area of shapes is not a concern. /// - `overlay_rule`: The boolean operation rule to apply when extracting shapes from the graph, such as union or intersection. /// - `buffer`: Reusable buffer, optimisation purpose only. - /// - Returns: A vector of `IntShape`, representing the geometric result of the applied overlay rule. + /// - Returns: A vector of `IntShape`, representing the geometric result of the applied overlay rule. /// # Shape Representation - /// The output is a `IntShapes`, where: - /// - The outer `Vec` represents a set of shapes. - /// - Each shape `Vec` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. + /// The output is a `IntShapes`, where: + /// - The outer `Vec>` represents a set of shapes. + /// - Each shape `Vec>` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. /// - Each path `Vec` is a sequence of points, forming a closed path. /// /// Note: Outer boundary paths have a counterclockwise order, and holes have a clockwise order. @@ -50,8 +67,8 @@ impl OverlayGraph<'_> { pub fn extract_shapes( &self, overlay_rule: OverlayRule, - buffer: &mut BooleanExtractionBuffer, - ) -> IntShapes { + buffer: &mut BooleanExtractionBuffer, + ) -> IntShapes { self.links .filter_by_overlay_into(overlay_rule, &mut buffer.visited); if self.options.ogc { @@ -76,8 +93,8 @@ impl OverlayGraph<'_> { pub fn extract_contours_into( &self, overlay_rule: OverlayRule, - buffer: &mut BooleanExtractionBuffer, - output: &mut FlatContoursBuffer, + buffer: &mut BooleanExtractionBuffer, + output: &mut FlatContoursBuffer, ) { self.links .filter_by_overlay_into(overlay_rule, &mut buffer.visited); @@ -87,8 +104,8 @@ impl OverlayGraph<'_> { pub(crate) fn extract( &self, overlay_rule: OverlayRule, - buffer: &mut BooleanExtractionBuffer, - ) -> IntShapes { + buffer: &mut BooleanExtractionBuffer, + ) -> IntShapes { let clockwise = self.options.output_direction == ContourDirection::Clockwise; let mut shapes = Vec::new(); @@ -160,7 +177,7 @@ impl OverlayGraph<'_> { } }; - debug_assert_eq!(v_segment, contour.left_bottom_segment()); + 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); @@ -170,7 +187,7 @@ impl OverlayGraph<'_> { } if !anchors_already_sorted { - anchors.sort_unstable_by(|s0, s1| s0.v_segment.a.cmp(&s1.v_segment.a)); + anchors.sort_unstable_by_key(|s0| s0.v_segment.a); } shapes.join_sorted_holes(holes, anchors, clockwise); @@ -180,11 +197,11 @@ impl OverlayGraph<'_> { pub(crate) fn find_contour( &self, - start_data: &StartPathData, + start_data: &StartPathData, clockwise: bool, visited_state: VisitState, visited: &mut [VisitState], - points: &mut Vec, + points: &mut Vec>, ) { let mut link_id = start_data.link_id; let mut node_id = start_data.node_id; @@ -212,8 +229,8 @@ impl OverlayGraph<'_> { fn extract_contours( &self, overlay_rule: OverlayRule, - buffer: &mut BooleanExtractionBuffer, - output: &mut FlatContoursBuffer, + buffer: &mut BooleanExtractionBuffer, + output: &mut FlatContoursBuffer, ) { let clockwise = self.options.output_direction == ContourDirection::Clockwise; let len = buffer.visited.len(); @@ -265,16 +282,16 @@ impl OverlayGraph<'_> { } } -pub(crate) struct StartPathData { - pub(crate) begin: IntPoint, +pub(crate) struct StartPathData { + pub(crate) begin: IntPoint, pub(crate) node_id: usize, pub(crate) link_id: usize, pub(crate) last_node_id: usize, } -impl StartPathData { +impl StartPathData { #[inline(always)] - pub(crate) fn new(direction: bool, link: &OverlayLink, link_id: usize) -> Self { + pub(crate) fn new(direction: bool, link: &OverlayLink, link_id: usize) -> Self { if direction { Self { begin: link.b.point, @@ -293,14 +310,14 @@ impl StartPathData { } } -pub(crate) trait GraphContour { - fn validate(&mut self, min_output_area: u64, preserve_output_collinear: bool) -> (bool, bool); - fn push_node_and_get_other(&mut self, link: &OverlayLink, node_id: usize) -> usize; +pub(crate) trait GraphContour { + fn validate(&mut self, min_output_area: I::WideUInt, preserve_output_collinear: bool) -> (bool, bool); + fn push_node_and_get_other(&mut self, link: &OverlayLink, node_id: usize) -> usize; } -impl GraphContour for IntContour { +impl GraphContour for IntContour { #[inline] - fn validate(&mut self, min_output_area: u64, preserve_output_collinear: bool) -> (bool, bool) { + 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 { @@ -311,7 +328,7 @@ impl GraphContour for IntContour { return (false, is_modified); } - if min_output_area == 0 { + if min_output_area == I::WideUInt::ZERO { return (true, is_modified); } let area = self.unsafe_area(); @@ -322,7 +339,7 @@ impl GraphContour for IntContour { } #[inline] - fn push_node_and_get_other(&mut self, link: &OverlayLink, node_id: usize) -> usize { + fn push_node_and_get_other(&mut self, link: &OverlayLink, node_id: usize) -> usize { if link.a.id == node_id { self.push(link.a.point); link.b.id @@ -386,8 +403,8 @@ impl GraphUtil { /// * For bridge nodes, both `bridge[k] < links.len()` /// * `visited` is at least `links.len()` long (or whatever invariant applies) #[inline] - pub(crate) unsafe fn find_left_top_link( - links: &[OverlayLink], + pub(crate) unsafe fn find_left_top_link( + links: &[OverlayLink], nodes: &[OverlayNode], link_index: usize, visited: &[VisitState], @@ -414,9 +431,9 @@ impl GraphUtil { } #[inline(always)] - fn find_left_top_link_on_indices( - links: &[OverlayLink], - link: &OverlayLink, + fn find_left_top_link_on_indices( + links: &[OverlayLink], + link: &OverlayLink, link_index: usize, indices: &[usize], visited: &[VisitState], @@ -434,7 +451,7 @@ impl GraphUtil { // SAFETY: indices holds link ids emitted by GraphBuilder, so each i < links.len(). links.get_unchecked(i) }; - if !link.is_direct() || Triangle::is_clockwise_point(top.a.point, top.b.point, link.b.point) { + if !link.is_direct() || Triangle::is_clockwise(top.a.point, top.b.point, link.b.point) { continue; } @@ -450,11 +467,14 @@ impl GraphUtil { } #[inline(always)] - fn find_left_top_link_on_bridge(links: &[OverlayLink], bridge: &[usize; 2]) -> usize { + fn find_left_top_link_on_bridge( + links: &[OverlayLink], + bridge: &[usize; 2], + ) -> usize { // SAFETY: every bridge index comes straight from GraphBuilder::build_nodes_and_connect_links, // which only records values in 0..links.len(), so the unchecked lookups stay in-bounds. let (l0, l1) = unsafe { (links.get_unchecked(bridge[0]), links.get_unchecked(bridge[1])) }; - if Triangle::is_clockwise_point(l0.a.point, l0.b.point, l1.b.point) { + if Triangle::is_clockwise(l0.a.point, l0.b.point, l1.b.point) { bridge[0] } else { bridge[1] @@ -462,8 +482,8 @@ impl GraphUtil { } #[inline(always)] - pub(crate) fn next_link( - links: &[OverlayLink], + pub(crate) fn next_link( + links: &[OverlayLink], nodes: &[OverlayNode], link_id: usize, node_id: usize, @@ -493,8 +513,8 @@ impl GraphUtil { // so every element is a valid index into `links`, and at least one of them is // still unvisited when we enter. The unchecked accesses rely on that invariant. #[inline] - fn find_nearest_link_to( - links: &[OverlayLink], + fn find_nearest_link_to( + links: &[OverlayLink], target_index: usize, node_id: usize, clockwise: bool, diff --git a/iOverlay/src/core/extract_ogc.rs b/iOverlay/src/core/extract_ogc.rs index 0ab1ebcd..8ff78248 100644 --- a/iOverlay/src/core/extract_ogc.rs +++ b/iOverlay/src/core/extract_ogc.rs @@ -9,16 +9,22 @@ 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; use i_float::int::point::IntPoint; +use i_key_sort::sort::key::SortKey; use i_shape::int::shape::{IntShape, IntShapes}; use i_shape::util::reserve::Reserve; +use i_tree::Expiration; -impl OverlayGraph<'_> { +impl OverlayGraph<'_, I> +where + I: IntNumber + Expiration + SortKey, +{ pub(crate) fn extract_ogc( &self, overlay_rule: OverlayRule, - buffer: &mut BooleanExtractionBuffer, - ) -> IntShapes { + buffer: &mut BooleanExtractionBuffer, + ) -> IntShapes { let is_main_dir_cw = self.options.output_direction == ContourDirection::Clockwise; let mut contour_visited = if let Some(mut visited) = buffer.contour_visited.take() { @@ -161,14 +167,14 @@ impl OverlayGraph<'_> { } }; - debug_assert_eq!(v_segment, contour.left_bottom_segment()); + 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(|s0, s1| s0.v_segment.a.cmp(&s1.v_segment.a)); + anchors.sort_unstable_by_key(|s0| s0.v_segment.a); } shapes.join_sorted_holes(holes, anchors, is_main_dir_cw); @@ -181,7 +187,7 @@ impl OverlayGraph<'_> { fn skip_contour( &self, - start_data: &StartPathData, + start_data: &StartPathData, clockwise: bool, visited_state: VisitState, visited: &mut [VisitState], @@ -214,12 +220,12 @@ impl OverlayGraph<'_> { fn collect_shape( &self, - start_data: &StartPathData, + start_data: &StartPathData, clockwise: bool, global_visited: &mut [VisitState], contour_visited: &mut [VisitState], - points: &mut Vec, - ) -> Option { + points: &mut Vec>, + ) -> Option> { 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; diff --git a/iOverlay/src/core/graph.rs b/iOverlay/src/core/graph.rs index 9936bc4f..0df6a072 100644 --- a/iOverlay/src/core/graph.rs +++ b/iOverlay/src/core/graph.rs @@ -6,6 +6,7 @@ use super::link::OverlayLink; use crate::build::builder::GraphNode; use crate::core::overlay::IntOverlayOptions; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; /// A representation of geometric shapes organized for efficient boolean operations. /// @@ -13,13 +14,12 @@ use alloc::vec::Vec; /// /// Use `OverlayGraph` to perform boolean operations on the geometric shapes you've added to an `Overlay`, after it has processed the shapes according to the specified build and overlay rules. /// [More information](https://ishape-rust.github.io/iShape-js/overlay/overlay_graph/overlay_graph.html) about Overlay Graph. -pub struct OverlayGraph<'a> { - pub(crate) options: IntOverlayOptions, +pub struct OverlayGraph<'a, I: IntNumber, D = ()> { + pub(crate) options: IntOverlayOptions, pub(crate) nodes: &'a [OverlayNode], - pub(crate) links: &'a [OverlayLink], + pub(crate) links: &'a [OverlayLink], } -#[derive(Debug)] pub(crate) enum OverlayNode { Bridge([usize; 2]), Cross(Vec), @@ -36,7 +36,7 @@ impl GraphNode for OverlayNode { } } -impl OverlayGraph<'_> { +impl OverlayGraph<'_, I, D> { pub fn validate(&self) { for node in self.nodes.iter() { if let OverlayNode::Cross(indices) = node { diff --git a/iOverlay/src/core/link.rs b/iOverlay/src/core/link.rs index bd672569..53fa93b1 100644 --- a/iOverlay/src/core/link.rs +++ b/iOverlay/src/core/link.rs @@ -3,22 +3,29 @@ use crate::core::overlay_rule::OverlayRule; use crate::geom::id_point::IdPoint; use crate::segm::segment::SegmentFill; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; -#[derive(Debug, Clone, Copy)] -pub(crate) struct OverlayLink { - pub(crate) a: IdPoint, - pub(crate) b: IdPoint, +#[derive(Clone)] +pub(crate) struct OverlayLink { + pub(crate) a: IdPoint, + pub(crate) b: IdPoint, pub(crate) fill: SegmentFill, + pub(crate) data: D, } -impl OverlayLink { +impl OverlayLink { #[inline(always)] - pub(crate) fn new(a: IdPoint, b: IdPoint, fill: SegmentFill) -> OverlayLink { - OverlayLink { a, b, fill } + pub(crate) fn new_with_data( + a: IdPoint, + b: IdPoint, + fill: SegmentFill, + data: D, + ) -> OverlayLink { + OverlayLink { a, b, fill, data } } #[inline(always)] - pub(crate) fn other(&self, node_id: usize) -> IdPoint { + pub(crate) fn other(&self, node_id: usize) -> IdPoint { if self.a.id == node_id { self.b } else { self.a } } diff --git a/iOverlay/src/core/mod.rs b/iOverlay/src/core/mod.rs index c8ae4d97..dff945b8 100644 --- a/iOverlay/src/core/mod.rs +++ b/iOverlay/src/core/mod.rs @@ -1,4 +1,6 @@ pub mod divide; +pub mod edge_data; +pub mod edge_overlay; pub mod extract; mod extract_ogc; pub mod fill_rule; diff --git a/iOverlay/src/core/nearest_vector.rs b/iOverlay/src/core/nearest_vector.rs index 1f7a1d4f..b5b01c59 100644 --- a/iOverlay/src/core/nearest_vector.rs +++ b/iOverlay/src/core/nearest_vector.rs @@ -1,24 +1,32 @@ -use i_float::fix_vec::FixVec; +use i_float::int::number::int::IntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; +use i_float::int::vector::IntVector; -pub(crate) struct NearestVector { - c: IntPoint, // center - va: FixVec, // our target vector - vb: FixVec, // nearest vector to Va by specified rotation +pub(crate) struct NearestVector { + c: IntPoint, // center + va: IntVector, // our target vector + vb: IntVector, // nearest vector to Va by specified rotation ab_more_180: bool, // is angle between Va and Vb more than 180 degrees pub(crate) best_id: usize, - rotation_factor: i64, // +1 for clockwise, -1 for counterclockwise + rotation_factor: I::Wide, // +1 for clockwise, -1 for counterclockwise } -impl NearestVector { +impl NearestVector { #[inline] - pub(crate) fn new(c: IntPoint, a: IntPoint, b: IntPoint, best_id: usize, clockwise: bool) -> Self { - let va = a.subtract(c); - let vb = b.subtract(c); + pub(crate) fn new( + c: IntPoint, + a: IntPoint, + b: IntPoint, + best_id: usize, + clockwise: bool, + ) -> Self { + let va = a - c; + let vb = b - c; let (ab_more_180, rotation_factor) = if clockwise { - (va.cross_product(vb) >= 0, 1) + (va.cross_product(vb) >= I::Wide::ZERO, I::Wide::ONE) } else { - (va.cross_product(vb) <= 0, -1) + (va.cross_product(vb) <= I::Wide::ZERO, -I::Wide::ONE) }; Self { c, @@ -31,12 +39,12 @@ impl NearestVector { } #[inline] - pub(crate) fn add(&mut self, p: IntPoint, id: usize) { - let vp = p.subtract(self.c); - let ap_more_180 = self.va.cross_product(vp) * self.rotation_factor >= 0; + pub(crate) fn add(&mut self, p: IntPoint, id: usize) { + let vp = p - self.c; + let ap_more_180 = self.va.cross_product(vp) * self.rotation_factor >= I::Wide::ZERO; if self.ab_more_180 == ap_more_180 { - if vp.cross_product(self.vb) * self.rotation_factor < 0 { + if vp.cross_product(self.vb) * self.rotation_factor < I::Wide::ZERO { self.vb = vp; self.best_id = id; } @@ -51,8 +59,8 @@ impl NearestVector { #[cfg(test)] mod tests { use crate::core::nearest_vector::NearestVector; - use i_float::fix_vec::FixVec; use i_float::int::point::IntPoint; + use i_float::int::vector::IntVector; #[test] fn test_nearest_ccw_vector_creation() { @@ -62,8 +70,8 @@ mod tests { let nearest_ccw = NearestVector::new(c, a, b, 0, false); - assert_eq!(nearest_ccw.va, FixVec::new(1, 0)); - assert_eq!(nearest_ccw.vb, FixVec::new(0, 1)); + assert_eq!(nearest_ccw.va, IntVector::::new(1, 0)); + assert_eq!(nearest_ccw.vb, IntVector::::new(0, 1)); assert!(!nearest_ccw.ab_more_180); } @@ -77,7 +85,7 @@ mod tests { let p = IntPoint::new(-1, 0); nearest_ccw.add(p, 1); - assert_eq!(nearest_ccw.vb, FixVec::new(0, 1)); + assert_eq!(nearest_ccw.vb, IntVector::::new(0, 1)); assert!(!nearest_ccw.ab_more_180); } @@ -90,7 +98,7 @@ mod tests { let mut nearest_ccw = NearestVector::new(c, a, b, 0, false); let p = IntPoint::new(0, 1); nearest_ccw.add(p, 1); - assert_eq!(nearest_ccw.vb, FixVec::new(0, 1)); + assert_eq!(nearest_ccw.vb, IntVector::::new(0, 1)); assert!(!nearest_ccw.ab_more_180); } @@ -105,7 +113,7 @@ mod tests { nearest_ccw.add(p, 1); - assert_eq!(nearest_ccw.vb, FixVec::new(1, 1)); + assert_eq!(nearest_ccw.vb, IntVector::::new(1, 1)); } #[test] @@ -130,8 +138,8 @@ mod tests { let nearest_cw = NearestVector::new(c, a, b, 0, true); - assert_eq!(nearest_cw.va, FixVec::new(1, 0)); - assert_eq!(nearest_cw.vb, FixVec::new(0, -1)); + assert_eq!(nearest_cw.va, IntVector::::new(1, 0)); + assert_eq!(nearest_cw.vb, IntVector::::new(0, -1)); assert!(!nearest_cw.ab_more_180); } @@ -145,7 +153,7 @@ mod tests { let p = IntPoint::new(-1, 0); nearest_cw.add(p, 1); - assert_eq!(nearest_cw.vb, FixVec::new(0, -1)); + assert_eq!(nearest_cw.vb, IntVector::::new(0, -1)); assert!(!nearest_cw.ab_more_180); } @@ -158,7 +166,7 @@ mod tests { let mut nearest_cw = NearestVector::new(c, a, b, 0, true); let p = IntPoint::new(0, -1); nearest_cw.add(p, 1); - assert_eq!(nearest_cw.vb, FixVec::new(0, -1)); + assert_eq!(nearest_cw.vb, IntVector::::new(0, -1)); assert!(!nearest_cw.ab_more_180); } @@ -173,7 +181,7 @@ mod tests { nearest_cw.add(p, 1); - assert_eq!(nearest_cw.vb, FixVec::new(0, -1)); + assert_eq!(nearest_cw.vb, IntVector::::new(0, -1)); } #[test] diff --git a/iOverlay/src/core/overlay.rs b/iOverlay/src/core/overlay.rs index 4a334bf7..7074f3e0 100644 --- a/iOverlay/src/core/overlay.rs +++ b/iOverlay/src/core/overlay.rs @@ -12,11 +12,15 @@ use crate::segm::boolean::ShapeCountBoolean; use crate::segm::build::BuildSegments; use crate::segm::segment::Segment; use crate::split::solver::SplitSolver; -use crate::vector::edge::{VectorEdge, VectorShape}; +use crate::vector::edge::{DataVectorEdge, VectorShape}; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_float::int::number::uint::UIntNumber; use i_float::int::point::IntPoint; +use i_key_sort::sort::key::SortKey; use i_shape::int::count::PointsCount; use i_shape::int::shape::{IntContour, IntShape, IntShapes}; +use i_tree::{Expiration, LayoutNumber}; use super::graph::{OverlayGraph, OverlayNode}; @@ -26,7 +30,7 @@ use super::graph::{OverlayGraph, OverlayNode}; /// during the Boolean operation process. You can use this to adjust output /// direction, eliminate small artifacts, or retain collinear points. #[derive(Debug, Clone, Copy)] -pub struct IntOverlayOptions { +pub struct IntOverlayOptions { /// Preserve collinear points in the input before Boolean operations. pub preserve_input_collinear: bool, @@ -37,7 +41,7 @@ pub struct IntOverlayOptions { pub preserve_output_collinear: bool, /// Minimum area threshold to include a contour in the result. - pub min_output_area: u64, + pub min_output_area: U, /// If true, extract OGC-valid shapes. pub ogc: bool, @@ -61,16 +65,19 @@ pub enum ContourDirection { } /// This struct is essential for describing and uploading the geometry or shapes required to construct an `OverlayGraph`. It prepares the necessary data for boolean operations. -pub struct Overlay { +pub struct Overlay { pub solver: Solver, - pub options: IntOverlayOptions, - pub boolean_buffer: Option, - pub(crate) segments: Vec>, - pub(crate) split_solver: SplitSolver, - pub(crate) graph_builder: GraphBuilder, + pub options: IntOverlayOptions, + pub boolean_buffer: Option>, + pub(crate) segments: Vec>, + pub(crate) split_solver: SplitSolver, + pub(crate) graph_builder: GraphBuilder, } -impl Overlay { +impl Overlay +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Constructs a new `Overlay` instance, initializing it with a capacity that should closely match the total count of edges from all shapes being processed. /// This pre-allocation helps in optimizing memory usage and performance. /// - `capacity`: The initial capacity for storing edge data. Ideally, this should be set to the sum of the edges of all shapes to be added to the overlay, ensuring efficient data management. @@ -81,7 +88,7 @@ impl Overlay { boolean_buffer: Some(Default::default()), segments: Vec::with_capacity(capacity), split_solver: SplitSolver::new(), - graph_builder: GraphBuilder::::new(), + graph_builder: GraphBuilder::::new(), } } @@ -90,21 +97,21 @@ impl Overlay { /// - `capacity`: The initial capacity for storing edge data. Ideally, this should be set to the sum of the edges of all shapes to be added to the overlay, ensuring efficient data management. /// - `options`: Adjust custom behavior. /// - `solver`: Type of solver to use. - pub fn new_custom(capacity: usize, options: IntOverlayOptions, solver: Solver) -> Self { + pub fn new_custom(capacity: usize, options: IntOverlayOptions, solver: Solver) -> Self { Self { solver, options, boolean_buffer: Some(Default::default()), segments: Vec::with_capacity(capacity), split_solver: SplitSolver::new(), - graph_builder: GraphBuilder::::new(), + graph_builder: GraphBuilder::::new(), } } /// Creates a new `Overlay` instance and initializes it with subject and clip contours. /// - `subj`: An array of contours that together define the subject. /// - `clip`: An array of contours that together define the clip. - pub fn with_contour(subj: &[IntPoint], clip: &[IntPoint]) -> Self { + pub fn with_contour(subj: &[IntPoint], clip: &[IntPoint]) -> Self { let mut overlay = Self::new(subj.len() + clip.len()); overlay.add_contour(subj, ShapeType::Subject); overlay.add_contour(clip, ShapeType::Clip); @@ -117,9 +124,9 @@ impl Overlay { /// - `options`: Adjust custom behavior. /// - `solver`: Type of solver to use. pub fn with_contour_custom( - subj: &[IntPoint], - clip: &[IntPoint], - options: IntOverlayOptions, + subj: &[IntPoint], + clip: &[IntPoint], + options: IntOverlayOptions, solver: Solver, ) -> Self { let mut overlay = Self::new_custom(subj.len() + clip.len(), options, solver); @@ -131,7 +138,7 @@ impl Overlay { /// Creates a new `Overlay` instance and initializes it with subject and clip contours. /// - `subj`: An array of contours that together define the subject shape. /// - `clip`: An array of contours that together define the clip shape. - pub fn with_contours(subj: &[IntContour], clip: &[IntContour]) -> Self { + pub fn with_contours(subj: &[IntContour], clip: &[IntContour]) -> Self { let mut overlay = Self::new(subj.points_count() + clip.points_count()); overlay.add_contours(subj, ShapeType::Subject); overlay.add_contours(clip, ShapeType::Clip); @@ -144,9 +151,9 @@ impl Overlay { /// - `options`: Adjust custom behavior. /// - `solver`: Type of solver to use. pub fn with_contours_custom( - subj: &[IntContour], - clip: &[IntContour], - options: IntOverlayOptions, + subj: &[IntContour], + clip: &[IntContour], + options: IntOverlayOptions, solver: Solver, ) -> Self { let mut overlay = Self::new_custom(subj.points_count() + clip.points_count(), options, solver); @@ -158,7 +165,7 @@ impl Overlay { /// Creates a new `Overlay` instance and initializes it with subject and clip shapes. /// - `subj`: An array of shapes to be used as the subject in the overlay operation. /// - `clip`: An array of shapes to be used as the clip in the overlay operation. - pub fn with_shapes(subj: &[IntShape], clip: &[IntShape]) -> Self { + pub fn with_shapes(subj: &[IntShape], clip: &[IntShape]) -> Self { let mut overlay = Self::new(subj.points_count() + clip.points_count()); overlay.add_shapes(subj, ShapeType::Subject); overlay.add_shapes(clip, ShapeType::Clip); @@ -171,9 +178,9 @@ impl Overlay { /// - `options`: Adjust custom behavior. /// - `solver`: Type of solver to use. pub fn with_shapes_options( - subj: &[IntShape], - clip: &[IntShape], - options: IntOverlayOptions, + subj: &[IntShape], + clip: &[IntShape], + options: IntOverlayOptions, solver: Solver, ) -> Self { let mut overlay = Self::new_custom(subj.points_count() + clip.points_count(), options, solver); @@ -188,7 +195,7 @@ impl Overlay { /// - `iter`: An iterator over references to `IntPoint` that defines the path. /// - `shape_type`: Specifies the role of the added path in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_path_iter>(&mut self, iter: I, shape_type: ShapeType) { + pub fn add_path_iter>>(&mut self, iter: It, shape_type: ShapeType) { self.segments .append_path_iter(iter, shape_type, self.options.preserve_input_collinear); } @@ -197,7 +204,7 @@ impl Overlay { /// - `contour`: An array of points that form a closed path. /// - `shape_type`: Specifies the role of the added path in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_contour(&mut self, contour: &[IntPoint], shape_type: ShapeType) { + pub fn add_contour(&mut self, contour: &[IntPoint], shape_type: ShapeType) { self.segments.append_path_iter( contour.iter().copied(), shape_type, @@ -206,28 +213,28 @@ impl Overlay { } /// Adds multiple paths to the overlay as either subject or clip paths. - /// - `contours`: An array of `IntContour` instances to be added to the overlay. + /// - `contours`: An array of `IntContour` instances to be added to the overlay. /// - `shape_type`: Specifies the role of the added paths in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_contours(&mut self, contours: &[IntContour], shape_type: ShapeType) { + pub fn add_contours(&mut self, contours: &[IntContour], shape_type: ShapeType) { for contour in contours.iter() { self.add_contour(contour, shape_type); } } /// Adds a single shape to the overlay as either a subject or clip shape. - /// - `shape`: A reference to a `IntShape` instance to be added. + /// - `shape`: A reference to a `IntShape` instance to be added. /// - `shape_type`: Specifies the role of the added shape in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_shape(&mut self, shape: &IntShape, shape_type: ShapeType) { + pub fn add_shape(&mut self, shape: &IntShape, shape_type: ShapeType) { self.add_contours(shape, shape_type); } /// Adds multiple shapes to the overlay as either subject or clip shapes. - /// - `shapes`: An array of `IntShape` instances to be added to the overlay. + /// - `shapes`: An array of `IntShape` instances to be added to the overlay. /// - `shape_type`: Specifies the role of the added shapes in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_shapes(&mut self, shapes: &[IntShape], shape_type: ShapeType) { + pub fn add_shapes(&mut self, shapes: &[IntShape], shape_type: ShapeType) { for shape in shapes.iter() { self.add_contours(shape, shape_type); } @@ -239,10 +246,10 @@ impl Overlay { } /// Adds multiple flat-shape to the overlay as either subject or clip shapes. - /// - `buffer`: A buffer of `IntShapes` instances to be added to the overlay. + /// - `buffer`: A buffer of `IntShapes` instances to be added to the overlay. /// - `shape_type`: Specifies the role of the added shapes in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_flat_buffer(&mut self, buffer: &FlatContoursBuffer, shape_type: ShapeType) { + pub fn add_flat_buffer(&mut self, buffer: &FlatContoursBuffer, shape_type: ShapeType) { for range in buffer.ranges.iter() { let contour = &buffer.points[range.clone()]; self.add_contour(contour, shape_type); @@ -256,7 +263,7 @@ impl Overlay { &mut self, fill_rule: FillRule, overlay_rule: OverlayRule, - ) -> Vec { + ) -> Vec> { self.split_solver.split_segments(&mut self.segments, &self.solver); if self.segments.is_empty() { return Vec::new(); @@ -273,7 +280,7 @@ impl Overlay { &self.solver, &self.segments, ) - .extract_shape_vectors(overlay_rule, &mut buffer); + .extract_vector_shapes(overlay_rule, &mut buffer); self.boolean_buffer = Some(buffer); @@ -282,7 +289,7 @@ impl Overlay { /// Convert into vectors from the added paths or shapes, applying the specified build rule. This method is particularly useful for development purposes and for creating visualizations in educational demos, where understanding the impact of different rules on the final geometry is crucial. /// - `fill_rule`: The build rule to use for the shapes. - pub fn build_separate_vectors(&mut self, fill_rule: FillRule) -> Vec { + pub fn build_separate_vectors(&mut self, fill_rule: FillRule) -> Vec> { self.split_solver.split_segments(&mut self.segments, &self.solver); if self.segments.is_empty() { return Vec::new(); @@ -295,7 +302,7 @@ impl Overlay { /// Convert into `OverlayGraph` from the added paths or shapes using the specified build rule. This graph is the foundation for executing boolean operations, allowing for the analysis and manipulation of the geometric data. The `OverlayGraph` created by this method represents a preprocessed state of the input shapes, optimized for the application of boolean operations based on the provided build rule. /// - `fill_rule`: Specifies the rule for determining filled areas within the shapes, influencing how the resulting graph represents intersections and unions. #[inline] - pub fn build_graph_view(&mut self, fill_rule: FillRule) -> Option> { + pub fn build_graph_view(&mut self, fill_rule: FillRule) -> Option> { self.split_solver.split_segments(&mut self.segments, &self.solver); if self.segments.is_empty() { return None; @@ -316,11 +323,11 @@ impl Overlay { /// ### Parameters: /// - `overlay_rule`: The boolean operation rule to apply, determining how shapes are combined or subtracted. /// - `fill_rule`: Specifies the rule for determining filled areas within the shapes, influencing how the resulting graph represents intersections and unions. - /// - Returns: A vector of `IntShape` that meet the specified area criteria, representing the cleaned-up geometric result. + /// - Returns: A vector of `IntShape` that meet the specified area criteria, representing the cleaned-up geometric result. /// # Shape Representation - /// The output is a `IntShapes`, where: - /// - The outer `Vec` represents a set of shapes. - /// - Each shape `Vec` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. + /// The output is a `IntShapes`, where: + /// - The outer `Vec>` represents a set of shapes. + /// - Each shape `Vec>` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. /// - Each path `Vec` is a sequence of points, forming a closed path. /// /// Note: Outer boundary paths have a counterclockwise order, and holes have a clockwise order. @@ -346,7 +353,7 @@ impl Overlay { /// without subsequent modifications. By excluding unnecessary graph structures, it optimizes performance, /// particularly for complex or resource-intensive geometries. #[inline] - pub fn overlay(&mut self, overlay_rule: OverlayRule, fill_rule: FillRule) -> IntShapes { + pub fn overlay(&mut self, overlay_rule: OverlayRule, fill_rule: FillRule) -> IntShapes { self.split_solver.split_segments(&mut self.segments, &self.solver); if self.segments.is_empty() { return Vec::new(); @@ -369,18 +376,18 @@ impl Overlay { /// Executes a single Boolean operation and writes the result into a flat contour buffer. /// /// This is a lower-allocation alternative to [`Self::overlay`] when you want flat contour - /// output (`points` + `ranges`) instead of nested `IntShapes`. + /// output (`points` + `ranges`) instead of nested `IntShapes`. /// /// - `overlay_rule`: The Boolean operation to apply. /// - `fill_rule`: Fill rule used to determine interior regions. - /// - `output`: Destination [`FlatContoursBuffer`] that receives resulting contours. + /// - `output`: Destination [`FlatContoursBuffer`] that receives resulting contours. /// Existing buffer contents are replaced. #[inline] pub fn overlay_into( &mut self, overlay_rule: OverlayRule, fill_rule: FillRule, - output: &mut FlatContoursBuffer, + output: &mut FlatContoursBuffer, ) { self.split_solver.split_segments(&mut self.segments, &self.solver); if self.segments.is_empty() { @@ -400,25 +407,25 @@ impl Overlay { } } -impl Default for IntOverlayOptions { +impl Default for IntOverlayOptions { fn default() -> Self { Self { preserve_input_collinear: false, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: false, - min_output_area: 0, + min_output_area: U::ZERO, ogc: false, } } } -impl IntOverlayOptions { +impl IntOverlayOptions { pub fn keep_all_points() -> Self { Self { preserve_input_collinear: true, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: true, - min_output_area: 0, + min_output_area: U::ZERO, ogc: false, } } @@ -427,7 +434,7 @@ impl IntOverlayOptions { preserve_input_collinear: false, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: true, - min_output_area: 0, + min_output_area: U::ZERO, ogc: false, } } @@ -436,7 +443,7 @@ impl IntOverlayOptions { preserve_input_collinear: false, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: false, - min_output_area: 0, + min_output_area: U::ZERO, ogc: true, } } @@ -468,7 +475,7 @@ mod tests { let shape = &result[0]; assert_eq!(shape.len(), 1); assert_eq!(shape[0].len(), 4); - assert_eq!(shape[0].area(), -100); + assert_eq!(shape[0].area(), 100i64); } #[test] @@ -495,7 +502,7 @@ mod tests { let shape = &result[0]; assert_eq!(shape.len(), 1); assert_eq!(shape[0].len(), 4); - assert_eq!(shape[0].area(), -100); + assert_eq!(shape[0].area(), 100i64); } #[test] @@ -522,9 +529,9 @@ mod tests { let shape = &result[0]; assert_eq!(shape.len(), 2); assert_eq!(shape[0].len(), 4); - assert_eq!(shape[0].area(), -16); + assert_eq!(shape[0].area(), 16i64); assert_eq!(shape[1].len(), 4); - assert_eq!(shape[1].area(), 4); + assert_eq!(shape[1].area(), -4i64); } #[test] @@ -550,7 +557,7 @@ mod tests { assert_eq!(result.len(), 1); let shape = &result[0]; assert_eq!(shape.len(), 1); - assert_eq!(shape[0].area(), -10); + assert_eq!(shape[0].area(), 10i64); } #[test] @@ -582,7 +589,7 @@ mod tests { assert_eq!(result.len(), 1); let shape = &result[0]; assert_eq!(shape.len(), 1); - assert_eq!(shape[0].area(), -17); + assert_eq!(shape[0].area(), 17i64); } #[test] @@ -608,7 +615,7 @@ mod tests { assert_eq!(result.len(), 1); let shape = &result[0]; assert_eq!(shape.len(), 1); - assert_eq!(shape[0].area(), -16); + assert_eq!(shape[0].area(), 16i64); } #[test] @@ -646,7 +653,7 @@ mod tests { assert_eq!(result.len(), 1); let shape = &result[0]; assert_eq!(shape.len(), 1); - assert_eq!(shape[0].area(), -27); + assert_eq!(shape[0].area(), 27i64); } #[test] @@ -672,7 +679,7 @@ mod tests { assert_eq!(result.len(), 1); let shape = &result[0]; assert_eq!(shape.len(), 1); - assert_eq!(shape[0].area(), -12); + assert_eq!(shape[0].area(), 12i64); } #[test] @@ -698,7 +705,7 @@ mod tests { assert_eq!(result.len(), 1); let shape = &result[0]; assert_eq!(shape.len(), 1); - assert_eq!(shape[0].area(), -4); + assert_eq!(shape[0].area(), 4i64); } #[test] @@ -724,7 +731,7 @@ mod tests { assert_eq!(result.len(), 2); assert_eq!(result[0].len(), 1); assert_eq!(result[1].len(), 1); - assert_eq!(result.area(), -4); + assert_eq!(result.area(), 4i64); } #[test] @@ -749,7 +756,7 @@ mod tests { assert_eq!(result.len(), 1); assert_eq!(result[0].len(), 1); - assert_eq!(result.area(), -6); + assert_eq!(result.area(), 6i64); } #[test] @@ -774,7 +781,7 @@ mod tests { assert_eq!(result.len(), 1); assert_eq!(result[0].len(), 1); - assert_eq!(result.area(), -14); + assert_eq!(result.area(), 14i64); } #[test] @@ -799,7 +806,7 @@ mod tests { assert_eq!(result.len(), 1); assert_eq!(result[0].len(), 1); - assert_eq!(result.area(), -25); + assert_eq!(result.area(), 25i64); } #[test] @@ -824,7 +831,7 @@ mod tests { assert_eq!(result.len(), 1); assert_eq!(result[0].len(), 1); - assert_eq!(result.area(), -25); + assert_eq!(result.area(), 25i64); } #[test] @@ -841,14 +848,14 @@ mod tests { assert_eq!(result.len(), 1); assert_eq!(result[0].len(), 1); - assert_eq!(result.area(), -8); + assert_eq!(result.area(), 8i64); } #[test] fn test_empty_input() { - let subj: &[IntContour] = &[]; + let subj: &[IntContour] = &[]; - let mut overlay = Overlay::with_contours(&subj, &[]); + let mut overlay = Overlay::with_contours(subj, &[]); let result = overlay.overlay(OverlayRule::Subject, FillRule::NonZero); assert_eq!(result.len(), 0); diff --git a/iOverlay/src/core/predicate.rs b/iOverlay/src/core/predicate.rs index 2bab0abf..5eee3a5c 100644 --- a/iOverlay/src/core/predicate.rs +++ b/iOverlay/src/core/predicate.rs @@ -6,19 +6,21 @@ use crate::segm::segment::{ }; use alloc::vec::Vec; use core::ops::ControlFlow; +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::TwoKeysSort; /// Collects segment endpoints and checks for coincidence between subject and clip. /// /// Uses optimized algorithm: collect into separate Vecs, sort with `sort_by_two_keys`, /// dedup, then binary search from shorter into longer array. -pub(crate) struct PointCoincidenceChecker { - subj_points: Vec, - clip_points: Vec, +pub(crate) struct PointCoincidenceChecker { + subj_points: Vec>, + clip_points: Vec>, } -impl PointCoincidenceChecker { +impl PointCoincidenceChecker { /// Create a new checker with pre-allocated capacity. /// /// `capacity` is the number of segments; each segment contributes 2 endpoints. @@ -37,7 +39,7 @@ impl PointCoincidenceChecker { /// clip in the segment are skipped for clip collection /// - Similarly for clip-only interior segments #[inline] - pub(crate) fn add_segment(&mut self, segment: &Segment, fill: SegmentFill) { + pub(crate) fn add_segment(&mut self, segment: &Segment, fill: SegmentFill) { // Skip inner segments optimization: // If segment is entirely inside one shape's interior (filled on both sides) // and has no contribution from the other shape, it's not on a boundary @@ -104,11 +106,11 @@ impl PointCoincidenceChecker { /// loop as soon as an intersection is detected, avoiding processing of remaining segments. /// /// Also collects endpoint information for point coincidence check in finalize. -pub(crate) struct IntersectsHandler { - point_checker: PointCoincidenceChecker, +pub(crate) struct IntersectsHandler { + point_checker: PointCoincidenceChecker, } -impl IntersectsHandler { +impl IntersectsHandler { pub(crate) fn new(capacity: usize) -> Self { Self { point_checker: PointCoincidenceChecker::new(capacity), @@ -116,14 +118,14 @@ impl IntersectsHandler { } } -impl FillHandler for IntersectsHandler { +impl FillHandler for IntersectsHandler { type Output = bool; #[inline(always)] fn handle( &mut self, _index: usize, - segment: &Segment, + segment: &Segment, fill: SegmentFill, ) -> ControlFlow { // Shapes intersect if both contribute to any segment (interior overlap or boundary contact) @@ -152,14 +154,14 @@ impl FillHandler for IntersectsHandler { /// Early-exits `true` on first interior overlap. pub(crate) struct InteriorsIntersectHandler; -impl FillHandler for InteriorsIntersectHandler { +impl FillHandler for InteriorsIntersectHandler { type Output = bool; #[inline(always)] fn handle( &mut self, _index: usize, - _segment: &Segment, + _segment: &Segment, fill: SegmentFill, ) -> ControlFlow { // Interiors intersect if both shapes fill the same side @@ -183,12 +185,12 @@ impl FillHandler for InteriorsIntersectHandler { /// the shapes don't just touch. /// /// Also collects endpoint information for point coincidence check in finalize. -pub(crate) struct TouchesHandler { +pub(crate) struct TouchesHandler { has_boundary_contact: bool, - point_checker: PointCoincidenceChecker, + point_checker: PointCoincidenceChecker, } -impl TouchesHandler { +impl TouchesHandler { pub(crate) fn new(capacity: usize) -> Self { Self { has_boundary_contact: false, @@ -197,14 +199,14 @@ impl TouchesHandler { } } -impl FillHandler for TouchesHandler { +impl FillHandler for TouchesHandler { type Output = bool; #[inline(always)] fn handle( &mut self, _index: usize, - segment: &Segment, + segment: &Segment, fill: SegmentFill, ) -> ControlFlow { // Interior overlap = not a touch (early exit false) @@ -231,11 +233,11 @@ impl FillHandler for TouchesHandler { /// - Returns `false` if there's interior overlap (early exit) /// - Returns `false` if there's edge/boundary contact (shared segments, early exit) /// - Returns `true` ONLY if shapes touch by point coincidence without any edge overlap -pub(crate) struct PointIntersectsHandler { - point_checker: PointCoincidenceChecker, +pub(crate) struct PointIntersectsHandler { + point_checker: PointCoincidenceChecker, } -impl PointIntersectsHandler { +impl PointIntersectsHandler { pub(crate) fn new(capacity: usize) -> Self { Self { point_checker: PointCoincidenceChecker::new(capacity), @@ -243,14 +245,14 @@ impl PointIntersectsHandler { } } -impl FillHandler for PointIntersectsHandler { +impl FillHandler for PointIntersectsHandler { type Output = bool; #[inline(always)] fn handle( &mut self, _index: usize, - segment: &Segment, + segment: &Segment, fill: SegmentFill, ) -> ControlFlow { // Interior overlap = not a point-only intersection (early exit false) @@ -285,14 +287,14 @@ impl WithinHandler { } } -impl FillHandler for WithinHandler { +impl FillHandler for WithinHandler { type Output = bool; #[inline(always)] fn handle( &mut self, _index: usize, - _segment: &Segment, + _segment: &Segment, fill: SegmentFill, ) -> ControlFlow { let subj_top = (fill & SUBJ_TOP) != 0; @@ -324,32 +326,47 @@ mod tests { use super::*; use crate::geom::x_segment::XSegment; - fn make_segment(ax: i32, ay: i32, bx: i32, by: i32, subj: i32, clip: i32) -> Segment { + fn make_segment( + ax: i32, + ay: i32, + bx: i32, + by: i32, + subj: i32, + clip: i32, + ) -> Segment { Segment { x_segment: XSegment { a: IntPoint::new(ax, ay), b: IntPoint::new(bx, by), }, count: ShapeCountBoolean { subj, clip }, + data: (), } } + fn finalize_i32(handler: H) -> H::Output + where + H: crate::build::sweep::FillHandler, + { + handler.finalize() + } + #[test] fn test_point_coincidence_no_points() { - let checker = PointCoincidenceChecker::new(10); + let checker = PointCoincidenceChecker::::new(10); assert!(!checker.has_coincidence()); } #[test] fn test_point_coincidence_subj_only() { - let mut checker = PointCoincidenceChecker::new(10); + let mut checker = PointCoincidenceChecker::::new(10); checker.add_segment(&make_segment(0, 0, 10, 0, 1, 0), SUBJ_TOP); assert!(!checker.has_coincidence()); } #[test] fn test_point_coincidence_coincident_point() { - let mut checker = PointCoincidenceChecker::new(10); + let mut checker = PointCoincidenceChecker::::new(10); // Subject segment with endpoint at (10, 10) checker.add_segment(&make_segment(0, 0, 10, 10, 1, 0), SUBJ_TOP); // Clip segment with endpoint at (10, 10) @@ -359,7 +376,7 @@ mod tests { #[test] fn test_point_coincidence_no_coincidence() { - let mut checker = PointCoincidenceChecker::new(10); + let mut checker = PointCoincidenceChecker::::new(10); checker.add_segment(&make_segment(0, 0, 5, 5, 1, 0), SUBJ_TOP); checker.add_segment(&make_segment(10, 10, 20, 20, 0, 1), CLIP_TOP); assert!(!checker.has_coincidence()); @@ -367,7 +384,7 @@ mod tests { #[test] fn test_point_coincidence_shared_segment_is_line_not_point() { - let mut checker = PointCoincidenceChecker::new(10); + let mut checker = PointCoincidenceChecker::::new(10); // Segment with both SUBJ and CLIP fill is a shared edge (line intersection), // not a point coincidence. Only one array gets populated, so no coincidence. checker.add_segment(&make_segment(0, 0, 10, 10, 1, 1), SUBJ_TOP | CLIP_BOTTOM); @@ -376,7 +393,7 @@ mod tests { #[test] fn test_point_coincidence_dedup_works() { - let mut checker = PointCoincidenceChecker::new(10); + let mut checker = PointCoincidenceChecker::::new(10); // Two subject segments sharing endpoint (5, 5) checker.add_segment(&make_segment(0, 0, 5, 5, 1, 0), SUBJ_TOP); checker.add_segment(&make_segment(5, 5, 10, 10, 1, 0), SUBJ_TOP); @@ -388,7 +405,7 @@ mod tests { #[test] fn test_intersects_handler_both_top() { let seg = make_segment(0, 0, 10, 0, 1, 1); - let mut handler = IntersectsHandler::new(10); + let mut handler = IntersectsHandler::::new(10); let fill = SUBJ_TOP | CLIP_TOP; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Break(true))); @@ -397,7 +414,7 @@ mod tests { #[test] fn test_intersects_handler_both_bottom() { let seg = make_segment(0, 0, 10, 0, 1, 1); - let mut handler = IntersectsHandler::new(10); + let mut handler = IntersectsHandler::::new(10); let fill = SUBJ_BOTTOM | CLIP_BOTTOM; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Break(true))); @@ -407,7 +424,7 @@ mod tests { fn test_intersects_handler_boundary_contact() { // Boundary contact (edge sharing) is still an intersection per DE-9IM let seg = make_segment(0, 0, 10, 0, 1, 1); - let mut handler = IntersectsHandler::new(10); + let mut handler = IntersectsHandler::::new(10); let fill = SUBJ_TOP | CLIP_BOTTOM; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Break(true))); @@ -417,7 +434,7 @@ mod tests { fn test_intersects_handler_no_intersection() { // Only subject contributes - no intersection let seg = make_segment(0, 0, 10, 0, 1, 0); - let mut handler = IntersectsHandler::new(10); + let mut handler = IntersectsHandler::::new(10); let fill = SUBJ_TOP; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Continue(()))); @@ -431,13 +448,13 @@ mod tests { #[test] fn test_intersects_handler_finalize_with_coincidence() { - let mut handler = IntersectsHandler::new(10); + let mut handler = IntersectsHandler::::new(10); // Add segments that don't trigger early exit but have point coincidence let seg1 = make_segment(0, 0, 10, 10, 1, 0); let seg2 = make_segment(10, 10, 20, 20, 0, 1); let _ = handler.handle(0, &seg1, SUBJ_TOP); let _ = handler.handle(1, &seg2, CLIP_TOP); - assert!(handler.finalize()); + assert!(finalize_i32(handler)); } #[test] @@ -466,13 +483,13 @@ mod tests { let fill = SUBJ_TOP | CLIP_BOTTOM; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Continue(()))); - assert!(!handler.finalize()); + assert!(!finalize_i32(handler)); } #[test] fn test_touches_handler_boundary_only() { let seg = make_segment(0, 0, 10, 0, 1, 1); - let mut handler = TouchesHandler::new(10); + let mut handler = TouchesHandler::::new(10); let fill = SUBJ_TOP | CLIP_BOTTOM; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Continue(()))); @@ -482,7 +499,7 @@ mod tests { #[test] fn test_touches_handler_interior_overlap() { let seg = make_segment(0, 0, 10, 0, 1, 1); - let mut handler = TouchesHandler::new(10); + let mut handler = TouchesHandler::::new(10); let fill = SUBJ_TOP | CLIP_TOP; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Break(false))); // early exit on interior overlap @@ -491,7 +508,7 @@ mod tests { #[test] fn test_touches_handler_no_contact() { let seg = make_segment(0, 0, 10, 0, 1, 0); - let mut handler = TouchesHandler::new(10); + let mut handler = TouchesHandler::::new(10); let fill = SUBJ_TOP; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Continue(()))); @@ -500,13 +517,13 @@ mod tests { #[test] fn test_touches_handler_point_coincidence() { - let mut handler = TouchesHandler::new(10); + let mut handler = TouchesHandler::::new(10); // Add segments that don't touch via fill but have point coincidence let seg1 = make_segment(0, 0, 10, 10, 1, 0); let seg2 = make_segment(10, 10, 20, 20, 0, 1); let _ = handler.handle(0, &seg1, SUBJ_TOP); let _ = handler.handle(1, &seg2, CLIP_TOP); - assert!(handler.finalize()); + assert!(finalize_i32(handler)); } #[test] @@ -517,7 +534,7 @@ mod tests { let fill = SUBJ_TOP | CLIP_TOP; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Continue(()))); - assert!(handler.finalize()); + assert!(finalize_i32(handler)); } #[test] @@ -534,7 +551,7 @@ mod tests { fn test_within_handler_empty_subject() { let handler = WithinHandler::new(); // Empty subject is not within anything - assert!(!handler.finalize()); + assert!(!finalize_i32(handler)); } #[test] @@ -545,12 +562,12 @@ mod tests { let fill = CLIP_TOP; let result = handler.handle(0, &seg, fill); assert!(matches!(result, ControlFlow::Continue(()))); - assert!(!handler.finalize()); + assert!(!finalize_i32(handler)); } #[test] fn test_point_intersects_handler_point_only() { - let mut handler = PointIntersectsHandler::new(10); + let mut handler = PointIntersectsHandler::::new(10); // Subject segment ending at (10, 10) let seg1 = make_segment(0, 0, 10, 10, 1, 0); // Clip segment starting at (10, 10) @@ -565,7 +582,7 @@ mod tests { fn test_point_intersects_handler_edge_contact() { // Segment belongs to both subject and clip (shared edge) let seg = make_segment(0, 0, 10, 0, 1, 1); - let mut handler = PointIntersectsHandler::new(10); + let mut handler = PointIntersectsHandler::::new(10); // Both shapes have fill on opposite sides (boundary contact) let fill = SUBJ_TOP | CLIP_BOTTOM; let result = handler.handle(0, &seg, fill); @@ -576,7 +593,7 @@ mod tests { #[test] fn test_point_intersects_handler_interior_overlap() { let seg = make_segment(0, 0, 10, 0, 1, 1); - let mut handler = PointIntersectsHandler::new(10); + let mut handler = PointIntersectsHandler::::new(10); // Interior overlap (both shapes fill the same side) let fill = SUBJ_TOP | CLIP_TOP; let result = handler.handle(0, &seg, fill); @@ -588,7 +605,7 @@ mod tests { fn test_point_intersects_handler_no_contact() { let seg1 = make_segment(0, 0, 5, 5, 1, 0); let seg2 = make_segment(10, 10, 20, 20, 0, 1); - let mut handler = PointIntersectsHandler::new(10); + let mut handler = PointIntersectsHandler::::new(10); let _ = handler.handle(0, &seg1, SUBJ_TOP); let _ = handler.handle(1, &seg2, CLIP_TOP); // No contact at all → false diff --git a/iOverlay/src/core/relate.rs b/iOverlay/src/core/relate.rs index e62cd7e9..602e98e0 100644 --- a/iOverlay/src/core/relate.rs +++ b/iOverlay/src/core/relate.rs @@ -10,8 +10,11 @@ use crate::segm::build::BuildSegments; use crate::segm::segment::Segment; use crate::split::solver::SplitSolver; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; +use i_key_sort::sort::key::SortKey; use i_shape::int::shape::{IntContour, IntShape}; +use i_tree::{Expiration, LayoutNumber}; /// Overlay structure optimized for spatial predicate evaluation. /// @@ -26,24 +29,27 @@ use i_shape::int::shape::{IntContour, IntShape}; /// use i_overlay::core::relate::PredicateOverlay; /// use i_overlay::core::overlay::ShapeType; /// -/// let mut overlay = PredicateOverlay::new(16); +/// let mut overlay = PredicateOverlay::::new(16); /// // Add subject and clip segments... /// let intersects = overlay.intersects(); /// ``` /// /// For float coordinates, prefer using [`FloatPredicateOverlay`](crate::float::relate::FloatPredicateOverlay) /// or the [`FloatRelate`](crate::float::relate::FloatRelate) trait. -pub struct PredicateOverlay { +pub struct PredicateOverlay { /// Solver configuration for segment operations. pub solver: Solver, /// Fill rule for determining polygon interiors. pub fill_rule: FillRule, - pub(crate) segments: Vec>, - pub(crate) split_solver: SplitSolver, - sweep_runner: SweepRunner, + pub(crate) segments: Vec>, + pub(crate) split_solver: SplitSolver, + sweep_runner: SweepRunner, } -impl PredicateOverlay { +impl PredicateOverlay +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] pub fn new(capacity: usize) -> Self { Self { @@ -55,7 +61,7 @@ impl PredicateOverlay { } } - fn evaluate>(&mut self, handler: H) -> T { + fn evaluate>(&mut self, handler: H) -> T { if self.segments.is_empty() { return T::default(); } @@ -73,7 +79,7 @@ impl PredicateOverlay { #[inline] pub fn intersects(&mut self) -> bool { let capacity = self.segments.len(); - self.evaluate(IntersectsHandler::new(capacity)) + self.evaluate(IntersectsHandler::::new(capacity)) } /// Returns `true` if the interiors of subject and clip shapes overlap. @@ -92,7 +98,7 @@ impl PredicateOverlay { #[inline] pub fn touches(&mut self) -> bool { let capacity = self.segments.len(); - self.evaluate(TouchesHandler::new(capacity)) + self.evaluate(TouchesHandler::::new(capacity)) } /// Returns `true` if subject and clip shapes intersect by point coincidence only. @@ -102,7 +108,7 @@ impl PredicateOverlay { #[inline] pub fn point_intersects(&mut self) -> bool { let capacity = self.segments.len(); - self.evaluate(PointIntersectsHandler::new(capacity)) + self.evaluate(PointIntersectsHandler::::new(capacity)) } /// Returns `true` if subject is completely within clip. @@ -120,7 +126,7 @@ impl PredicateOverlay { /// - `iter`: An iterator over references to `IntPoint` that defines the path. /// - `shape_type`: Specifies the role of the added path in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_path_iter>(&mut self, iter: I, shape_type: ShapeType) { + pub fn add_path_iter>>(&mut self, iter: It, shape_type: ShapeType) { self.segments.append_path_iter(iter, shape_type, false); } @@ -128,34 +134,34 @@ impl PredicateOverlay { /// - `contour`: An array of points that form a closed path. /// - `shape_type`: Specifies the role of the added path in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_contour(&mut self, contour: &[IntPoint], shape_type: ShapeType) { + pub fn add_contour(&mut self, contour: &[IntPoint], shape_type: ShapeType) { self.segments .append_path_iter(contour.iter().copied(), shape_type, false); } /// Adds multiple paths to the overlay as either subject or clip paths. - /// - `contours`: An array of `IntContour` instances to be added to the overlay. + /// - `contours`: An array of `IntContour` instances to be added to the overlay. /// - `shape_type`: Specifies the role of the added paths in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_contours(&mut self, contours: &[IntContour], shape_type: ShapeType) { + pub fn add_contours(&mut self, contours: &[IntContour], shape_type: ShapeType) { for contour in contours.iter() { self.add_contour(contour, shape_type); } } /// Adds a single shape to the overlay as either a subject or clip shape. - /// - `shape`: A reference to a `IntShape` instance to be added. + /// - `shape`: A reference to a `IntShape` instance to be added. /// - `shape_type`: Specifies the role of the added shape in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_shape(&mut self, shape: &IntShape, shape_type: ShapeType) { + pub fn add_shape(&mut self, shape: &IntShape, shape_type: ShapeType) { self.add_contours(shape, shape_type); } /// Adds multiple shapes to the overlay as either subject or clip shapes. - /// - `shapes`: An array of `IntShape` instances to be added to the overlay. + /// - `shapes`: An array of `IntShape` instances to be added to the overlay. /// - `shape_type`: Specifies the role of the added shapes in the overlay operation, either as `Subject` or `Clip`. #[inline] - pub fn add_shapes(&mut self, shapes: &[IntShape], shape_type: ShapeType) { + pub fn add_shapes(&mut self, shapes: &[IntShape], shape_type: ShapeType) { for shape in shapes.iter() { self.add_contours(shape, shape_type); } @@ -189,6 +195,30 @@ mod tests { assert!(overlay.intersects()); } + #[test] + fn test_add_contour_intersects_i64() { + let mut overlay = PredicateOverlay::::new(16); + overlay.add_contour( + &[ + IntPoint::new(0, 0), + IntPoint::new(0, 10), + IntPoint::new(10, 10), + IntPoint::new(10, 0), + ], + ShapeType::Subject, + ); + overlay.add_contour( + &[ + IntPoint::new(5, 5), + IntPoint::new(5, 15), + IntPoint::new(15, 15), + IntPoint::new(15, 5), + ], + ShapeType::Clip, + ); + assert!(overlay.intersects()); + } + #[test] fn test_add_contour_disjoint() { let mut overlay = PredicateOverlay::new(16); diff --git a/iOverlay/src/core/simplify.rs b/iOverlay/src/core/simplify.rs index a4196bd1..29f15572 100644 --- a/iOverlay/src/core/simplify.rs +++ b/iOverlay/src/core/simplify.rs @@ -6,36 +6,42 @@ use crate::core::overlay::ContourDirection; use crate::core::overlay::ContourDirection::Clockwise; use crate::core::overlay::{IntOverlayOptions, Overlay, ShapeType}; use crate::core::overlay_rule::OverlayRule; +use crate::i_float::int::number::int::IntNumber; use crate::i_float::int::point::IntPoint; use alloc::vec; +use i_key_sort::sort::key::SortKey; use i_shape::flat::buffer::FlatContoursBuffer; use crate::segm::build::BuildSegments; use i_shape::int::count::PointsCount; use i_shape::int::path::ContourExtension; use i_shape::int::shape::{IntContour, IntShape, IntShapes}; +use i_tree::{Expiration, LayoutNumber}; /// Trait `Simplify` provides a method to simplify geometric shapes by reducing the number of points in contours or shapes /// while preserving overall shape and topology. The method applies a minimum area threshold and a build rule to /// determine which areas should be retained or excluded. -pub trait Simplify { +pub trait Simplify { /// Simplifies the shape or collection of points, contours, or shapes, based on a specified minimum area threshold. /// /// - `fill_rule`: Fill rule to determine filled areas (non-zero, even-odd, positive, negative). /// - `options`: Adjust custom behavior. /// # Shape Representation - /// The output is a `IntShapes`, where: - /// - The outer `Vec` represents a set of shapes. - /// - Each shape `Vec` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. + /// The output is a `IntShapes`, where: + /// - The outer `Vec>` represents a set of shapes. + /// - Each shape `Vec>` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. /// - Each path `Vec` is a sequence of points, forming a closed path. /// /// Note: Outer boundary paths have a **main_direction** order, and holes have an opposite to **main_direction** order. - fn simplify(&self, fill_rule: FillRule, options: IntOverlayOptions) -> IntShapes; + fn simplify(&self, fill_rule: FillRule, options: IntOverlayOptions) -> IntShapes; } -impl Simplify for [IntPoint] { +impl Simplify for [IntPoint] +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn simplify(&self, fill_rule: FillRule, options: IntOverlayOptions) -> IntShapes { + fn simplify(&self, fill_rule: FillRule, options: IntOverlayOptions) -> IntShapes { match Overlay::new_custom(self.len(), options, Default::default()).simplify_contour(self, fill_rule) { Some(shapes) => shapes, None => vec![vec![self.to_vec()]], @@ -43,9 +49,12 @@ impl Simplify for [IntPoint] { } } -impl Simplify for [IntContour] { +impl Simplify for [IntContour] +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn simplify(&self, fill_rule: FillRule, options: IntOverlayOptions) -> IntShapes { + fn simplify(&self, fill_rule: FillRule, options: IntOverlayOptions) -> IntShapes { match Overlay::new_custom(self.len(), options, Default::default()).simplify_shape(self, fill_rule) { Some(shapes) => shapes, None => vec![self.to_vec()], @@ -53,9 +62,12 @@ impl Simplify for [IntContour] { } } -impl Simplify for [IntShape] { +impl Simplify for [IntShape] +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn simplify(&self, fill_rule: FillRule, options: IntOverlayOptions) -> IntShapes { + fn simplify(&self, fill_rule: FillRule, options: IntOverlayOptions) -> IntShapes { Overlay::new_custom(self.points_count(), options, Default::default()).simplify_shapes(self, fill_rule) } } @@ -66,15 +78,18 @@ enum ContourFillDirection { Empty, } -impl Overlay { +impl Overlay +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Fast-path simplification for a single contour. /// /// Skips full overlay if the contour is already simple (no splits, no loops, no collinear issues). /// Ensures correct winding order based on `fill_rule` and `options.output_direction`. /// - /// Returns `None` if the contour is valid and needs no changes, or `Some(IntShapes)` with the simplified result. + /// Returns `None` if the contour is valid and needs no changes, or `Some(IntShapes)` with the simplified result. #[inline] - pub fn simplify_contour(&mut self, contour: &[IntPoint], fill_rule: FillRule) -> Option { + pub fn simplify_contour(&mut self, contour: &[IntPoint], fill_rule: FillRule) -> Option> { self.clear(); let is_perfect = self.find_intersections(contour); @@ -117,7 +132,7 @@ impl Overlay { fn contour_direction( output_direction: ContourDirection, fill_rule: FillRule, - contour: &[IntPoint], + contour: &[IntPoint], ) -> ContourFillDirection { let contour_clockwise = contour.is_clockwise_ordered(); let output_clockwise = output_direction == Clockwise; @@ -148,7 +163,7 @@ impl Overlay { } #[inline] - pub fn simplify_shape(&mut self, shape: &[IntContour], fill_rule: FillRule) -> Option { + pub fn simplify_shape(&mut self, shape: &[IntContour], fill_rule: FillRule) -> Option> { if shape.len() == 1 { return self.simplify_contour(&shape[0], fill_rule); } @@ -158,14 +173,14 @@ impl Overlay { } #[inline] - pub fn simplify_shapes(&mut self, shapes: &[IntShape], fill_rule: FillRule) -> IntShapes { + pub fn simplify_shapes(&mut self, shapes: &[IntShape], fill_rule: FillRule) -> IntShapes { self.clear(); self.add_shapes(shapes, ShapeType::Subject); self.overlay(OverlayRule::Subject, fill_rule) } #[inline] - pub fn simplify_flat_buffer(&mut self, flat_buffer: &mut FlatContoursBuffer, fill_rule: FillRule) { + pub fn simplify_flat_buffer(&mut self, flat_buffer: &mut FlatContoursBuffer, fill_rule: FillRule) { self.clear(); if flat_buffer.is_single_contour() { @@ -212,7 +227,7 @@ impl Overlay { self.boolean_buffer = Some(boolean_buffer); } - fn find_intersections(&mut self, contour: &[IntPoint]) -> bool { + fn find_intersections(&mut self, contour: &[IntPoint]) -> bool { let append_modified = self.segments.append_path_iter( contour.iter().copied(), ShapeType::Subject, diff --git a/iOverlay/src/core/solver.rs b/iOverlay/src/core/solver.rs index 144011c6..679fe1b2 100644 --- a/iOverlay/src/core/solver.rs +++ b/iOverlay/src/core/solver.rs @@ -1,5 +1,6 @@ use crate::core::solver::Strategy::{Auto, Frag, List, Tree}; use crate::segm::segment::Segment; +use i_float::int::number::int::IntNumber; /// Represents the selection strategy or algorithm for processing geometric data, aimed at optimizing performance under various conditions. /// @@ -161,7 +162,10 @@ impl Solver { } } - pub(crate) fn is_list_split(&self, segments: &[Segment]) -> bool { + pub(crate) fn is_list_split( + &self, + segments: &[Segment], + ) -> bool { match self.strategy { List => true, Tree | Frag => false, @@ -169,11 +173,14 @@ impl Solver { } } - pub(crate) fn is_fragmentation_required(&self, segments: &[Segment]) -> bool { + pub(crate) fn is_fragmentation_required( + &self, + segments: &[Segment], + ) -> bool { segments.len() > Self::MIN_FRAGMENT_COUNT || self.strategy == Frag } - pub(crate) fn is_list_fill(&self, segments: &[Segment]) -> bool { + pub(crate) fn is_list_fill(&self, segments: &[Segment]) -> bool { match self.strategy { List => true, Tree | Frag => false, diff --git a/iOverlay/src/float/clip.rs b/iOverlay/src/float/clip.rs index 9f83a6dc..85969c7a 100644 --- a/iOverlay/src/float/clip.rs +++ b/iOverlay/src/float/clip.rs @@ -4,9 +4,32 @@ use crate::float::scale::FixedScaleOverlayError; use crate::float::string_overlay::FloatStringOverlay; use crate::string::clip::ClipRule; use i_float::float::compatible::FloatPointCompatible; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Paths; use i_shape::source::resource::ShapeResource; +use i_tree::{Expiration, LayoutNumber}; +/// Trait for clipping float string paths by float shapes. +/// +/// This convenience trait uses the default integer engine (`i32`). Use the `*_as::` methods +/// when you need to select `i16`, `i32`, or `i64` explicitly. +/// +/// # Example +/// +/// ``` +/// use i_overlay::core::fill_rule::FillRule; +/// use i_overlay::float::clip::FloatClip; +/// use i_overlay::string::clip::ClipRule; +/// +/// let shape = vec![[0.0, 0.0], [0.0, 2.0], [2.0, 2.0], [2.0, 0.0]]; +/// let line = vec![[-1.0, 1.0], [3.0, 1.0]]; +/// let clip_rule = ClipRule { invert: false, boundary_included: false }; +/// +/// let result = line.clip_by_as::(&shape, FillRule::EvenOdd, clip_rule); +/// +/// assert_eq!(result.len(), 1); +/// ``` pub trait FloatClip where R: ShapeResource

, @@ -25,6 +48,11 @@ where /// A `Paths

` collection of string lines that meet the clipping conditions. fn clip_by(&self, source: &R, fill_rule: FillRule, clip_rule: ClipRule) -> Paths

; + /// Same as [`Self::clip_by`], but with an explicit integer engine. + fn clip_by_as(&self, source: &R, fill_rule: FillRule, clip_rule: ClipRule) -> Paths

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Clips paths according to the specified build and clip rules using a fixed float-to-integer scale. /// - `resource`: A clipping shape. /// `ShapeResource` can be one of the following: @@ -45,6 +73,17 @@ where scale: P::Scalar, ) -> Result, FixedScaleOverlayError>; + /// Same as [`Self::clip_by_fixed_scale`], but with an explicit integer engine. + fn clip_by_fixed_scale_as( + &self, + source: &R, + fill_rule: FillRule, + clip_rule: ClipRule, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Clips paths according to the specified build and clip rules. /// - `resource`: A clipping shape. /// `ShapeResource` can be one of the following: @@ -65,6 +104,17 @@ where solver: Solver, ) -> Paths

; + /// Same as [`Self::clip_by_with_solver`], but with an explicit integer engine. + fn clip_by_with_solver_as( + &self, + source: &R, + fill_rule: FillRule, + clip_rule: ClipRule, + solver: Solver, + ) -> Paths

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Clips paths according to the specified build and clip rules using a fixed float-to-integer scale. /// - `resource`: A clipping shape. /// `ShapeResource` can be one of the following: @@ -86,9 +136,22 @@ where solver: Solver, scale: P::Scalar, ) -> Result, FixedScaleOverlayError>; + + /// Same as [`Self::clip_by_fixed_scale_with_solver`], but with an explicit integer engine. + fn clip_by_fixed_scale_with_solver_as( + &self, + source: &R, + fill_rule: FillRule, + clip_rule: ClipRule, + solver: Solver, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey; } #[cfg(test)] +#[allow(clippy::items_after_test_module)] mod tests { use crate::core::fill_rule::FillRule; use crate::float::clip::FloatClip; @@ -110,6 +173,12 @@ mod tests { assert_eq!(paths.len(), 1); assert_eq!(paths[0], [[0.0, 1.0], [2.0, 1.0]]); + + let paths = string + .clip_by_fixed_scale_as::(&shape, FillRule::EvenOdd, clip_rule, 10.0) + .unwrap(); + + assert_eq!(paths.len(), 1); } #[test] @@ -155,6 +224,14 @@ where self.clip_by_with_solver(resource, fill_rule, clip_rule, Default::default()) } + #[inline] + fn clip_by_as(&self, resource: &R0, fill_rule: FillRule, clip_rule: ClipRule) -> Paths

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + self.clip_by_with_solver_as::(resource, fill_rule, clip_rule, Default::default()) + } + #[inline] fn clip_by_with_solver( &self, @@ -163,7 +240,22 @@ where clip_rule: ClipRule, solver: Solver, ) -> Paths

{ - FloatStringOverlay::with_shape_and_string(resource, self) + FloatStringOverlay::

::with_shape_and_string(resource, self) + .clip_string_lines_with_solver(fill_rule, clip_rule, solver) + } + + #[inline] + fn clip_by_with_solver_as( + &self, + resource: &R0, + fill_rule: FillRule, + clip_rule: ClipRule, + solver: Solver, + ) -> Paths

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatStringOverlay::::from_shape_and_string(resource, self) .clip_string_lines_with_solver(fill_rule, clip_rule, solver) } @@ -178,6 +270,26 @@ where self.clip_by_fixed_scale_with_solver(resource, fill_rule, clip_rule, Default::default(), scale) } + #[inline] + fn clip_by_fixed_scale_as( + &self, + resource: &R0, + fill_rule: FillRule, + clip_rule: ClipRule, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + self.clip_by_fixed_scale_with_solver_as::( + resource, + fill_rule, + clip_rule, + Default::default(), + scale, + ) + } + #[inline] fn clip_by_fixed_scale_with_solver( &self, @@ -188,7 +300,25 @@ where scale: P::Scalar, ) -> Result, FixedScaleOverlayError> { Ok( - FloatStringOverlay::with_shape_and_string_fixed_scale(resource, self, scale)? + FloatStringOverlay::

::with_shape_and_string_fixed_scale(resource, self, scale)? + .clip_string_lines_with_solver(fill_rule, clip_rule, solver), + ) + } + + #[inline] + fn clip_by_fixed_scale_with_solver_as( + &self, + resource: &R0, + fill_rule: FillRule, + clip_rule: ClipRule, + solver: Solver, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok( + FloatStringOverlay::::from_shape_and_string_fixed_scale(resource, self, scale)? .clip_string_lines_with_solver(fill_rule, clip_rule, solver), ) } diff --git a/iOverlay/src/float/graph.rs b/iOverlay/src/float/graph.rs index a0209dbc..caa987a5 100644 --- a/iOverlay/src/float/graph.rs +++ b/iOverlay/src/float/graph.rs @@ -7,23 +7,34 @@ use crate::core::graph::OverlayGraph; use crate::core::overlay_rule::OverlayRule; use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::float::adapter::ShapesToFloat; use i_shape::float::despike::DeSpikeContour; use i_shape::float::simple::SimplifyContour; +use i_tree::{Expiration, LayoutNumber}; /// The `FloatOverlayGraph` struct represents an overlay graph with floating point precision, /// providing methods to extract geometric shapes from the graph after applying boolean operations. /// [More information](https://ishape-rust.github.io/iShape-js/overlay/overlay_graph/overlay_graph.html) about Overlay Graph. -pub struct FloatOverlayGraph<'a, P: FloatPointCompatible> { - pub graph: OverlayGraph<'a>, - pub adapter: FloatPointAdapter

, +pub struct FloatOverlayGraph<'a, P: FloatPointCompatible, I: IntNumber = i32> { + pub graph: OverlayGraph<'a, I>, + pub adapter: FloatPointAdapter, clean_result: bool, } -impl<'a, P: FloatPointCompatible> FloatOverlayGraph<'a, P> { +impl<'a, P, I> FloatOverlayGraph<'a, P, I> +where + P: FloatPointCompatible, + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - pub(crate) fn new(graph: OverlayGraph<'a>, adapter: FloatPointAdapter

, clean_result: bool) -> Self { + pub(crate) fn new( + graph: OverlayGraph<'a, I>, + adapter: FloatPointAdapter, + clean_result: bool, + ) -> Self { Self { graph, adapter, @@ -52,7 +63,7 @@ impl<'a, P: FloatPointCompatible> FloatOverlayGraph<'a, P> { pub fn extract_shapes( &self, overlay_rule: OverlayRule, - buffer: &mut BooleanExtractionBuffer, + buffer: &mut BooleanExtractionBuffer, ) -> Shapes

{ let shapes = self.graph.extract_shapes(overlay_rule, buffer); let mut float = shapes.to_float(&self.adapter); diff --git a/iOverlay/src/float/overlay.rs b/iOverlay/src/float/overlay.rs index cf383e6c..52b48d21 100644 --- a/iOverlay/src/float/overlay.rs +++ b/iOverlay/src/float/overlay.rs @@ -8,19 +8,30 @@ use crate::core::overlay_rule::OverlayRule; use crate::core::solver::Solver; use crate::float::graph::FloatOverlayGraph; use crate::i_shape::source::resource::ShapeResource; +use core::marker::PhantomData; use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; use i_float::float::rect::FloatRect; +use i_float::int::number::int::IntNumber; +use i_float::int::number::uint::UIntNumber; +use i_float::int::number::wide_int::WideIntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::flat::buffer::FlatContoursBuffer; use i_shape::flat::float::FloatFlatContoursBuffer; use i_shape::float::adapter::ShapesToFloat; use i_shape::float::despike::DeSpikeContour; use i_shape::float::simple::SimplifyContour; +use i_tree::{Expiration, LayoutNumber}; +/// Options for float overlay extraction. +/// +/// `F` is the floating-point scalar type (`f32` or `f64`). `I` is the integer engine +/// (`i16`, `i32`, or `i64`) used internally for float-to-integer conversion and +/// precision limits. The default integer engine is `i32`. #[derive(Debug, Clone, Copy)] -pub struct OverlayOptions { +pub struct OverlayOptions { /// Preserve collinear points in the input before Boolean operations. pub preserve_input_collinear: bool, @@ -31,7 +42,7 @@ pub struct OverlayOptions { pub preserve_output_collinear: bool, /// Minimum area threshold to include a contour in the result. - pub min_output_area: T, + pub min_output_area: F, /// If true, extract OGC-valid shapes. pub ogc: bool, @@ -39,16 +50,22 @@ pub struct OverlayOptions { /// If true, the result will be cleaned from precision-related issues /// such as duplicate or nearly identical points. Especially useful for `f32` coordinates. pub clean_result: bool, + + phantom_data: PhantomData, } /// This struct is essential for describing and uploading the geometry or shapes required to construct an `FloatOverlay`. It prepares the necessary data for boolean operations. -pub struct FloatOverlay { - pub(super) overlay: Overlay, +pub struct FloatOverlay { + pub(super) overlay: Overlay, pub(super) clean_result: bool, - pub(super) adapter: FloatPointAdapter

, + pub(super) adapter: FloatPointAdapter, } -impl FloatOverlay

{ +impl FloatOverlay +where + P: FloatPointCompatible, + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Constructs a new `FloatOverlay`, a builder for overlaying geometric shapes /// by converting float-based geometry to integer space, using a pre-configured adapter. /// @@ -57,7 +74,7 @@ impl FloatOverlay

{ /// - `capacity`: Initial capacity for storing segments, ideally matching the total number of /// segments for efficient memory allocation. #[inline] - pub fn with_adapter(adapter: FloatPointAdapter

, capacity: usize) -> Self { + pub fn with_adapter(adapter: FloatPointAdapter, capacity: usize) -> Self { Self::new_custom(adapter, Default::default(), Default::default(), capacity) } @@ -72,8 +89,8 @@ impl FloatOverlay

{ /// segments for efficient memory allocation. #[inline] pub fn new_custom( - adapter: FloatPointAdapter

, - options: OverlayOptions, + adapter: FloatPointAdapter, + options: OverlayOptions, solver: Solver, capacity: usize, ) -> Self { @@ -93,7 +110,7 @@ impl FloatOverlay

{ /// - `capacity`: Initial capacity for storing segments, ideally matching the total number of /// segments for efficient memory allocation. #[inline] - pub fn new_empty(options: OverlayOptions, solver: Solver, capacity: usize) -> Self { + pub fn new_empty(options: OverlayOptions, solver: Solver, capacity: usize) -> Self { let clean_result = options.clean_result; let adapter = FloatPointAdapter::new(FloatRect::zero()); let overlay = Overlay::new_custom(capacity, options.int_default(), solver); @@ -111,7 +128,23 @@ impl FloatOverlay

{ /// - `Contour`: A contour representing a closed path. This path is interpreted as closed, so it doesn’t require the start and endpoint to be the same for processing. /// - `Contours`: A collection of contours, each representing a closed path. /// - `Shapes`: A collection of shapes, where each shape may consist of multiple contours. - pub fn with_subj_and_clip(subj: &R0, clip: &R1) -> Self + /// + /// # Example + /// + /// ``` + /// use i_overlay::core::fill_rule::FillRule; + /// use i_overlay::core::overlay_rule::OverlayRule; + /// use i_overlay::float::overlay::FloatOverlay; + /// + /// let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]]; + /// let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]]; + /// + /// let mut overlay = FloatOverlay::<[f64; 2], i64>::from_subj_and_clip(&subj, &clip); + /// let result = overlay.overlay(OverlayRule::Difference, FillRule::EvenOdd); + /// + /// assert_eq!(result.len(), 1); + /// ``` + pub fn from_subj_and_clip(subj: &R0, clip: &R1) -> Self where R0: ShapeResource

+ ?Sized, R1: ShapeResource

+ ?Sized, @@ -135,10 +168,10 @@ impl FloatOverlay

{ /// - `Shapes`: A collection of shapes, where each shape may consist of multiple contours. /// - `options`: Adjust custom behavior. /// - `solver`: Type of solver to use. - pub fn with_subj_and_clip_custom( + pub fn from_subj_and_clip_custom( subj: &R0, clip: &R1, - options: OverlayOptions, + options: OverlayOptions, solver: Solver, ) -> Self where @@ -161,7 +194,7 @@ impl FloatOverlay

{ /// - `Contour`: A contour representing a closed path. This path is interpreted as closed, so it doesn’t require the start and endpoint to be the same for processing. /// - `Contours`: A collection of contours, each representing a closed path. /// - `Shapes`: A collection of shapes, where each shape may consist of multiple contours. - pub fn with_subj(subj: &R) -> Self + pub fn from_subj(subj: &R) -> Self where R: ShapeResource

+ ?Sized, { @@ -180,7 +213,7 @@ impl FloatOverlay

{ /// - `Shapes`: A collection of shapes, where each shape may consist of multiple contours. /// - `options`: Adjust custom behavior. /// - `solver`: Type of solver to use. - pub fn with_subj_custom(subj: &R, options: OverlayOptions, solver: Solver) -> Self + pub fn from_subj_custom(subj: &R, options: OverlayOptions, solver: Solver) -> Self where R: ShapeResource

+ ?Sized, { @@ -272,7 +305,7 @@ impl FloatOverlay

{ /// Convert into `FloatOverlayGraph` from the added paths or shapes using the specified build rule. This graph is the foundation for executing boolean operations, allowing for the analysis and manipulation of the geometric data. The `OverlayGraph` created by this method represents a preprocessed state of the input shapes, optimized for the application of boolean operations based on the provided build rule. /// - `fill_rule`: Specifies the rule for determining filled areas within the shapes, influencing how the resulting graph represents intersections and unions. #[inline] - pub fn build_graph_view(&mut self, fill_rule: FillRule) -> Option> { + pub fn build_graph_view(&mut self, fill_rule: FillRule) -> Option> { let graph = self.overlay.build_graph_view(fill_rule)?; Some(FloatOverlayGraph::new( graph, @@ -352,7 +385,7 @@ impl FloatOverlay

{ output: &mut FloatFlatContoursBuffer

, ) { let preserve_output_collinear = self.overlay.options.preserve_output_collinear; - let mut int_output = FlatContoursBuffer::default(); + let mut int_output = FlatContoursBuffer::::with_capacity(0); self.overlay .overlay_into(overlay_rule, fill_rule, &mut int_output); let iter = int_output.points.iter().map(|p| self.adapter.int_to_float(p)); @@ -368,48 +401,96 @@ impl FloatOverlay

{ } } -impl Default for OverlayOptions { +impl FloatOverlay

{ + /// Creates a new `FloatOverlay` instance and initializes it with subject and clip shapes. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_and_clip(subj: &R0, clip: &R1) -> Self + where + R0: ShapeResource

+ ?Sized, + R1: ShapeResource

+ ?Sized, + { + Self::from_subj_and_clip(subj, clip) + } + + /// Creates a new `FloatOverlay` instance and initializes it with subject and clip shapes. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_and_clip_custom( + subj: &R0, + clip: &R1, + options: OverlayOptions, + solver: Solver, + ) -> Self + where + R0: ShapeResource

+ ?Sized, + R1: ShapeResource

+ ?Sized, + { + Self::from_subj_and_clip_custom(subj, clip, options, solver) + } + + /// Creates a new `FloatOverlay` instance and initializes it with subject. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj(subj: &R) -> Self + where + R: ShapeResource

+ ?Sized, + { + Self::from_subj(subj) + } + + /// Creates a new `FloatOverlay` instance and initializes it with subject. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_custom(subj: &R, options: OverlayOptions, solver: Solver) -> Self + where + R: ShapeResource

+ ?Sized, + { + Self::from_subj_custom(subj, options, solver) + } +} + +impl Default for OverlayOptions { fn default() -> Self { - // f32 precision is not enough to cover i32 - let clean_result = T::bit_width() <= 32; + let clean_result = F::BITS <= I::BITS; Self { preserve_input_collinear: false, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: false, - min_output_area: T::from_float(0.0), + min_output_area: F::from_float(0.0), ogc: false, clean_result, + phantom_data: Default::default(), } } } -impl OverlayOptions { +impl OverlayOptions { pub(crate) fn int_with_adapter>( &self, - adapter: &FloatPointAdapter

, - ) -> IntOverlayOptions { + adapter: &FloatPointAdapter, + ) -> IntOverlayOptions { IntOverlayOptions { preserve_input_collinear: self.preserve_input_collinear, output_direction: self.output_direction, preserve_output_collinear: self.preserve_output_collinear, - min_output_area: adapter.sqr_float_to_int(self.min_output_area), + min_output_area: adapter.round_sqr_len_to_int(self.min_output_area).to_uint(), ogc: self.ogc, } } - pub(crate) fn int_default(&self) -> IntOverlayOptions { + pub(crate) fn int_default(&self) -> IntOverlayOptions { IntOverlayOptions { preserve_input_collinear: self.preserve_input_collinear, output_direction: self.output_direction, preserve_output_collinear: self.preserve_output_collinear, - min_output_area: 0, + min_output_area: I::WideUInt::ZERO, ogc: self.ogc, } } pub fn ogc() -> Self { - // f32 precision is not enough to cover i32 - let clean_result = T::bit_width() <= 32; + let clean_result = T::BITS <= I::BITS; Self { preserve_input_collinear: false, output_direction: ContourDirection::CounterClockwise, @@ -417,6 +498,7 @@ impl OverlayOptions { min_output_area: T::from_float(0.0), ogc: true, clean_result, + phantom_data: Default::default(), } } } @@ -425,7 +507,7 @@ impl OverlayOptions { mod tests { use crate::core::fill_rule::FillRule; use crate::core::overlay_rule::OverlayRule; - use crate::float::overlay::FloatOverlay; + use crate::float::overlay::{FloatOverlay, OverlayOptions}; use alloc::vec; #[test] @@ -454,6 +536,28 @@ mod tests { assert_eq!(shapes[0][0].len(), 4); } + #[test] + fn test_custom_int_engines() { + let left_rect = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]]; + let right_rect = [[1.0, 0.0], [1.0, 1.0], [2.0, 1.0], [2.0, 0.0]]; + + let low = FloatOverlay::<[f64; 2], i16>::from_subj_and_clip(&left_rect, &right_rect) + .overlay(OverlayRule::Union, FillRule::EvenOdd); + let high = FloatOverlay::<[f64; 2], i64>::from_subj_and_clip(&left_rect, &right_rect) + .overlay(OverlayRule::Union, FillRule::EvenOdd); + + assert_eq!(low[0][0].len(), 4); + assert_eq!(high[0][0].len(), 4); + } + + #[test] + fn test_options_clean_result_depends_on_int_bits() { + assert!(!OverlayOptions::::default().clean_result); + assert!(OverlayOptions::::default().clean_result); + assert!(!OverlayOptions::::default().clean_result); + assert!(OverlayOptions::::default().clean_result); + } + #[test] fn test_contour_slice() { let left_rect = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]]; diff --git a/iOverlay/src/float/relate.rs b/iOverlay/src/float/relate.rs index 1e360f30..7ac97a9f 100644 --- a/iOverlay/src/float/relate.rs +++ b/iOverlay/src/float/relate.rs @@ -4,7 +4,10 @@ use crate::core::relate::PredicateOverlay; use crate::core::solver::Solver; use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::source::resource::ShapeResource; +use i_tree::{Expiration, LayoutNumber}; /// Float-coordinate wrapper for spatial predicate evaluation. /// @@ -26,12 +29,16 @@ use i_shape::source::resource::ShapeResource; /// /// For a more ergonomic API, see the [`FloatRelate`] trait which provides /// methods directly on shape types. -pub struct FloatPredicateOverlay { - pub(crate) overlay: PredicateOverlay, - pub(crate) adapter: FloatPointAdapter

, +pub struct FloatPredicateOverlay { + pub(crate) overlay: PredicateOverlay, + pub(crate) adapter: FloatPointAdapter, } -impl FloatPredicateOverlay

{ +impl FloatPredicateOverlay +where + P: FloatPointCompatible, + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Creates a new predicate overlay with a pre-configured adapter. /// /// Use this when you need fixed-scale precision via `FloatPointAdapter::with_scale()`. @@ -40,7 +47,7 @@ impl FloatPredicateOverlay

{ /// * `adapter` - A `FloatPointAdapter` instance for coordinate conversion. /// * `capacity` - Initial capacity for storing segments. #[inline] - pub fn with_adapter(adapter: FloatPointAdapter

, capacity: usize) -> Self { + pub fn with_adapter(adapter: FloatPointAdapter, capacity: usize) -> Self { Self { overlay: PredicateOverlay::new(capacity), adapter, @@ -58,7 +65,7 @@ impl FloatPredicateOverlay

{ /// * `capacity` - Initial capacity for storing segments. #[inline] pub fn with_adapter_custom( - adapter: FloatPointAdapter

, + adapter: FloatPointAdapter, fill_rule: FillRule, solver: Solver, capacity: usize, @@ -70,13 +77,13 @@ impl FloatPredicateOverlay

{ } /// Creates a new predicate overlay from subject and clip shapes. - pub fn with_subj_and_clip(subj: &R0, clip: &R1) -> Self + pub fn from_subj_and_clip(subj: &R0, clip: &R1) -> Self where R0: ShapeResource

+ ?Sized, R1: ShapeResource

+ ?Sized, { let iter = subj.iter_paths().chain(clip.iter_paths()).flatten(); - let adapter = FloatPointAdapter::with_iter(iter); + let adapter = FloatPointAdapter::<_, I>::with_iter(iter); let subj_capacity = subj.iter_paths().fold(0, |s, c| s + c.len()); let clip_capacity = clip.iter_paths().fold(0, |s, c| s + c.len()); @@ -90,7 +97,7 @@ impl FloatPredicateOverlay

{ } /// Creates a new predicate overlay with custom solver and fill rule. - pub fn with_subj_and_clip_custom( + pub fn from_subj_and_clip_custom( subj: &R0, clip: &R1, fill_rule: FillRule, @@ -101,7 +108,7 @@ impl FloatPredicateOverlay

{ R1: ShapeResource

+ ?Sized, { let iter = subj.iter_paths().chain(clip.iter_paths()).flatten(); - let adapter = FloatPointAdapter::with_iter(iter); + let adapter = FloatPointAdapter::<_, I>::with_iter(iter); let subj_capacity = subj.iter_paths().fold(0, |s, c| s + c.len()); let clip_capacity = clip.iter_paths().fold(0, |s, c| s + c.len()); @@ -167,12 +174,44 @@ impl FloatPredicateOverlay

{ } } +impl FloatPredicateOverlay

{ + /// Creates a new predicate overlay from subject and clip shapes. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_and_clip(subj: &R0, clip: &R1) -> Self + where + R0: ShapeResource

+ ?Sized, + R1: ShapeResource

+ ?Sized, + { + Self::from_subj_and_clip(subj, clip) + } + + /// Creates a new predicate overlay with custom solver and fill rule. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_and_clip_custom( + subj: &R0, + clip: &R1, + fill_rule: FillRule, + solver: Solver, + ) -> Self + where + R0: ShapeResource

+ ?Sized, + R1: ShapeResource

+ ?Sized, + { + Self::from_subj_and_clip_custom(subj, clip, fill_rule, solver) + } +} + /// Ergonomic trait for spatial predicate operations on shape resources. /// /// This trait provides convenient methods for testing spatial relationships /// directly on contours, shapes, and shape collections without explicit /// overlay construction. /// +/// This convenience trait uses the default integer engine (`i32`). Use the `*_as::` methods +/// when you need to select `i16`, `i32`, or `i64` explicitly. +/// /// # Example /// /// ``` @@ -191,6 +230,9 @@ impl FloatPredicateOverlay

{ /// // Non-overlapping shapes (fast bounding-box rejection) /// assert!(!square.intersects(&distant)); /// assert!(square.disjoint(&distant)); +/// +/// // Select the integer engine explicitly. +/// assert!(square.intersects_as::(&other)); /// ``` /// /// # Supported Types @@ -214,35 +256,70 @@ where /// overlap and boundary contact (shapes sharing an edge). fn intersects(&self, other: &R1) -> bool; + /// Same as [`Self::intersects`], but with an explicit integer engine. + fn intersects_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if the interiors of this shape and another overlap. /// /// Unlike `intersects()`, this returns `false` for shapes that only share /// boundary points (edges or vertices) without interior overlap. fn interiors_intersect(&self, other: &R1) -> bool; + /// Same as [`Self::interiors_intersect`], but with an explicit integer engine. + fn interiors_intersect_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if this shape touches another (boundaries intersect but interiors don't). /// /// Returns `true` when shapes share boundary points but their interiors don't overlap. fn touches(&self, other: &R1) -> bool; + /// Same as [`Self::touches`], but with an explicit integer engine. + fn touches_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if this shape intersects another by point coincidence only. fn point_intersects(&self, other: &R1) -> bool; + /// Same as [`Self::point_intersects`], but with an explicit integer engine. + fn point_intersects_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if this shape is completely within another. /// /// Subject is within clip if everywhere the subject has fill, the clip /// also has fill on the same side. fn within(&self, other: &R1) -> bool; + /// Same as [`Self::within`], but with an explicit integer engine. + fn within_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if this shape does not intersect with another (no shared points). /// /// This is the negation of `intersects()`. fn disjoint(&self, other: &R1) -> bool; + /// Same as [`Self::disjoint`], but with an explicit integer engine. + fn disjoint_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if this shape completely covers another. /// /// `covers(A, B)` is equivalent to `within(B, A)`. fn covers(&self, other: &R1) -> bool; + + /// Same as [`Self::covers`], but with an explicit integer engine. + fn covers_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey; } impl FloatRelate for R0 @@ -253,27 +330,67 @@ where { #[inline] fn intersects(&self, other: &R1) -> bool { - FloatPredicateOverlay::with_subj_and_clip(self, other).intersects() + FloatPredicateOverlay::

::with_subj_and_clip(self, other).intersects() + } + + #[inline] + fn intersects_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatPredicateOverlay::::from_subj_and_clip(self, other).intersects() } #[inline] fn interiors_intersect(&self, other: &R1) -> bool { - FloatPredicateOverlay::with_subj_and_clip(self, other).interiors_intersect() + FloatPredicateOverlay::

::with_subj_and_clip(self, other).interiors_intersect() + } + + #[inline] + fn interiors_intersect_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatPredicateOverlay::::from_subj_and_clip(self, other).interiors_intersect() } #[inline] fn touches(&self, other: &R1) -> bool { - FloatPredicateOverlay::with_subj_and_clip(self, other).touches() + FloatPredicateOverlay::

::with_subj_and_clip(self, other).touches() + } + + #[inline] + fn touches_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatPredicateOverlay::::from_subj_and_clip(self, other).touches() } #[inline] fn point_intersects(&self, other: &R1) -> bool { - FloatPredicateOverlay::with_subj_and_clip(self, other).point_intersects() + FloatPredicateOverlay::

::with_subj_and_clip(self, other).point_intersects() + } + + #[inline] + fn point_intersects_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatPredicateOverlay::::from_subj_and_clip(self, other).point_intersects() } #[inline] fn within(&self, other: &R1) -> bool { - FloatPredicateOverlay::with_subj_and_clip(self, other).within() + FloatPredicateOverlay::

::with_subj_and_clip(self, other).within() + } + + #[inline] + fn within_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatPredicateOverlay::::from_subj_and_clip(self, other).within() } #[inline] @@ -281,10 +398,26 @@ where !self.intersects(other) } + #[inline] + fn disjoint_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + !self.intersects_as::(other) + } + #[inline] fn covers(&self, other: &R1) -> bool { other.within(self) } + + #[inline] + fn covers_as(&self, other: &R1) -> bool + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + other.within_as::(self) + } } #[cfg(test)] @@ -300,6 +433,7 @@ mod tests { assert!(square.intersects(&other)); assert!(other.intersects(&square)); + assert!(square.intersects_as::(&other)); } #[test] @@ -537,7 +671,7 @@ mod tests { let other = vec![[5.0, 5.0], [5.0, 15.0], [15.0, 15.0], [15.0, 5.0]]; let iter = square.iter().chain(other.iter()); - let adapter = FloatPointAdapter::with_iter(iter); + let adapter = FloatPointAdapter::<_, i32>::with_iter(iter); let mut overlay = FloatPredicateOverlay::with_adapter(adapter, 16); overlay.add_source(&square, ShapeType::Subject); @@ -556,7 +690,7 @@ mod tests { let other = vec![[5.0, 5.0], [5.0, 15.0], [15.0, 15.0], [15.0, 5.0]]; let iter = square.iter().chain(other.iter()); - let adapter = FloatPointAdapter::with_iter(iter); + let adapter = FloatPointAdapter::<_, i32>::with_iter(iter); let mut overlay = FloatPredicateOverlay::with_adapter_custom(adapter, FillRule::NonZero, Solver::default(), 16); diff --git a/iOverlay/src/float/scale.rs b/iOverlay/src/float/scale.rs index e2012e49..6e567e80 100644 --- a/iOverlay/src/float/scale.rs +++ b/iOverlay/src/float/scale.rs @@ -4,11 +4,14 @@ use crate::core::overlay_rule::OverlayRule; use crate::core::solver::Solver; use crate::float::overlay::{FloatOverlay, OverlayOptions}; use crate::float::relate::FloatPredicateOverlay; -use i_float::adapter::FloatPointAdapter; +use i_float::adapter::{FloatPointAdapter, FloatPointAdapterScaleError}; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::source::resource::ShapeResource; +use i_tree::{Expiration, LayoutNumber}; #[derive(Debug, Clone, Copy)] pub enum FixedScaleOverlayError { @@ -34,12 +37,46 @@ impl FixedScaleOverlayError { } } +impl From for FixedScaleOverlayError { + #[inline] + fn from(error: FloatPointAdapterScaleError) -> Self { + match error { + FloatPointAdapterScaleError::ScaleTooLarge => Self::ScaleTooLarge, + FloatPointAdapterScaleError::ScaleNonPositive => Self::ScaleNonPositive, + FloatPointAdapterScaleError::ScaleNotFinite => Self::ScaleNotFinite, + } + } +} + /// Trait `FixedScaleFloatOverlay` provides methods for overlay operations between various geometric entities. /// This trait supports boolean operations on contours, shapes, and collections of shapes, using customizable overlay and build rules. /// /// The `scale` parameter defines the float-to-integer conversion: /// `x_int = (x_float - offset_x) * scale`. /// Larger `scale` gives higher precision but must fit within the safe integer bounds. +/// +/// This convenience trait uses the default integer engine (`i32`). Use the `*_as::` methods +/// when you need to select `i16`, `i32`, or `i64` explicitly. +/// +/// # Example +/// +/// ``` +/// use i_overlay::core::fill_rule::FillRule; +/// use i_overlay::core::overlay_rule::OverlayRule; +/// use i_overlay::float::scale::FixedScaleFloatOverlay; +/// +/// let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]]; +/// let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]]; +/// +/// let result = subj.overlay_with_fixed_scale_as::( +/// &clip, +/// OverlayRule::Difference, +/// FillRule::EvenOdd, +/// 1000.0, +/// ); +/// +/// assert!(result.is_ok()); +/// ``` pub trait FixedScaleFloatOverlay where R0: ShapeResource

, @@ -64,6 +101,17 @@ where fill_rule: FillRule, scale: P::Scalar, ) -> Result, FixedScaleOverlayError>; + + /// Same as [`Self::overlay_with_fixed_scale`], but with an explicit integer engine. + fn overlay_with_fixed_scale_as( + &self, + source: &R1, + overlay_rule: OverlayRule, + fill_rule: FillRule, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey; } impl FixedScaleFloatOverlay for R0 @@ -80,12 +128,35 @@ where fill_rule: FillRule, scale: P::Scalar, ) -> Result, FixedScaleOverlayError> { - Ok(FloatOverlay::with_subj_and_clip_fixed_scale(self, source, scale)? - .overlay(overlay_rule, fill_rule)) + Ok( + FloatOverlay::

::with_subj_and_clip_fixed_scale(self, source, scale)? + .overlay(overlay_rule, fill_rule), + ) + } + + #[inline] + fn overlay_with_fixed_scale_as( + &self, + source: &R1, + overlay_rule: OverlayRule, + fill_rule: FillRule, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok( + FloatOverlay::::from_subj_and_clip_fixed_scale(self, source, scale)? + .overlay(overlay_rule, fill_rule), + ) } } -impl FloatOverlay

{ +impl FloatOverlay +where + P: FloatPointCompatible, + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Creates a new `FloatOverlay` instance and initializes it with subject and clip shapes. /// /// This variant uses a fixed float-to-integer scale instead of auto-scaling. @@ -98,7 +169,7 @@ impl FloatOverlay

{ /// - `Contour`: A contour representing a closed path. This path is interpreted as closed, so it doesn’t require the start and endpoint to be the same for processing. /// - `Contours`: A collection of contours, each representing a closed path. /// - `Shapes`: A collection of shapes, where each shape may consist of multiple contours. - pub fn with_subj_and_clip_fixed_scale( + pub fn from_subj_and_clip_fixed_scale( subj: &R0, clip: &R1, scale: P::Scalar, @@ -107,16 +178,8 @@ impl FloatOverlay

{ R0: ShapeResource

+ ?Sized, R1: ShapeResource

+ ?Sized, { - let s = FixedScaleOverlayError::validate_scale(scale)?; - let iter = subj.iter_paths().chain(clip.iter_paths()).flatten(); - let mut adapter = FloatPointAdapter::with_iter(iter); - if adapter.dir_scale < scale { - return Err(FixedScaleOverlayError::ScaleTooLarge); - } - - adapter.dir_scale = scale; - adapter.inv_scale = P::Scalar::from_float(1.0 / s); + let adapter = FloatPointAdapter::with_iter_and_scale_checked(iter, scale)?; let subj_capacity = subj.iter_paths().fold(0, |s, c| s + c.len()); let clip_capacity = clip.iter_paths().fold(0, |s, c| s + c.len()); @@ -140,10 +203,10 @@ impl FloatOverlay

{ /// - `Shapes`: A collection of shapes, where each shape may consist of multiple contours. /// - `options`: Adjust custom behavior. /// - `solver`: Type of solver to use. - pub fn with_subj_and_clip_fixed_scale_custom( + pub fn from_subj_and_clip_fixed_scale_custom( subj: &R0, clip: &R1, - options: OverlayOptions, + options: OverlayOptions, solver: Solver, scale: P::Scalar, ) -> Result @@ -151,16 +214,8 @@ impl FloatOverlay

{ R0: ShapeResource

+ ?Sized, R1: ShapeResource

+ ?Sized, { - let s = FixedScaleOverlayError::validate_scale(scale)?; - let iter = subj.iter_paths().chain(clip.iter_paths()).flatten(); - let mut adapter = FloatPointAdapter::with_iter(iter); - if adapter.dir_scale < scale { - return Err(FixedScaleOverlayError::ScaleTooLarge); - } - - adapter.dir_scale = scale; - adapter.inv_scale = P::Scalar::from_float(1.0 / s); + let adapter = FloatPointAdapter::with_iter_and_scale_checked(iter, scale)?; let subj_capacity = subj.iter_paths().fold(0, |s, c| s + c.len()); let clip_capacity = clip.iter_paths().fold(0, |s, c| s + c.len()); @@ -173,7 +228,45 @@ impl FloatOverlay

{ } } -impl FloatPredicateOverlay

{ +impl FloatOverlay

{ + /// Creates a new `FloatOverlay` instance with a fixed float-to-integer scale. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_and_clip_fixed_scale( + subj: &R0, + clip: &R1, + scale: P::Scalar, + ) -> Result + where + R0: ShapeResource

+ ?Sized, + R1: ShapeResource

+ ?Sized, + { + FloatOverlay::::from_subj_and_clip_fixed_scale(subj, clip, scale) + } + + /// Creates a new `FloatOverlay` instance with a fixed float-to-integer scale. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_and_clip_fixed_scale_custom( + subj: &R0, + clip: &R1, + options: OverlayOptions, + solver: Solver, + scale: P::Scalar, + ) -> Result + where + R0: ShapeResource

+ ?Sized, + R1: ShapeResource

+ ?Sized, + { + FloatOverlay::::from_subj_and_clip_fixed_scale_custom(subj, clip, options, solver, scale) + } +} + +impl FloatPredicateOverlay +where + P: FloatPointCompatible, + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Creates a new predicate overlay with subject and clip shapes using fixed-scale precision. /// /// This variant uses a fixed float-to-integer scale instead of auto-scaling. @@ -185,7 +278,7 @@ impl FloatPredicateOverlay

{ /// * `subj` - A `ShapeResource` defining the subject geometry. /// * `clip` - A `ShapeResource` defining the clip geometry. /// * `scale` - Fixed float-to-integer scale factor. - pub fn with_subj_and_clip_fixed_scale( + pub fn from_subj_and_clip_fixed_scale( subj: &R0, clip: &R1, scale: P::Scalar, @@ -194,16 +287,8 @@ impl FloatPredicateOverlay

{ R0: ShapeResource

+ ?Sized, R1: ShapeResource

+ ?Sized, { - let s = FixedScaleOverlayError::validate_scale(scale)?; - let iter = subj.iter_paths().chain(clip.iter_paths()).flatten(); - let mut adapter = FloatPointAdapter::with_iter(iter); - if adapter.dir_scale < scale { - return Err(FixedScaleOverlayError::ScaleTooLarge); - } - - adapter.dir_scale = scale; - adapter.inv_scale = P::Scalar::from_float(1.0 / s); + let adapter = FloatPointAdapter::with_iter_and_scale_checked(iter, scale)?; let subj_capacity = subj.iter_paths().fold(0, |s, c| s + c.len()); let clip_capacity = clip.iter_paths().fold(0, |s, c| s + c.len()); @@ -223,7 +308,7 @@ impl FloatPredicateOverlay

{ /// * `fill_rule` - Fill rule to determine filled areas. /// * `solver` - Type of solver to use. /// * `scale` - Fixed float-to-integer scale factor. - pub fn with_subj_and_clip_fixed_scale_custom( + pub fn from_subj_and_clip_fixed_scale_custom( subj: &R0, clip: &R1, fill_rule: FillRule, @@ -234,16 +319,8 @@ impl FloatPredicateOverlay

{ R0: ShapeResource

+ ?Sized, R1: ShapeResource

+ ?Sized, { - let s = FixedScaleOverlayError::validate_scale(scale)?; - let iter = subj.iter_paths().chain(clip.iter_paths()).flatten(); - let mut adapter = FloatPointAdapter::with_iter(iter); - if adapter.dir_scale < scale { - return Err(FixedScaleOverlayError::ScaleTooLarge); - } - - adapter.dir_scale = scale; - adapter.inv_scale = P::Scalar::from_float(1.0 / s); + let adapter = FloatPointAdapter::with_iter_and_scale_checked(iter, scale)?; let subj_capacity = subj.iter_paths().fold(0, |s, c| s + c.len()); let clip_capacity = clip.iter_paths().fold(0, |s, c| s + c.len()); @@ -255,12 +332,51 @@ impl FloatPredicateOverlay

{ } } +impl FloatPredicateOverlay

{ + /// Creates a new predicate overlay with subject and clip shapes using fixed-scale precision. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_and_clip_fixed_scale( + subj: &R0, + clip: &R1, + scale: P::Scalar, + ) -> Result + where + R0: ShapeResource

+ ?Sized, + R1: ShapeResource

+ ?Sized, + { + FloatPredicateOverlay::::from_subj_and_clip_fixed_scale(subj, clip, scale) + } + + /// Creates a new predicate overlay with subject and clip shapes using fixed-scale precision + /// and custom fill rule and solver. Uses the default integer engine (`i32`). + #[inline] + pub fn with_subj_and_clip_fixed_scale_custom( + subj: &R0, + clip: &R1, + fill_rule: FillRule, + solver: Solver, + scale: P::Scalar, + ) -> Result + where + R0: ShapeResource

+ ?Sized, + R1: ShapeResource

+ ?Sized, + { + FloatPredicateOverlay::::from_subj_and_clip_fixed_scale_custom( + subj, clip, fill_rule, solver, scale, + ) + } +} + /// Trait for spatial predicate operations with fixed-scale precision. /// /// This trait provides methods for testing spatial relationships using a fixed /// float-to-integer scale, which is useful when you need consistent precision /// across multiple operations or when working with known coordinate bounds. /// +/// This convenience trait uses the default integer engine (`i32`). Use the `*_as::` methods +/// when you need to select `i16`, `i32`, or `i64` explicitly. +/// /// # Example /// /// ``` @@ -272,6 +388,10 @@ impl FloatPredicateOverlay

{ /// // Use fixed scale of 1000.0 for consistent precision /// let result = square.intersects_with_fixed_scale(&other, 1000.0); /// assert!(result.unwrap()); +/// +/// // Select the integer engine explicitly. +/// let result = square.intersects_with_fixed_scale_as::(&other, 1000.0); +/// assert!(result.unwrap()); /// ``` pub trait FixedScaleFloatRelate where @@ -285,6 +405,15 @@ where scale: P::Scalar, ) -> Result; + /// Same as [`Self::intersects_with_fixed_scale`], but with an explicit integer engine. + fn intersects_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if interiors of shapes overlap, using fixed-scale precision. fn interiors_intersect_with_fixed_scale( &self, @@ -292,18 +421,63 @@ where scale: P::Scalar, ) -> Result; + /// Same as [`Self::interiors_intersect_with_fixed_scale`], but with an explicit integer engine. + fn interiors_intersect_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if shapes touch (boundaries intersect but interiors don't), using fixed-scale precision. fn touches_with_fixed_scale(&self, other: &R1, scale: P::Scalar) -> Result; + /// Same as [`Self::touches_with_fixed_scale`], but with an explicit integer engine. + fn touches_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if this shape is completely within another, using fixed-scale precision. fn within_with_fixed_scale(&self, other: &R1, scale: P::Scalar) -> Result; + /// Same as [`Self::within_with_fixed_scale`], but with an explicit integer engine. + fn within_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if shapes do not intersect, using fixed-scale precision. fn disjoint_with_fixed_scale(&self, other: &R1, scale: P::Scalar) -> Result; + /// Same as [`Self::disjoint_with_fixed_scale`], but with an explicit integer engine. + fn disjoint_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Returns `true` if this shape completely covers another, using fixed-scale precision. fn covers_with_fixed_scale(&self, other: &R1, scale: P::Scalar) -> Result; + + /// Same as [`Self::covers_with_fixed_scale`], but with an explicit integer engine. + fn covers_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey; } impl FixedScaleFloatRelate for R0 @@ -318,7 +492,19 @@ where other: &R1, scale: P::Scalar, ) -> Result { - Ok(FloatPredicateOverlay::with_subj_and_clip_fixed_scale(self, other, scale)?.intersects()) + Ok(FloatPredicateOverlay::

::with_subj_and_clip_fixed_scale(self, other, scale)?.intersects()) + } + + #[inline] + fn intersects_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok(FloatPredicateOverlay::::from_subj_and_clip_fixed_scale(self, other, scale)?.intersects()) } #[inline] @@ -327,17 +513,59 @@ where other: &R1, scale: P::Scalar, ) -> Result { - Ok(FloatPredicateOverlay::with_subj_and_clip_fixed_scale(self, other, scale)?.interiors_intersect()) + Ok( + FloatPredicateOverlay::

::with_subj_and_clip_fixed_scale(self, other, scale)? + .interiors_intersect(), + ) + } + + #[inline] + fn interiors_intersect_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok( + FloatPredicateOverlay::::from_subj_and_clip_fixed_scale(self, other, scale)? + .interiors_intersect(), + ) } #[inline] fn touches_with_fixed_scale(&self, other: &R1, scale: P::Scalar) -> Result { - Ok(FloatPredicateOverlay::with_subj_and_clip_fixed_scale(self, other, scale)?.touches()) + Ok(FloatPredicateOverlay::

::with_subj_and_clip_fixed_scale(self, other, scale)?.touches()) + } + + #[inline] + fn touches_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok(FloatPredicateOverlay::::from_subj_and_clip_fixed_scale(self, other, scale)?.touches()) } #[inline] fn within_with_fixed_scale(&self, other: &R1, scale: P::Scalar) -> Result { - Ok(FloatPredicateOverlay::with_subj_and_clip_fixed_scale(self, other, scale)?.within()) + Ok(FloatPredicateOverlay::

::with_subj_and_clip_fixed_scale(self, other, scale)?.within()) + } + + #[inline] + fn within_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok(FloatPredicateOverlay::::from_subj_and_clip_fixed_scale(self, other, scale)?.within()) } #[inline] @@ -346,12 +574,36 @@ where other: &R1, scale: P::Scalar, ) -> Result { - Ok(!FloatPredicateOverlay::with_subj_and_clip_fixed_scale(self, other, scale)?.intersects()) + Ok(!FloatPredicateOverlay::

::with_subj_and_clip_fixed_scale(self, other, scale)?.intersects()) + } + + #[inline] + fn disjoint_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok(!FloatPredicateOverlay::::from_subj_and_clip_fixed_scale(self, other, scale)?.intersects()) } #[inline] fn covers_with_fixed_scale(&self, other: &R1, scale: P::Scalar) -> Result { - Ok(FloatPredicateOverlay::with_subj_and_clip_fixed_scale(other, self, scale)?.within()) + Ok(FloatPredicateOverlay::

::with_subj_and_clip_fixed_scale(other, self, scale)?.within()) + } + + #[inline] + fn covers_with_fixed_scale_as( + &self, + other: &R1, + scale: P::Scalar, + ) -> Result + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok(FloatPredicateOverlay::::from_subj_and_clip_fixed_scale(other, self, scale)?.within()) } } @@ -376,6 +628,12 @@ mod tests { assert_eq!(shapes.len(), 1); assert_eq!(shapes[0].len(), 1); assert_eq!(shapes[0][0].len(), 4); + + let shapes = left_rect + .overlay_with_fixed_scale_as::(&right_rect, OverlayRule::Union, FillRule::EvenOdd, 10.0) + .unwrap(); + + assert_eq!(shapes.len(), 1); } #[test] @@ -427,7 +685,7 @@ mod tests { let result = FloatOverlay::with_subj_and_clip_fixed_scale(&shapes, &right_bottom_rect, scale); - assert!(!result.is_ok()); + assert!(result.is_err()); } #[test] diff --git a/iOverlay/src/float/simplify.rs b/iOverlay/src/float/simplify.rs index 74053bcc..82ec47bb 100644 --- a/iOverlay/src/float/simplify.rs +++ b/iOverlay/src/float/simplify.rs @@ -3,12 +3,31 @@ use crate::core::overlay_rule::OverlayRule; use crate::core::solver::Solver; use crate::float::overlay::{FloatOverlay, OverlayOptions}; use i_float::float::compatible::FloatPointCompatible; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::source::resource::ShapeResource; +use i_tree::{Expiration, LayoutNumber}; /// Trait `Simplify` provides a method to simplify geometric shapes by reducing the number of points in contours or shapes /// while preserving overall shape and topology. The method applies a minimum area threshold and a build rule to /// determine which areas should be retained or excluded. +/// +/// This convenience trait uses the default integer engine (`i32`). Use the `*_as::` methods +/// when you need to select `i16`, `i32`, or `i64` explicitly. +/// +/// # Example +/// +/// ``` +/// use i_overlay::core::fill_rule::FillRule; +/// use i_overlay::float::simplify::SimplifyShape; +/// +/// let shape = vec![[0.0, 0.0], [0.0, 0.5], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]]; +/// +/// let result = shape.simplify_shape_as::(FillRule::NonZero); +/// +/// assert_eq!(result.len(), 1); +/// ``` pub trait SimplifyShape { /// Simplifies the shape or collection of points, contours, or shapes, based on a specified minimum area threshold. /// @@ -17,6 +36,11 @@ pub trait SimplifyShape { /// Note: Outer boundary paths have a **main_direction** order, and holes have an opposite to **main_direction** order. fn simplify_shape(&self, fill_rule: FillRule) -> Shapes

; + /// Same as [`Self::simplify_shape`], but with an explicit integer engine. + fn simplify_shape_as(&self, fill_rule: FillRule) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Simplifies the shape or collection of points, contours, or shapes, based on a specified minimum area threshold. /// - `options`: Adjust custom behavior. /// - `solver`: Type of solver to use. @@ -29,6 +53,16 @@ pub trait SimplifyShape { options: OverlayOptions, solver: Solver, ) -> Shapes

; + + /// Same as [`Self::simplify_shape_custom`], but with an explicit integer engine. + fn simplify_shape_custom_as( + &self, + fill_rule: FillRule, + options: OverlayOptions, + solver: Solver, + ) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey; } impl SimplifyShape

for S @@ -38,7 +72,16 @@ where { #[inline] fn simplify_shape(&self, fill_rule: FillRule) -> Shapes

{ - FloatOverlay::with_subj_custom(self, Default::default(), Default::default()) + FloatOverlay::

::with_subj_custom(self, Default::default(), Default::default()) + .overlay(OverlayRule::Subject, fill_rule) + } + + #[inline] + fn simplify_shape_as(&self, fill_rule: FillRule) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatOverlay::::from_subj_custom(self, Default::default(), Default::default()) .overlay(OverlayRule::Subject, fill_rule) } @@ -49,7 +92,20 @@ where options: OverlayOptions, solver: Solver, ) -> Shapes

{ - FloatOverlay::with_subj_custom(self, options, solver).overlay(OverlayRule::Subject, fill_rule) + FloatOverlay::

::with_subj_custom(self, options, solver).overlay(OverlayRule::Subject, fill_rule) + } + + #[inline] + fn simplify_shape_custom_as( + &self, + fill_rule: FillRule, + options: OverlayOptions, + solver: Solver, + ) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatOverlay::::from_subj_custom(self, options, solver).overlay(OverlayRule::Subject, fill_rule) } } @@ -70,6 +126,17 @@ mod tests { assert_eq!(shapes[0][0].len(), 4); } + #[test] + fn test_contour_slice_as() { + let rect = [[0.0, 0.0], [0.0, 0.5], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]]; + + let shapes = rect.as_slice().simplify_shape_as::(FillRule::NonZero); + + assert_eq!(shapes.len(), 1); + assert_eq!(shapes[0].len(), 1); + assert_eq!(shapes[0][0].len(), 4); + } + #[test] fn test_contour_vec() { let rect = vec![[0.0, 0.0], [0.0, 0.5], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]]; diff --git a/iOverlay/src/float/single.rs b/iOverlay/src/float/single.rs index 19d7197f..567b2d5d 100644 --- a/iOverlay/src/float/single.rs +++ b/iOverlay/src/float/single.rs @@ -2,11 +2,32 @@ use crate::core::fill_rule::FillRule; use crate::core::overlay_rule::OverlayRule; use crate::float::overlay::FloatOverlay; use i_float::float::compatible::FloatPointCompatible; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::source::resource::ShapeResource; +use i_tree::{Expiration, LayoutNumber}; /// Trait `SingleFloatOverlay` provides methods for overlay operations between various geometric entities. /// This trait supports boolean operations on contours, shapes, and collections of shapes, using customizable overlay and build rules. +/// +/// This convenience trait uses the default integer engine (`i32`). Use the `*_as::` methods +/// when you need to select `i16`, `i32`, or `i64` explicitly. +/// +/// # Example +/// +/// ``` +/// use i_overlay::core::fill_rule::FillRule; +/// use i_overlay::core::overlay_rule::OverlayRule; +/// use i_overlay::float::single::SingleFloatOverlay; +/// +/// let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]]; +/// let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]]; +/// +/// let result = subj.overlay_as::(&clip, OverlayRule::Difference, FillRule::EvenOdd); +/// +/// assert_eq!(result.len(), 1); +/// ``` pub trait SingleFloatOverlay where R0: ShapeResource

, @@ -24,6 +45,11 @@ where /// - `fill_rule`: Fill rule to determine filled areas (non-zero, even-odd, positive, negative). /// - Returns: A vector of `Shapes

` representing the cleaned-up geometric result. fn overlay(&self, source: &R1, overlay_rule: OverlayRule, fill_rule: FillRule) -> Shapes

; + + /// Same as [`Self::overlay`], but with an explicit integer engine. + fn overlay_as(&self, source: &R1, overlay_rule: OverlayRule, fill_rule: FillRule) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey; } impl SingleFloatOverlay for R0 @@ -34,7 +60,15 @@ where { #[inline] fn overlay(&self, resource: &R1, overlay_rule: OverlayRule, fill_rule: FillRule) -> Shapes

{ - FloatOverlay::with_subj_and_clip(self, resource).overlay(overlay_rule, fill_rule) + FloatOverlay::

::with_subj_and_clip(self, resource).overlay(overlay_rule, fill_rule) + } + + #[inline] + fn overlay_as(&self, resource: &R1, overlay_rule: OverlayRule, fill_rule: FillRule) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatOverlay::::from_subj_and_clip(self, resource).overlay(overlay_rule, fill_rule) } } @@ -58,6 +92,18 @@ mod tests { assert_eq!(shapes[0][0].len(), 4); } + #[test] + fn test_contour_as() { + let left_rect = vec![[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]]; + let right_rect = vec![[1.0, 0.0], [1.0, 1.0], [2.0, 1.0], [2.0, 0.0]]; + + let shapes = left_rect.overlay_as::(&right_rect, OverlayRule::Union, FillRule::EvenOdd); + + assert_eq!(shapes.len(), 1); + assert_eq!(shapes[0].len(), 1); + assert_eq!(shapes[0][0].len(), 4); + } + #[test] fn test_contours() { let r3 = vec![ diff --git a/iOverlay/src/float/slice.rs b/iOverlay/src/float/slice.rs index d434de7c..24e7cb2d 100644 --- a/iOverlay/src/float/slice.rs +++ b/iOverlay/src/float/slice.rs @@ -5,11 +5,31 @@ use crate::float::scale::FixedScaleOverlayError; use crate::float::string_overlay::FloatStringOverlay; use crate::string::rule::StringRule; use i_float::float::compatible::FloatPointCompatible; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::source::resource::ShapeResource; +use i_tree::{Expiration, LayoutNumber}; /// The `FloatSlice` trait provides methods to slice geometric shapes using a given path or set of paths, /// allowing for boolean operations based on the specified build rule. +/// +/// This convenience trait uses the default integer engine (`i32`). Use the `*_as::` methods +/// when you need to select `i16`, `i32`, or `i64` explicitly. +/// +/// # Example +/// +/// ``` +/// use i_overlay::core::fill_rule::FillRule; +/// use i_overlay::float::slice::FloatSlice; +/// +/// let shape = vec![[0.0, 0.0], [0.0, 2.0], [2.0, 2.0], [2.0, 0.0]]; +/// let line = vec![[-1.0, 1.0], [3.0, 1.0]]; +/// +/// let result = shape.slice_by_as::(&line, FillRule::EvenOdd); +/// +/// assert_eq!(result.len(), 2); +/// ``` pub trait FloatSlice where R: ShapeResource

, @@ -29,6 +49,11 @@ where /// Note: Outer boundary paths have a counterclockwise order, and holes have a clockwise order. fn slice_by(&self, resource: &R, fill_rule: FillRule) -> Shapes

; + /// Same as [`Self::slice_by`], but with an explicit integer engine. + fn slice_by_as(&self, resource: &R, fill_rule: FillRule) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Slices the current shapes by string lines with a fixed float-to-integer scale. /// /// - `resource`: A string lines. @@ -49,6 +74,16 @@ where scale: P::Scalar, ) -> Result, FixedScaleOverlayError>; + /// Same as [`Self::slice_by_fixed_scale`], but with an explicit integer engine. + fn slice_by_fixed_scale_as( + &self, + resource: &R, + fill_rule: FillRule, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Slices the current shapes by string lines. /// /// - `resource`: A string lines. @@ -70,6 +105,17 @@ where solver: Solver, ) -> Shapes

; + /// Same as [`Self::slice_custom_by`], but with an explicit integer engine. + fn slice_custom_by_as( + &self, + resource: &R, + fill_rule: FillRule, + options: OverlayOptions, + solver: Solver, + ) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey; + /// Slices the current shapes by string lines with a fixed float-to-integer scale. /// /// - `resource`: A string lines. @@ -92,6 +138,18 @@ where solver: Solver, scale: P::Scalar, ) -> Result, FixedScaleOverlayError>; + + /// Same as [`Self::slice_custom_by_fixed_scale`], but with an explicit integer engine. + fn slice_custom_by_fixed_scale_as( + &self, + resource: &R, + fill_rule: FillRule, + options: OverlayOptions, + solver: Solver, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey; } impl FloatSlice for R1 @@ -102,7 +160,18 @@ where { #[inline] fn slice_by(&self, resource: &R0, fill_rule: FillRule) -> Shapes

{ - FloatStringOverlay::with_shape_and_string(self, resource) + FloatStringOverlay::

::with_shape_and_string(self, resource) + .build_graph_view(fill_rule) + .map(|graph| graph.extract_shapes(StringRule::Slice)) + .unwrap_or_default() + } + + #[inline] + fn slice_by_as(&self, resource: &R0, fill_rule: FillRule) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatStringOverlay::::from_shape_and_string(self, resource) .build_graph_view(fill_rule) .map(|graph| graph.extract_shapes(StringRule::Slice)) .unwrap_or_default() @@ -116,7 +185,25 @@ where scale: P::Scalar, ) -> Result, FixedScaleOverlayError> { Ok( - FloatStringOverlay::with_shape_and_string_fixed_scale(self, resource, scale)? + FloatStringOverlay::

::with_shape_and_string_fixed_scale(self, resource, scale)? + .build_graph_view(fill_rule) + .map(|graph| graph.extract_shapes(StringRule::Slice)) + .unwrap_or_default(), + ) + } + + #[inline] + fn slice_by_fixed_scale_as( + &self, + resource: &R0, + fill_rule: FillRule, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok( + FloatStringOverlay::::from_shape_and_string_fixed_scale(self, resource, scale)? .build_graph_view(fill_rule) .map(|graph| graph.extract_shapes(StringRule::Slice)) .unwrap_or_default(), @@ -131,7 +218,24 @@ where options: OverlayOptions, solver: Solver, ) -> Shapes

{ - FloatStringOverlay::with_shape_and_string(self, resource) + FloatStringOverlay::

::with_shape_and_string(self, resource) + .build_graph_view_with_solver(fill_rule, solver) + .map(|graph| graph.extract_shapes_custom(StringRule::Slice, options)) + .unwrap_or_default() + } + + #[inline] + fn slice_custom_by_as( + &self, + resource: &R0, + fill_rule: FillRule, + options: OverlayOptions, + solver: Solver, + ) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + FloatStringOverlay::::from_shape_and_string(self, resource) .build_graph_view_with_solver(fill_rule, solver) .map(|graph| graph.extract_shapes_custom(StringRule::Slice, options)) .unwrap_or_default() @@ -147,7 +251,27 @@ where scale: P::Scalar, ) -> Result, FixedScaleOverlayError> { Ok( - FloatStringOverlay::with_shape_and_string_fixed_scale(self, resource, scale)? + FloatStringOverlay::

::with_shape_and_string_fixed_scale(self, resource, scale)? + .build_graph_view_with_solver(fill_rule, solver) + .map(|graph| graph.extract_shapes_custom(StringRule::Slice, options)) + .unwrap_or_default(), + ) + } + + #[inline] + fn slice_custom_by_fixed_scale_as( + &self, + resource: &R0, + fill_rule: FillRule, + options: OverlayOptions, + solver: Solver, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey, + { + Ok( + FloatStringOverlay::::from_shape_and_string_fixed_scale(self, resource, scale)? .build_graph_view_with_solver(fill_rule, solver) .map(|graph| graph.extract_shapes_custom(StringRule::Slice, options)) .unwrap_or_default(), @@ -198,6 +322,12 @@ mod tests { assert_eq!(shapes[1].len(), 1); assert_eq!(shapes[0][0].len(), 4); assert_eq!(shapes[1][0].len(), 4); + + let shapes = shape + .slice_by_fixed_scale_as::(&string, FillRule::EvenOdd, 10.0) + .unwrap(); + + assert_eq!(shapes.len(), 2); } #[test] diff --git a/iOverlay/src/float/string_graph.rs b/iOverlay/src/float/string_graph.rs index 592a6bb2..3abc86a8 100644 --- a/iOverlay/src/float/string_graph.rs +++ b/iOverlay/src/float/string_graph.rs @@ -3,19 +3,26 @@ use crate::string::graph::StringGraph; use crate::string::rule::StringRule; use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::float::adapter::ShapesToFloat; use i_shape::float::despike::DeSpikeContour; use i_shape::float::simple::SimplifyContour; +use i_tree::{Expiration, LayoutNumber}; /// The `FloatStringGraph` struct represents a graph structure with floating-point precision, /// providing methods to extract geometric shapes from the graph after applying string-based operations. -pub struct FloatStringGraph<'a, P: FloatPointCompatible> { - pub graph: StringGraph<'a>, - pub adapter: FloatPointAdapter

, +pub struct FloatStringGraph<'a, P: FloatPointCompatible, I: IntNumber = i32> { + pub graph: StringGraph<'a, I>, + pub adapter: FloatPointAdapter, } -impl FloatStringGraph<'_, P> { +impl FloatStringGraph<'_, P, I> +where + P: FloatPointCompatible, + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Extracts shapes from the overlay graph based on the specified string rule. /// This method is used to retrieve the final geometric shapes after boolean operations have been applied. /// It's suitable for most use cases where the minimum area of shapes is not a concern. @@ -59,7 +66,7 @@ impl FloatStringGraph<'_, P> { pub fn extract_shapes_custom( &self, string_rule: StringRule, - options: OverlayOptions, + options: OverlayOptions, ) -> Shapes

{ let shapes = self .graph diff --git a/iOverlay/src/float/string_overlay.rs b/iOverlay/src/float/string_overlay.rs index 5fc42d28..f6217514 100644 --- a/iOverlay/src/float/string_overlay.rs +++ b/iOverlay/src/float/string_overlay.rs @@ -6,10 +6,12 @@ use crate::string::clip::ClipRule; use crate::string::overlay::StringOverlay; use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; -use i_float::float::number::FloatNumber; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Paths; use i_shape::float::adapter::ShapeToFloat; use i_shape::source::resource::ShapeResource; +use i_tree::{Expiration, LayoutNumber}; /// The `FloatStringOverlay` struct is a builder for overlaying geometric shapes by converting /// floating-point geometry to integer space. It provides methods for adding paths and shapes, @@ -17,12 +19,16 @@ use i_shape::source::resource::ShapeResource; /// /// The float-to-integer conversion is controlled by the `FloatPointAdapter` scale: /// `x_int = (x_float - offset_x) * scale`. Use a fixed scale if you need predictable precision. -pub struct FloatStringOverlay { - pub(super) overlay: StringOverlay, - pub(super) adapter: FloatPointAdapter

, +pub struct FloatStringOverlay { + pub(super) overlay: StringOverlay, + pub(super) adapter: FloatPointAdapter, } -impl FloatStringOverlay

{ +impl FloatStringOverlay +where + P: FloatPointCompatible, + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Constructs a new `FloatStringOverlay`, a builder for overlaying geometric shapes /// by converting float-based geometry to integer space, using a pre-configured adapter. /// @@ -33,7 +39,7 @@ impl FloatStringOverlay

{ /// - `capacity`: Initial capacity for storing segments, ideally matching the total number of /// segments for efficient memory allocation. #[inline] - pub fn with_adapter(adapter: FloatPointAdapter

, capacity: usize) -> Self { + pub fn with_adapter(adapter: FloatPointAdapter, capacity: usize) -> Self { Self { overlay: StringOverlay::new(capacity), adapter, @@ -53,7 +59,7 @@ impl FloatStringOverlay

{ /// - `Path`: A path representing a string line. /// - `Paths`: A collection of paths, each representing a string line. /// - `Vec`: A collection of grouped paths, where each group may consist of multiple paths. - pub fn with_shape_and_string(shape: &R0, string: &R1) -> Self + pub fn from_shape_and_string(shape: &R0, string: &R1) -> Self where R0: ShapeResource

, R1: ShapeResource

, @@ -72,7 +78,7 @@ impl FloatStringOverlay

{ /// /// This variant validates that the requested scale is finite, positive, and fits the /// input bounds. Use `scale = 1.0 / grid_size` if you want a grid-size style parameter. - pub fn with_shape_and_string_fixed_scale( + pub fn from_shape_and_string_fixed_scale( shape: &R0, string: &R1, scale: P::Scalar, @@ -81,16 +87,8 @@ impl FloatStringOverlay

{ R0: ShapeResource

, R1: ShapeResource

, { - let s = FixedScaleOverlayError::validate_scale(scale)?; - let iter = shape.iter_paths().chain(string.iter_paths()).flatten(); - let mut adapter = FloatPointAdapter::with_iter(iter); - if adapter.dir_scale < scale { - return Err(FixedScaleOverlayError::ScaleTooLarge); - } - - adapter.dir_scale = scale; - adapter.inv_scale = P::Scalar::from_float(1.0 / s); + let adapter = FloatPointAdapter::with_iter_and_scale_checked(iter, scale)?; let shape_capacity = shape.iter_paths().fold(0, |s, c| s + c.len()); let string_capacity = string.iter_paths().fold(0, |s, c| s + c.len()); @@ -158,7 +156,7 @@ impl FloatStringOverlay

{ /// - `fill_rule`: Fill rule to determine filled areas (non-zero, even-odd, positive, negative). /// - Returns: A `FloatStringGraph` containing the graph representation of the overlay's geometry. #[inline] - pub fn build_graph_view(&mut self, fill_rule: FillRule) -> Option> { + pub fn build_graph_view(&mut self, fill_rule: FillRule) -> Option> { self.build_graph_view_with_solver(fill_rule, Default::default()) } @@ -172,7 +170,7 @@ impl FloatStringOverlay

{ &mut self, fill_rule: FillRule, solver: Solver, - ) -> Option> { + ) -> Option> { let graph = self.overlay.build_graph_view_with_solver(fill_rule, solver)?; Some(FloatStringGraph { graph, @@ -201,6 +199,34 @@ impl FloatStringOverlay

{ } } +impl FloatStringOverlay

{ + /// Creates a new `FloatStringOverlay` instance with shape and string paths. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_shape_and_string(shape: &R0, string: &R1) -> Self + where + R0: ShapeResource

, + R1: ShapeResource

, + { + Self::from_shape_and_string(shape, string) + } + + /// Creates a new `FloatStringOverlay` instance with a fixed float-to-integer scale. + /// Uses the default integer engine (`i32`). + #[inline] + pub fn with_shape_and_string_fixed_scale( + shape: &R0, + string: &R1, + scale: P::Scalar, + ) -> Result + where + R0: ShapeResource

, + R1: ShapeResource

, + { + Self::from_shape_and_string_fixed_scale(shape, string, scale) + } +} + #[cfg(test)] mod tests { use crate::float::string_overlay::FloatStringOverlay; diff --git a/iOverlay/src/geom/end.rs b/iOverlay/src/geom/end.rs index 9b72fe3c..6abf2da7 100644 --- a/iOverlay/src/geom/end.rs +++ b/iOverlay/src/geom/end.rs @@ -1,12 +1,13 @@ +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; #[derive(Clone, Copy)] -pub(crate) struct End { +pub(crate) struct End { pub(crate) index: usize, - pub(crate) point: IntPoint, + pub(crate) point: IntPoint, } -impl Default for End { +impl Default for End { #[inline(always)] fn default() -> Self { Self { diff --git a/iOverlay/src/geom/id_point.rs b/iOverlay/src/geom/id_point.rs index d9a82f1f..d2f99883 100644 --- a/iOverlay/src/geom/id_point.rs +++ b/iOverlay/src/geom/id_point.rs @@ -1,13 +1,14 @@ +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(crate) struct IdPoint { +#[derive(Debug, PartialEq, Clone, Copy)] +pub(crate) struct IdPoint { pub(crate) id: usize, - pub(crate) point: IntPoint, + pub(crate) point: IntPoint, } -impl IdPoint { - pub(crate) fn new(id: usize, point: IntPoint) -> Self { +impl IdPoint { + pub(crate) fn new(id: usize, point: IntPoint) -> Self { Self { id, point } } } diff --git a/iOverlay/src/geom/line_range.rs b/iOverlay/src/geom/line_range.rs index 1a0bc40d..f6bb7588 100644 --- a/iOverlay/src/geom/line_range.rs +++ b/iOverlay/src/geom/line_range.rs @@ -1,5 +1,7 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct LineRange { - pub(crate) min: i32, - pub(crate) max: i32, +use i_float::int::number::int::IntNumber; + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct LineRange { + pub(crate) min: I, + pub(crate) max: I, } diff --git a/iOverlay/src/geom/v_segment.rs b/iOverlay/src/geom/v_segment.rs index 3ea0e6d2..ee3b2450 100644 --- a/iOverlay/src/geom/v_segment.rs +++ b/iOverlay/src/geom/v_segment.rs @@ -1,39 +1,41 @@ use crate::geom::x_segment::XSegment; use core::cmp::Ordering; +use i_float::int::number::int::IntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; use i_float::triangle::Triangle; -use i_tree::ExpiredKey; +use i_tree::{Expiration, ExpiredKey}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct VSegment { - pub(crate) a: IntPoint, - pub(crate) b: IntPoint, +pub(crate) struct VSegment { + pub(crate) a: IntPoint, + pub(crate) b: IntPoint, } -impl VSegment { +impl VSegment { #[inline(always)] - fn is_under_segment_order(&self, other: &VSegment) -> Ordering { + fn is_under_segment_order(&self, other: &VSegment) -> Ordering { match self.a.cmp(&other.a) { - Ordering::Less => Triangle::clock_order_point(self.a, other.a, self.b), - Ordering::Equal => Triangle::clock_order_point(self.a, other.b, self.b), - Ordering::Greater => Triangle::clock_order_point(other.a, other.b, self.a), + Ordering::Less => Triangle::clock_order(self.a, other.a, self.b), + Ordering::Equal => Triangle::clock_order(self.a, other.b, self.b), + Ordering::Greater => Triangle::clock_order(other.a, other.b, self.a), } } #[inline(always)] - pub(crate) fn is_under_point_order(&self, p: IntPoint) -> Ordering { + pub(crate) fn is_under_point_order(&self, p: IntPoint) -> Ordering { debug_assert!(self.a.x <= p.x && p.x <= self.b.x); debug_assert!(p != self.a && p != self.b); - Triangle::clock_order_point(self.a, p, self.b) + Triangle::clock_order(self.a, p, self.b) } #[inline(always)] - pub(crate) fn is_under_segment(&self, other: &VSegment) -> bool { + pub(crate) fn is_under_segment(&self, other: &VSegment) -> bool { match self.a.cmp(&other.a) { - Ordering::Less => Triangle::is_clockwise_point(self.a, other.a, self.b), - Ordering::Equal => Triangle::is_clockwise_point(self.a, other.b, self.b), - Ordering::Greater => Triangle::is_clockwise_point(other.a, other.b, self.a), + Ordering::Less => Triangle::is_clockwise(self.a, other.a, self.b), + Ordering::Equal => Triangle::is_clockwise(self.a, other.b, self.b), + Ordering::Greater => Triangle::is_clockwise(other.a, other.b, self.a), } } @@ -41,37 +43,37 @@ impl VSegment { pub(crate) fn cmp_by_angle(&self, other: &Self) -> Ordering { // sort angles counterclockwise // debug_assert!(self.a == other.a); - let v0 = self.b.subtract(self.a); - let v1 = other.b.subtract(other.a); + let v0 = self.b - self.a; + let v1 = other.b - other.a; let cross = v0.cross_product(v1); - 0.cmp(&cross) + I::Wide::ZERO.cmp(&cross) } } -impl From for VSegment { +impl From> for VSegment { #[inline(always)] - fn from(seg: XSegment) -> Self { + fn from(seg: XSegment) -> Self { VSegment { a: seg.a, b: seg.b } } } -impl PartialOrd for VSegment { +impl PartialOrd for VSegment { #[inline(always)] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for VSegment { +impl Ord for VSegment { #[inline(always)] fn cmp(&self, other: &Self) -> Ordering { self.is_under_segment_order(other) } } -impl ExpiredKey for VSegment { +impl ExpiredKey for VSegment { #[inline] - fn expiration(&self) -> i32 { + fn expiration(&self) -> I { self.b.x } } diff --git a/iOverlay/src/geom/x_segment.rs b/iOverlay/src/geom/x_segment.rs index cae600bd..ac615ecb 100644 --- a/iOverlay/src/geom/x_segment.rs +++ b/iOverlay/src/geom/x_segment.rs @@ -1,16 +1,17 @@ use crate::geom::line_range::LineRange; use core::cmp::Ordering; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct XSegment { - pub(crate) a: IntPoint, - pub(crate) b: IntPoint, +pub(crate) struct XSegment { + pub(crate) a: IntPoint, + pub(crate) b: IntPoint, } -impl XSegment { +impl XSegment { #[inline(always)] - pub(crate) fn y_range(&self) -> LineRange { + pub(crate) fn y_range(&self) -> LineRange { if self.a.y < self.b.y { LineRange { min: self.a.y, @@ -30,19 +31,19 @@ impl XSegment { } #[inline(always)] - pub(crate) fn is_not_intersect_y_range(&self, range: &LineRange) -> bool { + pub(crate) fn is_not_intersect_y_range(&self, range: &LineRange) -> bool { range.min > self.a.y && range.min > self.b.y || range.max < self.a.y && range.max < self.b.y } } -impl PartialOrd for XSegment { +impl PartialOrd for XSegment { #[inline(always)] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for XSegment { +impl Ord for XSegment { #[inline(always)] fn cmp(&self, other: &Self) -> Ordering { let a = self.a.cmp(&other.a); diff --git a/iOverlay/src/lib.rs b/iOverlay/src/lib.rs index f7fd8ea5..dd7cc886 100644 --- a/iOverlay/src/lib.rs +++ b/iOverlay/src/lib.rs @@ -1,6 +1,6 @@ //! # iOverlay //! -//! The `iOverlay` library provides high-performance boolean operations on polygons, including union, intersection, difference, and xor. It is designed for applications that require precise polygon operations, such as computer graphics, CAD systems, and geographical information systems (GIS). By supporting both integer (i32) and floating-point (f32, f64) APIs, iOverlay offers flexibility and precision across diverse use cases. +//! The `iOverlay` library provides high-performance boolean operations on polygons, including union, intersection, difference, and xor. It is designed for applications that require precise polygon operations, such as computer graphics, CAD systems, and geographical information systems (GIS). By supporting integer (`i16`, `i32`, `i64`) and floating-point (`f32`, `f64`) APIs, iOverlay offers flexibility and precision across diverse use cases. //! //! ## Features //! - **Boolean Operations**: union, intersection, difference, and exclusion. @@ -8,7 +8,7 @@ //! - **Polygons**: with holes, self-intersections, and multiple contours. //! - **Simplification**: removes degenerate vertices and merges collinear edges. //! - **Fill Rules**: even-odd, non-zero, positive and negative. -//! - **Data Types**: Supports i32, f32, and f64 APIs. +//! - **Data Types**: Supports `i16`/`i32`/`i64` integer APIs and `f32`/`f64` floating-point APIs. //! //! ## Simple Example //! ![Simple Example](https://raw.githubusercontent.com/iShape-Rust/iOverlay/main/readme/example_union.svg) diff --git a/iOverlay/src/mesh/miter.rs b/iOverlay/src/mesh/miter.rs index 504d910f..55a2c51c 100644 --- a/iOverlay/src/mesh/miter.rs +++ b/iOverlay/src/mesh/miter.rs @@ -1,25 +1,26 @@ use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; pub(super) struct Miter; -pub(super) enum SharpMiter { +pub(super) enum SharpMiter { Degenerate, - AB(IntPoint, IntPoint), - AcB(IntPoint, IntPoint, IntPoint), + AB(IntPoint, IntPoint), + AcB(IntPoint, IntPoint, IntPoint), } impl Miter { #[inline] - pub(super) fn sharp( + pub(super) fn sharp( pa: P, pb: P, va: P, vb: P, - adapter: &FloatPointAdapter

, - ) -> SharpMiter { + adapter: &FloatPointAdapter, + ) -> SharpMiter { let ia = adapter.float_to_int(&pa); let ib = adapter.float_to_int(&pb); diff --git a/iOverlay/src/mesh/outline/builder.rs b/iOverlay/src/mesh/outline/builder.rs index d5c16c88..5262a812 100644 --- a/iOverlay/src/mesh/outline/builder.rs +++ b/iOverlay/src/mesh/outline/builder.rs @@ -13,34 +13,36 @@ use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; use i_float::float::vector::FloatPointMath; +use i_float::int::number::int::IntNumber; +use i_float::int::number::wide_int::WideIntNumber; -trait OutlineBuild { +trait OutlineBuild { fn build( &self, path: &[P], - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ); fn capacity(&self, points_count: usize) -> usize; fn additional_offset(&self, radius: P::Scalar) -> P::Scalar; } -pub(super) struct OutlineBuilder { - builder: Box>, +pub(super) struct OutlineBuilder { + builder: Box>, } -struct Builder, P: FloatPointCompatible> { +struct Builder, P: FloatPointCompatible, I: IntNumber> { extend: bool, radius: P::Scalar, join_builder: J, - _phantom: PhantomData

, + _phantom: PhantomData<(P, I)>, } -impl OutlineBuilder

{ - pub(super) fn new(radius: P::Scalar, join: &LineJoin) -> OutlineBuilder

{ +impl OutlineBuilder { + pub(super) fn new(radius: P::Scalar, join: &LineJoin) -> OutlineBuilder { let extend = radius > P::Scalar::from_float(0.0); - let builder: Box> = { + let builder: Box> = { match join { LineJoin::Miter(ratio) => Box::new(Builder { extend, @@ -70,8 +72,8 @@ impl OutlineBuilder

{ pub(super) fn build( &self, path: &[P], - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { self.builder.build(path, adapter, segments); } @@ -87,13 +89,13 @@ impl OutlineBuilder

{ } } -impl, P: FloatPointCompatible> OutlineBuild

for Builder { +impl, P: FloatPointCompatible, I: IntNumber> OutlineBuild for Builder { #[inline] fn build( &self, path: &[P], - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { if path.len() < 2 { return; @@ -113,12 +115,12 @@ impl, P: FloatPointCompatible> OutlineBuild

for Builder, P: FloatPointCompatible> Builder { +impl, P: FloatPointCompatible, I: IntNumber> Builder { fn build( &self, path: &[P], - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let iter = path.iter().map(|p| adapter.float_to_int(p)); let mut uniq_segments = if let Some(iter) = UniqueSegmentsIter::new(iter) { @@ -150,19 +152,20 @@ impl, P: FloatPointCompatible> Builder { #[inline] fn feed_join( &self, - s0: &OffsetSection

, - s1: &OffsetSection

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + s0: &OffsetSection, + s1: &OffsetSection, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let vi = s1.b - s1.a; let vp = s0.b - s0.a; let cross = vi.cross_product(vp); - let outer_corner = if cross != 0 { - (cross > 0) == self.extend + + let outer_corner = if cross != I::Wide::ZERO { + (cross > I::Wide::ZERO) == self.extend } else { - vi.dot_product(vp) < 0 + vi.dot_product(vp) < I::Wide::ZERO }; if outer_corner { @@ -177,9 +180,9 @@ impl, P: FloatPointCompatible> Builder { } } -impl OffsetSection

{ +impl OffsetSection { #[inline] - fn new(radius: P::Scalar, s: &UniqueSegment, adapter: &FloatPointAdapter

) -> Self { + fn new(radius: P::Scalar, s: &UniqueSegment, adapter: &FloatPointAdapter) -> Self { let a = adapter.int_to_float(&s.a); let b = adapter.int_to_float(&s.b); let ab = FloatPointMath::sub(&b, &a); @@ -213,3 +216,28 @@ impl VecPushSome for Vec { } } } + +#[cfg(test)] +mod tests { + use crate::mesh::outline::builder::OutlineBuilder; + use crate::mesh::style::LineJoin; + use crate::segm::boolean::ShapeCountBoolean; + use crate::segm::segment::Segment; + use alloc::vec::Vec; + use i_float::adapter::FloatPointAdapter; + use i_float::float::rect::FloatRect; + + #[test] + fn test_i64_builder() { + let path = [[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0]]; + let mut rect = FloatRect::with_iter(path.iter()).unwrap(); + rect.add_offset(2.0); + let adapter = FloatPointAdapter::<[f64; 2], i64>::new(rect); + let builder = OutlineBuilder::<[f64; 2], i64>::new(1.0, &LineJoin::Bevel); + + let mut segments = Vec::>::new(); + builder.build(&path, &adapter, &mut segments); + + assert!(!segments.is_empty()); + } +} diff --git a/iOverlay/src/mesh/outline/builder_join.rs b/iOverlay/src/mesh/outline/builder_join.rs index 5eabce4c..835ca768 100644 --- a/iOverlay/src/mesh/outline/builder_join.rs +++ b/iOverlay/src/mesh/outline/builder_join.rs @@ -9,14 +9,16 @@ use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; use i_float::float::vector::FloatPointMath; +use i_float::int::number::int::IntNumber; +use i_float::int::number::wide_int::WideIntNumber; -pub(super) trait JoinBuilder { +pub(super) trait JoinBuilder { fn add_join( &self, - s0: &OffsetSection

, - s1: &OffsetSection

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + s0: &OffsetSection, + s1: &OffsetSection, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ); fn capacity(&self) -> usize; fn additional_offset(&self, radius: P::Scalar) -> P::Scalar; @@ -26,25 +28,25 @@ pub(super) struct BevelJoinBuilder; impl BevelJoinBuilder { #[inline] - fn join( - s0: &OffsetSection

, - s1: &OffsetSection

, - _adapter: &FloatPointAdapter

, - segments: &mut Vec>, + fn join( + s0: &OffsetSection, + s1: &OffsetSection, + _adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { - debug_assert_ne!(s0.b_top, s1.a_top, "must be validated before"); + debug_assert!(s0.b_top != s1.a_top, "must be validated before"); segments.push(Segment::subject(s0.b_top, s1.a_top)); } } -impl JoinBuilder

for BevelJoinBuilder { +impl JoinBuilder for BevelJoinBuilder { #[inline] fn add_join( &self, - s0: &OffsetSection

, - s1: &OffsetSection

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + s0: &OffsetSection, + s1: &OffsetSection, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { Self::join(s0, s1, adapter, segments); } @@ -91,19 +93,19 @@ impl MiterJoinBuilder { } } -impl JoinBuilder

for MiterJoinBuilder { +impl JoinBuilder for MiterJoinBuilder { fn add_join( &self, - s0: &OffsetSection

, - s1: &OffsetSection

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + s0: &OffsetSection, + s1: &OffsetSection, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let ia = s0.b_top; let ib = s1.a_top; let sq_len = ia.sqr_distance(ib); - if sq_len < 4 { + if sq_len < I::Wide::from_usize(4) { BevelJoinBuilder::join(s0, s1, adapter, segments); return; } @@ -191,13 +193,13 @@ impl RoundJoinBuilder { } } } -impl JoinBuilder

for RoundJoinBuilder { +impl JoinBuilder for RoundJoinBuilder { fn add_join( &self, - s0: &OffsetSection

, - s1: &OffsetSection

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + s0: &OffsetSection, + s1: &OffsetSection, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let dot_product = FloatPointMath::dot_product(&s0.dir, &s1.dir); if self.limit_dot_product < dot_product { diff --git a/iOverlay/src/mesh/outline/offset.rs b/iOverlay/src/mesh/outline/offset.rs index 4937373d..41c27cf5 100644 --- a/iOverlay/src/mesh/outline/offset.rs +++ b/iOverlay/src/mesh/outline/offset.rs @@ -13,6 +13,10 @@ use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; use i_float::float::rect::FloatRect; +use i_float::int::number::int::IntNumber; +use i_float::int::number::uint::UIntNumber; +use i_float::int::number::wide_int::WideIntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::flat::buffer::FlatContoursBuffer; use i_shape::flat::float::FloatFlatContoursBuffer; @@ -21,7 +25,26 @@ use i_shape::float::despike::DeSpikeContour; use i_shape::float::int_area::IntArea; use i_shape::float::simple::SimplifyContour; use i_shape::source::resource::ShapeResource; - +use i_tree::{Expiration, LayoutNumber}; + +/// Trait for offsetting float contours and shapes. +/// +/// Default methods use the `i32` integer engine. Use the `*_as::` methods when you need to +/// select `i16`, `i32`, or `i64` explicitly. +/// +/// # Example +/// +/// ``` +/// use i_overlay::mesh::outline::offset::OutlineOffset; +/// use i_overlay::mesh::style::OutlineStyle; +/// +/// let path = [[0.0, 0.0], [10.0, 0.0], [0.0, 10.0]]; +/// let style = OutlineStyle::new(1.0); +/// +/// let result = path.outline_as::(&style); +/// +/// assert_eq!(result.len(), 1); +/// ``` pub trait OutlineOffset { /// Generates an outline shapes for contours, or shapes. /// @@ -119,6 +142,74 @@ pub trait OutlineOffset { scale: P::Scalar, output: &mut FloatFlatContoursBuffer

, ) -> Result<(), FixedScaleOverlayError>; + + /// Same as [`Self::outline`], but with an explicit integer engine. + fn outline_as(&self, style: &OutlineStyle) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::outline_into`], but with an explicit integer engine. + fn outline_into_as(&self, style: &OutlineStyle, output: &mut FloatFlatContoursBuffer

) + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::outline_custom`], but with an explicit integer engine. + fn outline_custom_as( + &self, + style: &OutlineStyle, + options: OverlayOptions, + ) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::outline_custom_into`], but with an explicit integer engine. + fn outline_custom_into_as( + &self, + style: &OutlineStyle, + options: OverlayOptions, + output: &mut FloatFlatContoursBuffer

, + ) where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::outline_fixed_scale`], but with an explicit integer engine. + fn outline_fixed_scale_as( + &self, + style: &OutlineStyle, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::outline_fixed_scale_into`], but with an explicit integer engine. + fn outline_fixed_scale_into_as( + &self, + style: &OutlineStyle, + scale: P::Scalar, + output: &mut FloatFlatContoursBuffer

, + ) -> Result<(), FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::outline_custom_fixed_scale`], but with an explicit integer engine. + fn outline_custom_fixed_scale_as( + &self, + style: &OutlineStyle, + options: OverlayOptions, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::outline_custom_fixed_scale_into`], but with an explicit integer engine. + fn outline_custom_fixed_scale_into_as( + &self, + style: &OutlineStyle, + options: OverlayOptions, + scale: P::Scalar, + output: &mut FloatFlatContoursBuffer

, + ) -> Result<(), FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; } impl OutlineOffset

for S @@ -139,7 +230,7 @@ where style: &OutlineStyle, options: OverlayOptions, ) -> Shapes

{ - match OutlineSolver::prepare(self, style) { + match OutlineSolver::::prepare(self, style) { Some(solver) => solver.build(self, options), None => vec![], } @@ -151,7 +242,7 @@ where options: OverlayOptions, output: &mut FloatFlatContoursBuffer

, ) { - match OutlineSolver::prepare(self, style) { + match OutlineSolver::::prepare(self, style) { Some(solver) => solver.build_into(self, options, output), None => output.clear_and_reserve(0, 0), } @@ -181,7 +272,7 @@ where scale: P::Scalar, ) -> Result, FixedScaleOverlayError> { let s = FixedScaleOverlayError::validate_scale(scale)?; - let mut solver = match OutlineSolver::prepare(self, style) { + let mut solver = match OutlineSolver::::prepare(self, style) { Some(solver) => solver, None => return Ok(vec![]), }; @@ -197,7 +288,113 @@ where output: &mut FloatFlatContoursBuffer

, ) -> Result<(), FixedScaleOverlayError> { let s = FixedScaleOverlayError::validate_scale(scale)?; - let mut solver = match OutlineSolver::prepare(self, style) { + let mut solver = match OutlineSolver::::prepare(self, style) { + Some(solver) => solver, + None => { + output.clear_and_reserve(0, 0); + return Ok(()); + } + }; + solver.apply_scale(s)?; + solver.build_into(self, options, output); + Ok(()) + } + + fn outline_as(&self, style: &OutlineStyle) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + self.outline_custom_as::(style, Default::default()) + } + + fn outline_into_as(&self, style: &OutlineStyle, output: &mut FloatFlatContoursBuffer

) + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + self.outline_custom_into_as::(style, Default::default(), output) + } + + fn outline_custom_as( + &self, + style: &OutlineStyle, + options: OverlayOptions, + ) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + match OutlineSolver::::prepare(self, style) { + Some(solver) => solver.build(self, options), + None => vec![], + } + } + + fn outline_custom_into_as( + &self, + style: &OutlineStyle, + options: OverlayOptions, + output: &mut FloatFlatContoursBuffer

, + ) where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + match OutlineSolver::::prepare(self, style) { + Some(solver) => solver.build_into(self, options, output), + None => output.clear_and_reserve(0, 0), + } + } + + fn outline_fixed_scale_as( + &self, + style: &OutlineStyle, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + self.outline_custom_fixed_scale_as::(style, Default::default(), scale) + } + + fn outline_fixed_scale_into_as( + &self, + style: &OutlineStyle, + scale: P::Scalar, + output: &mut FloatFlatContoursBuffer

, + ) -> Result<(), FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + self.outline_custom_fixed_scale_into_as::(style, Default::default(), scale, output) + } + + fn outline_custom_fixed_scale_as( + &self, + style: &OutlineStyle, + options: OverlayOptions, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + let s = FixedScaleOverlayError::validate_scale(scale)?; + let mut solver = match OutlineSolver::::prepare(self, style) { + Some(solver) => solver, + None => return Ok(vec![]), + }; + solver.apply_scale(s)?; + Ok(solver.build(self, options)) + } + + fn outline_custom_fixed_scale_into_as( + &self, + style: &OutlineStyle, + options: OverlayOptions, + scale: P::Scalar, + output: &mut FloatFlatContoursBuffer

, + ) -> Result<(), FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + let s = FixedScaleOverlayError::validate_scale(scale)?; + let mut solver = match OutlineSolver::::prepare(self, style) { Some(solver) => solver, None => { output.clear_and_reserve(0, 0); @@ -210,14 +407,18 @@ where } } -struct OutlineSolver { - outer_builder: OutlineBuilder

, - inner_builder: OutlineBuilder

, - adapter: FloatPointAdapter

, +struct OutlineSolver { + outer_builder: OutlineBuilder, + inner_builder: OutlineBuilder, + adapter: FloatPointAdapter, points_count: usize, } -impl OutlineSolver

{ +impl OutlineSolver +where + P: FloatPointCompatible + 'static, + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, +{ fn prepare>(source: &S, style: &OutlineStyle) -> Option { let (points_count, paths_count) = { let mut points_count = 0; @@ -234,8 +435,8 @@ impl OutlineSolver

{ } let join = style.join.clone().normalize(); - let outer_builder: OutlineBuilder

= OutlineBuilder::new(-style.outer_offset, &join); - let inner_builder: OutlineBuilder

= OutlineBuilder::new(-style.inner_offset, &join); + let outer_builder: OutlineBuilder = OutlineBuilder::new(-style.outer_offset, &join); + let inner_builder: OutlineBuilder = OutlineBuilder::new(-style.inner_offset, &join); let outer_radius = style.outer_offset; let inner_radius = style.inner_offset; @@ -248,7 +449,7 @@ impl OutlineSolver

{ let mut rect = FloatRect::with_iter(source.iter_paths().flatten()).unwrap_or(FloatRect::zero()); rect.add_offset(additional_offset); - let adapter = FloatPointAdapter::new(rect); + let adapter = FloatPointAdapter::::new(rect); Some(Self { outer_builder, @@ -260,17 +461,15 @@ impl OutlineSolver

{ fn apply_scale(&mut self, scale: f64) -> Result<(), FixedScaleOverlayError> { let s = P::Scalar::from_float(scale); - if self.adapter.dir_scale < s { - return Err(FixedScaleOverlayError::ScaleTooLarge); - } - - self.adapter.dir_scale = s; - self.adapter.inv_scale = P::Scalar::from_float(1.0 / scale); - + self.adapter = FloatPointAdapter::try_with_scale(*self.adapter.rect(), s)?; Ok(()) } - fn build_overlay>(&self, source: &S, options: OverlayOptions) -> Overlay { + fn build_overlay>( + &self, + source: &S, + options: OverlayOptions, + ) -> Overlay { let total_capacity = self.outer_builder.capacity(self.points_count); let mut overlay = Overlay::new_custom( total_capacity, @@ -283,11 +482,11 @@ impl OutlineSolver

{ let mut segments = Vec::new(); let mut bool_buffer = BooleanExtractionBuffer::default(); - let mut flat_buffer = FlatContoursBuffer::default(); + let mut flat_buffer = FlatContoursBuffer::::with_capacity(0); for path in source.iter_paths() { let area = path.unsafe_int_area(&self.adapter); - if area.abs() <= 1 { + if area.unsigned_abs() <= ::from_u64(1) { // ignore degenerate paths continue; } @@ -295,7 +494,7 @@ impl OutlineSolver

{ offset_overlay.clear(); segments.clear(); - let contour_fill_rule = if area < 0 { + let contour_fill_rule = if area > I::Wide::ZERO { offset_overlay.options.output_direction = ContourDirection::CounterClockwise; segments.reserve(self.outer_builder.capacity(path.len())); self.outer_builder.build(path, &self.adapter, &mut segments); @@ -320,7 +519,7 @@ impl OutlineSolver

{ overlay } - fn build>(self, source: &S, options: OverlayOptions) -> Shapes

{ + fn build>(self, source: &S, options: OverlayOptions) -> Shapes

{ let preserve_output_collinear = options.preserve_output_collinear; let clean_result = options.clean_result; let mut overlay = self.build_overlay(source, options); @@ -342,14 +541,14 @@ impl OutlineSolver

{ fn build_into>( self, source: &S, - options: OverlayOptions, + options: OverlayOptions, output: &mut FloatFlatContoursBuffer

, ) { let preserve_output_collinear = options.preserve_output_collinear; let clean_result = options.clean_result; let mut overlay = self.build_overlay(source, options); - let mut int_output = FlatContoursBuffer::default(); + let mut int_output = FlatContoursBuffer::::with_capacity(0); overlay.overlay_into(OverlayRule::Subject, FillRule::Positive, &mut int_output); let iter = int_output.points.iter().map(|p| self.adapter.int_to_float(p)); output.set_with_iter(iter, &int_output.ranges); @@ -427,6 +626,16 @@ mod tests { assert_eq!(shape.len(), 1); } + #[test] + fn test_triangle_round_corner_as() { + let path = [[0.0, 0.0f32], [10.0, 0.0f32], [0.0, 10.0f32]]; + + let style = OutlineStyle::new(5.0).line_join(LineJoin::Round(0.25 * PI)); + let shapes = path.outline_as::(&style); + + assert_eq!(shapes.len(), 1); + } + #[test] fn test_reversed_triangle_round_corner() { let path = [[0.0, 0.0f32], [0.0, 10.0f32], [10.0, 0.0f32]]; @@ -1189,25 +1398,25 @@ mod tests { #[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], + [410_000.0, 5_847_000.0], + [413_000.0, 5_847_000.0], + [413_000.0, 5_850_000.0], + [410_000.0, 5_850_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], + [411_294.2500422625, 5_848_072.318_972_548_5], + [411_373.9180110125, 5_848_124.016_970_595], + [411_377.22904616874, 5_848_118.990_969_619], + [411_393.0859797625, 5_848_020.979_983_291], + [411_394.7030451922, 5_848_005.639_040_908], + [411_397.4359553484, 5_848_003.376_955_947], + [411_431.1029475359, 5_847_937.537_966_689], + [411_431.2639582781, 5_847_933.187_991_103], + [411_314.8390315203, 5_848_005.308_962_783], + [411_314.5590022234, 5_848_009.708_010_634], + [411_309.3459895281, 5_848_009.447_024_306], + [411_305.3719905047, 5_848_010.244_997_939], ]; let shape = vec![main, hole]; diff --git a/iOverlay/src/mesh/outline/section.rs b/iOverlay/src/mesh/outline/section.rs index 3f36e996..9f95ad58 100644 --- a/iOverlay/src/mesh/outline/section.rs +++ b/iOverlay/src/mesh/outline/section.rs @@ -1,20 +1,21 @@ use crate::segm::boolean::ShapeCountBoolean; use crate::segm::segment::Segment; use i_float::float::compatible::FloatPointCompatible; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; -#[derive(Debug, Clone)] -pub(super) struct OffsetSection { - pub(super) a: IntPoint, - pub(super) b: IntPoint, - pub(super) a_top: IntPoint, - pub(super) b_top: IntPoint, +#[derive(Clone)] +pub(super) struct OffsetSection { + pub(super) a: IntPoint, + pub(super) b: IntPoint, + pub(super) a_top: IntPoint, + pub(super) b_top: IntPoint, pub(super) dir: P, } -impl OffsetSection

{ +impl OffsetSection { #[inline] - pub(super) fn top_segment(&self) -> Option> { + pub(super) fn top_segment(&self) -> Option> { if self.a_top != self.b_top { Some(Segment::subject(self.a_top, self.b_top)) } else { @@ -23,7 +24,7 @@ impl OffsetSection

{ } #[inline] - pub(super) fn a_segment(&self) -> Option> { + pub(super) fn a_segment(&self) -> Option> { if self.a_top != self.a { Some(Segment::subject(self.a, self.a_top)) } else { @@ -32,7 +33,7 @@ impl OffsetSection

{ } #[inline] - pub(super) fn b_segment(&self) -> Option> { + pub(super) fn b_segment(&self) -> Option> { if self.b_top != self.b { Some(Segment::subject(self.b_top, self.b)) } else { diff --git a/iOverlay/src/mesh/outline/uniq_iter.rs b/iOverlay/src/mesh/outline/uniq_iter.rs index 82bdd82c..c4733201 100644 --- a/iOverlay/src/mesh/outline/uniq_iter.rs +++ b/iOverlay/src/mesh/outline/uniq_iter.rs @@ -1,26 +1,30 @@ use core::iter::Chain; +use i_float::int::number::int::IntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; -pub(super) struct UniqueSegment { - pub(super) a: IntPoint, - pub(super) b: IntPoint, +pub(super) struct UniqueSegment { + pub(super) a: IntPoint, + pub(super) b: IntPoint, } -pub(super) struct UniqueSegmentsIter +pub(super) struct UniqueSegmentsIter where - I: Iterator, + It: Iterator>, + I: IntNumber, { - iter: Chain>, - p0: IntPoint, - p1: IntPoint, + iter: Chain, 2>>, + p0: IntPoint, + p1: IntPoint, } -impl UniqueSegmentsIter +impl UniqueSegmentsIter where - I: Iterator, + It: Iterator>, + I: IntNumber, { #[inline] - pub(super) fn new(iter: I) -> Option { + pub(super) fn new(iter: It) -> Option { let mut iter = iter; let mut p0 = iter.next()?; @@ -49,11 +53,12 @@ where } } -impl Iterator for UniqueSegmentsIter +impl Iterator for UniqueSegmentsIter where - I: Iterator, + It: Iterator>, + I: IntNumber, { - type Item = UniqueSegment; + type Item = UniqueSegment; #[inline] fn next(&mut self) -> Option { for p2 in &mut self.iter { @@ -87,17 +92,17 @@ where } #[inline] -fn include_point(p0: IntPoint, p1: IntPoint, p2: IntPoint) -> bool { - let a = p1.subtract(p0); - let b = p1.subtract(p2); +fn include_point(p0: IntPoint, p1: IntPoint, p2: IntPoint) -> bool { + let a = p1 - p0; + let b = p1 - p2; - if a.cross_product(b) != 0 { + if a.cross_product(b) != I::Wide::ZERO { // not collinear return true; } // collinear – keep only if we keep going opposite direction - a.dot_product(b) > 0 + a.dot_product(b) > I::Wide::ZERO } #[cfg(test)] mod tests { @@ -108,7 +113,7 @@ mod tests { #[test] fn test_empty() { - let uniq_iter = UniqueSegmentsIter::new(core::iter::empty::()); + let uniq_iter = UniqueSegmentsIter::new(core::iter::empty::>()); assert!(uniq_iter.is_none()); } @@ -169,7 +174,7 @@ mod tests { validate_case_all_rotations(&path, 4); } - fn validate_case_all_rotations(path: &[IntPoint], expected_segments_count: usize) { + fn validate_case_all_rotations(path: &[IntPoint], expected_segments_count: usize) { assert!(!path.is_empty(), "path must not be empty"); for shift in 0..path.len() { @@ -189,7 +194,7 @@ mod tests { } } - fn validate_segments(segments: &[UniqueSegment]) { + fn validate_segments(segments: &[UniqueSegment]) { assert!(!segments.is_empty(), "expected at least one segment"); for (i, s) in segments.iter().enumerate() { @@ -207,7 +212,7 @@ mod tests { validate_pair(&segments[last_i], &segments[0], last_i, 0); } - fn validate_pair(s0: &UniqueSegment, s1: &UniqueSegment, i: usize, j: usize) { + fn validate_pair(s0: &UniqueSegment, s1: &UniqueSegment, i: usize, j: usize) { assert_eq!(s0.b, s1.a, "segment {} end does not match segment {} start", i, j); let v0 = s0.a - s0.b; diff --git a/iOverlay/src/mesh/overlay.rs b/iOverlay/src/mesh/overlay.rs index 64e759af..aa6d8855 100644 --- a/iOverlay/src/mesh/overlay.rs +++ b/iOverlay/src/mesh/overlay.rs @@ -5,22 +5,24 @@ use crate::segm::boolean::ShapeCountBoolean; use crate::segm::segment::Segment; use crate::split::solver::SplitSolver; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_tree::Expiration; -impl Overlay { +impl Overlay { #[inline] - pub(crate) fn add_segments(&mut self, segments: &[Segment]) { + pub(crate) fn add_segments(&mut self, segments: &[Segment]) { self.segments.extend_from_slice(segments); } #[inline] - pub(crate) fn with_segments(segments: Vec>) -> Self { + pub(crate) fn with_segments(segments: Vec>) -> Self { Self { solver: Default::default(), options: Default::default(), boolean_buffer: None, segments, split_solver: SplitSolver::new(), - graph_builder: GraphBuilder::::new(), + graph_builder: GraphBuilder::::new(), } } } diff --git a/iOverlay/src/mesh/stroke/builder.rs b/iOverlay/src/mesh/stroke/builder.rs index 54895739..d5ac0b77 100644 --- a/iOverlay/src/mesh/stroke/builder.rs +++ b/iOverlay/src/mesh/stroke/builder.rs @@ -6,59 +6,65 @@ use crate::segm::boolean::ShapeCountBoolean; use crate::segm::segment::Segment; use alloc::boxed::Box; use alloc::vec::Vec; +use core::marker::PhantomData; use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; +use i_float::int::number::int::IntNumber; -trait StrokeBuild { +trait StrokeBuild { fn build( &self, path: &[P], is_closed_path: bool, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ); fn capacity(&self, paths_count: usize, points_count: usize, is_closed_path: bool) -> usize; fn additional_offset(&self, radius: P::Scalar) -> P::Scalar; } -pub(super) struct StrokeBuilder { - builder: Box>, +pub(super) struct StrokeBuilder { + builder: Box>, } -struct Builder, P: FloatPointCompatible> { +struct Builder, P: FloatPointCompatible, I: IntNumber> { radius: P::Scalar, join_builder: J, start_cap_builder: CapBuilder

, end_cap_builder: CapBuilder

, + _phantom: PhantomData, } -impl StrokeBuilder

{ - pub(super) fn new(style: StrokeStyle

) -> StrokeBuilder

{ +impl StrokeBuilder { + pub(super) fn new(style: StrokeStyle

) -> StrokeBuilder { let radius = P::Scalar::from_float(0.5 * style.width.to_f64().max(0.0)); let start_cap_builder = CapBuilder::new(style.start_cap.normalize(), radius); let end_cap_builder = CapBuilder::new(style.end_cap.normalize(), radius); - let builder: Box> = match style.join.normalize() { + let builder: Box> = match style.join.normalize() { LineJoin::Miter(ratio) => Box::new(Builder { radius, join_builder: MiterJoinBuilder::new(ratio, radius), start_cap_builder, end_cap_builder, + _phantom: Default::default(), }), LineJoin::Round(ratio) => Box::new(Builder { radius, join_builder: RoundJoinBuilder::new(ratio, radius), start_cap_builder, end_cap_builder, + _phantom: Default::default(), }), LineJoin::Bevel => Box::new(Builder { radius, join_builder: BevelJoinBuilder {}, start_cap_builder, end_cap_builder, + _phantom: Default::default(), }), }; @@ -70,8 +76,8 @@ impl StrokeBuilder

{ &self, path: &[P], is_closed_path: bool, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { self.builder.build(path, is_closed_path, adapter, segments); } @@ -87,14 +93,14 @@ impl StrokeBuilder

{ } } -impl, P: FloatPointCompatible> StrokeBuild

for Builder { +impl, P: FloatPointCompatible, I: IntNumber> StrokeBuild for Builder { #[inline] fn build( &self, path: &[P], is_closed_path: bool, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { if is_closed_path { self.closed_segments(path, adapter, segments); @@ -122,12 +128,12 @@ impl, P: FloatPointCompatible> StrokeBuild

for Builder, P: FloatPointCompatible> Builder { +impl, P: FloatPointCompatible, I: IntNumber> Builder { fn open_segments( &self, path: &[P], - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { // build segments only from points which are not equal in int space @@ -179,8 +185,8 @@ impl, P: FloatPointCompatible> Builder { fn closed_segments( &self, path: &[P], - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { if path.len() < 2 { return; @@ -212,7 +218,7 @@ impl, P: FloatPointCompatible> Builder { } #[inline] - fn next_unique_point(start: usize, index: usize, path: &[P], adapter: &FloatPointAdapter

) -> usize { + fn next_unique_point(start: usize, index: usize, path: &[P], adapter: &FloatPointAdapter) -> usize { let a = adapter.float_to_int(&path[start]); for (j, p) in path.iter().enumerate().skip(index) { let b = adapter.float_to_int(p); @@ -224,3 +230,28 @@ impl, P: FloatPointCompatible> Builder { usize::MAX } } + +#[cfg(test)] +mod tests { + use crate::mesh::stroke::builder::StrokeBuilder; + use crate::mesh::style::StrokeStyle; + use crate::segm::boolean::ShapeCountBoolean; + use crate::segm::segment::Segment; + use alloc::vec::Vec; + use i_float::adapter::FloatPointAdapter; + use i_float::float::rect::FloatRect; + + #[test] + fn test_i64_builder() { + let path = [[0.0, 0.0], [10.0, 0.0], [10.0, 10.0]]; + let mut rect = FloatRect::with_iter(path.iter()).unwrap(); + rect.add_offset(2.0); + let adapter = FloatPointAdapter::<[f64; 2], i64>::new(rect); + let builder = StrokeBuilder::<[f64; 2], i64>::new(StrokeStyle::new(2.0)); + + let mut segments = Vec::>::new(); + builder.build(&path, false, &adapter, &mut segments); + + assert!(!segments.is_empty()); + } +} diff --git a/iOverlay/src/mesh/stroke/builder_cap.rs b/iOverlay/src/mesh/stroke/builder_cap.rs index 12a67322..d580a4f5 100644 --- a/iOverlay/src/mesh/stroke/builder_cap.rs +++ b/iOverlay/src/mesh/stroke/builder_cap.rs @@ -11,8 +11,8 @@ use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; use i_float::float::rect::FloatRect; use i_float::float::vector::FloatPointMath; +use i_float::int::number::int::IntNumber; -#[derive(Debug, Clone)] pub(super) struct CapBuilder

{ points: Option>, } @@ -66,11 +66,11 @@ impl CapBuilder

{ scaled } - pub(super) fn add_to_start( + pub(super) fn add_to_start( &self, section: &Section

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let mut a = adapter.float_to_int(§ion.a_top); if let Some(points) = &self.points { @@ -88,11 +88,11 @@ impl CapBuilder

{ segments.push(Segment::subject(a, last)); } - pub(super) fn add_to_end( + pub(super) fn add_to_end( &self, section: &Section

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let mut a = adapter.float_to_int(§ion.b_bot); if let Some(points) = &self.points { diff --git a/iOverlay/src/mesh/stroke/builder_join.rs b/iOverlay/src/mesh/stroke/builder_join.rs index b3e09613..8dcaadc0 100644 --- a/iOverlay/src/mesh/stroke/builder_join.rs +++ b/iOverlay/src/mesh/stroke/builder_join.rs @@ -9,14 +9,15 @@ use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; use i_float::float::vector::FloatPointMath; +use i_float::int::number::int::IntNumber; -pub(super) trait JoinBuilder { +pub(super) trait JoinBuilder { fn add_join( &self, s0: &Section

, s1: &Section

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ); fn capacity(&self) -> usize; fn additional_offset(&self, radius: P::Scalar) -> P::Scalar; @@ -26,31 +27,31 @@ pub(super) struct BevelJoinBuilder; impl BevelJoinBuilder { #[inline] - fn join_top( + fn join_top( s0: &Section

, s1: &Section

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { Self::add_segment(&s0.b_top, &s1.a_top, adapter, segments); } #[inline] - fn join_bot( + fn join_bot( s0: &Section

, s1: &Section

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { Self::add_segment(&s1.a_bot, &s0.b_bot, adapter, segments); } #[inline] - fn add_segment( + fn add_segment( a: &P, b: &P, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let ia = adapter.float_to_int(a); let ib = adapter.float_to_int(b); @@ -60,14 +61,14 @@ impl BevelJoinBuilder { } } -impl JoinBuilder

for BevelJoinBuilder { +impl JoinBuilder for BevelJoinBuilder { #[inline] fn add_join( &self, s0: &Section

, s1: &Section

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { Self::join_top(s0, s1, adapter, segments); Self::join_bot(s0, s1, adapter, segments); @@ -117,13 +118,13 @@ impl MiterJoinBuilder { } } -impl JoinBuilder

for MiterJoinBuilder { +impl JoinBuilder for MiterJoinBuilder { fn add_join( &self, s0: &Section

, s1: &Section

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let cross_product = FloatPointMath::cross_product(&s0.dir, &s1.dir); if cross_product.abs() < P::Scalar::from_float(0.0001) { @@ -237,13 +238,13 @@ impl RoundJoinBuilder { } } } -impl JoinBuilder

for RoundJoinBuilder { +impl JoinBuilder for RoundJoinBuilder { fn add_join( &self, s0: &Section

, s1: &Section

, - adapter: &FloatPointAdapter

, - segments: &mut Vec>, + adapter: &FloatPointAdapter, + segments: &mut Vec>, ) { let dot_product = FloatPointMath::dot_product(&s0.dir, &s1.dir); if self.limit_dot_product < dot_product { diff --git a/iOverlay/src/mesh/stroke/offset.rs b/iOverlay/src/mesh/stroke/offset.rs index 768e9f74..150e5253 100644 --- a/iOverlay/src/mesh/stroke/offset.rs +++ b/iOverlay/src/mesh/stroke/offset.rs @@ -12,13 +12,36 @@ use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::number::FloatNumber; use i_float::float::rect::FloatRect; +use i_float::int::number::int::IntNumber; +use i_float::int::number::uint::UIntNumber; +use i_float::int::number::wide_int::WideIntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::base::data::Shapes; use i_shape::flat::buffer::FlatContoursBuffer; use i_shape::flat::float::FloatFlatContoursBuffer; use i_shape::float::adapter::ShapesToFloat; use i_shape::float::despike::DeSpikeContour; use i_shape::float::simple::SimplifyContour; - +use i_tree::{Expiration, LayoutNumber}; + +/// Trait for generating stroke outlines from float paths. +/// +/// Default methods use the `i32` integer engine. Use the `*_as::` methods when you need to +/// select `i16`, `i32`, or `i64` explicitly. +/// +/// # Example +/// +/// ``` +/// use i_overlay::mesh::stroke::offset::StrokeOffset; +/// use i_overlay::mesh::style::StrokeStyle; +/// +/// let path = [[0.0, 0.0], [10.0, 0.0]]; +/// let style = StrokeStyle::new(2.0); +/// +/// let result = path.stroke_as::(style, false); +/// +/// assert_eq!(result.len(), 1); +/// ``` pub trait StrokeOffset { /// Generates a stroke shapes for paths, contours, or shapes. /// @@ -139,6 +162,84 @@ pub trait StrokeOffset { scale: P::Scalar, output: &mut FloatFlatContoursBuffer

, ) -> Result<(), FixedScaleOverlayError>; + + /// Same as [`Self::stroke`], but with an explicit integer engine. + fn stroke_as(&self, style: StrokeStyle

, is_closed_path: bool) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::stroke_into`], but with an explicit integer engine. + fn stroke_into_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + output: &mut FloatFlatContoursBuffer

, + ) where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::stroke_custom`], but with an explicit integer engine. + fn stroke_custom_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + options: OverlayOptions, + ) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::stroke_custom_into`], but with an explicit integer engine. + fn stroke_custom_into_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + options: OverlayOptions, + output: &mut FloatFlatContoursBuffer

, + ) where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::stroke_fixed_scale`], but with an explicit integer engine. + fn stroke_fixed_scale_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::stroke_fixed_scale_into`], but with an explicit integer engine. + fn stroke_fixed_scale_into_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + scale: P::Scalar, + output: &mut FloatFlatContoursBuffer

, + ) -> Result<(), FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::stroke_custom_fixed_scale`], but with an explicit integer engine. + fn stroke_custom_fixed_scale_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + options: OverlayOptions, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; + + /// Same as [`Self::stroke_custom_fixed_scale_into`], but with an explicit integer engine. + fn stroke_custom_fixed_scale_into_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + options: OverlayOptions, + scale: P::Scalar, + output: &mut FloatFlatContoursBuffer

, + ) -> Result<(), FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static; } impl StrokeOffset

for S @@ -184,7 +285,7 @@ where is_closed_path: bool, options: OverlayOptions, ) -> Shapes

{ - match StrokeSolver::prepare(self, style) { + match StrokeSolver::::prepare(self, style) { Some(solver) => solver.build(self, is_closed_path, options), None => vec![], } @@ -197,7 +298,7 @@ where options: OverlayOptions, output: &mut FloatFlatContoursBuffer

, ) { - match StrokeSolver::prepare(self, style) { + match StrokeSolver::::prepare(self, style) { Some(solver) => solver.build_into(self, is_closed_path, options, output), None => output.clear_and_reserve(0, 0), } @@ -210,7 +311,7 @@ where options: OverlayOptions, scale: P::Scalar, ) -> Result, FixedScaleOverlayError> { - let mut solver = match StrokeSolver::prepare(self, style) { + let mut solver = match StrokeSolver::::prepare(self, style) { Some(solver) => solver, None => return Ok(vec![]), }; @@ -226,7 +327,121 @@ where scale: P::Scalar, output: &mut FloatFlatContoursBuffer

, ) -> Result<(), FixedScaleOverlayError> { - let mut solver = match StrokeSolver::prepare(self, style) { + let mut solver = match StrokeSolver::::prepare(self, style) { + Some(solver) => solver, + None => { + output.clear_and_reserve(0, 0); + return Ok(()); + } + }; + solver.apply_scale(scale)?; + solver.build_into(self, is_closed_path, options, output); + Ok(()) + } + + fn stroke_as(&self, style: StrokeStyle

, is_closed_path: bool) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + self.stroke_custom_as::(style, is_closed_path, Default::default()) + } + + fn stroke_into_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + output: &mut FloatFlatContoursBuffer

, + ) where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + self.stroke_custom_into_as::(style, is_closed_path, Default::default(), output) + } + + fn stroke_custom_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + options: OverlayOptions, + ) -> Shapes

+ where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + match StrokeSolver::::prepare(self, style) { + Some(solver) => solver.build(self, is_closed_path, options), + None => vec![], + } + } + + fn stroke_custom_into_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + options: OverlayOptions, + output: &mut FloatFlatContoursBuffer

, + ) where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + match StrokeSolver::::prepare(self, style) { + Some(solver) => solver.build_into(self, is_closed_path, options, output), + None => output.clear_and_reserve(0, 0), + } + } + + fn stroke_fixed_scale_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + self.stroke_custom_fixed_scale_as::(style, is_closed_path, Default::default(), scale) + } + + fn stroke_fixed_scale_into_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + scale: P::Scalar, + output: &mut FloatFlatContoursBuffer

, + ) -> Result<(), FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + self.stroke_custom_fixed_scale_into_as::(style, is_closed_path, Default::default(), scale, output) + } + + fn stroke_custom_fixed_scale_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + options: OverlayOptions, + scale: P::Scalar, + ) -> Result, FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + let mut solver = match StrokeSolver::::prepare(self, style) { + Some(solver) => solver, + None => return Ok(vec![]), + }; + solver.apply_scale(scale)?; + Ok(solver.build(self, is_closed_path, options)) + } + + fn stroke_custom_fixed_scale_into_as( + &self, + style: StrokeStyle

, + is_closed_path: bool, + options: OverlayOptions, + scale: P::Scalar, + output: &mut FloatFlatContoursBuffer

, + ) -> Result<(), FixedScaleOverlayError> + where + I: IntNumber + Expiration + LayoutNumber + SortKey + 'static, + { + let mut solver = match StrokeSolver::::prepare(self, style) { Some(solver) => solver, None => { output.clear_and_reserve(0, 0); @@ -239,15 +454,19 @@ where } } -struct StrokeSolver { +struct StrokeSolver { r: P::Scalar, - builder: StrokeBuilder

, - adapter: FloatPointAdapter

, + builder: StrokeBuilder, + adapter: FloatPointAdapter, paths_count: usize, points_count: usize, } -impl StrokeSolver

{ +impl StrokeSolver +where + P: 'static + FloatPointCompatible, + I: 'static + IntNumber + Expiration + LayoutNumber + SortKey, +{ fn prepare>(source: &S, style: StrokeStyle

) -> Option { let mut paths_count = 0; let mut points_count = 0; @@ -261,12 +480,12 @@ impl StrokeSolver

{ } let r = P::Scalar::from_float(0.5 * style.width.to_f64()); - let builder = StrokeBuilder::new(style); + let builder = StrokeBuilder::::new(style); let a = builder.additional_offset(r); let mut rect = FloatRect::with_iter(source.iter_paths().flatten()).unwrap_or(FloatRect::zero()); rect.add_offset(a); - let adapter = FloatPointAdapter::new(rect); + let adapter = FloatPointAdapter::::new(rect); Some(Self { r, @@ -278,14 +497,7 @@ impl StrokeSolver

{ } fn apply_scale(&mut self, scale: P::Scalar) -> Result<(), FixedScaleOverlayError> { - let s = FixedScaleOverlayError::validate_scale(scale)?; - if self.adapter.dir_scale < scale { - return Err(FixedScaleOverlayError::ScaleTooLarge); - } - - self.adapter.dir_scale = scale; - self.adapter.inv_scale = P::Scalar::from_float(1.0 / s); - + self.adapter = FloatPointAdapter::try_with_scale(*self.adapter.rect(), scale)?; Ok(()) } @@ -293,10 +505,10 @@ impl StrokeSolver

{ self, source: &S, is_closed_path: bool, - options: OverlayOptions, + options: OverlayOptions, ) -> Shapes

{ - let ir = self.adapter.len_float_to_int(self.r).abs(); - if ir <= 1 { + let ir = self.adapter.round_len_to_int(self.r).wide().unsigned_abs(); + if ir <= I::WideUInt::ONE { // offset is too small return vec![]; } @@ -333,11 +545,11 @@ impl StrokeSolver

{ self, source: &S, is_closed_path: bool, - options: OverlayOptions, + options: OverlayOptions, output: &mut FloatFlatContoursBuffer

, ) { - let ir = self.adapter.len_float_to_int(self.r).abs(); - if ir <= 1 { + let ir = self.adapter.round_len_to_int(self.r).wide().unsigned_abs(); + if ir <= I::WideUInt::ONE { // offset is too small output.clear_and_reserve(0, 0); return; @@ -356,7 +568,7 @@ impl StrokeSolver

{ let mut overlay = Overlay::with_segments(segments); overlay.options = options.int_with_adapter(&self.adapter); - let mut int_output = FlatContoursBuffer::default(); + let mut int_output = FlatContoursBuffer::::with_capacity(0); overlay.overlay_into(OverlayRule::Subject, FillRule::Positive, &mut int_output); let iter = int_output.points.iter().map(|p| self.adapter.int_to_float(p)); @@ -417,6 +629,16 @@ mod tests { assert_eq!(shapes.len(), 1); } + #[test] + fn test_simple_as() { + let path = [[0.0, 0.0], [10.0, 0.0]]; + + let style = StrokeStyle::new(2.0); + let shapes = path.stroke_as::(style, false); + + assert_eq!(shapes.len(), 1); + } + #[test] fn test_bevel_join() { let path = [[-10.0, 0.0], [0.0, 0.0], [0.0, 10.0]]; diff --git a/iOverlay/src/mesh/stroke/section.rs b/iOverlay/src/mesh/stroke/section.rs index 2fcbdf25..d647b0f4 100644 --- a/iOverlay/src/mesh/stroke/section.rs +++ b/iOverlay/src/mesh/stroke/section.rs @@ -5,8 +5,9 @@ use alloc::vec::Vec; use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_float::float::vector::FloatPointMath; +use i_float::int::number::int::IntNumber; -#[derive(Debug, Clone)] +#[derive(Clone)] pub(super) struct Section { pub(super) a: P, pub(super) b: P, @@ -40,12 +41,12 @@ impl Section

{ } } -pub(crate) trait SectionToSegment { - fn add_section(&mut self, section: &Section

, adapter: &FloatPointAdapter

); +pub(crate) trait SectionToSegment { + fn add_section(&mut self, section: &Section

, adapter: &FloatPointAdapter); } -impl SectionToSegment

for Vec> { - fn add_section(&mut self, section: &Section

, adapter: &FloatPointAdapter

) { +impl SectionToSegment for Vec> { + fn add_section(&mut self, section: &Section

, adapter: &FloatPointAdapter) { let a_top = adapter.float_to_int(§ion.a_top); let b_top = adapter.float_to_int(§ion.b_top); let a_bot = adapter.float_to_int(§ion.a_bot); diff --git a/iOverlay/src/mesh/subject.rs b/iOverlay/src/mesh/subject.rs index 3189623b..3cde1bcf 100644 --- a/iOverlay/src/mesh/subject.rs +++ b/iOverlay/src/mesh/subject.rs @@ -1,20 +1,23 @@ use crate::geom::x_segment::XSegment; use crate::segm::boolean::ShapeCountBoolean; use crate::segm::segment::Segment; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; -impl Segment { +impl Segment { #[inline] - pub(crate) fn subject(p0: IntPoint, p1: IntPoint) -> Self { + pub(crate) fn subject(p0: IntPoint, p1: IntPoint) -> Self { if p0 < p1 { Self { x_segment: XSegment { a: p0, b: p1 }, count: ShapeCountBoolean { subj: 1, clip: 0 }, + data: (), } } else { Self { x_segment: XSegment { a: p1, b: p0 }, count: ShapeCountBoolean { subj: -1, clip: 0 }, + data: (), } } } diff --git a/iOverlay/src/segm/boolean.rs b/iOverlay/src/segm/boolean.rs index 17793892..572594bd 100644 --- a/iOverlay/src/segm/boolean.rs +++ b/iOverlay/src/segm/boolean.rs @@ -33,6 +33,22 @@ impl WindingCount for ShapeCountBoolean { } } + #[inline(always)] + fn direct_count(shape_type: ShapeType) -> Self { + match shape_type { + ShapeType::Subject => ShapeCountBoolean::SUBJ_DIRECT, + ShapeType::Clip => ShapeCountBoolean::CLIP_DIRECT, + } + } + + #[inline(always)] + fn invert_count(shape_type: ShapeType) -> Self { + match shape_type { + ShapeType::Subject => ShapeCountBoolean::SUBJ_INVERT, + ShapeType::Clip => ShapeCountBoolean::CLIP_INVERT, + } + } + #[inline(always)] fn add(self, count: Self) -> Self { let subj = self.subj + count.subj; @@ -41,12 +57,6 @@ impl WindingCount for ShapeCountBoolean { Self { subj, clip } } - #[inline(always)] - fn apply(&mut self, count: Self) { - self.subj += count.subj; - self.clip += count.clip; - } - #[inline(always)] fn invert(self) -> Self { Self { diff --git a/iOverlay/src/segm/build.rs b/iOverlay/src/segm/build.rs index 57f3cbd9..5293fbe2 100644 --- a/iOverlay/src/segm/build.rs +++ b/iOverlay/src/segm/build.rs @@ -3,36 +3,43 @@ use crate::geom::x_segment::XSegment; use crate::segm::segment::Segment; use crate::segm::winding::WindingCount; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; -pub(crate) trait BuildSegments { - fn append_path_iter>( +pub(crate) trait BuildSegments { + fn append_path_iter>>( &mut self, - iter: I, + iter: It, shape_type: ShapeType, keep_same_line_points: bool, ) -> bool; } -impl BuildSegments for Vec> { +impl BuildSegments for Vec> { #[inline] - fn append_path_iter>( + fn append_path_iter>>( &mut self, - iter: I, + iter: It, shape_type: ShapeType, keep_same_line_points: bool, ) -> bool { if keep_same_line_points { - build_segments_with_filter::(self, iter, shape_type) + build_segments_with_filter::(self, iter, shape_type) } else { - build_segments_with_filter::(self, iter, shape_type) + build_segments_with_filter::(self, iter, shape_type) } } } -fn build_segments_with_filter, C: WindingCount>( - segments: &mut Vec>, - mut iter: I, +fn build_segments_with_filter< + I: IntNumber, + F: PointFilter, + It: Iterator>, + C: WindingCount, +>( + segments: &mut Vec>, + mut iter: It, shape_type: ShapeType, ) -> bool { // our goal add all not degenerate segments @@ -87,50 +94,52 @@ fn build_segments_with_filter, C: W filtered } -trait PointFilter { - fn include_point(a: IntPoint, b: IntPoint, c: IntPoint) -> bool; +trait PointFilter { + fn include_point(a: IntPoint, b: IntPoint, c: IntPoint) -> bool; } struct DropOppositeCollinear; struct DropCollinear; -impl PointFilter for DropOppositeCollinear { +impl PointFilter for DropOppositeCollinear { #[inline] - fn include_point(p0: IntPoint, p1: IntPoint, p2: IntPoint) -> bool { - let a = p1.subtract(p0); - let b = p1.subtract(p2); + fn include_point(p0: IntPoint, p1: IntPoint, p2: IntPoint) -> bool { + let a = p1 - p0; + let b = p1 - p2; - if a.cross_product(b) != 0 { + if a.cross_product(b) != I::Wide::ZERO { // not collinear return true; } // collinear – keep only if we keep going same direction - a.dot_product(b) < 0 + a.dot_product(b) < I::Wide::ZERO } } -impl PointFilter for DropCollinear { +impl PointFilter for DropCollinear { #[inline] - fn include_point(p0: IntPoint, p1: IntPoint, p2: IntPoint) -> bool { - let a = p1.subtract(p0); - let b = p1.subtract(p2); - a.cross_product(b) != 0 + fn include_point(p0: IntPoint, p1: IntPoint, p2: IntPoint) -> bool { + let a = p1 - p0; + let b = p1 - p2; + a.cross_product(b) != I::Wide::ZERO } } -impl Segment { +impl Segment { #[inline] - pub(crate) fn with_ab(p0: IntPoint, p1: IntPoint, direct: C, invert: C) -> Self { + pub(crate) fn with_ab(p0: IntPoint, p1: IntPoint, direct: C, invert: C) -> Self { if p0 < p1 { Self { x_segment: XSegment { a: p0, b: p1 }, count: direct, + data: (), } } else { Self { x_segment: XSegment { a: p1, b: p0 }, count: invert, + data: (), } } } @@ -391,7 +400,7 @@ mod tests { } fn test_count(points: &[IntPoint], count: usize, keep_same_line_points: bool) { - let mut segments: Vec> = Vec::new(); + let mut segments: Vec> = Vec::new(); segments.append_path_iter(points.iter().copied(), ShapeType::Subject, keep_same_line_points); segments.merge_if_needed(); @@ -401,7 +410,7 @@ mod tests { fn test_roll_count(slice: &[IntPoint], count: usize, keep_same_line_points: bool) { let mut points = slice.to_vec(); let n = points.len(); - let mut segments: Vec> = Vec::with_capacity(n); + let mut segments: Vec> = Vec::with_capacity(n); for _ in 0..n { segments.append_path_iter(points.iter().copied(), ShapeType::Subject, keep_same_line_points); segments.merge_if_needed(); diff --git a/iOverlay/src/segm/merge.rs b/iOverlay/src/segm/merge.rs index d015d9ae..66a6d609 100644 --- a/iOverlay/src/segm/merge.rs +++ b/iOverlay/src/segm/merge.rs @@ -1,13 +1,29 @@ +use crate::core::edge_data::{EdgeDataMerge, OverlayEdgeData}; use crate::segm::segment::Segment; use crate::segm::winding::WindingCount; use alloc::vec::Vec; - -pub(crate) trait ShapeSegmentsMerge { +use i_float::int::number::int::IntNumber; + +pub(crate) trait ShapeSegmentsMerge +where + C: WindingCount, + D: OverlayEdgeData, +{ + #[cfg(test)] fn merge_if_needed(&mut self) -> bool; + fn merge_if_needed_with_store(&mut self, store: &mut D::Store) -> bool; } -impl ShapeSegmentsMerge for Vec> { +impl> ShapeSegmentsMerge + for Vec> +{ + #[cfg(test)] fn merge_if_needed(&mut self) -> bool { + let mut store = D::Store::default(); + self.merge_if_needed_with_store(&mut store) + } + + fn merge_if_needed_with_store(&mut self, store: &mut D::Store) -> bool { if self.len() < 2 { return false; } @@ -16,7 +32,7 @@ impl ShapeSegmentsMerge for Vec> { for i in 1..self.len() { let this = &self[i].x_segment; if prev.eq(this) { - let new_len = merge(self, i); + let new_len = merge(self, i, store); self.truncate(new_len); return true; } @@ -27,14 +43,31 @@ impl ShapeSegmentsMerge for Vec> { } } -fn merge(segments: &mut [Segment], after: usize) -> usize { +fn merge>( + segments: &mut [Segment], + after: usize, + store: &mut D::Store, +) -> usize { let mut i = after; let mut j = i - 1; let mut prev = segments[j]; while i < segments.len() { if prev.x_segment.eq(&segments[i].x_segment) { - prev.count.apply(segments[i].count); + let lhs_count = prev.count; + let rhs_count = segments[i].count; + let out_count = lhs_count.add(rhs_count); + prev.data = D::merge( + EdgeDataMerge { + lhs_data: prev.data, + lhs_count, + rhs_data: segments[i].data, + rhs_count, + out_count, + }, + store, + ); + prev.count = out_count; } else { if prev.count.is_not_empty() { segments[j] = prev; @@ -60,9 +93,16 @@ mod tests { use alloc::vec; use i_float::int::point::IntPoint; + impl Segment { + fn create_and_validate(a: IntPoint, b: IntPoint, count: C) -> Self { + let mut store = (); + Self::create_and_validate_with_data(a, b, count, (), &mut store) + } + } + #[test] fn test_merge_if_needed_empty() { - let mut segments: Vec> = Vec::new(); + let mut segments: Vec> = Vec::new(); segments.merge_if_needed(); assert!( segments.is_empty(), diff --git a/iOverlay/src/segm/mod.rs b/iOverlay/src/segm/mod.rs index 5f582876..98b4e743 100644 --- a/iOverlay/src/segm/mod.rs +++ b/iOverlay/src/segm/mod.rs @@ -1,7 +1,7 @@ pub mod boolean; pub(crate) mod build; pub(crate) mod merge; -pub(crate) mod segment; +pub mod segment; pub(crate) mod sort; pub mod string; -pub mod winding; +pub(crate) mod winding; diff --git a/iOverlay/src/segm/segment.rs b/iOverlay/src/segm/segment.rs index b20ad135..1c0f6883 100644 --- a/iOverlay/src/segm/segment.rs +++ b/iOverlay/src/segm/segment.rs @@ -1,6 +1,10 @@ +use crate::core::edge_data::OverlayEdgeData; +use crate::core::overlay::ShapeType; use crate::geom::x_segment::XSegment; +use crate::segm::boolean::ShapeCountBoolean; use crate::segm::winding::WindingCount; use core::cmp::Ordering; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; pub type SegmentFill = u8; @@ -20,45 +24,79 @@ pub const BOTH_BOTTOM: SegmentFill = SUBJ_BOTTOM | CLIP_BOTTOM; pub const ALL: SegmentFill = SUBJ_BOTH | CLIP_BOTH; #[derive(Debug, Clone, Copy)] -pub(crate) struct Segment { - pub(crate) x_segment: XSegment, +pub(crate) struct Segment { + pub(crate) x_segment: XSegment, pub(crate) count: C, + pub(crate) data: D, } -impl Segment { +impl> Segment { #[inline(always)] - pub(crate) fn create_and_validate(a: IntPoint, b: IntPoint, count: C) -> Self { + pub(crate) fn create_and_validate_with_data( + a: IntPoint, + b: IntPoint, + count: C, + data: D, + store: &mut D::Store, + ) -> Self { if a < b { Self { x_segment: XSegment { a, b }, count, + data, } } else { Self { x_segment: XSegment { a: b, b: a }, count: count.invert(), + data: data.reversed(store), } } } } -impl PartialEq for Segment { +impl> Segment { + #[inline] + pub(crate) fn try_ab_and_data( + a: IntPoint, + b: IntPoint, + shape_type: ShapeType, + data: D, + store: &mut D::Store, + ) -> Option { + match a.cmp(&b) { + Ordering::Equal => None, + Ordering::Less => Some(Self { + x_segment: XSegment { a, b }, + count: ShapeCountBoolean::direct_count(shape_type), + data, + }), + Ordering::Greater => Some(Self { + x_segment: XSegment { a: b, b: a }, + count: ShapeCountBoolean::invert_count(shape_type), + data: data.reversed(store), + }), + } + } +} + +impl PartialEq for Segment { #[inline(always)] fn eq(&self, other: &Self) -> bool { self.x_segment == other.x_segment } } -impl Eq for Segment {} +impl Eq for Segment {} -impl PartialOrd for Segment { +impl PartialOrd for Segment { #[inline(always)] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for Segment { +impl Ord for Segment { #[inline(always)] fn cmp(&self, other: &Self) -> Ordering { self.x_segment.cmp(&other.x_segment) diff --git a/iOverlay/src/segm/sort.rs b/iOverlay/src/segm/sort.rs index 263265ea..8bd4fde0 100644 --- a/iOverlay/src/segm/sort.rs +++ b/iOverlay/src/segm/sort.rs @@ -1,11 +1,15 @@ use crate::segm::segment::Segment; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_key_sort::sort::two_keys_cmp::TwoKeysAndCmpSort; pub(crate) trait ShapeSegmentsSort { fn sort_by_ab(&mut self, parallel: bool); } -impl ShapeSegmentsSort for [Segment] { +impl ShapeSegmentsSort + for [Segment] +{ #[inline] fn sort_by_ab(&mut self, parallel: bool) { self.sort_by_two_keys_then_by( diff --git a/iOverlay/src/segm/string.rs b/iOverlay/src/segm/string.rs index 21bce440..c42ac576 100644 --- a/iOverlay/src/segm/string.rs +++ b/iOverlay/src/segm/string.rs @@ -46,6 +46,26 @@ impl WindingCount for ShapeCountString { } } + fn direct_count(shape_type: ShapeType) -> Self { + match shape_type { + ShapeType::Subject => Self { subj: 1, clip: 0 }, + ShapeType::Clip => Self { + subj: 0, + clip: STRING_FORWARD_CLIP, + }, + } + } + + fn invert_count(shape_type: ShapeType) -> Self { + match shape_type { + ShapeType::Subject => Self { subj: -1, clip: 0 }, + ShapeType::Clip => Self { + subj: 0, + clip: STRING_BACK_CLIP, + }, + } + } + #[inline(always)] fn add(self, count: Self) -> Self { let subj = self.subj + count.subj; @@ -54,12 +74,6 @@ impl WindingCount for ShapeCountString { Self { subj, clip } } - #[inline(always)] - fn apply(&mut self, count: Self) { - self.subj += count.subj; - self.clip |= count.clip; - } - #[inline(always)] fn invert(self) -> Self { let b0 = self.clip & 0b01; diff --git a/iOverlay/src/segm/winding.rs b/iOverlay/src/segm/winding.rs index 81cc7b34..110a970e 100644 --- a/iOverlay/src/segm/winding.rs +++ b/iOverlay/src/segm/winding.rs @@ -7,7 +7,8 @@ where fn is_not_empty(&self) -> bool; fn new(subj: i32, clip: i32) -> Self; fn with_shape_type(shape_type: ShapeType) -> (Self, Self); + fn direct_count(shape_type: ShapeType) -> Self; + fn invert_count(shape_type: ShapeType) -> Self; fn add(self, count: Self) -> Self; - fn apply(&mut self, count: Self); fn invert(self) -> Self; } diff --git a/iOverlay/src/split/cross_solver.rs b/iOverlay/src/split/cross_solver.rs index 7b0c5447..993f221d 100644 --- a/iOverlay/src/split/cross_solver.rs +++ b/iOverlay/src/split/cross_solver.rs @@ -1,8 +1,11 @@ use crate::geom::x_segment::XSegment; -use i_float::fix_vec::FixVec; +use core::marker::PhantomData; +use i_float::int::number::int::IntNumber; +use i_float::int::number::product_uint::UIntProduct; +use i_float::int::number::uint::UIntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; use i_float::triangle::Triangle; -use i_float::u128::UInt128; pub(super) type CollinearMask = u8; @@ -52,8 +55,8 @@ impl EndMask for CollinearMask { } } -pub(super) struct CrossResult { - pub(super) point: IntPoint, +pub(super) struct CrossResult { + pub(super) point: IntPoint, pub(super) cross_type: CrossType, pub(super) is_round: bool, } @@ -65,19 +68,28 @@ pub(super) enum CrossType { Overlay, } -pub(super) struct CrossSolver {} +pub(super) struct CrossSolver { + phantom_data: PhantomData, +} + +impl CrossSolver { + pub(super) fn cross( + target: &XSegment, + other: &XSegment, + radius: I::Wide, + ) -> Option> { + let a0b0a1 = Triangle::clock_direction(target.a, target.b, other.a); + let a0b0b1 = Triangle::clock_direction(target.a, target.b, other.b); -impl CrossSolver { - pub(super) fn cross(target: &XSegment, other: &XSegment, radius: i64) -> Option { - let a0b0a1 = Triangle::clock_direction_point(target.a, target.b, other.a); - let a0b0b1 = Triangle::clock_direction_point(target.a, target.b, other.b); + let a1b1a0 = Triangle::clock_direction(other.a, other.b, target.a); + let a1b1b0 = Triangle::clock_direction(other.a, other.b, target.b); - let a1b1a0 = Triangle::clock_direction_point(other.a, other.b, target.a); - let a1b1b0 = Triangle::clock_direction_point(other.a, other.b, target.b); + let one = I::Wide::ONE; - let s = (1 & (a0b0a1 + 1)) + (1 & (a0b0b1 + 1)) + (1 & (a1b1a0 + 1)) + (1 & (a1b1b0 + 1)); + let s = + (one & (a0b0a1 + one)) + (one & (a0b0b1 + one)) + (one & (a1b1a0 + one)) + (one & (a1b1b0 + one)); - if s == 4 { + if s == I::Wide::FOUR { return Some(CrossResult { point: IntPoint::ZERO, cross_type: CrossType::Overlay, @@ -87,24 +99,24 @@ impl CrossSolver { let is_not_cross = a0b0a1 == a0b0b1 || a1b1a0 == a1b1b0; - if s > 1 || is_not_cross { + if s > I::Wide::ONE || is_not_cross { return None; } - if s != 0 { - return if a0b0a1 == 0 { + if s != I::Wide::ZERO { + return if a0b0a1 == I::Wide::ZERO { Some(CrossResult { point: other.a, cross_type: CrossType::OtherEnd, is_round: false, }) - } else if a0b0b1 == 0 { + } else if a0b0b1 == I::Wide::ZERO { Some(CrossResult { point: other.b, cross_type: CrossType::OtherEnd, is_round: false, }) - } else if a1b1a0 == 0 { + } else if a1b1a0 == I::Wide::ZERO { Some(CrossResult { point: target.a, cross_type: CrossType::TargetEnd, @@ -122,11 +134,11 @@ impl CrossSolver { Self::middle_cross(target, other, radius) } - pub(super) fn collinear(target: &XSegment, other: &XSegment) -> CollinearMask { - let a0 = FixVec::new_point(target.a); - let b0 = FixVec::new_point(target.b); - let a1 = FixVec::new_point(other.a); - let b1 = FixVec::new_point(other.b); + pub(super) fn collinear(target: &XSegment, other: &XSegment) -> CollinearMask { + let a0 = target.a; + let b0 = target.b; + let a1 = other.a; + let b1 = other.b; let v1 = b1 - a1; @@ -140,19 +152,19 @@ impl CrossSolver { let ba1 = -ab0; let bb1 = -bb0; - let is_target_a = aa0 == -ab0 && aa0 != 0; - let is_target_b = ba0 == -bb0 && ba0 != 0; + let is_target_a = aa0 == -ab0 && aa0 != I::Wide::ZERO; + let is_target_b = ba0 == -bb0 && ba0 != I::Wide::ZERO; - let is_other_a = aa1 == -ab1 && aa1 != 0; - let is_other_b = ba1 == -bb1 && ba1 != 0; + let is_other_a = aa1 == -ab1 && aa1 != I::Wide::ZERO; + let is_other_b = ba1 == -bb1 && ba1 != I::Wide::ZERO; CollinearMask::new(is_target_a, is_target_b, is_other_a, is_other_b) } - fn middle_cross(target: &XSegment, other: &XSegment, radius: i64) -> Option { + fn middle_cross(target: &XSegment, other: &XSegment, radius: I::Wide) -> Option> { let p = CrossSolver::cross_point(target, other); - if Triangle::is_line_point(target.a, p, target.b) && Triangle::is_line_point(other.a, p, other.b) { + if Triangle::is_line(target.a, p, target.b) && Triangle::is_line(other.a, p, other.b) { return Some(CrossResult { point: p, cross_type: CrossType::Pure, @@ -176,7 +188,7 @@ impl CrossSolver { if r0 <= r1 { let p = if ra0 < rb0 { target.a } else { target.b }; // ignore if it's a clean point - if Triangle::is_not_line_point(other.a, p, other.b) { + if Triangle::is_not_line(other.a, p, other.b) { return Some(CrossResult { point: p, cross_type: CrossType::TargetEnd, @@ -187,7 +199,7 @@ impl CrossSolver { let p = if ra1 < rb1 { other.a } else { other.b }; // ignore if it's a clean point - if Triangle::is_not_line_point(target.a, p, target.b) { + if Triangle::is_not_line(target.a, p, target.b) { return Some(CrossResult { point: p, cross_type: CrossType::OtherEnd, @@ -204,7 +216,7 @@ impl CrossSolver { }) } - fn cross_point(target: &XSegment, other: &XSegment) -> IntPoint { + fn cross_point(target: &XSegment, other: &XSegment) -> IntPoint { // edges are not parallel // any abs(x) and abs(y) < 2^30 // The result must be < 2^30 @@ -230,25 +242,25 @@ impl CrossSolver { // let x = kx / divider // let y = ky / divider // - // return FixVec(x, y) + // return IntPoint(x, y) // offset approach // move all picture by -a0. Point a0 will be equal (0, 0) // move a0.x to 0 // move all by a0.x - let a0x = target.a.x as i64; - let a0y = target.a.y as i64; + let a0x = target.a.x.wide(); + let a0y = target.a.y.wide(); - let a1x = target.b.x as i64 - a0x; - let b0x = other.a.x as i64 - a0x; - let b1x = other.b.x as i64 - a0x; + let a1x = target.b.x.wide() - a0x; + let b0x = other.a.x.wide() - a0x; + let b1x = other.b.x.wide() - a0x; // move a0.y to 0 // move all by a0.y - let a1y = target.b.y as i64 - a0y; - let b0y = other.a.y as i64 - a0y; - let b1y = other.b.y as i64 - a0y; + let a1y = target.b.y.wide() - a0y; + let b0y = other.a.y.wide() - a0y; + let b1y = other.b.y.wide() - a0y; let dy_b = b0y - b1y; let dx_b = b0x - b1x; @@ -256,18 +268,18 @@ impl CrossSolver { // let xyA = 0 let xy_b = b0x * b1y - b0y * b1x; - let x0: i64; - let y0: i64; + let x0: I::Wide; + let y0: I::Wide; // a1y and a1x cannot be zero simultaneously, because we will get edge a0<>a1 zero length and it is impossible - if a1x == 0 { + if a1x == I::Wide::ZERO { // dxB is not zero because it will be parallel case and it's impossible - x0 = 0; + x0 = I::Wide::ZERO; y0 = xy_b / dx_b; - } else if a1y == 0 { + } else if a1y == I::Wide::ZERO { // dyB is not zero because it will be parallel case and it's impossible - y0 = 0; + y0 = I::Wide::ZERO; x0 = -xy_b / dy_b; } else { // divider @@ -282,78 +294,30 @@ impl CrossSolver { let uxy_b = xy_b.unsigned_abs(); let udiv = div.unsigned_abs(); - let kx = UInt128::multiply(a1x.unsigned_abs(), uxy_b); - let ky = UInt128::multiply(a1y.unsigned_abs(), uxy_b); + let kx = ::Product::multiply(a1x.unsigned_abs(), uxy_b); + let ky = ::Product::multiply(a1y.unsigned_abs(), uxy_b); let ux = kx.divide_with_rounding(udiv); let uy = ky.divide_with_rounding(udiv); - // get i64 bit result - x0 = sx * ux as i64; - y0 = sy * uy as i64; + x0 = sx * I::Wide::from_uint(ux); + y0 = sy * I::Wide::from_uint(uy); } - let x = (x0 + a0x) as i32; - let y = (y0 + a0y) as i32; + let x = I::from_wide(x0 + a0x); + let y = I::from_wide(y0 + a0y); IntPoint::new(x, y) } } -const LAST_BIT_INDEX: usize = 63; - -trait RoundDivide { - fn divide_with_rounding(&self, divisor: u64) -> u64; -} - -impl RoundDivide for UInt128 { - fn divide_with_rounding(&self, divisor: u64) -> u64 { - if self.high == 0 { - let result = self.low / divisor; - let remainder = self.low - result * divisor; - return if remainder >= (divisor + 1) >> 1 { - result + 1 - } else { - result - }; - } - - let dn = divisor.leading_zeros(); - let norm_divisor = divisor << dn; - let mut norm_dividend_high = (self.high << dn) | (self.low >> (u64::BITS - dn)); - let mut norm_dividend_low = self.low << dn; - - let mut quotient = 0; - let one = 1 << LAST_BIT_INDEX; - - for _ in 0..u64::BITS { - let bit = (norm_dividend_high & one) != 0; - norm_dividend_high = (norm_dividend_high << 1) | (norm_dividend_low >> LAST_BIT_INDEX); - norm_dividend_low <<= 1; - quotient <<= 1; - if norm_dividend_high >= norm_divisor || bit { - norm_dividend_high = norm_dividend_high.wrapping_sub(norm_divisor); - quotient |= 1; - } - } - - // Check remainder for rounding - let remainder = (norm_dividend_high << (u64::BITS - dn)) | (norm_dividend_low >> dn); - if remainder >= (divisor + 1) >> 1 { - quotient += 1; - } - - quotient - } -} - #[cfg(test)] mod tests { use crate::geom::x_segment::XSegment; use crate::split::cross_solver::{CrossSolver, CrossType}; use i_float::int::point::IntPoint; - impl XSegment { + impl XSegment { fn new(a: IntPoint, b: IntPoint) -> Self { Self { a, b } } @@ -380,7 +344,7 @@ mod tests { #[test] fn test_big_cross_1() { - let s: i32 = 1024_000_000; + let s: i32 = 1_024_000_000; let ea = XSegment::new(IntPoint::new(-s, 0), IntPoint::new(s, 0)); let eb = XSegment::new(IntPoint::new(0, -s), IntPoint::new(0, s)); @@ -399,7 +363,7 @@ mod tests { #[test] fn test_big_cross_2() { - let s: i32 = 1024_000_000; + let s: i32 = 1_024_000_000; let ea = XSegment::new(IntPoint::new(-s, 0), IntPoint::new(s, 0)); let eb = XSegment::new(IntPoint::new(1024, -s), IntPoint::new(1024, s)); @@ -418,7 +382,7 @@ mod tests { #[test] fn test_big_cross_3() { - let s: i32 = 1024_000_000; + let s: i32 = 1_024_000_000; let q: i32 = s / 2; let ea = XSegment::new(IntPoint::new(-s, -s), IntPoint::new(s, s)); @@ -438,7 +402,7 @@ mod tests { #[test] fn test_left_end() { - let s: i32 = 1024_000_000; + let s: i32 = 1_024_000_000; let ea = XSegment::new(IntPoint::new(-s, 0), IntPoint::new(s, 0)); let eb = XSegment::new(IntPoint::new(-s, -s), IntPoint::new(-s, s)); @@ -457,7 +421,7 @@ mod tests { #[test] fn test_right_end() { - let s: i32 = 1024_000_000; + let s: i32 = 1_024_000_000; let ea = XSegment::new(IntPoint::new(-s, 0), IntPoint::new(s, 0)); let eb = XSegment::new(IntPoint::new(s, -s), IntPoint::new(s, s)); @@ -476,7 +440,7 @@ mod tests { #[test] fn test_left_top() { - let s: i32 = 1024_000_000; + let s: i32 = 1_024_000_000; let ea = XSegment::new(IntPoint::new(-s, s), IntPoint::new(s, s)); let eb = XSegment::new(IntPoint::new(-s, s), IntPoint::new(-s, -s)); @@ -509,7 +473,7 @@ mod tests { match result.cross_type { CrossType::Pure => { - assert_eq!(IntPoint::new(-1048691, -5244), result.point); + assert_eq!(IntPoint::new(-1048691, -5243), result.point); } _ => { panic!("Fail cross result"); diff --git a/iOverlay/src/split/fragment.rs b/iOverlay/src/split/fragment.rs index 63e898d7..5bab3561 100644 --- a/iOverlay/src/split/fragment.rs +++ b/iOverlay/src/split/fragment.rs @@ -1,16 +1,17 @@ use crate::geom::x_segment::XSegment; +use i_float::int::number::int::IntNumber; use i_float::int::rect::IntRect; -#[derive(Debug, Clone)] -pub(super) struct Fragment { +#[derive(Clone)] +pub(super) struct Fragment { pub(super) index: usize, - pub(super) rect: IntRect, - pub(super) x_segment: XSegment, + pub(super) rect: IntRect, + pub(super) x_segment: XSegment, } -impl Fragment { +impl Fragment { #[inline] - pub(super) fn with_index_and_segment(index: usize, x_segment: XSegment) -> Self { + pub(super) fn with_index_and_segment(index: usize, x_segment: XSegment) -> Self { let (min_y, max_y) = if x_segment.a.y < x_segment.b.y { (x_segment.a.y, x_segment.b.y) } else { diff --git a/iOverlay/src/split/grid_layout.rs b/iOverlay/src/split/grid_layout.rs index fc47f1c9..f56f25f0 100644 --- a/iOverlay/src/split/grid_layout.rs +++ b/iOverlay/src/split/grid_layout.rs @@ -3,24 +3,26 @@ use crate::geom::x_segment::XSegment; use crate::split::fragment::Fragment; use alloc::vec; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_float::int::number::uint::UIntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::rect::IntRect; -#[derive(Debug, Clone)] -pub(super) struct BorderVSegment { +pub(super) struct BorderVSegment { pub(super) id: usize, - pub(super) x: i32, - pub(super) y_range: LineRange, + pub(super) x: I, + pub(super) y_range: LineRange, } -pub(super) struct FragmentBuffer { - pub(super) layout: GridLayout, - pub(super) groups: Vec>, - pub(super) on_border: Vec, +pub(super) struct FragmentBuffer { + pub(super) layout: GridLayout, + pub(super) groups: Vec>>, + pub(super) on_border: Vec>, } -impl FragmentBuffer { +impl FragmentBuffer { #[inline] - pub(super) fn new(layout: GridLayout) -> Self { + pub(super) fn new(layout: GridLayout) -> Self { let n = layout.index(layout.max_x) + 1; Self { layout, @@ -29,15 +31,15 @@ impl FragmentBuffer { } } - pub(super) fn init_fragment_buffer(&mut self, iter: I) + pub(super) fn init_fragment_buffer(&mut self, iter: It) where - I: Iterator, + It: Iterator>, { let mut counts = vec![0; self.groups.len()]; for s in iter { let i0 = self.layout.index(s.a.x); if s.a.x < s.b.x { - let i1 = self.layout.index(s.b.x - 1); + let i1 = self.layout.index(s.b.x - I::ONE); for count in counts.iter_mut().take(i1).skip(i0) { *count += 1; } @@ -52,7 +54,7 @@ impl FragmentBuffer { } #[inline] - fn insert(&mut self, fragment: Fragment, bin_index: usize) { + fn insert(&mut self, fragment: Fragment, bin_index: usize) { debug_assert!(bin_index < self.groups.len()); // SAFETY: `bin_index` is produced by GridLayout::index(...) on coordinates clamped to // [min_x, max_x]. We sized `groups` to `index(max_x) + 1` in `new`, so @@ -62,7 +64,7 @@ impl FragmentBuffer { } #[inline] - fn insert_vertical(&mut self, fragment: Fragment, bin_index: usize) { + fn insert_vertical(&mut self, fragment: Fragment, bin_index: usize) { let x = fragment.x_segment.a.x; if bin_index > 0 && x == self.layout.pos(bin_index) { self.on_border.push(BorderVSegment { @@ -83,7 +85,7 @@ impl FragmentBuffer { } #[inline] - pub(super) fn add_segment(&mut self, segment_index: usize, s: XSegment) { + pub(super) fn add_segment(&mut self, segment_index: usize, s: XSegment) { if s.a.y == s.b.y { self.add_horizontal(segment_index, s); return; @@ -95,7 +97,7 @@ impl FragmentBuffer { return; } - let i1 = self.layout.index(s.b.x - 1); + let i1 = self.layout.index(s.b.x - I::ONE); if i0 >= i1 { self.insert(Fragment::with_index_and_segment(segment_index, s), i0); return; @@ -109,44 +111,46 @@ impl FragmentBuffer { let is_inc = s.a.y <= s.b.y; - let width = (s.b.x - s.a.x) as u64; - let height = (s.b.y - s.a.y).unsigned_abs() as u64; + let width = (s.b.x - s.a.x).to_uint(); + let height = (s.b.y.wide() - s.a.y.wide()).unsigned_abs(); let log = (width * height).ilog2(); - let p = 63 - log; + let p = I::WideUInt::LAST_BIT_INDEX - log; let k = (height << p) / width; - let mut w = (self.layout.pos(i0 + 1) - s.a.x) as u64; - let dw = 1 << self.layout.power; + let mut w = (self.layout.pos(i0 + 1) - s.a.x).to_uint(); + let dw = I::WideUInt::ONE << self.layout.power; for i in i0..i1 { let h_min = (w * k) >> p; let mut h_max = h_min; - while h_max * width < height * w { - h_max += 1; + + let hw = height * w; + if h_max * width < hw { + h_max = (hw + width - I::WideUInt::ONE) / width; } - let max_x = x0 + (w as i32); + let max_x = x0 + I::from_uint(w); let rect = if is_inc { - let max_y = y0 + h_max as i32; + let max_y = y0 + I::from_uint(h_max); let rect = IntRect { min_x: prev_x, max_x, min_y: prev_y, max_y, }; - prev_y = y0 + h_min as i32; + prev_y = y0 + I::from_uint(h_min); rect } else { - let min_y = y0 - h_max as i32; + let min_y = y0 - I::from_uint(h_max); let rect = IntRect { min_x: prev_x, max_x, min_y, max_y: prev_y, }; - prev_y = y0 - h_min as i32; + prev_y = y0 - I::from_uint(h_min); rect }; @@ -189,9 +193,9 @@ impl FragmentBuffer { ); } - fn add_horizontal(&mut self, segment_index: usize, s: XSegment) { + fn add_horizontal(&mut self, segment_index: usize, s: XSegment) { let i0 = self.layout.index(s.a.x); - let i1 = self.layout.index(s.b.x - 1); + let i1 = self.layout.index(s.b.x - I::ONE); let y = s.a.y; let mut x0 = s.a.x; @@ -244,26 +248,26 @@ impl FragmentBuffer { } } -pub(super) struct GridLayout { - min_x: i32, - max_x: i32, +pub(super) struct GridLayout { + min_x: I, + max_x: I, power: u32, } -impl GridLayout { +impl GridLayout { #[inline] - pub(super) fn index(&self, x: i32) -> usize { - ((x - self.min_x) >> self.power) as usize + pub(super) fn index(&self, x: I) -> usize { + ((x.wide() - self.min_x.wide()) >> self.power).to_usize() } #[inline] - pub(super) fn pos(&self, index: usize) -> i32 { - (index << self.power) as i32 + self.min_x + pub(super) fn pos(&self, index: usize) -> I { + I::from_usize(index << self.power) + self.min_x } - pub(super) fn new(iter: I, count: usize) -> Option + pub(super) fn new(iter: It, count: usize) -> Option where - I: Iterator, + It: Iterator>, { let mut iter = iter.peekable(); let first = iter.peek()?; @@ -280,9 +284,9 @@ impl GridLayout { Self::with_min_max(min_x, max_x, max_power) } - fn with_min_max(min_x: i32, max_x: i32, max_power: u32) -> Option { - let dx = max_x - min_x; - if dx < 4 { + fn with_min_max(min_x: I, max_x: I, max_power: u32) -> Option { + let dx = max_x.wide() - min_x.wide(); + if dx < I::Wide::FOUR { return None; } let log = dx.ilog2(); @@ -294,6 +298,8 @@ impl GridLayout { #[cfg(test)] mod tests { + #![allow(clippy::useless_vec)] + use crate::geom::x_segment::XSegment; use crate::split::grid_layout::vec; use crate::split::grid_layout::{FragmentBuffer, GridLayout}; @@ -1105,8 +1111,8 @@ mod tests { #[test] fn test_10() { let layout = GridLayout { - min_x: -1000_000, - max_x: 1000_000, + min_x: -1_000_000, + max_x: 1_000_000, power: 10, }; @@ -1134,10 +1140,10 @@ mod tests { let p2 = IntPoint::new(rect.min_x, rect.max_y); let p3 = IntPoint::new(rect.max_x, rect.max_y); - assert!(Triangle::is_cw_or_line_point(p0, segment.a, segment.b)); - assert!(Triangle::is_cw_or_line_point(p1, segment.a, segment.b)); - assert!(Triangle::is_cw_or_line_point(p2, segment.b, segment.a)); - assert!(Triangle::is_cw_or_line_point(p3, segment.b, segment.a)); + assert!(Triangle::is_cw_or_line(p0, segment.a, segment.b)); + assert!(Triangle::is_cw_or_line(p1, segment.a, segment.b)); + assert!(Triangle::is_cw_or_line(p2, segment.b, segment.a)); + assert!(Triangle::is_cw_or_line(p3, segment.b, segment.a)); } } @@ -1537,7 +1543,7 @@ mod tests { } #[inline] - fn rect_compare(a: &IntRect, b: &IntRect) { + fn rect_compare(a: &IntRect, b: &IntRect) { assert_eq!(a.min_x, b.min_x); assert_eq!(a.max_x, b.max_x); assert_eq!(a.min_y, b.min_y); @@ -1545,21 +1551,21 @@ mod tests { } #[inline] - fn validate_rect(segment: &XSegment, rect: &IntRect) { + fn validate_rect(segment: &XSegment, rect: &IntRect) { let p0 = IntPoint::new(rect.min_x, rect.min_y); let p1 = IntPoint::new(rect.max_x, rect.min_y); let p2 = IntPoint::new(rect.min_x, rect.max_y); let p3 = IntPoint::new(rect.max_x, rect.max_y); - let a0 = Triangle::area_two_point(p0, segment.a, segment.b); - let a1 = Triangle::area_two_point(p1, segment.a, segment.b); - let a2 = Triangle::area_two_point(p2, segment.b, segment.a); - let a3 = Triangle::area_two_point(p3, segment.b, segment.a); + let a0 = Triangle::area_two(p0, segment.a, segment.b); + let a1 = Triangle::area_two(p1, segment.a, segment.b); + let a2 = Triangle::area_two(p2, segment.b, segment.a); + let a3 = Triangle::area_two(p3, segment.b, segment.a); - assert!(a0 >= 0); - assert!(a1 >= 0); - assert!(a2 >= 0); - assert!(a3 >= 0); + assert!(a0 <= 0); + assert!(a1 <= 0); + assert!(a2 <= 0); + assert!(a3 <= 0); } #[inline] diff --git a/iOverlay/src/split/line_mark.rs b/iOverlay/src/split/line_mark.rs index 21e79318..15e806ea 100644 --- a/iOverlay/src/split/line_mark.rs +++ b/iOverlay/src/split/line_mark.rs @@ -1,20 +1,21 @@ use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; use i_key_sort::sort::one_key_cmp::OneKeyAndCmpSort; #[derive(Clone, Copy, PartialEq)] -pub(super) struct LineMark { +pub(super) struct LineMark { pub(super) index: usize, - pub(super) point: IntPoint, + pub(super) point: IntPoint, } -pub(super) trait SortMarkByIndexAndPoint { - fn sort_by_index_and_point(&mut self, parallel: bool, reusable_buffer: &mut Vec); +pub(super) trait SortMarkByIndexAndPoint { + fn sort_by_index_and_point(&mut self, parallel: bool, reusable_buffer: &mut Vec>); } -impl SortMarkByIndexAndPoint for [LineMark] { +impl SortMarkByIndexAndPoint for [LineMark] { #[inline] - fn sort_by_index_and_point(&mut self, parallel: bool, reusable_buffer: &mut Vec) { + fn sort_by_index_and_point(&mut self, parallel: bool, reusable_buffer: &mut Vec>) { self.sort_by_one_key_then_by_and_buffer( parallel, reusable_buffer, diff --git a/iOverlay/src/split/snap_radius.rs b/iOverlay/src/split/snap_radius.rs index 2a13b7ad..08eb92af 100644 --- a/iOverlay/src/split/snap_radius.rs +++ b/iOverlay/src/split/snap_radius.rs @@ -1,4 +1,6 @@ use crate::core::solver::Solver; +use i_float::int::number::int::IntNumber; +use i_float::int::number::wide_int::WideIntNumber; pub(super) struct SnapRadius { current: usize, @@ -10,8 +12,8 @@ impl SnapRadius { self.current = 60.min(self.current + self.step); } - pub(super) fn radius(&self) -> i64 { - 1 << self.current + pub(super) fn radius(&self) -> I::Wide { + I::Wide::ONE << self.current as u32 } } diff --git a/iOverlay/src/split/solver.rs b/iOverlay/src/split/solver.rs index 60ccc6fc..6da3267c 100644 --- a/iOverlay/src/split/solver.rs +++ b/iOverlay/src/split/solver.rs @@ -1,3 +1,4 @@ +use crate::core::edge_data::{EdgeDataSplit, OverlayEdgeData}; use crate::core::solver::Solver; use crate::geom::x_segment::XSegment; use crate::segm::merge::ShapeSegmentsMerge; @@ -7,61 +8,87 @@ use crate::segm::winding::WindingCount; use crate::split::cross_solver::{CrossSolver, CrossType, EndMask}; use crate::split::line_mark::{LineMark, SortMarkByIndexAndPoint}; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; +use i_tree::{Expiration, LayoutNumber}; -#[derive(Clone)] -pub(crate) struct SplitSolver { - pub(super) marks: Vec, +pub(crate) struct SplitSolver { + pub(super) marks: Vec>, } -impl SplitSolver { +impl SplitSolver { #[inline(always)] pub(crate) fn new() -> Self { Self { marks: Vec::new() } } +} + +impl SplitSolver +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ + #[inline] + pub(crate) fn split_segments>( + &mut self, + segments: &mut Vec>, + solver: &Solver, + ) -> bool { + let mut store = D::Store::default(); + self.split_segments_with_store(segments, solver, &mut store) + } #[inline] - pub(crate) fn split_segments( + pub(crate) fn split_segments_with_store>( &mut self, - segments: &mut Vec>, + segments: &mut Vec>, solver: &Solver, + store: &mut D::Store, ) -> bool { if segments.is_empty() { return false; } segments.sort_by_ab(solver.is_parallel_sort_allowed()); - let any_merged = segments.merge_if_needed(); - let any_intersection = self.split(segments, solver); + let any_merged = segments.merge_if_needed_with_store(store); + if segments.is_empty() { + return true; + } + let any_intersection = self.split(segments, solver, store); any_merged | any_intersection } #[inline] - fn split(&mut self, segments: &mut Vec>, solver: &Solver) -> bool { + fn split>( + &mut self, + segments: &mut Vec>, + solver: &Solver, + store: &mut D::Store, + ) -> bool { let is_list = solver.is_list_split(segments); let snap_radius = solver.snap_radius(); if is_list { - return self.list_split(snap_radius, segments, solver); + return self.list_split(snap_radius, segments, solver, store); } let is_fragmentation = solver.is_fragmentation_required(segments); if is_fragmentation { - self.fragment_split(snap_radius, segments, solver) + self.fragment_split(snap_radius, segments, solver, store) } else { - self.tree_split(snap_radius, segments, solver) + self.tree_split(snap_radius, segments, solver, store) } } pub(super) fn cross( i: usize, j: usize, - ei: &XSegment, - ej: &XSegment, - marks: &mut Vec, - radius: i64, + ei: &XSegment, + ej: &XSegment, + marks: &mut Vec>, + radius: I::Wide, ) -> bool { - let cross = if let Some(cross) = CrossSolver::cross(ei, ej, radius) { + let cross = if let Some(cross) = CrossSolver::::cross(ei, ej, radius) { cross } else { return false; @@ -91,7 +118,7 @@ impl SplitSolver { }); } CrossType::Overlay => { - let mask = CrossSolver::collinear(ei, ej); + let mask = CrossSolver::::collinear(ei, ej); if mask == 0 { return false; } @@ -129,11 +156,12 @@ impl SplitSolver { cross.is_round } - pub(super) fn apply( + pub(super) fn apply>( &mut self, - segments: &mut Vec>, - reusable_buffer: &mut Vec, + segments: &mut Vec>, + reusable_buffer: &mut Vec>, solver: &Solver, + store: &mut D::Store, ) { self.marks .sort_by_index_and_point(solver.is_parallel_sort_allowed(), reusable_buffer); @@ -161,40 +189,70 @@ impl SplitSolver { }; let count = s0.count; + let data = s0.data; let x_seg = s0.x_segment; if start + 1 == i { // single split - *s0 = Segment::create_and_validate(x_seg.a, m0.point, count); - let s1 = Segment::create_and_validate(m0.point, x_seg.b, count); + let (d0, d1) = data.split( + EdgeDataSplit { + a: x_seg.a, + p: m0.point, + b: x_seg.b, + }, + store, + ); + *s0 = Segment::create_and_validate_with_data(x_seg.a, m0.point, count, d0, store); + let s1 = Segment::create_and_validate_with_data(m0.point, x_seg.b, count, d1, store); segments.push(s1); continue; } - // we have servral points + // we have several points let sub_marks = &mut self.marks[start..i]; Self::sort_sub_marks(sub_marks, x_seg); let m0 = sub_marks[0]; - *s0 = Segment::create_and_validate(x_seg.a, m0.point, count); + let (d0, mut rest_data) = data.split( + EdgeDataSplit { + a: x_seg.a, + p: m0.point, + b: x_seg.b, + }, + store, + ); + *s0 = Segment::create_and_validate_with_data(x_seg.a, m0.point, count, d0, store); let mut p0 = m0.point; for mi in sub_marks.iter().skip(1) { - segments.push(Segment::create_and_validate(p0, mi.point, count)); + let (di, next_data) = rest_data.split( + EdgeDataSplit { + a: p0, + p: mi.point, + b: x_seg.b, + }, + store, + ); + segments.push(Segment::create_and_validate_with_data( + p0, mi.point, count, di, store, + )); + rest_data = next_data; p0 = mi.point; } - segments.push(Segment::create_and_validate(p0, x_seg.b, count)); + segments.push(Segment::create_and_validate_with_data( + p0, x_seg.b, count, rest_data, store, + )); } segments.sort_by_ab(solver.is_parallel_sort_allowed()); - segments.merge_if_needed(); + segments.merge_if_needed_with_store(store); } #[inline] - fn sort_sub_marks(marks: &mut [LineMark], x_seg: XSegment) { + fn sort_sub_marks(marks: &mut [LineMark], x_seg: XSegment) { let mut j0 = 0; let mut j = 1; @@ -224,7 +282,7 @@ impl SplitSolver { } #[inline] - fn y_range(j0: usize, j1: usize, s: XSegment, marks: &[LineMark]) -> (i32, i32) { + fn y_range(j0: usize, j1: usize, s: XSegment, marks: &[LineMark]) -> (I, I) { let y0 = if j0 == 0 { s.a.y } else { marks[j0 - 1].point.y }; let y1 = if j1 == marks.len() { s.b.y @@ -235,7 +293,7 @@ impl SplitSolver { } #[inline] - fn sort_sub_marks_by_y(y0: i32, y1: i32, marks: &mut [LineMark]) { + fn sort_sub_marks_by_y(y0: I, y1: I, marks: &mut [LineMark]) { // The x-coordinate is the same for every point // By default, the range should be sorted in ascending order by the y-coordinate. if y0 > y1 { diff --git a/iOverlay/src/split/solver_fragment.rs b/iOverlay/src/split/solver_fragment.rs index 01ac08a6..6a228ac5 100644 --- a/iOverlay/src/split/solver_fragment.rs +++ b/iOverlay/src/split/solver_fragment.rs @@ -1,3 +1,4 @@ +use crate::core::edge_data::OverlayEdgeData; use crate::core::solver::Solver; use crate::segm::segment::Segment; use crate::segm::winding::WindingCount; @@ -8,19 +9,26 @@ use crate::split::line_mark::LineMark; use crate::split::snap_radius::SnapRadius; use crate::split::solver::SplitSolver; use alloc::vec::Vec; - -impl SplitSolver { - pub(super) fn fragment_split( +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; +use i_tree::{Expiration, LayoutNumber}; + +impl SplitSolver +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ + pub(super) fn fragment_split>( &mut self, snap_radius: SnapRadius, - segments: &mut Vec>, + segments: &mut Vec>, solver: &Solver, + store: &mut D::Store, ) -> bool { let layout = if let Some(layout) = GridLayout::new(segments.iter().map(|it| it.x_segment), segments.len()) { layout } else { - return self.tree_split(snap_radius, segments, solver); + return self.tree_split(snap_radius, segments, solver, store); }; let mut reusable_buffer = Vec::new(); @@ -39,7 +47,7 @@ impl SplitSolver { buffer.add_segment(i, segment.x_segment); } - need_to_fix = self.process(snap_radius.radius(), &mut buffer, solver); + need_to_fix = self.process(snap_radius.radius::(), &mut buffer, solver); #[cfg(debug_assertions)] debug_assert!(buffer.is_on_border_sorted()); @@ -65,7 +73,7 @@ impl SplitSolver { any_intersection = true; buffer.clear(); - self.apply(segments, &mut reusable_buffer, solver); + self.apply(segments, &mut reusable_buffer, solver, store); snap_radius.increment(); } @@ -74,7 +82,7 @@ impl SplitSolver { } #[inline] - fn process(&mut self, radius: i64, buffer: &mut FragmentBuffer, _solver: &Solver) -> bool { + fn process(&mut self, radius: I::Wide, buffer: &mut FragmentBuffer, _solver: &Solver) -> bool { #[cfg(feature = "allow_multithreading")] { if _solver.multithreading.is_some() { @@ -86,37 +94,37 @@ impl SplitSolver { } #[inline] - fn serial_split(&mut self, radius: i64, buffer: &mut FragmentBuffer) -> bool { + fn serial_split(&mut self, radius: I::Wide, buffer: &mut FragmentBuffer) -> bool { let mut is_any_round = false; for group in buffer.groups.iter_mut() { if group.is_empty() { continue; } - let any_round = SplitSolver::bin_split(radius, group, &mut self.marks); + let any_round = Self::bin_split(radius, group, &mut self.marks); is_any_round = is_any_round || any_round; } is_any_round } #[cfg(feature = "allow_multithreading")] - fn parallel_split(&mut self, radius: i64, buffer: &mut FragmentBuffer) -> bool { + fn parallel_split(&mut self, radius: I::Wide, buffer: &mut FragmentBuffer) -> bool { use rayon::iter::IntoParallelRefMutIterator; use rayon::iter::ParallelIterator; - struct TaskResult { + struct TaskResult { any_round: bool, - marks: Vec, + marks: Vec>, } debug_assert!(!buffer.groups.is_empty(), "groups.len() >= 1"); let marks_capacity = self.marks.capacity() / buffer.groups.len(); - let results: Vec = buffer + let results: Vec> = buffer .groups .par_iter_mut() .map(|group| { let mut marks = Vec::with_capacity(marks_capacity); - let any_round = SplitSolver::bin_split(radius, group, &mut marks); + let any_round = Self::bin_split(radius, group, &mut marks); TaskResult { any_round, marks } }) .collect(); @@ -144,12 +152,12 @@ impl SplitSolver { is_any_round } - fn bin_split(radius: i64, fragments: &mut [Fragment], marks: &mut Vec) -> bool { + fn bin_split(radius: I::Wide, fragments: &mut [Fragment], marks: &mut Vec>) -> bool { if fragments.len() < 2 { return false; } - fragments.sort_unstable_by(|a, b| a.rect.min_y.cmp(&b.rect.min_y)); + fragments.sort_unstable_by_key(|a| a.rect.min_y); let mut any_round = false; @@ -179,9 +187,9 @@ impl SplitSolver { fn on_border_split( &mut self, - border_x: i32, - fragments: &[Fragment], - vertical_segments: &mut [BorderVSegment], + border_x: I, + fragments: &[Fragment], + vertical_segments: &mut [BorderVSegment], ) { let mut points = Vec::new(); for fragment in fragments.iter() { @@ -194,8 +202,8 @@ impl SplitSolver { return; } - points.sort_unstable_by(|p0, p1| p0.y.cmp(&p1.y)); - vertical_segments.sort_by(|s0, s1| s0.y_range.min.cmp(&s1.y_range.min)); + points.sort_unstable_by_key(|p0| p0.y); + vertical_segments.sort_by_key(|s0| s0.y_range.min); let mut i = 0; for s in vertical_segments.iter() { @@ -213,18 +221,23 @@ impl SplitSolver { } } - fn cross_fragments(fi: &Fragment, fj: &Fragment, radius: i64, marks: &mut Vec) -> bool { - let cross = if let Some(cross) = CrossSolver::cross(&fi.x_segment, &fj.x_segment, radius) { + fn cross_fragments( + fi: &Fragment, + fj: &Fragment, + radius: I::Wide, + marks: &mut Vec>, + ) -> bool { + let cross = if let Some(cross) = CrossSolver::::cross(&fi.x_segment, &fj.x_segment, radius) { cross } else { return false; }; - let r = radius as i32; + let r = I::from_wide(radius); match cross.cross_type { CrossType::Overlay => { - let mask = CrossSolver::collinear(&fi.x_segment, &fj.x_segment); + let mask = CrossSolver::::collinear(&fi.x_segment, &fj.x_segment); if mask == 0 { return false; } diff --git a/iOverlay/src/split/solver_list.rs b/iOverlay/src/split/solver_list.rs index 039bfadd..794081c7 100644 --- a/iOverlay/src/split/solver_list.rs +++ b/iOverlay/src/split/solver_list.rs @@ -1,16 +1,24 @@ +use crate::core::edge_data::OverlayEdgeData; use crate::core::solver::Solver; use crate::segm::segment::Segment; use crate::segm::winding::WindingCount; use crate::split::snap_radius::SnapRadius; use crate::split::solver::SplitSolver; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; +use i_tree::{Expiration, LayoutNumber}; -impl SplitSolver { - pub(super) fn list_split( +impl SplitSolver +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ + pub(super) fn list_split>( &mut self, snap_radius: SnapRadius, - segments: &mut Vec>, + segments: &mut Vec>, solver: &Solver, + store: &mut D::Store, ) -> bool { let mut need_to_fix = true; @@ -22,7 +30,7 @@ impl SplitSolver { need_to_fix = false; self.marks.clear(); - let radius: i64 = snap_radius.radius(); + let radius = snap_radius.radius::(); for (i, si) in segments.iter().enumerate() { let xsi = &si.x_segment; @@ -37,7 +45,7 @@ impl SplitSolver { continue; } - let is_round = SplitSolver::cross(i, j, xsi, xsj, &mut self.marks, radius); + let is_round = Self::cross(i, j, xsi, xsj, &mut self.marks, radius); need_to_fix = need_to_fix || is_round } } @@ -46,13 +54,13 @@ impl SplitSolver { return any_intersection; } any_intersection = true; - self.apply(segments, &mut reusable_buffer, solver); + self.apply(segments, &mut reusable_buffer, solver, store); snap_radius.increment(); if need_to_fix && !solver.is_list_split(segments) { // finish with tree solver if edges is become large - self.tree_split(snap_radius, segments, solver); + self.tree_split(snap_radius, segments, solver, store); return true; } } diff --git a/iOverlay/src/split/solver_tree.rs b/iOverlay/src/split/solver_tree.rs index 83f9509e..1f69555d 100644 --- a/iOverlay/src/split/solver_tree.rs +++ b/iOverlay/src/split/solver_tree.rs @@ -1,3 +1,4 @@ +use crate::core::edge_data::OverlayEdgeData; use crate::core::solver::Solver; use crate::geom::line_range::LineRange; use crate::geom::x_segment::XSegment; @@ -6,35 +7,46 @@ use crate::segm::winding::WindingCount; use crate::split::snap_radius::SnapRadius; use crate::split::solver::SplitSolver; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_tree::ExpiredVal; use i_tree::seg::exp::{SegExpCollection, SegRange}; use i_tree::seg::tree::SegExpTree; +use i_tree::{Expiration, LayoutNumber}; -#[derive(Debug, Clone, Copy)] -struct IdSegment { +#[derive(Clone, Copy)] +struct IdSegment { id: usize, - x_segment: XSegment, + x_segment: XSegment, } -impl ExpiredVal for IdSegment { +impl ExpiredVal for IdSegment { #[inline] - fn expiration(&self) -> i32 { + fn expiration(&self) -> I { self.x_segment.b.x } } -impl SplitSolver { - pub(super) fn tree_split( +impl SplitSolver +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ + pub(super) fn tree_split>( &mut self, snap_radius: SnapRadius, - segments: &mut Vec>, + segments: &mut Vec>, solver: &Solver, + store: &mut D::Store, ) -> bool { - let range: SegRange = segments.ver_range().into(); - let mut tree: SegExpTree = if let Some(tree) = SegExpTree::new(range) { + let range: SegRange = if let Some(range) = segments.ver_range() { + range.into() + } else { + return false; + }; + let mut tree: SegExpTree> = if let Some(tree) = SegExpTree::new(range) { tree } else { - return self.list_split(snap_radius, segments, solver); + return self.list_split(snap_radius, segments, solver, store); }; let mut reusable_buffer = Vec::new(); @@ -48,7 +60,7 @@ impl SplitSolver { need_to_fix = false; self.marks.clear(); - let radius = snap_radius.radius(); + let radius = snap_radius.radius::(); for (i, si) in segments.iter().enumerate() { let time = si.x_segment.a.x; @@ -60,8 +72,7 @@ impl SplitSolver { (sj.id, i, &sj.x_segment, &si.x_segment) }; - let is_round = - SplitSolver::cross(this_index, scan_index, this, scan, &mut self.marks, radius); + let is_round = Self::cross(this_index, scan_index, this, scan, &mut self.marks, radius); need_to_fix = is_round || need_to_fix; } @@ -76,7 +87,7 @@ impl SplitSolver { any_intersection = true; tree.clear(); - self.apply(segments, &mut reusable_buffer, solver); + self.apply(segments, &mut reusable_buffer, solver, store); snap_radius.increment(); } @@ -85,9 +96,9 @@ impl SplitSolver { } } -impl From for SegRange { +impl From> for SegRange { #[inline] - fn from(value: LineRange) -> Self { + fn from(value: LineRange) -> Self { Self { min: value.min, max: value.max, @@ -96,12 +107,16 @@ impl From for SegRange { } trait VerticalRange { - fn ver_range(&self) -> LineRange; + type Int: IntNumber; + + fn ver_range(&self) -> Option>; } -impl VerticalRange for Vec> { - fn ver_range(&self) -> LineRange { - let mut min_y = self[0].x_segment.a.y; +impl VerticalRange for Vec> { + type Int = I; + + fn ver_range(&self) -> Option> { + let mut min_y = self.first()?.x_segment.a.y; let mut max_y = min_y; for edge in self.iter() { @@ -111,16 +126,16 @@ impl VerticalRange for Vec> { max_y = max_y.max(edge.x_segment.b.y); } - LineRange { + Some(LineRange { min: min_y, max: max_y, - } + }) } } -impl Segment { +impl Segment { #[inline] - fn id_segment(&self, id: usize) -> IdSegment { + fn id_segment(&self, id: usize) -> IdSegment { IdSegment { id, x_segment: self.x_segment, diff --git a/iOverlay/src/string/clip.rs b/iOverlay/src/string/clip.rs index 8066212a..e45f0f12 100644 --- a/iOverlay/src/string/clip.rs +++ b/iOverlay/src/string/clip.rs @@ -7,9 +7,12 @@ use crate::string::graph::StringGraph; use crate::string::line::IntLine; use crate::string::overlay::StringOverlay; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; +use i_key_sort::sort::key::SortKey; use i_shape::int::path::IntPath; use i_shape::int::shape::{IntShape, IntShapes}; +use i_tree::{Expiration, LayoutNumber}; #[derive(Debug, Clone, Copy)] pub struct ClipRule { @@ -20,9 +23,9 @@ pub struct ClipRule { pub boundary_included: bool, } -impl StringGraph<'_> { +impl StringGraph<'_, I> { #[inline] - pub(super) fn into_clip_string_lines(self) -> Vec { + pub(super) fn into_clip_string_lines(self) -> Vec> { let mut paths = Vec::new(); let links = self.links; @@ -61,10 +64,10 @@ impl StringGraph<'_> { #[inline] fn find_next_point( nodes: &[Vec], - links: &mut [OverlayLink], - a: IdPoint, + links: &mut [OverlayLink], + a: IdPoint, is_out_node: bool, - ) -> Option { + ) -> Option> { let node = unsafe { // SAFETY: a.id comes from an existing link endpoint, so it indexes nodes. nodes.get_unchecked(a.id) @@ -89,7 +92,7 @@ const CLIP_BACK: SegmentFill = STRING_BACK_CLIP << 2; const CLIP_FORWARD: SegmentFill = STRING_FORWARD_CLIP << 2; const CLIP_ALL: SegmentFill = CLIP_BACK | CLIP_FORWARD; -impl OverlayLink { +impl OverlayLink { #[inline] fn visit_if_possible(&mut self, is_forward: bool) -> bool { if is_forward { @@ -119,15 +122,15 @@ impl OverlayLink { } } -pub trait IntClip { +pub trait IntClip { /// Clips a single line according to the specified build and clip rules. /// - `line`: The line to be clipped, represented by two points. /// - `fill_rule`: Specifies the rule determining the filled areas, influencing the inclusion of line segments. /// - `clip_rule`: The rule for clipping, determining how the boundary and inversion settings affect the result. /// /// # Returns - /// A vector of `IntPath` instances representing the clipped sections of the input line. - fn clip_line(&self, line: IntLine, fill_rule: FillRule, clip_rule: ClipRule) -> Vec; + /// A vector of `IntPath` instances representing the clipped sections of the input line. + fn clip_line(&self, line: IntLine, fill_rule: FillRule, clip_rule: ClipRule) -> Vec>; /// Clips multiple lines according to the specified build and clip rules. /// - `lines`: A slice of `IntLine` instances representing lines to be clipped. @@ -135,112 +138,121 @@ pub trait IntClip { /// - `clip_rule`: The rule for clipping, determining how boundary and inversion settings affect the results. /// /// # Returns - /// A vector of `IntPath` instances containing the clipped portions of the input lines. - fn clip_lines(&self, lines: &[IntLine], fill_rule: FillRule, clip_rule: ClipRule) -> Vec; + /// A vector of `IntPath` instances containing the clipped portions of the input lines. + fn clip_lines(&self, lines: &[IntLine], fill_rule: FillRule, clip_rule: ClipRule) -> Vec>; /// Clips a single path according to the specified build and clip rules. - /// - `path`: A reference to an `IntPath`, which is a sequence of points representing the path to be clipped. + /// - `path`: A reference to an `IntPath`, which is a sequence of points representing the path to be clipped. /// - `fill_rule`: Specifies the rule determining the filled areas, influencing the inclusion of path segments. /// - `clip_rule`: The rule for clipping, determining how boundary and inversion settings affect the result. /// /// # Returns - /// A vector of `IntPath` instances representing the clipped sections of the path. - fn clip_path(&self, path: &IntPath, fill_rule: FillRule, clip_rule: ClipRule) -> Vec; + /// A vector of `IntPath` instances representing the clipped sections of the path. + fn clip_path(&self, path: &IntPath, fill_rule: FillRule, clip_rule: ClipRule) -> Vec>; /// Clips multiple paths according to the specified build and clip rules. - /// - `paths`: A slice of `IntPath` instances, each representing a path to be clipped. + /// - `paths`: A slice of `IntPath` instances, each representing a path to be clipped. /// - `fill_rule`: Specifies the rule determining the filled areas, influencing the inclusion of path segments. /// - `clip_rule`: The rule for clipping, determining how boundary and inversion settings affect the result. /// /// # Returns - /// A vector of `IntPath` instances containing the clipped portions of the input paths. - fn clip_paths(&self, paths: &[IntPath], fill_rule: FillRule, clip_rule: ClipRule) -> Vec; + /// A vector of `IntPath` instances containing the clipped portions of the input paths. + fn clip_paths(&self, paths: &[IntPath], fill_rule: FillRule, clip_rule: ClipRule) -> Vec>; } -impl IntClip for IntShapes { +impl IntClip for IntShapes +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn clip_line(&self, line: IntLine, fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_line(&self, line: IntLine, fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shapes(self); overlay.add_string_line(line); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_lines(&self, lines: &[IntLine], fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_lines(&self, lines: &[IntLine], fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shapes(self); overlay.add_string_lines(lines); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_path(&self, path: &IntPath, fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_path(&self, path: &IntPath, fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shapes(self); overlay.add_string_path(path); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_paths(&self, paths: &[IntPath], fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_paths(&self, paths: &[IntPath], fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shapes(self); overlay.add_string_paths(paths); overlay.clip_string_lines(fill_rule, clip_rule) } } -impl IntClip for IntShape { +impl IntClip for IntShape +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn clip_line(&self, line: IntLine, fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_line(&self, line: IntLine, fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shape(self); overlay.add_string_line(line); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_lines(&self, lines: &[IntLine], fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_lines(&self, lines: &[IntLine], fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shape(self); overlay.add_string_lines(lines); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_path(&self, path: &IntPath, fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_path(&self, path: &IntPath, fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shape(self); overlay.add_string_path(path); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_paths(&self, paths: &[IntPath], fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_paths(&self, paths: &[IntPath], fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shape(self); overlay.add_string_paths(paths); overlay.clip_string_lines(fill_rule, clip_rule) } } -impl IntClip for [IntPoint] { +impl IntClip for [IntPoint] +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn clip_line(&self, line: IntLine, fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_line(&self, line: IntLine, fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shape_contour(self); overlay.add_string_line(line); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_lines(&self, lines: &[IntLine], fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_lines(&self, lines: &[IntLine], fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shape_contour(self); overlay.add_string_lines(lines); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_path(&self, path: &IntPath, fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_path(&self, path: &IntPath, fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shape_contour(self); overlay.add_string_path(path); overlay.clip_string_lines(fill_rule, clip_rule) } #[inline] - fn clip_paths(&self, paths: &[IntPath], fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + fn clip_paths(&self, paths: &[IntPath], fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { let mut overlay = StringOverlay::with_shape_contour(self); overlay.add_string_paths(paths); overlay.clip_string_lines(fill_rule, clip_rule) @@ -249,6 +261,8 @@ impl IntClip for [IntPoint] { #[cfg(test)] mod tests { + #![allow(clippy::useless_vec)] + use crate::core::fill_rule::FillRule; use crate::string::clip::{ClipRule, IntClip}; use alloc::vec; @@ -257,7 +271,7 @@ mod tests { #[test] fn test_empty_path() { - let path: IntPath = vec![]; + let path: IntPath = vec![]; let result_0 = path.clip_line( [IntPoint::new(0, 0), IntPoint::new(0, 0)], FillRule::NonZero, @@ -311,6 +325,27 @@ mod tests { assert_eq!(result_1.len(), 2); } + #[test] + fn test_i64_clip() { + let path = vec![ + IntPoint::::new(-10, -10), + IntPoint::new(-10, 10), + IntPoint::new(10, 10), + IntPoint::new(10, -10), + ]; + + let result = path.clip_line( + [IntPoint::new(0, -15), IntPoint::new(0, 15)], + FillRule::NonZero, + ClipRule { + invert: false, + boundary_included: false, + }, + ); + + assert_eq!(result.len(), 1); + } + #[test] fn test_boundary() { let path = vec![ diff --git a/iOverlay/src/string/extract.rs b/iOverlay/src/string/extract.rs index fdf22da1..0d063d5f 100644 --- a/iOverlay/src/string/extract.rs +++ b/iOverlay/src/string/extract.rs @@ -7,21 +7,24 @@ use crate::string::rule::StringRule; use crate::string::split::{BinStore, Split}; use alloc::vec; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_key_sort::sort::key::SortKey; use i_shape::int::path::{ContourExtension, IntPath}; use i_shape::int::shape::IntShapes; +use i_tree::Expiration; -impl StringGraph<'_> { +impl StringGraph<'_, I> { /// Extracts shapes from the graph based on the specified `StringRule`. /// - `string_rule`: The rule used to determine how shapes are extracted. /// # Shape Representation - /// The output is a `IntShapes`, where: - /// - The outer `Vec` represents a set of shapes. - /// - Each shape `Vec` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. + /// The output is a `IntShapes`, where: + /// - The outer `Vec>` represents a set of shapes. + /// - Each shape `Vec>` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. /// - Each path `Vec` is a sequence of points, forming a closed path. /// /// Note: Outer boundary paths have a counterclockwise order, and holes have a clockwise order. #[inline(always)] - pub fn extract_shapes(&self, string_rule: StringRule) -> IntShapes { + pub fn extract_shapes(&self, string_rule: StringRule) -> IntShapes { self.extract_shapes_custom(string_rule, Default::default()) } @@ -29,15 +32,19 @@ impl StringGraph<'_> { /// - `string_rule`: The rule used to determine how shapes are extracted. /// - `main_direction`: Winding direction for the **output** main (outer) contour. All hole contours will automatically use the opposite direction. Impact on **output** only! /// - `min_area`: The minimum area that a shape must have to be included in the results. Shapes smaller than this will be excluded. - /// - Returns: A vector of `IntShape`, representing the geometric result of the applied overlay rule. + /// - Returns: A vector of `IntShape`, representing the geometric result of the applied overlay rule. /// # Shape Representation - /// The output is a `IntShapes`, where: - /// - The outer `Vec` represents a set of shapes. - /// - Each shape `Vec` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. + /// The output is a `IntShapes`, where: + /// - The outer `Vec>` represents a set of shapes. + /// - Each shape `Vec>` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary. /// - Each path `Vec` is a sequence of points, forming a closed path. /// /// Note: Outer boundary paths have a **main_direction** order, and holes have an opposite to **main_direction** order. - pub fn extract_shapes_custom(&self, string_rule: StringRule, options: IntOverlayOptions) -> IntShapes { + pub fn extract_shapes_custom( + &self, + string_rule: StringRule, + options: IntOverlayOptions, + ) -> IntShapes { let clockwise = options.output_direction == ContourDirection::Clockwise; let mut fills = self.filter(string_rule); let mut shapes = Vec::new(); @@ -85,7 +92,7 @@ impl StringGraph<'_> { } #[inline] - fn get_paths(&self, start_index: usize, clockwise: bool, fills: &mut [u8]) -> IntPath { + fn get_paths(&self, start_index: usize, clockwise: bool, fills: &mut [u8]) -> IntPath { let start_link = unsafe { // SAFETY: start_index originates from iterating the fills array, which mirrors links. self.links.get_unchecked(start_index) @@ -95,7 +102,7 @@ impl StringGraph<'_> { let mut node_id = start_link.b.id; let last_node_id = start_link.a.id; - let mut path = IntPath::new(); + let mut path = IntPath::::new(); path.push(start_link.a.point); fills[start_index] = start_link.visit_fill(fills[start_index], start_link.a.id, clockwise); @@ -258,4 +265,29 @@ mod tests { assert_eq!(r.len(), 2); } + + #[test] + fn test_i64_overlay() { + let paths = vec![vec![ + IntPoint::::new(-10, 10), + IntPoint::new(-10, -10), + IntPoint::new(10, -10), + IntPoint::new(10, 10), + ]]; + + let window = vec![ + IntPoint::new(-5, -5), + IntPoint::new(-5, 5), + IntPoint::new(5, 5), + IntPoint::new(5, -5), + ]; + + let mut overlay = StringOverlay::::with_shape(&paths); + overlay.add_string_contour(&window); + let graph = overlay.build_graph_view(FillRule::NonZero).unwrap(); + + let r = graph.extract_shapes_custom(StringRule::Slice, Default::default()); + + assert_eq!(r.len(), 2); + } } diff --git a/iOverlay/src/string/filter.rs b/iOverlay/src/string/filter.rs index c02815ac..fe460dd9 100644 --- a/iOverlay/src/string/filter.rs +++ b/iOverlay/src/string/filter.rs @@ -3,8 +3,9 @@ use crate::segm::segment::{SUBJ_BOTH, SUBJ_BOTTOM, SUBJ_TOP}; use crate::string::graph::StringGraph; use crate::string::rule::StringRule; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; -impl OverlayLink { +impl OverlayLink { #[inline] pub(super) fn visit_fill(&self, fill: u8, node_id: usize, clockwise: bool) -> u8 { let is_a = self.a.id == node_id; @@ -42,7 +43,7 @@ impl OverlayLink { } } -impl StringGraph<'_> { +impl StringGraph<'_, I> { #[inline(always)] pub(super) fn filter(&self, ext_rule: StringRule) -> Vec { match ext_rule { diff --git a/iOverlay/src/string/graph.rs b/iOverlay/src/string/graph.rs index 901c2393..d8340cfc 100644 --- a/iOverlay/src/string/graph.rs +++ b/iOverlay/src/string/graph.rs @@ -1,10 +1,11 @@ use crate::build::builder::GraphNode; use crate::core::link::OverlayLink; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; -pub struct StringGraph<'a> { +pub struct StringGraph<'a, I: IntNumber> { pub(crate) nodes: &'a [Vec], - pub(crate) links: &'a mut [OverlayLink], + pub(crate) links: &'a mut [OverlayLink], } impl GraphNode for Vec { diff --git a/iOverlay/src/string/line.rs b/iOverlay/src/string/line.rs index eca01ccb..0dca5dce 100644 --- a/iOverlay/src/string/line.rs +++ b/iOverlay/src/string/line.rs @@ -1,3 +1,3 @@ use i_float::int::point::IntPoint; -pub type IntLine = [IntPoint; 2]; +pub type IntLine = [IntPoint; 2]; diff --git a/iOverlay/src/string/overlay.rs b/iOverlay/src/string/overlay.rs index 631da415..62d9e4cf 100644 --- a/iOverlay/src/string/overlay.rs +++ b/iOverlay/src/string/overlay.rs @@ -14,19 +14,25 @@ use crate::string::graph::StringGraph; use crate::string::line::IntLine; 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_shape::int::count::PointsCount; use i_shape::int::path::IntPath; use i_shape::int::shape::{IntContour, IntShape}; +use i_tree::{Expiration, LayoutNumber}; -pub struct StringOverlay { - pub options: IntOverlayOptions, - pub(super) segments: Vec>, - pub(crate) split_solver: SplitSolver, - pub(crate) graph_builder: GraphBuilder>, +pub struct StringOverlay { + pub options: IntOverlayOptions, + pub(super) segments: Vec>, + pub(crate) split_solver: SplitSolver, + pub(crate) graph_builder: GraphBuilder, I>, } -impl StringOverlay { +impl StringOverlay +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ /// Constructs a new `StringOverlay` instance, initializing it with a capacity that should closely match the total count of edges from all shapes being processed. /// This pre-allocation helps in optimizing memory usage and performance. /// - `capacity`: The initial capacity for storing edge data. Ideally, this should be set to the sum of the edges of all shapes to be added to the overlay, ensuring efficient data management. @@ -36,7 +42,7 @@ impl StringOverlay { options: Default::default(), segments: Vec::with_capacity(capacity), split_solver: SplitSolver::new(), - graph_builder: GraphBuilder::>::new(), + graph_builder: GraphBuilder::, I>::new(), } } @@ -44,46 +50,46 @@ impl StringOverlay { /// This pre-allocation helps in optimizing memory usage and performance. /// - `capacity`: The initial capacity for storing edge data. Ideally, this should be set to the sum of the edges of all shapes to be added to the overlay, ensuring efficient data management. /// - `options`: Adjust custom behavior. - pub fn with_options(capacity: usize, options: IntOverlayOptions) -> Self { + pub fn with_options(capacity: usize, options: IntOverlayOptions) -> Self { Self { options, segments: Vec::with_capacity(capacity), split_solver: SplitSolver::new(), - graph_builder: GraphBuilder::>::new(), + graph_builder: GraphBuilder::, I>::new(), } } /// Creates a new `StringOverlay` instance and initializes it with a single shape contour. /// - `contour`: An array of points that form a closed path. #[inline] - pub fn with_shape_contour(contour: &[IntPoint]) -> Self { + pub fn with_shape_contour(contour: &[IntPoint]) -> Self { let mut overlay = Self::new(contour.len()); overlay.add_shape_contour(contour); overlay } /// Creates a new `StringOverlay` instance and initializes it with multiple shape contours. - /// - `contours`: An array of `IntContour` instances to be added to the overlay. + /// - `contours`: An array of `IntContour` instances to be added to the overlay. #[inline] - pub fn with_shape_contours(contours: &[IntContour]) -> Self { + pub fn with_shape_contours(contours: &[IntContour]) -> Self { let mut overlay = Self::new(contours.points_count()); overlay.add_shape_contours(contours); overlay } /// Creates a new `StringOverlay` instance and initializes it with s shape. - /// - `shape`: An `IntShape` instances to be added to the overlay. + /// - `shape`: An `IntShape` instances to be added to the overlay. #[inline] - pub fn with_shape(shape: &[IntContour]) -> Self { + pub fn with_shape(shape: &[IntContour]) -> Self { let mut overlay = Self::new(shape.points_count()); overlay.add_shape_contours(shape); overlay } /// Creates a new `StringOverlay` instance and initializes it with subject and clip shapes. - /// - `shapes`: An array of `IntShape` instances to be added to the overlay. + /// - `shapes`: An array of `IntShape` instances to be added to the overlay. #[inline] - pub fn with_shapes(shapes: &[IntShape]) -> Self { + pub fn with_shapes(shapes: &[IntShape]) -> Self { let mut overlay = Self::new(shapes.points_count()); overlay.add_shapes(shapes); overlay @@ -94,29 +100,29 @@ impl StringOverlay { /// when paths are not directly stored in a collection. /// - `iter`: An iterator over references to `IntPoint` that defines the path. #[inline] - pub fn add_shape_contour_iter>(&mut self, iter: I) { + pub fn add_shape_contour_iter>>(&mut self, iter: It) { self.segments.append_path_iter(iter, ShapeType::Subject, false); } /// Adds a single path to the overlay as a shape paths. /// - `contour`: An array of points that form a closed path. #[inline] - pub fn add_shape_contour(&mut self, contour: &[IntPoint]) { + pub fn add_shape_contour(&mut self, contour: &[IntPoint]) { self.add_shape_contour_iter(contour.iter().copied()); } /// Adds multiple paths to the overlay as shape paths. - /// - `contours`: An array of `IntContour` instances to be added to the overlay. - pub fn add_shape_contours(&mut self, contours: &[IntContour]) { + /// - `contours`: An array of `IntContour` instances to be added to the overlay. + pub fn add_shape_contours(&mut self, contours: &[IntContour]) { for contour in contours.iter() { self.add_shape_contour(contour); } } /// Adds a list of shape to the overlay. - /// - `shapes`: An array of `IntShape` instances to be added to the overlay. + /// - `shapes`: An array of `IntShape` instances to be added to the overlay. #[inline] - pub fn add_shapes(&mut self, shapes: &[IntShape]) { + pub fn add_shapes(&mut self, shapes: &[IntShape]) { for shape in shapes { self.add_shape_contours(shape); } @@ -125,7 +131,7 @@ impl StringOverlay { /// Adds a single line (open path) to the overlay. /// - `line`: An `IntLine` representing the open line (defined by two points). #[inline] - pub fn add_string_line(&mut self, line: IntLine) { + pub fn add_string_line(&mut self, line: IntLine) { let a = line[0]; let b = line[1]; let segment = match a.cmp(&b) { @@ -135,6 +141,7 @@ impl StringOverlay { subj: 0, clip: STRING_BACK_CLIP, }, + data: (), }, Ordering::Greater => Segment { x_segment: XSegment { a: b, b: a }, @@ -142,6 +149,7 @@ impl StringOverlay { subj: 0, clip: STRING_FORWARD_CLIP, }, + data: (), }, Ordering::Equal => return, }; @@ -152,7 +160,7 @@ impl StringOverlay { /// Adds multiple lines (open paths) to the overlay. /// - `lines`: An array of `IntLine` instances to be added. #[inline] - pub fn add_string_lines(&mut self, lines: &[IntLine]) { + pub fn add_string_lines(&mut self, lines: &[IntLine]) { for &line in lines { self.add_string_line(line); } @@ -161,7 +169,7 @@ impl StringOverlay { /// Adds a string path to the overlay. /// - `path`: A path representing a string line. #[inline] - pub fn add_string_path(&mut self, path: &[IntPoint]) { + pub fn add_string_path(&mut self, path: &[IntPoint]) { if path.len() < 2 { return; } @@ -179,7 +187,7 @@ impl StringOverlay { /// Adds a string line contour to the overlay. /// - `contour`: A contour representing a string line closed path. This path is interpreted as closed, so it doesn’t require the start and endpoint to be the same for processing. #[inline] - pub fn add_string_contour(&mut self, contour: &[IntPoint]) { + pub fn add_string_contour(&mut self, contour: &[IntPoint]) { if contour.len() < 2 { return; } @@ -197,7 +205,7 @@ impl StringOverlay { /// Adds a string line paths to the overlay. /// - `paths`: A collection of paths, each representing a string line. #[inline] - pub fn add_string_paths(&mut self, paths: &[IntPath]) { + pub fn add_string_paths(&mut self, paths: &[IntPath]) { for path in paths { self.add_string_path(path); } @@ -206,7 +214,7 @@ impl StringOverlay { /// Adds a string line contours to the overlay. /// - `contours`: A collection of contours, each representing a string line closed path. #[inline] - pub fn add_string_contours(&mut self, contours: &[IntContour]) { + pub fn add_string_contours(&mut self, contours: &[IntContour]) { for contour in contours { self.add_string_contour(contour); } @@ -216,9 +224,9 @@ impl StringOverlay { /// - `fill_rule`: Specifies the rule determining the filled areas, influencing the inclusion of line segments. /// - `clip_rule`: The rule for clipping, determining how the boundary and inversion settings affect the result. /// # Returns - /// A vector of `IntPath` instances representing the clipped sections of the input lines. + /// A vector of `IntPath` instances representing the clipped sections of the input lines. #[inline] - pub fn clip_string_lines(self, fill_rule: FillRule, clip_rule: ClipRule) -> Vec { + pub fn clip_string_lines(self, fill_rule: FillRule, clip_rule: ClipRule) -> Vec> { self.clip_string_lines_with_solver(fill_rule, clip_rule, Default::default()) } @@ -228,14 +236,14 @@ impl StringOverlay { /// - `solver`: A solver type to be used for advanced control over the graph building process. /// /// # Returns - /// A vector of `IntPath` instances representing the clipped sections of the input lines. + /// A vector of `IntPath` instances representing the clipped sections of the input lines. #[inline] pub fn clip_string_lines_with_solver( mut self, fill_rule: FillRule, clip_rule: ClipRule, solver: Solver, - ) -> Vec { + ) -> Vec> { self.split_solver.split_segments(&mut self.segments, &solver); if self.segments.is_empty() { return Vec::new(); @@ -249,7 +257,7 @@ impl StringOverlay { /// This graph is used for string operations, enabling analysis and manipulation of geometric data. /// - `fill_rule`: The rule that defines how to build shapes (e.g., non-zero, even-odd). #[inline] - pub fn build_graph_view(&mut self, fill_rule: FillRule) -> Option> { + pub fn build_graph_view(&mut self, fill_rule: FillRule) -> Option> { self.build_graph_view_with_solver(fill_rule, Default::default()) } @@ -262,7 +270,7 @@ impl StringOverlay { &mut self, fill_rule: FillRule, solver: Solver, - ) -> Option> { + ) -> Option> { self.split_solver.split_segments(&mut self.segments, &solver); if self.segments.is_empty() { return None; diff --git a/iOverlay/src/string/slice.rs b/iOverlay/src/string/slice.rs index fe35fb0b..9c82f038 100644 --- a/iOverlay/src/string/slice.rs +++ b/iOverlay/src/string/slice.rs @@ -2,20 +2,26 @@ use crate::core::fill_rule::FillRule; use crate::string::line::IntLine; use crate::string::overlay::StringOverlay; use crate::string::rule::StringRule; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; +use i_key_sort::sort::key::SortKey; use i_shape::int::path::IntPath; use i_shape::int::shape::{IntShape, IntShapes}; +use i_tree::{Expiration, LayoutNumber}; -pub trait IntSlice { - fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes; - fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes; - fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes; - fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes; +pub trait IntSlice { + fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes; + fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes; + fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes; + fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes; } -impl IntSlice for IntShapes { +impl IntSlice for IntShapes +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes { + fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shapes(self); overlay.add_string_line(line); overlay @@ -25,7 +31,7 @@ impl IntSlice for IntShapes { } #[inline] - fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes { + fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shapes(self); overlay.add_string_lines(lines); overlay @@ -35,7 +41,7 @@ impl IntSlice for IntShapes { } #[inline] - fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes { + fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shapes(self); overlay.add_string_path(path); overlay @@ -45,7 +51,7 @@ impl IntSlice for IntShapes { } #[inline] - fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes { + fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shapes(self); overlay.add_string_paths(paths); overlay @@ -55,9 +61,12 @@ impl IntSlice for IntShapes { } } -impl IntSlice for IntShape { +impl IntSlice for IntShape +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes { + fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shape(self); overlay.add_string_line(line); overlay @@ -67,7 +76,7 @@ impl IntSlice for IntShape { } #[inline] - fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes { + fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shape(self); overlay.add_string_lines(lines); overlay @@ -77,7 +86,7 @@ impl IntSlice for IntShape { } #[inline] - fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes { + fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shape(self); overlay.add_string_path(path); overlay @@ -87,7 +96,7 @@ impl IntSlice for IntShape { } #[inline] - fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes { + fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shape(self); overlay.add_string_paths(paths); overlay @@ -97,9 +106,12 @@ impl IntSlice for IntShape { } } -impl IntSlice for [IntPoint] { +impl IntSlice for [IntPoint] +where + I: IntNumber + Expiration + LayoutNumber + SortKey, +{ #[inline] - fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes { + fn slice_by_line(&self, line: IntLine, fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shape_contour(self); overlay.add_string_line(line); overlay @@ -109,7 +121,7 @@ impl IntSlice for [IntPoint] { } #[inline] - fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes { + fn slice_by_lines(&self, lines: &[IntLine], fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shape_contour(self); overlay.add_string_lines(lines); overlay @@ -119,7 +131,7 @@ impl IntSlice for [IntPoint] { } #[inline] - fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes { + fn slice_by_path(&self, path: &IntPath, fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shape_contour(self); overlay.add_string_path(path); overlay @@ -129,7 +141,7 @@ impl IntSlice for [IntPoint] { } #[inline] - fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes { + fn slice_by_paths(&self, paths: &[IntPath], fill_rule: FillRule) -> IntShapes { let mut overlay = StringOverlay::with_shape_contour(self); overlay.add_string_paths(paths); overlay @@ -141,6 +153,8 @@ impl IntSlice for [IntPoint] { #[cfg(test)] mod tests { + #![allow(clippy::useless_vec)] + use crate::core::fill_rule::FillRule; use crate::string::slice::IntSlice; use alloc::vec; @@ -175,6 +189,20 @@ mod tests { assert_eq!(result[1][0].len(), 4); } + #[test] + fn test_i64_slice() { + let paths = vec![vec![ + IntPoint::::new(-10, 10), + IntPoint::new(-10, -10), + IntPoint::new(10, -10), + IntPoint::new(10, 10), + ]]; + + let result = paths.slice_by_line([IntPoint::new(-20, 0), IntPoint::new(20, 0)], FillRule::NonZero); + + assert_eq!(result.len(), 2); + } + #[test] fn test_1() { let paths = vec![ diff --git a/iOverlay/src/string/split.rs b/iOverlay/src/string/split.rs index 9e63705b..9484b161 100644 --- a/iOverlay/src/string/split.rs +++ b/iOverlay/src/string/split.rs @@ -1,26 +1,29 @@ use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_float::int::number::uint::UIntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; use i_shape::int::path::ContourExtension; use i_shape::int::shape::IntContour; use i_shape::util::reserve::Reserve; -pub(super) trait Split { +pub(super) trait Split { fn split_loops( self, - min_area: u64, - contour_buffer: &mut IntContour, - bin_store: &mut BinStore, + min_area: I::WideUInt, + contour_buffer: &mut IntContour, + bin_store: &mut BinStore, ) -> Vec where Self: Sized; } -impl Split for IntContour { +impl Split for IntContour { fn split_loops( self, - min_area: u64, - contour_buffer: &mut IntContour, - bin_store: &mut BinStore, + min_area: I::WideUInt, + contour_buffer: &mut IntContour, + bin_store: &mut BinStore, ) -> Vec { if self.is_empty() { return Vec::new(); @@ -30,7 +33,7 @@ impl Split for IntContour { bin_store.init(&self); - let mut result: Vec = Vec::with_capacity(16); + let mut result: Vec> = Vec::with_capacity(16); for point in self { let next_pos = contour_buffer.len() + 1; @@ -61,25 +64,25 @@ impl Split for IntContour { } } -#[derive(Debug, Clone, Copy)] -struct PointItem { - point: IntPoint, +#[derive(Clone)] +struct PointItem { + point: IntPoint, pos: usize, } -#[derive(Debug, Clone, Copy)] +#[derive(Clone)] struct Bin { offset: usize, data: usize, } -pub(super) struct BinStore { +pub(super) struct BinStore { mask: u32, bins: Vec, - items: Vec, + items: Vec>, } -impl BinStore { +impl BinStore { pub(super) fn new() -> Self { Self { mask: 0, @@ -88,7 +91,7 @@ impl BinStore { } } - fn init(&mut self, contour: &IntContour) { + fn init(&mut self, contour: &IntContour) { let log = contour.len().ilog2().saturating_sub(4).clamp(1, 30); let bins_count = (1 << log) as usize; @@ -123,7 +126,7 @@ impl BinStore { } #[inline] - fn insert_if_not_exist(&mut self, point: IntPoint, pos: usize) -> usize { + fn insert_if_not_exist(&mut self, point: IntPoint, pos: usize) -> usize { let index = self.bin_index(point); let bin = unsafe { // SAFETY: insert_if_not_exist only touches bins within the pre-sized array; bin_index shares the same invariant. @@ -150,22 +153,22 @@ impl BinStore { } #[inline] - fn bin_index(&self, p: IntPoint) -> usize { - let x = p.x.unsigned_abs(); - let y = p.y.unsigned_abs(); - let hash = x.wrapping_mul(31) ^ y.wrapping_mul(17); - (hash & self.mask) as usize + fn bin_index(&self, p: IntPoint) -> usize { + let x = p.x.wide().wrapping_mul(I::Wide::from_usize(31)); + let y = p.y.wide().wrapping_mul(I::Wide::from_usize(17)); + let hash = x.wrapping_add(y); + (hash & I::Wide::from_usize(self.mask as usize)).to_usize() } } -trait ValidateArea { - fn validate_area(&self, min_area: u64) -> bool; +trait ValidateArea { + fn validate_area(&self, min_area: I::WideUInt) -> bool; } -impl ValidateArea for IntContour { +impl ValidateArea for IntContour { #[inline] - fn validate_area(&self, min_area: u64) -> bool { - if min_area == 0 { + fn validate_area(&self, min_area: I::WideUInt) -> bool { + if min_area == I::WideUInt::ZERO { return true; } let abs_area = self.unsafe_area().unsigned_abs() >> 1; @@ -181,17 +184,17 @@ mod tests { #[test] fn test_empty_path() { - let path: IntPath = vec![]; - let mut contour: IntContour = Vec::new(); + let path: IntPath = vec![]; + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.split_loops(0, &mut contour, &mut bin_store); - assert_eq!(result, vec![] as Vec); + assert_eq!(result, vec![] as Vec>); } #[test] fn test_single_point() { let path = vec![IntPoint::new(0, 0)]; - let mut contour: IntContour = Vec::new(); + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.split_loops(0, &mut contour, &mut bin_store); assert!(result.is_empty()); @@ -200,7 +203,7 @@ mod tests { #[test] fn test_two_points() { let path = vec![IntPoint::new(0, 0), IntPoint::new(1, 1)]; - let mut contour: IntContour = Vec::new(); + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.split_loops(0, &mut contour, &mut bin_store); assert!(result.is_empty()); @@ -215,7 +218,7 @@ mod tests { IntPoint::new(1, 0), ]; - let mut contour: IntContour = Vec::new(); + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.clone().split_loops(0, &mut contour, &mut bin_store); assert_eq!(result, vec![path]); @@ -234,7 +237,7 @@ mod tests { IntPoint::new(1, -1), ]; - let mut contour: IntContour = Vec::new(); + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.split_loops(0, &mut contour, &mut bin_store); assert_eq!(result.len(), 2); @@ -272,7 +275,7 @@ mod tests { IntPoint::new(1, -1), ]; - let mut contour: IntContour = Vec::new(); + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.split_loops(0, &mut contour, &mut bin_store); assert_eq!(result.len(), 2); @@ -309,7 +312,7 @@ mod tests { IntPoint::new(0, 0), ]; - let mut contour: IntContour = Vec::new(); + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.split_loops(0, &mut contour, &mut bin_store); assert_eq!(result.len(), 2); @@ -344,7 +347,7 @@ mod tests { IntPoint::new(0, 0), // same point, forms a loop ]; - let mut contour: IntContour = Vec::new(); + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.split_loops(0, &mut contour, &mut bin_store); assert_eq!(result.len(), 1); @@ -371,7 +374,7 @@ mod tests { IntPoint::new(0, 0), // same point, forms a loop ]; - let mut contour: IntContour = Vec::new(); + let mut contour: IntContour = Vec::new(); let mut bin_store = BinStore::new(); let result = path.split_loops(0, &mut contour, &mut bin_store); assert_eq!(result.len(), 4); diff --git a/iOverlay/src/vector/edge.rs b/iOverlay/src/vector/edge.rs index a4eae6a1..09971cfe 100644 --- a/iOverlay/src/vector/edge.rs +++ b/iOverlay/src/vector/edge.rs @@ -1,10 +1,14 @@ +use crate::core::edge_data::OverlayEdgeData; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; use i_float::int::point::IntPoint; use i_shape::int::path::IntPath; pub type SideFill = u8; -pub type VectorPath = Vec; -pub type VectorShape = Vec; +pub type DataVectorPath = Vec>; +pub type DataVectorShape = Vec>; +pub type VectorPath = DataVectorPath; +pub type VectorShape = DataVectorShape; pub const SUBJ_LEFT: u8 = 0b0001; pub const SUBJ_RIGHT: u8 = 0b0010; @@ -27,26 +31,42 @@ impl Reverse for SideFill { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct VectorEdge { - pub a: IntPoint, - pub b: IntPoint, +pub struct DataVectorEdge { + pub a: IntPoint, + pub b: IntPoint, pub fill: SideFill, + pub data: D, } -impl VectorEdge { - pub(crate) fn new(fill: SideFill, a: IntPoint, b: IntPoint) -> Self { - let fill = if a < b { fill } else { fill.reverse() }; +impl DataVectorEdge { + pub(crate) fn new(fill: SideFill, a: IntPoint, b: IntPoint, data: D) -> Self { + let mut store = D::Store::default(); + Self::new_with_store(fill, a, b, data, &mut store) + } + + pub(crate) fn new_with_store( + fill: SideFill, + a: IntPoint, + b: IntPoint, + data: D, + store: &mut D::Store, + ) -> Self { + let (fill, data) = if a < b { + (fill, data) + } else { + (fill.reverse(), data.reversed(store)) + }; - Self { a, b, fill } + Self { a, b, fill, data } } } -pub trait ToPath { - fn to_path(&self) -> IntPath; +pub trait ToPath { + fn to_path(&self) -> IntPath; } -impl ToPath for VectorPath { - fn to_path(&self) -> IntPath { +impl ToPath for VectorPath { + fn to_path(&self) -> IntPath { self.iter().map(|e| e.a).collect() } } diff --git a/iOverlay/src/vector/extract.rs b/iOverlay/src/vector/extract.rs index 49454b7a..c6553ce0 100644 --- a/iOverlay/src/vector/extract.rs +++ b/iOverlay/src/vector/extract.rs @@ -1,35 +1,51 @@ use crate::bind::segment::{ContourIndex, IdSegment, IdSegments}; use crate::bind::solver::{ShapeBinder, SortByAngle}; -use crate::core::extract::{BooleanExtractionBuffer, GraphContour, GraphUtil, Visit, VisitState}; +use crate::core::edge_data::OverlayEdgeData; +use crate::core::extract::{BooleanExtractionBuffer, GraphUtil, Visit, VisitState}; 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::segm::segment::SegmentFill; -use crate::vector::edge::{VectorEdge, VectorPath, VectorShape}; +use crate::vector::edge::{DataVectorEdge, DataVectorPath, DataVectorShape}; use crate::vector::simplify::VectorSimplify; use alloc::vec; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_float::int::number::uint::UIntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; - -impl OverlayGraph<'_> { - pub fn extract_separate_vectors(&self) -> Vec { +use i_key_sort::sort::key::SortKey; +use i_tree::Expiration; + +impl OverlayGraph<'_, I, D> +where + I: IntNumber + Expiration + SortKey, + D: OverlayEdgeData, +{ + pub fn extract_separate_vectors(&self) -> Vec> { self.links .iter() - .map(|link| VectorEdge { - a: link.a.point, - b: link.b.point, - fill: link.fill, - }) + .map(|link| DataVectorEdge::new(link.fill, link.a.point, link.b.point, ())) .collect() } - pub fn extract_shape_vectors( + pub fn extract_vector_shapes( &self, overlay_rule: OverlayRule, - buffer: &mut BooleanExtractionBuffer, - ) -> Vec { + buffer: &mut BooleanExtractionBuffer, + ) -> Vec> { + let mut store = D::Store::default(); + self.extract_vector_shapes_with_store(overlay_rule, buffer, &mut store) + } + + pub fn extract_vector_shapes_with_store( + &self, + overlay_rule: OverlayRule, + buffer: &mut BooleanExtractionBuffer, + store: &mut D::Store, + ) -> Vec> { let clockwise = self.options.output_direction == ContourDirection::Clockwise; self.links .filter_by_overlay_into(overlay_rule, &mut buffer.visited); @@ -64,7 +80,7 @@ impl OverlayGraph<'_> { let start_data = StartVectorPathData::new(direction, link, left_top_link); let mut contour = - self.find_vector_contour(start_data, direction, visited_state, &mut buffer.visited); + self.find_vector_contour(start_data, direction, visited_state, &mut buffer.visited, store); let (is_valid, is_modified) = contour.validate( self.options.min_output_area, self.options.preserve_output_collinear, @@ -95,7 +111,7 @@ impl OverlayGraph<'_> { } }; - debug_assert_eq!(v_segment, most_left_bottom(&contour)); + debug_assert!(v_segment == most_left_bottom(&contour)); let id_data = ContourIndex::new_hole(holes.len()); anchors.push(IdSegment::with_segment(id_data, v_segment)); holes.push(contour); @@ -105,7 +121,7 @@ impl OverlayGraph<'_> { } if !anchors_already_sorted { - anchors.sort_by(|s0, s1| s0.v_segment.a.cmp(&s1.v_segment.a)); + anchors.sort_by_key(|s0| s0.v_segment.a); } shapes.join_sorted_holes(holes, anchors, clockwise); @@ -115,19 +131,26 @@ impl OverlayGraph<'_> { fn find_vector_contour( &self, - start_data: StartVectorPathData, + start_data: StartVectorPathData, clockwise: bool, visited_state: VisitState, visited: &mut [VisitState], - ) -> VectorPath { + store: &mut D::Store, + ) -> DataVectorPath { 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_edge(link_id, visited_state); - let mut contour = VectorPath::new(); - contour.push(VectorEdge::new(start_data.fill, start_data.a, start_data.b)); + let mut contour = DataVectorPath::new(); + contour.push(DataVectorEdge::new_with_store( + start_data.fill, + start_data.a, + start_data.b, + start_data.data, + store, + )); // Find a closed tour while node_id != last_node_id { @@ -138,7 +161,7 @@ impl OverlayGraph<'_> { // traversal helpers, so this stays in-bounds. self.links.get_unchecked(link_id) }; - node_id = contour.push_node_and_get_other(link, node_id); + node_id = contour.push_node_and_get_other(link, node_id, store); visited.visit_edge(link_id, visited_state); } @@ -147,18 +170,35 @@ impl OverlayGraph<'_> { } } -struct StartVectorPathData { - a: IntPoint, - b: IntPoint, +impl OverlayGraph<'_, I, D> { + pub fn extract_vectors(&self) -> Vec> { + let mut store = D::Store::default(); + self.extract_vectors_with_store(&mut store) + } + + pub fn extract_vectors_with_store(&self, store: &mut D::Store) -> Vec> { + self.links + .iter() + .map(|link| { + DataVectorEdge::new_with_store(link.fill, link.a.point, link.b.point, link.data, store) + }) + .collect() + } +} + +struct StartVectorPathData { + a: IntPoint, + b: IntPoint, node_id: usize, link_id: usize, last_node_id: usize, fill: SegmentFill, + data: D, } -impl StartVectorPathData { +impl StartVectorPathData { #[inline(always)] - fn new(direction: bool, link: &OverlayLink, link_id: usize) -> Self { + fn new(direction: bool, link: &OverlayLink, link_id: usize) -> Self { if direction { Self { a: link.b.point, @@ -167,6 +207,7 @@ impl StartVectorPathData { link_id, last_node_id: link.b.id, fill: link.fill, + data: link.data, } } else { Self { @@ -176,18 +217,42 @@ impl StartVectorPathData { link_id, last_node_id: link.a.id, fill: link.fill, + data: link.data, } } } } -trait JoinHoles { - fn join_sorted_holes(&mut self, holes: Vec, anchors: Vec, clockwise: bool); - fn scan_join(&mut self, holes: Vec, hole_segments: Vec, clockwise: bool); +trait JoinHoles +where + I: IntNumber + Expiration + SortKey, + D: OverlayEdgeData, +{ + fn join_sorted_holes( + &mut self, + holes: Vec>, + anchors: Vec>, + clockwise: bool, + ); + fn scan_join( + &mut self, + holes: Vec>, + hole_segments: Vec>, + clockwise: bool, + ); } -impl JoinHoles for Vec { - fn join_sorted_holes(&mut self, holes: Vec, anchors: Vec, clockwise: bool) { +impl JoinHoles for Vec> +where + I: IntNumber + Expiration + SortKey, + D: OverlayEdgeData, +{ + fn join_sorted_holes( + &mut self, + holes: Vec>, + anchors: Vec>, + clockwise: bool, + ) { if self.is_empty() || holes.is_empty() { return; } @@ -204,7 +269,12 @@ impl JoinHoles for Vec { self.scan_join(holes, anchors, clockwise); } - fn scan_join(&mut self, holes: Vec, hole_segments: Vec, clockwise: bool) { + fn scan_join( + &mut self, + holes: Vec>, + hole_segments: Vec>, + clockwise: bool, + ) { let x_min = hole_segments[0].v_segment.a.x; let x_max = hole_segments[hole_segments.len() - 1].v_segment.a.x; @@ -234,10 +304,10 @@ impl JoinHoles for Vec { } #[inline] -fn most_left_bottom(path: &VectorPath) -> VSegment { +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 (i, e) in path.iter().enumerate().skip(1) { if e.a < a { a = e.a; index = i; @@ -254,15 +324,25 @@ fn most_left_bottom(path: &VectorPath) -> VSegment { } #[inline] -fn is_sorted(segments: &[IdSegment]) -> bool { +fn is_sorted(segments: &[IdSegment]) -> bool { segments .windows(2) .all(|slice| slice[0].v_segment.a <= slice[1].v_segment.a) } -impl GraphContour for VectorPath { +trait DataGraphContour { + fn validate(&mut self, min_output_area: I::WideUInt, preserve_output_collinear: bool) -> (bool, bool); + fn push_node_and_get_other( + &mut self, + link: &OverlayLink, + node_id: usize, + store: &mut D::Store, + ) -> usize; +} + +impl DataGraphContour for DataVectorPath { #[inline] - fn validate(&mut self, min_output_area: u64, preserve_output_collinear: bool) -> (bool, bool) { + 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 { @@ -273,26 +353,41 @@ impl GraphContour for VectorPath { return (false, is_modified); } - if min_output_area == 0 { + if min_output_area == I::WideUInt::ZERO { return (true, is_modified); } let double_area = self .iter() - .fold(0i64, |acc, edge| acc + edge.a.cross_product(edge.b)); - - let is_valid = (double_area.unsigned_abs() >> 1) >= min_output_area; + .fold(I::Wide::ZERO, |acc, edge| acc + edge.a.cross_product(edge.b)); - (is_valid, is_modified) + ((double_area.unsigned_abs() >> 1) >= min_output_area, is_modified) } #[inline] - fn push_node_and_get_other(&mut self, link: &OverlayLink, node_id: usize) -> usize { + fn push_node_and_get_other( + &mut self, + link: &OverlayLink, + node_id: usize, + store: &mut D::Store, + ) -> usize { if link.a.id == node_id { - self.push(VectorEdge::new(link.fill, link.a.point, link.b.point)); + self.push(DataVectorEdge::new_with_store( + link.fill, + link.a.point, + link.b.point, + link.data, + store, + )); link.b.id } else { - self.push(VectorEdge::new(link.fill, link.b.point, link.a.point)); + self.push(DataVectorEdge::new_with_store( + link.fill, + link.b.point, + link.a.point, + link.data, + store, + )); link.a.id } } @@ -319,7 +414,7 @@ mod tests { let shapes = overlay .build_graph_view(FillRule::NonZero) .unwrap() - .extract_shape_vectors(OverlayRule::Subject, &mut buffer); + .extract_vector_shapes(OverlayRule::Subject, &mut buffer); debug_assert!(shapes[0][0].len() == 6); @@ -328,7 +423,7 @@ mod tests { let shapes = overlay .build_graph_view(FillRule::NonZero) .unwrap() - .extract_shape_vectors(OverlayRule::Subject, &mut buffer); + .extract_vector_shapes(OverlayRule::Subject, &mut buffer); debug_assert!(shapes[0][0].len() == 4); } @@ -346,7 +441,7 @@ mod tests { let shapes = overlay .build_graph_view(FillRule::NonZero) .unwrap() - .extract_shape_vectors(OverlayRule::Subject, &mut buffer); + .extract_vector_shapes(OverlayRule::Subject, &mut buffer); debug_assert!(shapes[0][0].len() == 4); } @@ -365,7 +460,7 @@ mod tests { let shapes_0 = overlay .build_graph_view(FillRule::NonZero) .unwrap() - .extract_shape_vectors(OverlayRule::Subject, &mut buffer); + .extract_vector_shapes(OverlayRule::Subject, &mut buffer); debug_assert!(shapes_0.len() == 1); @@ -374,7 +469,7 @@ mod tests { let shapes_1 = overlay .build_graph_view(FillRule::NonZero) .unwrap() - .extract_shape_vectors(OverlayRule::Subject, &mut buffer); + .extract_vector_shapes(OverlayRule::Subject, &mut buffer); debug_assert!(shapes_1.len() == 1); } @@ -392,7 +487,7 @@ mod tests { let shapes = overlay .build_graph_view(FillRule::NonZero) .unwrap() - .extract_shape_vectors(OverlayRule::Subject, &mut buffer); + .extract_vector_shapes(OverlayRule::Subject, &mut buffer); debug_assert!(shapes.len() == 1); debug_assert!(shapes[0][0].len() == 4); @@ -410,7 +505,7 @@ mod tests { let shapes = overlay .build_graph_view(FillRule::NonZero) .unwrap() - .extract_shape_vectors(OverlayRule::Subject, &mut buffer); + .extract_vector_shapes(OverlayRule::Subject, &mut buffer); debug_assert!(shapes.len() == 2); } @@ -433,7 +528,7 @@ mod tests { let shapes = overlay .build_graph_view(FillRule::NonZero) .unwrap() - .extract_shape_vectors(OverlayRule::Subject, &mut buffer); + .extract_vector_shapes(OverlayRule::Subject, &mut buffer); debug_assert!(shapes.len() == 2); } diff --git a/iOverlay/src/vector/simplify.rs b/iOverlay/src/vector/simplify.rs index 387cdfed..4e47ab8b 100644 --- a/iOverlay/src/vector/simplify.rs +++ b/iOverlay/src/vector/simplify.rs @@ -1,7 +1,11 @@ -use crate::vector::edge::{VectorEdge, VectorPath, VectorShape}; +use crate::core::edge_data::OverlayEdgeData; +use crate::vector::edge::{DataVectorEdge, DataVectorPath, DataVectorShape}; use alloc::vec; use alloc::vec::Vec; +use i_float::int::number::int::IntNumber; +use i_float::int::number::wide_int::WideIntNumber; use i_float::int::point::IntPoint; +use i_float::int::vector::IntVector; /// Simplifies vector contours by removing collinear points when possible. pub(super) trait VectorSimplify { @@ -12,16 +16,20 @@ pub(super) trait VectorSimplify { } pub(super) trait VectorSimpleContour { + type Int: IntNumber; + type Data: OverlayEdgeData; fn is_simple(&self) -> bool; - fn simplified(&self) -> Option; + fn simplified(&self) -> Option>; } pub(super) trait VectorSimpleShape { + type Int: IntNumber; + type Data: OverlayEdgeData; fn is_simple(&self) -> bool; - fn simplified(&self) -> Option; + fn simplified(&self) -> Option>; } -impl VectorSimplify for VectorPath { +impl VectorSimplify for DataVectorPath { #[inline] fn simplify_contour(&mut self) -> bool { if self.is_simple() { @@ -36,7 +44,7 @@ impl VectorSimplify for VectorPath { } } -impl VectorSimplify for VectorShape { +impl VectorSimplify for DataVectorShape { #[inline] fn simplify_contour(&mut self) -> bool { let mut any_simplified = false; @@ -67,7 +75,7 @@ impl VectorSimplify for VectorShape { } } -impl VectorSimplify for Vec { +impl VectorSimplify for Vec> { #[inline] fn simplify_contour(&mut self) -> bool { let mut any_simplified = false; @@ -94,7 +102,10 @@ impl VectorSimplify for Vec { } } -impl VectorSimpleContour for [VectorEdge] { +impl VectorSimpleContour for [DataVectorEdge] { + type Int = I; + type Data = D; + #[inline] fn is_simple(&self) -> bool { let count = self.len(); @@ -102,19 +113,19 @@ impl VectorSimpleContour for [VectorEdge] { return false; } - let mut prev = direction(&self[count - 1]); + let mut prev = &self[count - 1]; for edge in self.iter() { let curr = direction(edge); - if curr.cross_product(prev) == 0 { + if curr.cross_product(direction(prev)) == I::Wide::ZERO && edge.data == prev.data { return false; } - prev = curr; + prev = edge; } true } - fn simplified(&self) -> Option { + fn simplified(&self) -> Option> { if self.len() < 3 { return None; } @@ -155,7 +166,9 @@ impl VectorSimpleContour for [VectorEdge] { let p1 = self[node.index].b; let p2 = self[node.next].b; - if p1.subtract(p0).cross_product(p2.subtract(p1)) == 0 { + if (p1 - p0).cross_product(p2 - p1) == I::Wide::ZERO + && self[node.index].data == self[node.next].data + { n -= 1; if n < 3 { return None; @@ -192,7 +205,7 @@ impl VectorSimpleContour for [VectorEdge] { } } - let mut buffer = vec![VectorEdge::new(0, IntPoint::ZERO, IntPoint::ZERO); n]; + let mut buffer = vec![DataVectorEdge::new(0, IntPoint::ZERO, IntPoint::ZERO, self[0].data); n]; node = nodes[first]; let mut e0 = &self[node.index]; @@ -202,6 +215,7 @@ impl VectorSimpleContour for [VectorEdge] { item.a = e0.b; item.b = e1.b; item.fill = e1.fill; + item.data = e1.data; e0 = e1; } @@ -217,13 +231,16 @@ struct Node { prev: usize, } -impl VectorSimpleShape for [VectorPath] { +impl VectorSimpleShape for [DataVectorPath] { + type Int = I; + type Data = D; + #[inline] fn is_simple(&self) -> bool { self.iter().all(|contour| contour.is_simple()) } - fn simplified(&self) -> Option { + fn simplified(&self) -> Option> { let mut contours = Vec::with_capacity(self.len()); for (i, contour) in self.iter().enumerate() { if contour.is_simple() { @@ -240,27 +257,49 @@ impl VectorSimpleShape for [VectorPath] { } #[inline] -fn direction(edge: &VectorEdge) -> IntPoint { +fn direction(edge: &DataVectorEdge) -> IntVector { edge.b - edge.a } #[cfg(test)] mod tests { - use crate::vector::edge::VectorEdge; + use crate::core::edge_data::{EdgeDataMerge, OverlayEdgeData}; + use crate::vector::edge::DataVectorEdge; use crate::vector::simplify::{IntPoint, VectorSimplify}; use alloc::vec; use i_float::int_pnt; + #[derive(Clone, Copy, PartialEq)] + enum TestData { + A, + B, + } + + impl OverlayEdgeData for TestData + where + C: Copy + Send + Sync, + { + type Store = (); + + fn merge(ctx: EdgeDataMerge, _: &mut Self::Store) -> Self { + if ctx.lhs_data == ctx.rhs_data { + ctx.lhs_data + } else { + TestData::B + } + } + } + #[test] fn test_0() { #[rustfmt::skip] let mut contour = vec![ - VectorEdge::new(1, int_pnt!(0, -1), int_pnt!(0, -3)), - VectorEdge::new(2, int_pnt!(0, -3), int_pnt!(1, -3)), - VectorEdge::new(3, int_pnt!(1, -3), int_pnt!(3, -3)), - VectorEdge::new(4, int_pnt!(3, -3), int_pnt!(3, 0)), - VectorEdge::new(5, int_pnt!(3, 0), int_pnt!(0, 0)), - VectorEdge::new(6, int_pnt!(0, 0), int_pnt!(0, -1)), + DataVectorEdge::new(1, int_pnt!(0, -1), int_pnt!(0, -3), ()), + DataVectorEdge::new(2, int_pnt!(0, -3), int_pnt!(1, -3), ()), + DataVectorEdge::new(3, int_pnt!(1, -3), int_pnt!(3, -3), ()), + DataVectorEdge::new(4, int_pnt!(3, -3), int_pnt!(3, 0), ()), + DataVectorEdge::new(5, int_pnt!(3, 0), int_pnt!(0, 0), ()), + DataVectorEdge::new(6, int_pnt!(0, 0), int_pnt!(0, -1), ()), ]; let result = contour.simplify_contour(); @@ -273,16 +312,16 @@ mod tests { fn test_duplicate_points() { #[rustfmt::skip] let mut contour = vec![ - VectorEdge::new(1, int_pnt!(-1, 3), int_pnt!(-1, 1)), - VectorEdge::new(2, int_pnt!(-1, 1), int_pnt!(-1, 1)), - VectorEdge::new(3, int_pnt!(-1, 1), int_pnt!(-3, 1)), - VectorEdge::new(4, int_pnt!(-3, 1), int_pnt!(-3, -2)), - VectorEdge::new(5, int_pnt!(-3, -2), int_pnt!(3, -2)), - VectorEdge::new(6, int_pnt!(3, -2), int_pnt!(3, 1)), - VectorEdge::new(7, int_pnt!(3, 1), int_pnt!(3, 1)), - VectorEdge::new(8, int_pnt!(3, 1), int_pnt!(1, 1)), - VectorEdge::new(9, int_pnt!(1, 1), int_pnt!(1, 3)), - VectorEdge::new(10, int_pnt!(1, 3), int_pnt!(-1, 3)), + DataVectorEdge::new(1, int_pnt!(-1, 3), int_pnt!(-1, 1), ()), + DataVectorEdge::new(2, int_pnt!(-1, 1), int_pnt!(-1, 1), ()), + DataVectorEdge::new(3, int_pnt!(-1, 1), int_pnt!(-3, 1), ()), + DataVectorEdge::new(4, int_pnt!(-3, 1), int_pnt!(-3, -2), ()), + DataVectorEdge::new(5, int_pnt!(-3, -2), int_pnt!(3, -2), ()), + DataVectorEdge::new(6, int_pnt!(3, -2), int_pnt!(3, 1), ()), + DataVectorEdge::new(7, int_pnt!(3, 1), int_pnt!(3, 1), ()), + DataVectorEdge::new(8, int_pnt!(3, 1), int_pnt!(1, 1), ()), + DataVectorEdge::new(9, int_pnt!(1, 1), int_pnt!(1, 3), ()), + DataVectorEdge::new(10, int_pnt!(1, 3), int_pnt!(-1, 3), ()), ]; let result = contour.simplify_contour(); @@ -295,12 +334,12 @@ mod tests { fn test_tiny_segments() { #[rustfmt::skip] let mut contour = vec![ - VectorEdge::new(1, int_pnt!(0, 2), int_pnt!(-1, 1)), - VectorEdge::new(2, int_pnt!(-1, 1), int_pnt!(-2, 0)), - VectorEdge::new(3, int_pnt!(-2, 0), int_pnt!(0, -1)), - VectorEdge::new(4, int_pnt!(0, -1), int_pnt!(2, 0)), - VectorEdge::new(5, int_pnt!(2, 0), int_pnt!(1, 1)), - VectorEdge::new(6, int_pnt!(1, 1), int_pnt!(0, 2)), + DataVectorEdge::new(1, int_pnt!(0, 2), int_pnt!(-1, 1), ()), + DataVectorEdge::new(2, int_pnt!(-1, 1), int_pnt!(-2, 0), ()), + DataVectorEdge::new(3, int_pnt!(-2, 0), int_pnt!(0, -1), ()), + DataVectorEdge::new(4, int_pnt!(0, -1), int_pnt!(2, 0), ()), + DataVectorEdge::new(5, int_pnt!(2, 0), int_pnt!(1, 1), ()), + DataVectorEdge::new(6, int_pnt!(1, 1), int_pnt!(0, 2), ()), ]; let result = contour.simplify_contour(); @@ -313,14 +352,31 @@ mod tests { fn test_collinear_runs() { #[rustfmt::skip] let mut contour = vec![ - VectorEdge::new(1, int_pnt!(-2, -2), int_pnt!(0, -2)), - VectorEdge::new(2, int_pnt!(0, -2), int_pnt!(2, -2)), - VectorEdge::new(3, int_pnt!(2, -2), int_pnt!(2, 0)), - VectorEdge::new(4, int_pnt!(2, 0), int_pnt!(2, 2)), - VectorEdge::new(5, int_pnt!(2, 2), int_pnt!(0, 2)), - VectorEdge::new(6, int_pnt!(0, 2), int_pnt!(-2, 2)), - VectorEdge::new(7, int_pnt!(-2, 2), int_pnt!(-2, 0)), - VectorEdge::new(8, int_pnt!(-2, 0), int_pnt!(-2, -2)), + DataVectorEdge::new(1, int_pnt!(-2, -2), int_pnt!(0, -2), ()), + DataVectorEdge::new(2, int_pnt!(0, -2), int_pnt!(2, -2), ()), + DataVectorEdge::new(3, int_pnt!(2, -2), int_pnt!(2, 0), ()), + DataVectorEdge::new(4, int_pnt!(2, 0), int_pnt!(2, 2), ()), + DataVectorEdge::new(5, int_pnt!(2, 2), int_pnt!(0, 2), ()), + DataVectorEdge::new(6, int_pnt!(0, 2), int_pnt!(-2, 2), ()), + DataVectorEdge::new(7, int_pnt!(-2, 2), int_pnt!(-2, 0), ()), + DataVectorEdge::new(8, int_pnt!(-2, 0), int_pnt!(-2, -2), ()), + ]; + + let result = contour.simplify_contour(); + + debug_assert!(result); + debug_assert!(contour.len() == 4); + } + + #[test] + fn test_collinear_same_data() { + #[rustfmt::skip] + let mut contour = vec![ + DataVectorEdge::new(1, int_pnt!(0, 0), int_pnt!(2, 0), TestData::A), + DataVectorEdge::new(2, int_pnt!(2, 0), int_pnt!(4, 0), TestData::A), + DataVectorEdge::new(3, int_pnt!(4, 0), int_pnt!(4, 4), TestData::A), + DataVectorEdge::new(4, int_pnt!(4, 4), int_pnt!(0, 4), TestData::A), + DataVectorEdge::new(5, int_pnt!(0, 4), int_pnt!(0, 0), TestData::A), ]; let result = contour.simplify_contour(); @@ -329,13 +385,30 @@ mod tests { debug_assert!(contour.len() == 4); } + #[test] + fn test_collinear_different_data() { + #[rustfmt::skip] + let mut contour = vec![ + DataVectorEdge::new(1, int_pnt!(0, 0), int_pnt!(2, 0), TestData::A), + DataVectorEdge::new(2, int_pnt!(2, 0), int_pnt!(4, 0), TestData::B), + DataVectorEdge::new(3, int_pnt!(4, 0), int_pnt!(4, 4), TestData::A), + DataVectorEdge::new(4, int_pnt!(4, 4), int_pnt!(0, 4), TestData::A), + DataVectorEdge::new(5, int_pnt!(0, 4), int_pnt!(0, 0), TestData::A), + ]; + + let result = contour.simplify_contour(); + + debug_assert!(!result); + debug_assert!(contour.len() == 5); + } + #[test] fn test_zero_area_path() { #[rustfmt::skip] let mut contour = vec![ - VectorEdge::new(1, int_pnt!(-3, 0), int_pnt!(0, 0)), - VectorEdge::new(2, int_pnt!(0, 0), int_pnt!(3, 0)), - VectorEdge::new(3, int_pnt!(3, 0), int_pnt!(-3, 0)), + DataVectorEdge::new(1, int_pnt!(-3, 0), int_pnt!(0, 0), ()), + DataVectorEdge::new(2, int_pnt!(0, 0), int_pnt!(3, 0), ()), + DataVectorEdge::new(3, int_pnt!(3, 0), int_pnt!(-3, 0), ()), ]; let result = contour.simplify_contour(); diff --git a/iOverlay/tests/board_tests.rs b/iOverlay/tests/board_tests.rs index b54be60b..7dc851aa 100644 --- a/iOverlay/tests/board_tests.rs +++ b/iOverlay/tests/board_tests.rs @@ -49,13 +49,13 @@ mod tests { result.len() } - fn many_squares(start: IntPoint, size: i32, offset: i32, n: usize) -> Vec { + fn many_squares(start: IntPoint, size: i32, offset: i32, n: usize) -> Vec> { let mut result = Vec::with_capacity(n * n); let mut y = start.y; for _ in 0..n { let mut x = start.x; for _ in 0..n { - let path: IntPath = vec![ + let path: IntPath = vec![ IntPoint::new(x, y), IntPoint::new(x, y + size), IntPoint::new(x + size, y + size), diff --git a/iOverlay/tests/boolean/test_135.json b/iOverlay/tests/boolean/test_135.json index 123f78c8..17312cfa 100644 --- a/iOverlay/tests/boolean/test_135.json +++ b/iOverlay/tests/boolean/test_135.json @@ -2,11 +2,11 @@ "fillRule": 1, "subjPaths": [[[-368243454, -716798528], [216742014, -234153472], [161596286, -387121984], [161210942, -390975072], [271787262, -224556880], [368243454, -588348048]], [[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]], "clipPaths": [[[209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [277238334, -271024672], [270924350, -278864608], [232277566, -247739648]]], - "subject": [[[[[-368243454, -716798528], [212436980, -237705361], [216742014, -234153472], [214995134, -238999135], [161596286, -387121984], [161210942, -390975072], [247980463, -260386291], [253574782, -251966799], [271787262, -224556880], [368243454, -588348048]]], [[[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]]]], - "clip": [[[[[209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [253574782, -251966799], [277238334, -271024672], [270924350, -278864608], [247980463, -260386291], [232277566, -247739648], [214995134, -238999135], [212436980, -237705361]]]]], - "union": [[[[[-368243454, -716798528], [212436980, -237705361], [209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [253574782, -251966799], [271787262, -224556880], [368243454, -588348048]], [[161596286, -387121984], [161210942, -390975072], [247980463, -260386291], [232277566, -247739648], [214995134, -238999135]]], [[[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]]]], - "intersect": [[[[[212436980, -237705361], [216742014, -234153472], [214995134, -238999135]]], [[[247980463, -260386291], [253574782, -251966799], [277238334, -271024672], [270924350, -278864608]]]]], - "difference": [[[[[-368243454, -716798528], [212436980, -237705361], [214995134, -238999135], [161596286, -387121984], [161210942, -390975072], [247980463, -260386291], [270924350, -278864608], [277238334, -271024672], [253574782, -251966799], [271787262, -224556880], [368243454, -588348048]]], [[[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]]]], - "inverseDifference": [[[[[209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [253574782, -251966799], [247980463, -260386291], [232277566, -247739648], [214995134, -238999135], [216742014, -234153472], [212436980, -237705361]]]]], - "xor": [[[[[-368243454, -716798528], [212436980, -237705361], [214995134, -238999135], [161596286, -387121984], [161210942, -390975072], [247980463, -260386291], [270924350, -278864608], [277238334, -271024672], [253574782, -251966799], [271787262, -224556880], [368243454, -588348048]]], [[[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]], [[[209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [253574782, -251966799], [247980463, -260386291], [232277566, -247739648], [214995134, -238999135], [216742014, -234153472], [212436980, -237705361]]]]] + "subject": [[[[[-368243454, -716798528], [212436981, -237705360], [216742014, -234153472], [214995134, -238999135], [161596286, -387121984], [161210942, -390975072], [247980464, -260386290], [253574783, -251966798], [271787262, -224556880], [368243454, -588348048]]], [[[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]]]], + "clip": [[[[[209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [253574783, -251966798], [277238334, -271024672], [270924350, -278864608], [247980464, -260386290], [232277566, -247739648], [214995134, -238999135], [212436981, -237705360]]]]], + "union": [[[[[-368243454, -716798528], [212436981, -237705360], [209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [253574783, -251966798], [271787262, -224556880], [368243454, -588348048]], [[161596286, -387121984], [161210942, -390975072], [247980464, -260386290], [232277566, -247739648], [214995134, -238999135]]], [[[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]]]], + "intersect": [[[[[212436981, -237705360], [216742014, -234153472], [214995134, -238999135]]], [[[247980464, -260386290], [253574783, -251966798], [277238334, -271024672], [270924350, -278864608]]]]], + "difference": [[[[[-368243454, -716798528], [212436981, -237705360], [214995134, -238999135], [161596286, -387121984], [161210942, -390975072], [247980464, -260386290], [270924350, -278864608], [277238334, -271024672], [253574783, -251966798], [271787262, -224556880], [368243454, -588348048]]], [[[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]]]], + "inverseDifference": [[[[[209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [253574783, -251966798], [247980464, -260386290], [232277566, -247739648], [214995134, -238999135], [216742014, -234153472], [212436981, -237705360]]]]], + "xor": [[[[[-368243454, -716798528], [212436981, -237705360], [214995134, -238999135], [161596286, -387121984], [161210942, -390975072], [247980464, -260386290], [270924350, -278864608], [277238334, -271024672], [253574783, -251966798], [271787262, -224556880], [368243454, -588348048]]], [[[-206371890, 284626496], [102168414, 680240192], [238020158, 716798528], [292483838, 355651392], [161731326, 385867967]], [[183615526, 693260116], [112710830, 677451824], [165983866, 395695016]], [[231794622, 706732224], [225376830, 598504960], [228629310, 596974400]]], [[[209943806, -236444448], [214486910, -227461600], [237766206, -239235072], [253574783, -251966798], [247980464, -260386290], [232277566, -247739648], [214995134, -238999135], [216742014, -234153472], [212436981, -237705360]]]]] } \ No newline at end of file diff --git a/iOverlay/tests/crash_tests.rs b/iOverlay/tests/crash_tests.rs new file mode 100644 index 00000000..ea1a39c7 --- /dev/null +++ b/iOverlay/tests/crash_tests.rs @@ -0,0 +1,143 @@ +#[cfg(test)] +mod tests { + use i_float::int::number::int::IntNumber; + 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_rule::OverlayRule; + use i_overlay::core::solver::{Precision, Solver, Strategy}; + use i_overlay::float::overlay::FloatOverlay; + use i_shape::base::data::{Path, Shape}; + use i_shape::{int_path, int_shape}; + use i_tree::{Expiration, LayoutNumber}; + + trait TestInt: IntNumber + Expiration + LayoutNumber + SortKey {} + impl TestInt for I where I: IntNumber + Expiration + LayoutNumber + SortKey {} + const SOLVERS: [Solver; 4] = [Solver::LIST, Solver::TREE, Solver::FRAG, Solver::AUTO]; + + fn point(x: i32, y: i32) -> IntPoint { + IntPoint { + x: I::from_float(x as f64), + y: I::from_float(y as f64), + } + } + + #[test] + fn test_00() { + let subj: Shape<_> = int_shape![ + [[0i16, 0], [0, 4], [3, -5]], + [[0, 0], [1, 7], [2, -8]], + [[0, 0], [4, -4], [5, 7]], + ]; + + let solver = Solver { + strategy: Strategy::List, + precision: Precision { + start: 0, + progression: 1, + }, + multithreading: None, + }; + + let mut overlay = Overlay::new_custom(4, Default::default(), solver); + overlay.add_contours(&subj, ShapeType::Subject); + if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { + graph.validate(); + let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); + assert!(!result.is_empty()); + } + } + + #[test] + fn test_01() { + let subj = [ + [-117.04171489206965, 1820.3621519926919], + [4619.6817058891429, -2133.11539650432], + [1902.5599837294722, -133.53167784432389], + [-3572.1275050425684, 3909.4677532724309], + [3047.0491344383845, -4087.6336157702817], + ]; + + let solver = Solver { + strategy: Strategy::Frag, + precision: Precision { + start: 0, + progression: 1, + }, + multithreading: None, + }; + + let mut overlay = FloatOverlay::<_, i16>::from_subj_custom(&subj, Default::default(), solver); + if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { + graph.graph.validate(); + let _ = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); + } + } + + #[test] + fn test_02() { + test_02_as::(); + test_02_as::(); + test_02_as::(); + } + + fn test_02_as() { + let subj_paths = [ + vec![point::(0, 0), point(1, 6), point(6, 4)], + vec![point::(0, 0), point(6, 5), point(2, -2)], + vec![point::(0, 0), point(3, -1), point(1, 3)], + ]; + for &solver in SOLVERS.iter() { + let mut overlay = Overlay::new_custom(4, Default::default(), solver); + overlay.add_contours(&subj_paths, ShapeType::Subject); + if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { + graph.validate(); + let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); + assert!(!result.is_empty()); + } + } + } + + #[test] + fn test_03() { + let subj: Path<_> = int_path![[3, 4], [5, 0], [3, 3], [4, 2], [5, -2]]; + + let solver = Solver { + strategy: Strategy::Tree, + precision: Precision { + start: 0, + progression: 1, + }, + multithreading: None, + }; + + let mut overlay = Overlay::new_custom(10, Default::default(), solver); + overlay.add_contour(&subj, ShapeType::Subject); + if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { + graph.validate(); + let _ = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); + } + } + + #[test] + fn test_04() { + let subj: Path> = int_path![[-4, -2], [1, -3], [-1, 3], [1, -4], [4, -3]]; + + let solver = Solver { + strategy: Strategy::Tree, + precision: Precision { + start: 0, + progression: 1, + }, + multithreading: None, + }; + + let mut overlay = Overlay::new_custom(10, Default::default(), solver); + overlay.add_contour(&subj, ShapeType::Subject); + if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { + graph.validate(); + let _ = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); + } + } +} diff --git a/iOverlay/tests/data.rs b/iOverlay/tests/data.rs index 9c0e760d..f1c4681c 100644 --- a/iOverlay/tests/data.rs +++ b/iOverlay/tests/data.rs @@ -28,17 +28,17 @@ pub mod overlay { #[serde(default, deserialize_with = "deserialize_fill_rule")] pub fill_rule: Option, #[serde(rename = "subjPaths")] - pub subj_paths: Vec, + pub subj_paths: Vec>, #[serde(rename = "clipPaths")] - pub clip_paths: Vec, - pub clip: Vec, - pub subject: Vec, - pub difference: Vec, + pub clip_paths: Vec>, + pub clip: Vec>, + pub subject: Vec>, + pub difference: Vec>, #[serde(rename = "inverseDifference")] - pub inverse_difference: Vec, - pub intersect: Vec, - pub union: Vec, - pub xor: Vec, + pub inverse_difference: Vec>, + pub intersect: Vec>, + pub union: Vec>, + pub xor: Vec>, } impl BooleanTest { @@ -72,11 +72,11 @@ pub mod overlay { #[serde(rename = "fillRule")] #[serde(default, deserialize_with = "deserialize_fill_rule")] pub fill_rule: Option, - pub body: Vec, - pub string: IntPaths, - pub slice: Vec, - pub clip_direct: Vec, - pub clip_invert: Vec, + pub body: Vec>, + pub string: IntPaths, + pub slice: Vec>, + pub clip_direct: Vec>, + pub clip_invert: Vec>, } impl StringTest { diff --git a/iOverlay/tests/direction_tests.rs b/iOverlay/tests/direction_tests.rs index 284a0626..0c9cda4e 100644 --- a/iOverlay/tests/direction_tests.rs +++ b/iOverlay/tests/direction_tests.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod tests { + #![allow(clippy::useless_vec)] + use i_float::int::point::IntPoint; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::{ContourDirection, IntOverlayOptions, Overlay}; @@ -20,7 +22,7 @@ mod tests { preserve_input_collinear: false, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: false, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; @@ -28,15 +30,15 @@ mod tests { preserve_input_collinear: false, output_direction: ContourDirection::Clockwise, preserve_output_collinear: false, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; let r0 = &path.simplify(FillRule::NonZero, op0)[0][0]; - debug_assert!(r0.area_two() < 0); + assert!(r0.area_two() > 0i64); let r1 = &path.simplify(FillRule::NonZero, op1)[0][0]; - debug_assert!(r1.area_two() > 0); + assert!(r1.area_two() < 0i64); } #[test] @@ -60,7 +62,7 @@ mod tests { preserve_input_collinear: false, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: false, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; @@ -68,17 +70,17 @@ mod tests { preserve_input_collinear: false, output_direction: ContourDirection::Clockwise, preserve_output_collinear: false, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; let r0 = &path.simplify(FillRule::NonZero, op0)[0]; - debug_assert!(r0[0].area_two() < 0); - debug_assert!(r0[1].area_two() > 0); + assert!(r0[0].area_two() > 0i64); + assert!(r0[1].area_two() < 0i64); let r1 = &path.simplify(FillRule::NonZero, op1)[0]; - debug_assert!(r1[0].area_two() > 0); - debug_assert!(r1[1].area_two() < 0); + assert!(r1[0].area_two() < 0i64); + assert!(r1[1].area_two() > 0i64); } #[test] @@ -100,7 +102,7 @@ mod tests { // test default behavior let r = Overlay::with_contours(&path, &[]).overlay(OverlayRule::Subject, FillRule::NonZero); - debug_assert!(r[0][0].area_two() < 0); - debug_assert!(r[0][1].area_two() > 0); + assert!(r[0][0].area_two() > 0i64); + assert!(r[0][1].area_two() < 0i64); } } diff --git a/iOverlay/tests/dynamic_tests.rs b/iOverlay/tests/dynamic_tests.rs index d49ec166..570dcf1f 100644 --- a/iOverlay/tests/dynamic_tests.rs +++ b/iOverlay/tests/dynamic_tests.rs @@ -1,6 +1,8 @@ #[cfg(test)] mod tests { + use i_float::int::number::int::IntNumber; 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_rule::OverlayRule; @@ -9,20 +11,32 @@ mod tests { use i_shape::base::data::Path; use i_shape::int::path::IntPath; use i_shape::int::shape::IntShape; + use i_tree::{Expiration, LayoutNumber}; use rand::RngExt; use std::f64::consts::PI; - const SOLVERS: [Solver; 3] = [Solver::LIST, Solver::TREE, Solver::AUTO]; + const SOLVERS: [Solver; 4] = [Solver::LIST, Solver::TREE, Solver::FRAG, Solver::AUTO]; + + trait TestInt: IntNumber + Expiration + LayoutNumber + SortKey {} + + impl TestInt for I where I: IntNumber + Expiration + LayoutNumber + SortKey {} #[test] fn test_0() { - let clip = create_star(1.0, 2.0, 7, 0.0); + test_0_as::(); + test_0_as::(); + test_0_as::(); + } + + fn test_0_as() { + let scale = scale_for::(2.0); + let clip = create_star::(1.0, 2.0, 7, 0.0, scale); for &solver in SOLVERS.iter() { let mut r = 0.9; while r < 1.2 { let mut a = 0.0; while a < 2.0 * PI { - let subj = create_star(1.0, r, 7, a); + let subj = create_star::(1.0, r, 7, a, scale); if let Some(graph) = Overlay::with_contours_custom(&subj, &clip, Default::default(), solver) @@ -30,79 +44,107 @@ mod tests { { graph.validate(); let result = graph.extract_shapes(OverlayRule::Union, &mut Default::default()); - assert!(result.len() > 0); + assert!(!result.is_empty()); } - a += 0.005 + a += 0.01 } - r += 0.01 + r += 0.02 } } } #[test] fn test_1() { - let clip = create_star(200.0, 30.0, 7, 0.0); + test_1_as::(); + test_1_as::(); + test_1_as::(); + } + + fn test_1_as() { + let scale = scale_for::(200.0); + let clip = create_star::(200.0, 30.0, 7, 0.0, scale); for &solver in SOLVERS.iter() { let mut a = 0.0; while a < 4.0 * PI { - let subj = create_star(200.0, 30.0, 7, a); + let subj = create_star::(200.0, 30.0, 7, a, scale); if let Some(graph) = Overlay::with_contours_custom(&subj, &clip, Default::default(), solver) .build_graph_view(FillRule::NonZero) { graph.validate(); let _ = graph.extract_shapes(OverlayRule::Xor, &mut Default::default()); } - a += 0.001 + a += 0.005 } } } #[test] fn test_2() { - let clip = create_star(202.5, 33.75, 24, 0.0); + test_2_as::(); + test_2_as::(); + test_2_as::(); + } + + fn test_2_as() { + let scale = scale_for::(202.5); + let clip = create_star::(202.5, 33.75, 24, 0.0, scale); for &solver in SOLVERS.iter() { let mut a = 0.0; while a < 2.0 * PI { - let subj = create_star(202.5, 33.75, 24, a); + let subj = create_star::(202.5, 33.75, 24, a, scale); if let Some(graph) = Overlay::with_contours_custom(&subj, &clip, Default::default(), solver) .build_graph_view(FillRule::NonZero) { graph.validate(); let _ = graph.extract_shapes(OverlayRule::Xor, &mut Default::default()); } - a += 0.001 + a += 0.005 } } } #[test] fn test_3() { - let clip = create_star(100.0, 10.0, 17, 0.0); + test_3_as::(); + test_3_as::(); + test_3_as::(); + } + + fn test_3_as() { + let scale = scale_for::(100.0); + let clip = create_star::(100.0, 10.0, 17, 0.0, scale); for &solver in SOLVERS.iter() { let mut a = 0.0; while a < 4.0 * PI { - let subj = create_star(100.0, 10.0, 17, a); + let subj = create_star::(100.0, 10.0, 17, a, scale); if let Some(graph) = Overlay::with_contours_custom(&subj, &clip, Default::default(), solver) .build_graph_view(FillRule::NonZero) { graph.validate(); let _ = graph.extract_shapes(OverlayRule::Xor, &mut Default::default()); } - a += 0.001 + a += 0.005 } } } #[test] fn test_4() { - let clip = create_star(202.5, 33.75, 24, 0.0); + test_4_as::(); + test_4_as::(); + test_4_as::(); + } + + fn test_4_as() { + let scale = scale_for::(202.5); + let clip = create_star::(202.5, 33.75, 24, 0.0, scale); for &solver in SOLVERS.iter() { let mut a = -0.000_001; while a < 0.000_001 { - let subj = create_star(202.5, 33.75, 24, a); + let subj = create_star::(202.5, 33.75, 24, a, scale); if let Some(graph) = Overlay::with_contours_custom(&subj, &clip, Default::default(), solver) .build_graph_view(FillRule::NonZero) { @@ -116,9 +158,16 @@ mod tests { #[test] fn test_5() { - let clip = create_star(202.5, 33.75, 24, 0.0); - let a = -9.9999999999999995E-7; - let subj = create_star(202.5, 33.75, 24, a); + test_5_as::(); + test_5_as::(); + test_5_as::(); + } + + fn test_5_as() { + let scale = scale_for::(202.5); + let clip = create_star::(202.5, 33.75, 24, 0.0, scale); + let a = -1E-6; + let subj = create_star::(202.5, 33.75, 24, a, scale); // println!("subj {:?}", subj); for &solver in SOLVERS.iter() { @@ -126,20 +175,26 @@ mod tests { .build_graph_view(FillRule::NonZero) { graph.validate(); - let result = graph.extract_shapes(OverlayRule::Xor, &mut Default::default()); - assert!(result.len() > 0); + let _ = graph.extract_shapes(OverlayRule::Xor, &mut Default::default()); } } } #[test] fn test_6() { - let clip = create_star(100.0, 50.0, 24, 0.0); + test_6_as::(); + test_6_as::(); + test_6_as::(); + } + + fn test_6_as() { + let scale = scale_for::(100.0); + let clip = create_star::(100.0, 50.0, 24, 0.0, scale); for &solver in SOLVERS.iter() { let mut a = -0.000_001; while a < 0.000_001 { - let subj = create_star(100.0, 50.0, 24, a); + let subj = create_star::(100.0, 50.0, 24, a, scale); if let Some(graph) = Overlay::with_contours_custom(&subj, &clip, Default::default(), solver) .build_graph_view(FillRule::NonZero) { @@ -153,26 +208,41 @@ mod tests { #[test] fn test_7() { + test_7_as::(); + test_7_as::(); + test_7_as::(); + } + + fn test_7_as() { let n = 1010; - let subj_paths = random_polygon(1000_000.0, 0.0, n); + let scale = scale_for::(1_000_000.0); + let subj_paths = random_polygon::(1_000_000.0, 0.0, n, scale); - let mut overlay = Overlay::new(n); - overlay.add_contour(&subj_paths, ShapeType::Subject); + for &solver in SOLVERS.iter() { + let mut overlay = Overlay::new_custom(n, Default::default(), solver); + overlay.add_contour(&subj_paths, ShapeType::Subject); - if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { - graph.validate(); - let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); - assert!(result.len() > 0); + if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { + graph.validate(); + let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); + assert!(!result.is_empty()); + } } } #[test] fn test_8() { + test_8_as::(); + test_8_as::(); + test_8_as::(); + } + + fn test_8_as() { for &solver in SOLVERS.iter() { let mut r = 0.004; while r < 1.0 { for n in 5..10 { - let subj_paths = random_polygon(r, 0.0, n); + let subj_paths = random_polygon::(r, 0.0, n, scale_for::(r)); let mut overlay = Overlay::new_custom(n, Default::default(), solver); overlay.add_contour(&subj_paths, ShapeType::Subject); @@ -180,7 +250,7 @@ mod tests { if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { graph.validate(); let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); - assert!(result.len() > 0); + assert!(!result.is_empty()); } } r += 0.001; @@ -190,22 +260,29 @@ mod tests { #[test] fn test_9() { + test_9_as::(); + test_9_as::(); + test_9_as::(); + } + + fn test_9_as() { let s = 0.02; let r0 = s * 1.0; - let clip = create_star(r0, s * 2.0, 4, 0.0); + let scale = scale_for::(s * 2.0); + let clip = create_star::(r0, s * 2.0, 4, 0.0, scale); for &solver in SOLVERS.iter() { let mut r = s * 0.9; while r < 1.2 * s { let mut a = 0.0; while a < 2.0 * PI { - let subj = create_star(r0, r, 4, a); + let subj = create_star::(r0, r, 4, a, scale); if let Some(graph) = Overlay::with_contours_custom(&subj, &clip, Default::default(), solver) .build_graph_view(FillRule::NonZero) { graph.validate(); let result = graph.extract_shapes(OverlayRule::Union, &mut Default::default()); - assert!(result.len() > 0); + assert!(!result.is_empty()); } a += 0.005 } @@ -216,77 +293,113 @@ mod tests { #[test] fn test_10() { + test_10_as::(); + test_10_as::(); + test_10_as::(); + } + + fn test_10_as() { let solver = Solver::AUTO; - let clip = create_star(1.0, 2.0, 7, 0.0); - let a = 0.44000000000000028; + let scale = scale_for::(2.0); + let clip = create_star::(1.0, 2.0, 7, 0.0, scale); + let a = 0.440_000_000_000_000_3; let r = 1.01; - let subj = create_star(1.0, r, 7, a); + let subj = create_star::(1.0, r, 7, a, scale); if let Some(graph) = Overlay::with_contours_custom(&subj, &clip, Default::default(), solver) .build_graph_view(FillRule::NonZero) { graph.validate(); let result = graph.extract_shapes(OverlayRule::Union, &mut Default::default()); - assert!(result.len() > 0); + assert!(!result.is_empty()); } } #[test] fn test_11() { + test_11_as::(); + test_11_as::(); + test_11_as::(); + } + + fn test_11_as() { let n = 6; - for _ in 0..10000 { - let subj_path = random_polygon(100.0, 0.0, n); - let clip_path = random_polygon(100.0, 0.5 * PI, n); - let mut overlay = Overlay::new(2 * n); + let scale = scale_for::(100.0); + for &solver in SOLVERS.iter() { + for _ in 0..2000 { + let subj_path = random_polygon::(100.0, 0.0, n, scale); + let clip_path = random_polygon::(100.0, 0.5 * PI, n, scale); + let mut overlay = Overlay::new_custom(2 * n, Default::default(), solver); - overlay.add_contour(&subj_path, ShapeType::Subject); - overlay.add_contour(&clip_path, ShapeType::Clip); + overlay.add_contour(&subj_path, ShapeType::Subject); + overlay.add_contour(&clip_path, ShapeType::Clip); - if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { - graph.validate(); - let result = graph.extract_shapes(OverlayRule::Union, &mut Default::default()); - assert!(result.len() > 0); + if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { + graph.validate(); + let result = graph.extract_shapes(OverlayRule::Union, &mut Default::default()); + assert!(!result.is_empty()); + } } } } #[test] fn test_12() { + test_12_as::(); + // test_12_as::(); + // test_12_as::(); + } + + fn test_12_as() { let n = 5; - for _ in 0..10000 { - let subj_path = random(10, n); - let mut overlay = Overlay::new(2 * n); - overlay.add_contour(&subj_path, ShapeType::Subject); - if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { - graph.validate(); - let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); - assert!(result.len() > 0); + for &solver in SOLVERS.iter() { + for _ in 0..10000 { + let subj_path = random::(10, n); + let mut overlay = Overlay::new_custom(2 * n, Default::default(), solver); + overlay.add_contour(&subj_path, ShapeType::Subject); + if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { + graph.validate(); + let _ = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); + } } } } #[test] fn test_13() { + test_13_as::(); + test_13_as::(); + test_13_as::(); + } + + fn test_13_as() { let n = 5; - for i in 1..50000 { + for i in 1..10000 { let r = i as f64; let subj_path = random_float(r, n); - let mut overlay = FloatOverlay::with_subj(&subj_path); + let mut overlay = + FloatOverlay::<_, I>::from_subj_custom(&subj_path, Default::default(), Default::default()); if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { graph.graph.validate(); - let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); - assert!(result.len() > 0); + let _ = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); } } } #[test] fn test_14() { - let p = IntPoint::new(0, 0); + test_14_as::(); + test_14_as::(); + test_14_as::(); + } + + fn test_14_as() { + let p = point::(0, 0); let mut rng = rand::rng(); let paths_count = 3; let mut subj_paths = Vec::with_capacity(paths_count); - for _ in 0..1000_000 { + + for _ in 0..100_000 { subj_paths.clear(); let x_range = 0..=8; let y_range = -8..=8; @@ -295,52 +408,36 @@ mod tests { let ay = rng.random_range(y_range.clone()); let bx = rng.random_range(x_range.clone()); let by = rng.random_range(y_range.clone()); - subj_paths.push(vec![p, IntPoint::new(ax, ay), IntPoint::new(bx, by)]); + subj_paths.push(vec![p, point::(ax, ay), point::(bx, by)]); } - let mut overlay = Overlay::new(4); + let mut overlay = Overlay::new_custom(4, Default::default(), Default::default()); overlay.add_contours(&subj_paths, ShapeType::Subject); if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { graph.validate(); let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); - assert!(result.len() > 0); + assert!(!result.is_empty()); } } } - #[test] - fn test_15() { - let subj_paths = [ - vec![IntPoint::new(0, 0), IntPoint::new(1, 6), IntPoint::new(6, 4)], - vec![IntPoint::new(0, 0), IntPoint::new(6, 5), IntPoint::new(2, -2)], - vec![IntPoint::new(0, 0), IntPoint::new(3, -1), IntPoint::new(1, 3)], - ]; - let mut overlay = Overlay::new(4); - overlay.add_contours(&subj_paths, ShapeType::Subject); - if let Some(graph) = overlay.build_graph_view(FillRule::NonZero) { - graph.validate(); - let result = graph.extract_shapes(OverlayRule::Subject, &mut Default::default()); - assert!(result.len() > 0); - } - } - - fn create_star(r0: f64, r1: f64, count: usize, angle: f64) -> IntShape { + fn create_star(r0: f64, r1: f64, count: usize, angle: f64, scale: f64) -> IntShape { let da = PI / count as f64; let mut a = angle; let mut points = Vec::new(); - let sr0 = r0 * 1024.0; - let sr1 = r1 * 1024.0; + let sr0 = r0 * scale; + let sr1 = r1 * scale; for _ in 0..count { - let xr0 = (sr0 * a.cos()) as i32; - let yr0 = (sr0 * a.sin()) as i32; + let xr0 = I::from_float(sr0 * a.cos()); + let yr0 = I::from_float(sr0 * a.sin()); a += da; - let xr1 = (sr1 * a.cos()) as i32; - let yr1 = (sr1 * a.sin()) as i32; + let xr1 = I::from_float(sr1 * a.cos()); + let yr1 = I::from_float(sr1 * a.sin()); a += da; @@ -351,25 +448,25 @@ mod tests { [points].to_vec() } - fn random_polygon(radius: f64, angle: f64, n: usize) -> IntPath { + fn random_polygon(radius: f64, angle: f64, n: usize, scale: f64) -> IntPath { let mut result = Vec::with_capacity(n); let da: f64 = PI * 0.7; let mut a: f64 = angle; - let r = 1024.0 * radius; + let r = scale * radius; for _ in 0..n { let (sin, cos) = a.sin_cos(); let x = r * cos; let y = r * sin; - result.push(IntPoint::new(x as i32, y as i32)); + result.push(IntPoint::new(I::from_float(x), I::from_float(y))); a += da; } result } - fn random(radius: i32, n: usize) -> IntPath { + fn random(radius: i32, n: usize) -> IntPath { let a = radius / 2; let range = -a..=a; let mut points = Vec::with_capacity(n); @@ -377,12 +474,27 @@ mod tests { for _ in 0..n { let x = rng.random_range(range.clone()); let y = rng.random_range(range.clone()); - points.push(IntPoint { x, y }) + points.push(point(x, y)) } points } + fn point(x: i32, y: i32) -> IntPoint { + IntPoint { + x: I::from_float(x as f64), + y: I::from_float(y as f64), + } + } + + fn scale_for(max_abs_coord: f64) -> f64 { + if max_abs_coord <= 0.0 { + return 1024.0; + } + + 1024.0_f64.min(I::MAX.to_f64() * 0.25 / max_abs_coord) + } + fn random_float(radius: f64, n: usize) -> Path<[f64; 2]> { let a = 0.5 * radius; let range = -a..=a; diff --git a/iOverlay/tests/edge_overlay_tests.rs b/iOverlay/tests/edge_overlay_tests.rs new file mode 100644 index 00000000..cb937434 --- /dev/null +++ b/iOverlay/tests/edge_overlay_tests.rs @@ -0,0 +1,297 @@ +#[cfg(test)] +mod tests { + use i_float::int::point::IntPoint; + use i_float::int_pnt; + use i_overlay::core::edge_data::{EdgeDataMerge, OverlayEdgeData}; + use i_overlay::core::edge_overlay::{EdgeOverlay, InputEdge}; + use i_overlay::core::fill_rule::FillRule; + use i_overlay::core::overlay::ShapeType; + use i_overlay::core::overlay_rule::OverlayRule; + use i_overlay::segm::boolean::ShapeCountBoolean; + use i_overlay::vector::edge::DataVectorEdge; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum Color { + Red, + Green, + None, + Undefined, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct EdgeColor { + subj: Color, + clip: Color, + } + + impl OverlayEdgeData for EdgeColor { + type Store = (); + + fn merge(ctx: EdgeDataMerge, _: &mut Self::Store) -> Self { + let subj = if ctx.lhs_data.subj == ctx.rhs_data.subj { + ctx.lhs_data.subj + } else if ctx.rhs_count.subj == 0 || ctx.lhs_count.subj == 0 { + if ctx.rhs_count.subj == ctx.lhs_count.subj { + Color::None + } else if ctx.rhs_count.subj == 0 { + ctx.lhs_data.subj + } else { + ctx.rhs_data.subj + } + } else { + Color::Undefined + }; + + let clip = if ctx.lhs_data.clip == ctx.rhs_data.clip { + ctx.lhs_data.clip + } else if ctx.rhs_count.clip == 0 || ctx.lhs_count.clip == 0 { + if ctx.rhs_count.clip == ctx.lhs_count.clip { + Color::None + } else if ctx.rhs_count.clip == 0 { + ctx.lhs_data.clip + } else { + ctx.rhs_data.clip + } + } else { + Color::Undefined + }; + Self { subj, clip } + } + } + + #[test] + fn union_squares() { + let subj = square( + 0, + 0, + 4, + 4, + EdgeColor { + subj: Color::Red, + clip: Color::None, + }, + ); + let clip = square( + 4, + 0, + 8, + 4, + EdgeColor { + subj: Color::None, + clip: Color::Green, + }, + ); + + let mut overlay = EdgeOverlay::::new(subj.len() + clip.len()); + overlay.add_edges(subj, ShapeType::Subject); + overlay.add_edges(clip, ShapeType::Clip); + + let shapes = overlay.build_vector_shapes(OverlayRule::Union, FillRule::NonZero); + + assert_eq!(shapes.len(), 1); + assert_eq!(shapes[0].len(), 1); + let rect = &shapes[0][0]; + assert_eq!(rect.len(), 6); + + let template = vec![ + DataVectorEdge { + a: int_pnt!(0, 4), + b: int_pnt!(0, 0), + fill: 1, + data: EdgeColor { + subj: Color::Red, + clip: Color::None, + }, + }, + DataVectorEdge { + a: int_pnt!(0, 0), + b: int_pnt!(4, 0), + fill: 1, + data: EdgeColor { + subj: Color::Red, + clip: Color::None, + }, + }, + DataVectorEdge { + a: int_pnt!(4, 0), + b: int_pnt!(8, 0), + fill: 4, + data: EdgeColor { + subj: Color::None, + clip: Color::Green, + }, + }, + DataVectorEdge { + a: int_pnt!(8, 0), + b: int_pnt!(8, 4), + fill: 4, + data: EdgeColor { + subj: Color::None, + clip: Color::Green, + }, + }, + DataVectorEdge { + a: int_pnt!(8, 4), + b: int_pnt!(4, 4), + fill: 4, + data: EdgeColor { + subj: Color::None, + clip: Color::Green, + }, + }, + DataVectorEdge { + a: int_pnt!(4, 4), + b: int_pnt!(0, 4), + fill: 1, + data: EdgeColor { + subj: Color::Red, + clip: Color::None, + }, + }, + ]; + assert_eq!(rect, &template); + } + + #[test] + fn intersect_squares() { + let subj = square( + 0, + 0, + 4, + 4, + EdgeColor { + subj: Color::Red, + clip: Color::None, + }, + ); + let clip = square( + 2, + 0, + 6, + 4, + EdgeColor { + subj: Color::None, + clip: Color::Green, + }, + ); + + let mut overlay = EdgeOverlay::::new(subj.len() + clip.len()); + overlay.add_edges(subj, ShapeType::Subject); + overlay.add_edges(clip, ShapeType::Clip); + + let shapes = overlay.build_vector_shapes(OverlayRule::Intersect, FillRule::NonZero); + + assert_eq!(shapes.len(), 1); + assert_eq!(shapes[0].len(), 1); + let rect = &shapes[0][0]; + assert_eq!(rect.len(), 4); + + let template = vec![ + DataVectorEdge { + a: int_pnt!(2, 4), + b: int_pnt!(2, 0), + fill: 7, + data: EdgeColor { + subj: Color::None, + clip: Color::Green, + }, + }, + DataVectorEdge { + a: int_pnt!(2, 0), + b: int_pnt!(4, 0), + fill: 5, + data: EdgeColor { + subj: Color::Red, + clip: Color::Green, + }, + }, + DataVectorEdge { + a: int_pnt!(4, 0), + b: int_pnt!(4, 4), + fill: 13, + data: EdgeColor { + subj: Color::Red, + clip: Color::None, + }, + }, + DataVectorEdge { + a: int_pnt!(4, 4), + b: int_pnt!(2, 4), + fill: 5, + data: EdgeColor { + subj: Color::Red, + clip: Color::Green, + }, + }, + ]; + assert_eq!(rect, &template); + } + + #[test] + fn union_squares_i64() { + let subj = square_i64( + 0, + 0, + 4, + 4, + EdgeColor { + subj: Color::Red, + clip: Color::None, + }, + ); + let clip = square_i64( + 4, + 0, + 8, + 4, + EdgeColor { + subj: Color::None, + clip: Color::Green, + }, + ); + + let mut overlay = EdgeOverlay::::new(subj.len() + clip.len()); + overlay.add_edges(subj, ShapeType::Subject); + overlay.add_edges(clip, ShapeType::Clip); + + let shapes = overlay.build_vector_shapes(OverlayRule::Union, FillRule::NonZero); + + assert_eq!(shapes.len(), 1); + assert_eq!(shapes[0].len(), 1); + assert_eq!(shapes[0][0].len(), 6); + } + + fn square(x0: i32, y0: i32, x1: i32, y1: i32, data: EdgeColor) -> Vec> { + let points = [ + IntPoint::new(x0, y0), + IntPoint::new(x1, y0), + IntPoint::new(x1, y1), + IntPoint::new(x0, y1), + ]; + + points + .iter() + .copied() + .zip(points.iter().copied().cycle().skip(1)) + .take(points.len()) + .map(|(a, b)| InputEdge { a, b, data }) + .collect() + } + + fn square_i64(x0: i64, y0: i64, x1: i64, y1: i64, data: EdgeColor) -> Vec> { + let points = [ + IntPoint::new(x0, y0), + IntPoint::new(x1, y0), + IntPoint::new(x1, y1), + IntPoint::new(x0, y1), + ]; + + points + .iter() + .copied() + .zip(points.iter().copied().cycle().skip(1)) + .take(points.len()) + .map(|(a, b)| InputEdge { a, b, data }) + .collect() + } +} diff --git a/iOverlay/tests/fill_rule_tests.rs b/iOverlay/tests/fill_rule_tests.rs index 605a40a9..8489c42c 100644 --- a/iOverlay/tests/fill_rule_tests.rs +++ b/iOverlay/tests/fill_rule_tests.rs @@ -8,7 +8,7 @@ mod tests { #[test] fn test_both_clock_wise() { - fn overlay() -> Overlay { + fn overlay() -> Overlay { let mut overlay = Overlay::new(2); overlay.add_contour(&square(10, true), ShapeType::Subject); @@ -50,7 +50,7 @@ mod tests { #[test] fn test_both_counter_clock_wise() { - fn overlay() -> Overlay { + fn overlay() -> Overlay { let mut overlay = Overlay::new(2); overlay.add_contour(&square(10, false), ShapeType::Subject); @@ -92,7 +92,7 @@ mod tests { #[test] fn test_cw_and_ccw() { - fn overlay() -> Overlay { + fn overlay() -> Overlay { let mut overlay = Overlay::new(2); overlay.add_contour(&square(10, true), ShapeType::Subject); @@ -134,7 +134,7 @@ mod tests { #[test] fn test_ccw_and_cw() { - fn overlay() -> Overlay { + fn overlay() -> Overlay { let mut overlay = Overlay::new(2); overlay.add_contour(&square(10, false), ShapeType::Subject); @@ -174,7 +174,7 @@ mod tests { assert_eq!(positive[0].len(), 2); } - fn square(radius: i32, is_clockwise: bool) -> IntPath { + fn square(radius: i32, is_clockwise: bool) -> IntPath { let mut square = [ IntPoint::new(-radius, -radius), IntPoint::new(-radius, radius), diff --git a/iOverlay/tests/float_overlay_tests.rs b/iOverlay/tests/float_overlay_tests.rs index d51606fa..160af833 100644 --- a/iOverlay/tests/float_overlay_tests.rs +++ b/iOverlay/tests/float_overlay_tests.rs @@ -1,5 +1,12 @@ #[cfg(test)] mod tests { + #![allow( + clippy::bool_assert_comparison, + clippy::needless_range_loop, + clippy::useless_vec, + clippy::zero_repeat_side_effects + )] + use i_float::adapter::FloatPointAdapter; use i_float::float::compatible::FloatPointCompatible; use i_overlay::core::fill_rule::FillRule; @@ -272,11 +279,12 @@ mod tests { fn test_empty_0() { let path = vec![FPoint::new(-10.0, -10.0), FPoint::new(-10.0, 10.0)]; - let shapes = FloatOverlay::with_adapter(FloatPointAdapter::with_iter(path.iter()), path.len()) - .build_graph_view(FillRule::NonZero) - .map_or(Default::default(), |graph| { - graph.extract_shapes(OverlayRule::Subject, &mut Default::default()) - }); + let shapes = + FloatOverlay::with_adapter(FloatPointAdapter::<_, i32>::with_iter(path.iter()), path.len()) + .build_graph_view(FillRule::NonZero) + .map_or(Default::default(), |graph| { + graph.extract_shapes(OverlayRule::Subject, &mut Default::default()) + }); assert_eq!(shapes.is_empty(), true); } @@ -291,13 +299,15 @@ mod tests { ] .to_vec()]; - let shapes = - FloatOverlay::with_adapter(FloatPointAdapter::with_iter(shape.iter().flatten()), shape.len()) - .unsafe_add_source(&shape, ShapeType::Subject) - .build_graph_view(FillRule::NonZero) - .map_or(Default::default(), |graph| { - graph.extract_shapes(OverlayRule::Subject, &mut Default::default()) - }); + let shapes = FloatOverlay::with_adapter( + FloatPointAdapter::<_, i32>::with_iter(shape.iter().flatten()), + shape.len(), + ) + .unsafe_add_source(&shape, ShapeType::Subject) + .build_graph_view(FillRule::NonZero) + .map_or(Default::default(), |graph| { + graph.extract_shapes(OverlayRule::Subject, &mut Default::default()) + }); assert_eq!(shapes.len(), 1); assert_eq!(shapes[0].len(), 1); @@ -351,12 +361,13 @@ mod tests { #[test] fn test_empty_4() { let path = vec![FPoint::new(0.0, 0.0)]; - let shapes = FloatOverlay::with_adapter(FloatPointAdapter::with_iter(path.iter()), path.len()) - .unsafe_add_contour(&path, ShapeType::Subject) - .build_graph_view(FillRule::NonZero) - .map_or(Default::default(), |graph| { - graph.extract_shapes(OverlayRule::Subject, &mut Default::default()) - }); + let shapes = + FloatOverlay::with_adapter(FloatPointAdapter::<_, i32>::with_iter(path.iter()), path.len()) + .unsafe_add_contour(&path, ShapeType::Subject) + .build_graph_view(FillRule::NonZero) + .map_or(Default::default(), |graph| { + graph.extract_shapes(OverlayRule::Subject, &mut Default::default()) + }); assert_eq!(shapes.len(), 0); } @@ -376,12 +387,13 @@ mod tests { #[test] fn test_empty_6() { let path = vec![FPoint::new(0.0, 0.0), FPoint::new(1.0, 0.0)]; - let shapes = FloatOverlay::with_adapter(FloatPointAdapter::with_iter(path.iter()), path.len()) - .unsafe_add_contour(&path, ShapeType::Subject) - .build_graph_view(FillRule::NonZero) - .map_or(Default::default(), |graph| { - graph.extract_shapes(OverlayRule::Subject, &mut Default::default()) - }); + let shapes = + FloatOverlay::with_adapter(FloatPointAdapter::<_, i32>::with_iter(path.iter()), path.len()) + .unsafe_add_contour(&path, ShapeType::Subject) + .build_graph_view(FillRule::NonZero) + .map_or(Default::default(), |graph| { + graph.extract_shapes(OverlayRule::Subject, &mut Default::default()) + }); assert_eq!(shapes.len(), 0); } @@ -691,14 +703,13 @@ mod tests { let result_no_filter = FloatOverlay::with_subj_and_clip(&shape_0, &shape_1) .overlay(OverlayRule::Intersect, FillRule::EvenOdd); - let opt = OverlayOptions { - preserve_input_collinear: false, - output_direction: ContourDirection::CounterClockwise, - preserve_output_collinear: false, - min_output_area: 0.0, - ogc: false, - clean_result: false, - }; + let mut opt = OverlayOptions::default(); + opt.preserve_input_collinear = false; + opt.output_direction = ContourDirection::CounterClockwise; + opt.preserve_output_collinear = false; + opt.min_output_area = 0.0; + opt.ogc = false; + opt.clean_result = false; let result_with_filter = FloatOverlay::with_subj_and_clip_custom(&shape_0, &shape_1, opt, Default::default()) diff --git a/iOverlay/tests/float_point_adapter.rs b/iOverlay/tests/float_point_adapter.rs index 22d469ea..3580ba82 100644 --- a/iOverlay/tests/float_point_adapter.rs +++ b/iOverlay/tests/float_point_adapter.rs @@ -18,8 +18,8 @@ mod tests { [s * 1.0, s * 0.0], ]]; - let adapter_100 = FloatPointAdapter::new(FloatRect::new(-100.0, 100.0, -100.0, 100.0)); - let adapter_1000 = FloatPointAdapter::new(FloatRect::new(-1000.0, 1000.0, -1000.0, 1000.0)); + let adapter_100 = FloatPointAdapter::<_, i32>::new(FloatRect::new(-100.0, 100.0, -100.0, 100.0)); + let adapter_1000 = FloatPointAdapter::<_, i32>::new(FloatRect::new(-1000.0, 1000.0, -1000.0, 1000.0)); let subj_100 = FloatOverlay::with_adapter(adapter_100, shape.len()) .unsafe_add_source(&shape, ShapeType::Subject) @@ -58,8 +58,8 @@ mod tests { rect.max_y + 0.1, ); - let adapter_100 = FloatPointAdapter::with_scale(buffer_rect.clone(), 100.0); - let adapter_1000 = FloatPointAdapter::with_scale(buffer_rect, 1000.0); + let adapter_100 = FloatPointAdapter::<_, i32>::with_scale(buffer_rect.clone(), 100.0); + let adapter_1000 = FloatPointAdapter::<_, i32>::with_scale(buffer_rect, 1000.0); let subj_100 = FloatOverlay::with_adapter(adapter_100, shape.len()) .unsafe_add_source(&shape, ShapeType::Subject) diff --git a/iOverlay/tests/fragment_tests.rs b/iOverlay/tests/fragment_tests.rs index a9c4ffce..e3bed8f7 100644 --- a/iOverlay/tests/fragment_tests.rs +++ b/iOverlay/tests/fragment_tests.rs @@ -204,13 +204,13 @@ mod tests { println!("clip: {}", clip.json_print()); } - fn many_squares(start: IntPoint, size: i32, offset: i32, n: usize) -> Vec { + fn many_squares(start: IntPoint, size: i32, offset: i32, n: usize) -> Vec> { let mut result = Vec::with_capacity(n * n); let mut y = start.y; for _ in 0..n { let mut x = start.x; for _ in 0..n { - let path: IntPath = vec![ + let path: IntPath = vec![ IntPoint::new(x, y), IntPoint::new(x, y + size), IntPoint::new(x + size, y + size), @@ -225,13 +225,13 @@ mod tests { result } - fn many_lines_x(a: i32, n: usize) -> Vec { + fn many_lines_x(a: i32, n: usize) -> Vec> { let w = a / 2; let s = a * (n as i32) / 2; let mut x = -s + w / 2; let mut result = Vec::with_capacity(n); for _ in 0..n { - let path: IntPath = vec![ + let path: IntPath = vec![ IntPoint::new(x, -s), IntPoint::new(x, s), IntPoint::new(x + w, s), @@ -244,13 +244,13 @@ mod tests { result } - fn many_lines_y(a: i32, n: usize) -> Vec { + fn many_lines_y(a: i32, n: usize) -> Vec> { let h = a / 2; let s = a * (n as i32) / 2; let mut y = -s + h / 2; let mut result = Vec::with_capacity(n); for _ in 0..n { - let path: IntPath = vec![ + let path: IntPath = vec![ IntPoint::new(-s, y), IntPoint::new(s, y), IntPoint::new(s, y - h), @@ -263,7 +263,7 @@ mod tests { result } - fn discrete_spiral(count: usize, a: i32) -> Vec { + fn discrete_spiral(count: usize, a: i32) -> Vec> { let mut rects = Vec::with_capacity(8 * count); let a2 = 2 * a; @@ -353,7 +353,7 @@ mod tests { rects } - fn romb(x: i32, y: i32, a: i32) -> IntContour { + fn romb(x: i32, y: i32, a: i32) -> IntContour { vec![ IntPoint::new(x - a, y), IntPoint::new(x, y - a), @@ -362,7 +362,7 @@ mod tests { ] } - fn square(x: i32, y: i32, a: i32) -> IntContour { + fn square(x: i32, y: i32, a: i32) -> IntContour { vec![ IntPoint::new(x - a, y + a), IntPoint::new(x - a, y - a), @@ -371,7 +371,14 @@ mod tests { ] } - fn repeat_xy(origin: IntContour, x0: i32, y0: i32, dx: i32, dy: i32, count: usize) -> Vec { + fn repeat_xy( + origin: IntContour, + x0: i32, + y0: i32, + dx: i32, + dy: i32, + count: usize, + ) -> Vec> { let mut contours = Vec::with_capacity(8 * count); let mut x = x0; for _ in 0..count { @@ -391,7 +398,7 @@ mod tests { contours } - fn repeat_x(origin: IntContour, x0: i32, y0: i32, dx: i32, count: usize) -> Vec { + fn repeat_x(origin: IntContour, x0: i32, y0: i32, dx: i32, count: usize) -> Vec> { let mut contours = Vec::with_capacity(8 * count); let mut x = x0; for _ in 0..count { @@ -407,7 +414,7 @@ mod tests { contours } - fn repeat_y(origin: IntContour, x0: i32, y0: i32, dy: i32, count: usize) -> Vec { + fn repeat_y(origin: IntContour, x0: i32, y0: i32, dy: i32, count: usize) -> Vec> { let mut contours = Vec::with_capacity(8 * count); let mut y = y0; for _ in 0..count { diff --git a/iOverlay/tests/ocg_tests.rs b/iOverlay/tests/ocg_tests.rs index be52a82a..97e74738 100644 --- a/iOverlay/tests/ocg_tests.rs +++ b/iOverlay/tests/ocg_tests.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod tests { + #![allow(clippy::explicit_counter_loop)] + use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::{ContourDirection, IntOverlayOptions, Overlay}; use i_overlay::core::overlay_rule::OverlayRule; diff --git a/iOverlay/tests/overlay_tests.rs b/iOverlay/tests/overlay_tests.rs index 3c388dea..4f7f99d0 100644 --- a/iOverlay/tests/overlay_tests.rs +++ b/iOverlay/tests/overlay_tests.rs @@ -3,6 +3,8 @@ mod util; #[cfg(test)] mod tests { + #![allow(clippy::bool_assert_comparison)] + use crate::data::overlay::BooleanTest; use crate::util::overlay; use crate::util::overlay::JsonPrint; @@ -21,11 +23,11 @@ mod tests { preserve_input_collinear: false, output_direction: ContourDirection::Clockwise, preserve_output_collinear: false, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; - fn overlay(test: &BooleanTest, options: IntOverlayOptions, solver: Solver) -> Overlay { + fn overlay(test: &BooleanTest, options: IntOverlayOptions, solver: Solver) -> Overlay { Overlay::with_contours_custom(&test.subj_paths, &test.clip_paths, options, solver) } diff --git a/iOverlay/tests/simplify_tests.rs b/iOverlay/tests/simplify_tests.rs index 4e35f9c9..345d60c5 100644 --- a/iOverlay/tests/simplify_tests.rs +++ b/iOverlay/tests/simplify_tests.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod tests { + #![allow(clippy::field_reassign_with_default, clippy::useless_vec)] + use i_float::int::point::IntPoint; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::{ContourDirection, IntOverlayOptions, Overlay, ShapeType}; @@ -23,7 +25,7 @@ mod tests { preserve_input_collinear: true, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: true, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; @@ -51,7 +53,7 @@ mod tests { preserve_input_collinear: true, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: true, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; @@ -79,7 +81,7 @@ mod tests { preserve_input_collinear: true, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: true, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; @@ -142,7 +144,7 @@ mod tests { preserve_input_collinear: false, output_direction: ContourDirection::CounterClockwise, preserve_output_collinear: true, - min_output_area: 0, + min_output_area: 0u64, ogc: false, }; @@ -164,7 +166,7 @@ mod tests { .to_vec() } - fn square_shape(pos: IntPoint) -> IntShape { + fn square_shape(pos: IntPoint) -> IntShape { [square(pos)].to_vec() } } diff --git a/iOverlay/tests/slice_tests.rs b/iOverlay/tests/slice_tests.rs index 22e1cb6f..c19671ff 100644 --- a/iOverlay/tests/slice_tests.rs +++ b/iOverlay/tests/slice_tests.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod tests { + #![allow(clippy::len_zero, clippy::useless_vec)] + use i_float::int::point::IntPoint; use i_overlay::core::fill_rule::FillRule; use i_overlay::string::line::IntLine; @@ -487,7 +489,7 @@ mod tests { } } - fn random_polygon(radius: i32, n: usize) -> IntPath { + fn random_polygon(radius: i32, n: usize) -> IntPath { let a = radius / 2; let range = -a..=a; let mut points = Vec::with_capacity(n); @@ -501,7 +503,7 @@ mod tests { points } - fn random_lines(radius: i32, n: usize) -> Vec { + fn random_lines(radius: i32, n: usize) -> Vec> { let a = radius / 2; let range = -a..=a; let mut lines = Vec::with_capacity(n); diff --git a/iOverlay/tests/string_tests.rs b/iOverlay/tests/string_tests.rs index 9c984566..8252e7d4 100644 --- a/iOverlay/tests/string_tests.rs +++ b/iOverlay/tests/string_tests.rs @@ -3,6 +3,8 @@ mod util; #[cfg(test)] mod tests { + #![allow(clippy::bool_assert_comparison)] + use crate::data::overlay::StringTest; use crate::util::overlay; use crate::util::overlay::JsonPrint; diff --git a/iOverlay/tests/util.rs b/iOverlay/tests/util.rs index 3a92e8c2..59907f47 100644 --- a/iOverlay/tests/util.rs +++ b/iOverlay/tests/util.rs @@ -34,7 +34,7 @@ pub mod overlay { } } - impl CircleCompare for Vec { + impl CircleCompare for Vec> { fn are_equal(&self, other: &Self) -> bool { if self.len() != other.len() { return false; @@ -61,7 +61,7 @@ pub mod overlay { } #[allow(dead_code)] - pub fn is_group_of_shapes_one_of(group: &Vec, groups: &Vec>) -> bool { + pub fn is_group_of_shapes_one_of(group: &Vec>, groups: &[Vec>]) -> bool { for item in groups.iter() { if item.are_equal(group) { return true; @@ -72,7 +72,7 @@ pub mod overlay { } #[allow(dead_code)] - pub fn is_paths_one_of(paths: &IntPaths, groups: &Vec) -> bool { + pub fn is_paths_one_of(paths: &IntPaths, groups: &[IntPaths]) -> bool { for item in groups.iter() { if item.eq(paths) { return true; @@ -92,7 +92,7 @@ pub mod overlay { } } - impl JsonPrint for IntPath { + impl JsonPrint for IntPath { fn json_print(&self) -> String { let mut s = String::with_capacity(16 * self.len()); s.push('['); @@ -107,7 +107,7 @@ pub mod overlay { } } - impl JsonPrint for IntPaths { + impl JsonPrint for IntPaths { fn json_print(&self) -> String { let mut s = String::with_capacity(100 * self.len()); s.push('['); @@ -122,7 +122,7 @@ pub mod overlay { } } - impl JsonPrint for IntShapes { + impl JsonPrint for IntShapes { fn json_print(&self) -> String { let mut s = String::with_capacity(200 * self.len()); s.push('['); diff --git a/iOverlay/tests/vector_tests.rs b/iOverlay/tests/vector_tests.rs index 9724cf54..7926486a 100644 --- a/iOverlay/tests/vector_tests.rs +++ b/iOverlay/tests/vector_tests.rs @@ -4,7 +4,7 @@ mod tests { use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::{Overlay, ShapeType}; use i_overlay::core::overlay_rule::OverlayRule; - use i_overlay::vector::edge::VectorEdge; + use i_overlay::vector::edge::DataVectorEdge; #[test] fn test_0() { @@ -33,25 +33,29 @@ mod tests { let vectors = &shapes[0][0]; let template = [ - VectorEdge { + DataVectorEdge { a: IntPoint::new(-10240, 10240), b: IntPoint::new(-10240, -10240), fill: 1, + data: (), }, - VectorEdge { + DataVectorEdge { a: IntPoint::new(-10240, -10240), b: IntPoint::new(10240, -10240), fill: 1, + data: (), }, - VectorEdge { + DataVectorEdge { a: IntPoint::new(10240, -10240), b: IntPoint::new(10240, 10240), fill: 1, + data: (), }, - VectorEdge { + DataVectorEdge { a: IntPoint::new(10240, 10240), b: IntPoint::new(-10240, 10240), fill: 1, + data: (), }, ]; @@ -85,35 +89,41 @@ mod tests { let vectors = &shapes[0][0]; let template = [ - VectorEdge { + DataVectorEdge { a: IntPoint::new(-10240, 10240), b: IntPoint::new(-10240, -10240), fill: 1, + data: (), }, - VectorEdge { + DataVectorEdge { a: IntPoint::new(-10240, -10240), b: IntPoint::new(10240, -10240), fill: 1, + data: (), }, - VectorEdge { + DataVectorEdge { a: IntPoint::new(10240, -10240), b: IntPoint::new(10240, -5120), fill: 1, + data: (), }, - VectorEdge { + DataVectorEdge { a: IntPoint::new(10240, -5120), b: IntPoint::new(-5120, -5120), fill: 11, + data: (), }, - VectorEdge { + DataVectorEdge { a: IntPoint::new(-5120, -5120), b: IntPoint::new(-5120, 10240), fill: 11, + data: (), }, - VectorEdge { + DataVectorEdge { a: IntPoint::new(-5120, 10240), b: IntPoint::new(-10240, 10240), fill: 1, + data: (), }, ]; diff --git a/performance/rust_app/Cargo.toml b/performance/rust_app/Cargo.toml index b0ca4080..920cba13 100644 --- a/performance/rust_app/Cargo.toml +++ b/performance/rust_app/Cargo.toml @@ -12,4 +12,6 @@ codegen-units = 1 #i_overlay = { path = "../../iOverlay", default-features = true } i_overlay = { path = "../../iOverlay", features = ["allow_multithreading"] } -#i_overlay = "~3.4.0" \ No newline at end of file +#i_overlay = "6.0.0" +i_key_sort = "^0.10.1" +i_tree = { path = "../../../iTree" } diff --git a/performance/rust_app/run.sh b/performance/rust_app/run.sh index 6c36cf21..1969e792 100755 --- a/performance/rust_app/run.sh +++ b/performance/rust_app/run.sh @@ -1,3 +1,3 @@ #!/bin/bash -target/release/rust_app --multithreading false --complex true --test 0 \ No newline at end of file +target/release/rust_app --multithreading false --complex true --test 0 --int all diff --git a/performance/rust_app/src/main.rs b/performance/rust_app/src/main.rs index 9b8aba53..eccd745f 100644 --- a/performance/rust_app/src/main.rs +++ b/performance/rust_app/src/main.rs @@ -1,7 +1,3 @@ -use std::env; -use std::collections::HashMap; -use i_overlay::core::overlay_rule::OverlayRule; -use i_overlay::core::solver::{MultithreadOptions, Precision, Solver, Strategy}; use crate::test::test_0_checkerboard::CheckerboardTest; use crate::test::test_1_not_overlap::NotOverlapTest; use crate::test::test_2_lines_net::LinesNetTest; @@ -11,24 +7,54 @@ use crate::test::test_5_nested_squares::CrossTest; use crate::test::test_6_corrosion::CorrosionTest; use crate::test::test_7_concentric::ConcentricTest; use crate::test::test_8_wind_mill::WindMillTest; +use i_overlay::core::overlay_rule::OverlayRule; +use i_overlay::core::solver::{MultithreadOptions, Precision, Solver, Strategy}; +use std::collections::HashMap; +use std::env; mod test; +#[derive(Clone, Copy)] +enum IntEngine { + I16, + I32, + I64, +} + +impl IntEngine { + fn parse(value: &str) -> Vec { + match value { + "i16" | "16" => vec![Self::I16], + "i32" | "32" => vec![Self::I32], + "i64" | "64" => vec![Self::I64], + "all" => vec![Self::I16, Self::I32, Self::I64], + _ => panic!("Unknown int engine: {value}. Use i16, i32, i64, or all"), + } + } + + fn label(self) -> &'static str { + match self { + Self::I16 => "i16", + Self::I32 => "i32", + Self::I64 => "i64", + } + } +} + fn main() { let args = env::args(); let mut args_iter = args.peekable(); let mut args_map = HashMap::new(); while let Some(arg) = args_iter.next() { - if arg.starts_with("--") { - let key = arg.trim_start_matches("--").to_owned(); + if let Some(key) = arg.strip_prefix("--").or_else(|| arg.strip_prefix('-')) { // If the next argument is also a key, store a boolean flag; otherwise, store the value. - let value = if args_iter.peek().map_or(false, |a| a.starts_with("--")) { + let value = if args_iter.peek().map_or(false, |a| a.starts_with('-')) { "true".to_string() } else { args_iter.next().unwrap() }; - args_map.insert(key, value); + args_map.insert(key.to_owned(), value); } } @@ -37,28 +63,30 @@ fn main() { if args_map.is_empty() { args_map.insert("multithreading".to_string(), "false".to_string()); args_map.insert("complex".to_string(), "false".to_string()); - args_map.insert("test".to_string(), 7.to_string()); - let count = 32; + args_map.insert("test".to_string(), 3.to_string()); + args_map.insert("int".to_string(), "i64".to_string()); + let count = 4096; args_map.insert("count".to_string(), count.to_string()); - args_map.insert("geom".to_string(), true.to_string()); } } let test_key = args_map.get("test").expect("Test number is not set"); - let multithreading_key = args_map.get("multithreading").expect("Multithreading is not set"); + let multithreading_key = args_map + .get("multithreading") + .expect("Multithreading is not set"); let complex_key = args_map.get("complex").expect("Complex is not set"); - let geom_key = args_map.get("geom"); + let int_key = args_map.get("int").map(String::as_str).unwrap_or("i32"); - let test: usize = test_key.parse().expect("Unable to parse test as an integer"); - let multithreading: bool = multithreading_key.parse().expect("Unable to parse multithreading as an boolean"); - let complex: bool = complex_key.parse().expect("Unable to parse complex as an boolean"); - - let geom = if let Some(key) = geom_key { - let value: bool = key.parse().expect("Unable to parse complex as an boolean"); - value - } else { - false - }; + let test: usize = test_key + .parse() + .expect("Unable to parse test as an integer"); + let multithreading: bool = multithreading_key + .parse() + .expect("Unable to parse multithreading as an boolean"); + let complex: bool = complex_key + .parse() + .expect("Unable to parse complex as an boolean"); + let int_engines = IntEngine::parse(int_key); let multithreading = if multithreading { Some(MultithreadOptions::default()) @@ -66,36 +94,66 @@ fn main() { None }; - let solver = Solver { strategy: Strategy::Auto, precision: Precision::HIGH, multithreading}; + let solver = Solver { + strategy: Strategy::Auto, + precision: Precision::HIGH, + multithreading, + }; + + for int_engine in int_engines { + println!("int engine: {}", int_engine.label()); + run_selected_test(int_engine, test, complex, &args_map, solver); + } +} +fn run_selected_test( + int_engine: IntEngine, + test: usize, + complex: bool, + args_map: &HashMap, + solver: Solver, +) { + match int_engine { + IntEngine::I16 => run_selected_test_with_int::(test, complex, args_map, solver), + IntEngine::I32 => run_selected_test_with_int::(test, complex, args_map, solver), + IntEngine::I64 => run_selected_test_with_int::(test, complex, args_map, solver), + } +} + +fn run_selected_test_with_int( + test: usize, + complex: bool, + args_map: &HashMap, + solver: Solver, +) { if complex { match test { 0 => { - run_test_0(geom, solver); + run_test_0::(solver); } 1 => { - run_test_1(geom, solver); + run_test_1::(solver); } 2 => { - run_test_2(geom, solver); + run_test_2::(solver); } 3 => { - run_test_3(); + run_test_3::(solver); } 4 => { - run_test_4(geom, solver); + run_test_4::(solver); } 5 => { - run_test_5(geom, solver); + run_test_5::(solver); } 6 => { - run_test_6(solver); + run_test_6::(solver); } 7 => { - run_test_7(solver); + run_test_7::(solver); } 8 => { - run_test_8(geom, solver); + run_test_8::(solver); } _ => { println!("Test is not found"); @@ -103,34 +161,36 @@ fn main() { } } else { let count_key = args_map.get("count").expect("Count is not set"); - let count: usize = count_key.parse().expect("Unable to parse count as an integer"); + let count: usize = count_key + .parse() + .expect("Unable to parse count as an integer"); match test { 0 => { - CheckerboardTest::run(count, OverlayRule::Xor, solver, 1.0, geom); + CheckerboardTest::run::(count, OverlayRule::Xor, solver, 1.0); } 1 => { - NotOverlapTest::run(count, OverlayRule::Union, solver, 1.0, geom); + NotOverlapTest::run::(count, OverlayRule::Union, solver, 1.0); } 2 => { - LinesNetTest::run(count, OverlayRule::Intersect, solver, 1.0, geom); + LinesNetTest::run::(count, OverlayRule::Intersect, solver, 1.0); } 3 => { - SpiralTest::run(count, 100.0); + SpiralTest::run::(count, solver, 100.0); } 4 => { - WindowsTest::run(count, OverlayRule::Difference, solver, 1.0, geom); + WindowsTest::run::(count, OverlayRule::Difference, solver, 1.0); } 5 => { - CrossTest::run(count, OverlayRule::Xor, solver, 1.0, geom); + CrossTest::run::(count, OverlayRule::Xor, solver, 1.0); } 6 => { - CorrosionTest::run(count, OverlayRule::Difference, solver, 1.0); + CorrosionTest::run::(count, OverlayRule::Difference, solver, 1.0); } 7 => { - ConcentricTest::run(count, OverlayRule::Intersect, solver, 1.0); + ConcentricTest::run::(count, OverlayRule::Intersect, solver, 1.0); } 8 => { - WindMillTest::run(count, OverlayRule::Intersect, solver, 1.0, geom); + WindMillTest::run::(count, OverlayRule::Intersect, solver, 1.0); } _ => { println!("Test is not found"); @@ -139,78 +199,78 @@ fn main() { } } -fn run_test_0(geom: bool, solver: Solver) { +fn run_test_0(solver: Solver) { println!("run Checkerboard test"); - for i in 1..12 { + for i in 1..11 { let n = 1 << i; - CheckerboardTest::run(n, OverlayRule::Xor, solver, 1000.0, geom); + CheckerboardTest::run::(n, OverlayRule::Xor, solver, 1000.0); } } -fn run_test_1(geom: bool, solver: Solver) { +fn run_test_1(solver: Solver) { println!("run NotOverlap test"); - for i in 1..12 { + for i in 1..11 { let n = 1 << i; - NotOverlapTest::run(n, OverlayRule::Xor, solver, 1000.0, geom); + NotOverlapTest::run::(n, OverlayRule::Xor, solver, 1000.0); } } -fn run_test_2(geom: bool, solver: Solver) { +fn run_test_2(solver: Solver) { println!("run LinesNet test"); - for i in 1..12 { + for i in 1..11 { let n = 1 << i; - LinesNetTest::run(n, OverlayRule::Intersect, solver, 500.0, geom); + LinesNetTest::run::(n, OverlayRule::Intersect, solver, 500.0); } } -fn run_test_3() { +fn run_test_3(solver: Solver) { println!("run Spiral test"); - for i in 1..21 { + for i in 1..20 { let n = 1 << i; - SpiralTest::run(n, 1000.0) + SpiralTest::run::(n, solver, 1000.0) } } -fn run_test_4(geom: bool, solver: Solver) { +fn run_test_4(solver: Solver) { println!("run Windows test"); - for i in 1..12 { + for i in 1..11 { let n = 1 << i; - WindowsTest::run(n, OverlayRule::Difference, solver, 500.0, geom); + WindowsTest::run::(n, OverlayRule::Difference, solver, 500.0); } } -fn run_test_5(geom: bool, solver: Solver) { +fn run_test_5(solver: Solver) { println!("run NestedSquares test"); - for i in 1..19 { + for i in 1..17 { let n = 1 << i; - CrossTest::run(n, OverlayRule::Xor, solver, 500.0, geom); + CrossTest::run::(n, OverlayRule::Xor, solver, 500.0); } } -fn run_test_6(solver: Solver) { +fn run_test_6(solver: Solver) { println!("run Corrosion test"); let mut n = 1; - for _ in 1..12 { - CorrosionTest::run(n, OverlayRule::Difference, solver, 100.0); + for _ in 1..11 { + CorrosionTest::run::(n, OverlayRule::Difference, solver, 100.0); n = n << 1; } } -fn run_test_7(solver: Solver) { +fn run_test_7(solver: Solver) { println!("run Concentric test"); let mut n = 1; - for _ in 1..12 { - ConcentricTest::run(n, OverlayRule::Intersect, solver, 100.0); + for _ in 1..11 { + ConcentricTest::run::(n, OverlayRule::Intersect, solver, 100.0); n = n << 1; } } -fn run_test_8(geom: bool, solver: Solver) { +fn run_test_8(solver: Solver) { println!("run WindMill test"); let mut n = 1; - for _ in 1..12 { - WindMillTest::run(n, OverlayRule::Difference, solver, 100.0, geom); + for _ in 1..11 { + WindMillTest::run::(n, OverlayRule::Difference, solver, 100.0); n = n << 1; } - WindMillTest::validate(100, OverlayRule::Difference, solver); -} \ No newline at end of file + WindMillTest::validate::(100, OverlayRule::Difference, solver); +} diff --git a/performance/rust_app/src/test/mod.rs b/performance/rust_app/src/test/mod.rs index e263bf7b..bdac2a59 100644 --- a/performance/rust_app/src/test/mod.rs +++ b/performance/rust_app/src/test/mod.rs @@ -7,4 +7,4 @@ pub(crate) mod test_5_nested_squares; pub(crate) mod test_6_corrosion; pub(crate) mod test_7_concentric; pub(crate) mod test_8_wind_mill; -mod util; \ No newline at end of file +pub(crate) mod util; diff --git a/performance/rust_app/src/test/test_0_checkerboard.rs b/performance/rust_app/src/test/test_0_checkerboard.rs index 10b71b62..adeef5b2 100644 --- a/performance/rust_app/src/test/test_0_checkerboard.rs +++ b/performance/rust_app/src/test/test_0_checkerboard.rs @@ -1,68 +1,133 @@ -use std::time::Instant; +use crate::test::util::{OverlayInt, Util}; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::Overlay; use i_overlay::core::overlay_rule::OverlayRule; use i_overlay::core::solver::Solver; use i_overlay::i_float::int::point::IntPoint; -use crate::test::util::Util; +use std::time::Instant; pub(crate) struct CheckerboardTest; /* -// test 0 -// Xor: +test 0 +CheckerboardTest +Xor: + +i16 + +multithreading on + +2(5 0.7) - 0.000003(-5.6) +4(25 1.4) - 0.000015(-4.8) +8(113 2.1) - 0.000083(-4.1) +16(481 2.7) - 0.000437(-3.4) +32(1985 3.3) - 0.002305(-2.6) +64(8065 3.9) - 0.007140(-2.1) +128(32513 4.5) - 0.027857(-1.6) +256(130561 5.1) - 0.120606(-0.9) +512(523265 5.7) - 0.568957(-0.2) +1024(2095105 6.3) - 2.413621(0.4) + +multithreading off + +2(5 0.7) - 0.000002(-5.6) +4(25 1.4) - 0.000015(-4.8) +8(113 2.1) - 0.000082(-4.1) +16(481 2.7) - 0.000426(-3.4) +32(1985 3.3) - 0.002236(-2.7) +64(8065 3.9) - 0.008070(-2.1) +128(32513 4.5) - 0.037559(-1.4) +256(130561 5.1) - 0.167375(-0.8) +512(523265 5.7) - 0.776731(-0.1) +1024(2095105 6.3) - 3.385874(0.5) + +i32 multithreading on -2(5 0.7) - 0.000006(-5.2) -4(25 1.4) - 0.000040(-4.4) -8(113 2.1) - 0.000199(-3.7) -16(481 2.7) - 0.001071(-3.0) -32(1985 3.3) - 0.005099(-2.3) -64(8065 3.9) - 0.019657(-1.7) -128(32513 4.5) - 0.077459(-1.1) -256(130561 5.1) - 0.350502(-0.5) -512(523265 5.7) - 1.699005(0.2) -1024(2095105 6.3) - 7.520601(0.9) -2048(8384513 6.9) - 30.258030(1.5) +2(5 0.7) - 0.000002(-5.6) +4(25 1.4) - 0.000015(-4.8) +8(113 2.1) - 0.000081(-4.1) +16(481 2.7) - 0.000409(-3.4) +32(1985 3.3) - 0.002299(-2.6) +64(8065 3.9) - 0.007243(-2.1) +128(32513 4.5) - 0.028986(-1.5) +256(130561 5.1) - 0.125711(-0.9) +512(523265 5.7) - 0.592768(-0.2) +1024(2095105 6.3) - 2.610431(0.4) + +multithreading off +2(5 0.7) - 0.000002(-5.6) +4(25 1.4) - 0.000015(-4.8) +8(113 2.1) - 0.000082(-4.1) +16(481 2.7) - 0.000412(-3.4) +32(1985 3.3) - 0.002318(-2.6) +64(8065 3.9) - 0.008659(-2.1) +128(32513 4.5) - 0.040512(-1.4) +256(130561 5.1) - 0.178425(-0.7) +512(523265 5.7) - 0.827264(-0.1) +1024(2095105 6.3) - 3.611125(0.6) + +i64 + +multithreading on + +2(5 0.7) - 0.000003(-5.6) +4(25 1.4) - 0.000017(-4.8) +8(113 2.1) - 0.000099(-4.0) +16(481 2.7) - 0.000501(-3.3) +32(1985 3.3) - 0.002752(-2.6) +64(8065 3.9) - 0.008616(-2.1) +128(32513 4.5) - 0.035981(-1.4) +256(130561 5.1) - 0.158665(-0.8) +512(523265 5.7) - 0.724087(-0.1) +1024(2095105 6.3) - 3.127404(0.5) multithreading off -2(5 0.7) - 0.000006(-5.2) -4(25 1.4) - 0.000035(-4.5) -8(113 2.1) - 0.000194(-3.7) -16(481 2.7) - 0.001069(-3.0) -32(1985 3.3) - 0.005088(-2.3) -64(8065 3.9) - 0.021487(-1.7) -128(32513 4.5) - 0.095950(-1.0) -256(130561 5.1) - 0.441111(-0.4) -512(523265 5.7) - 2.146555(0.3) -1024(2095105 6.3) - 9.435037(1.0) -2048(8384513 6.9) - 38.408757(1.6) +2(5 0.7) - 0.000003(-5.6) +4(25 1.4) - 0.000017(-4.8) +8(113 2.1) - 0.000099(-4.0) +16(481 2.7) - 0.000498(-3.3) +32(1985 3.3) - 0.002733(-2.6) +64(8065 3.9) - 0.010388(-2.0) +128(32513 4.5) - 0.050085(-1.3) +256(130561 5.1) - 0.219622(-0.7) +512(523265 5.7) - 1.011703(0.0) +1024(2095105 6.3) - 4.393880(0.6) */ // A grid of overlapping squares forming a simple checkerboard pattern. impl CheckerboardTest { - pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64, simple_geometry: bool) { // 1000 - let subj_paths = Util::many_squares(IntPoint::new(0, 0), 20, 30, n); - let clip_paths = Util::many_squares(IntPoint::new(15, 15), 20, 30, n - 1); + pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { + // 1000 + if Util::skip_if_out_of_range::(n, 15 * n + 10) { + return; + } + + let subj_paths = Util::many_squares( + IntPoint::new(I::ZERO, I::ZERO), + I::from_usize(20), + I::from_usize(30), + n, + ); + let clip_paths = Util::many_squares( + IntPoint::new(I::from_usize(15), I::from_usize(15)), + I::from_usize(20), + I::from_usize(30), + n - 1, + ); let it_count = ((scale / (n as f64)) as usize).max(1); - let sq_it_count= it_count * it_count; + let sq_it_count = it_count * it_count; let start = Instant::now(); - if simple_geometry { - // for _i in 0..sq_it_count { - // let _ = Overlay::with_contours(&subj_paths, &clip_paths) - // .overlay_45geom_with_min_area_and_solver(rule, FillRule::NonZero, 0, solver); - // } - } else { - for _i in 0..sq_it_count { - let _ = Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) + for _i in 0..sq_it_count { + let _ = + Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) .overlay(rule, FillRule::NonZero); - } } let duration = start.elapsed(); @@ -72,6 +137,9 @@ impl CheckerboardTest { let count_log = (polygons_count as f64).log10(); let time_log = time.log10(); - println!("{}({} {:.1}) - {:.6}({:.1})", n, polygons_count, count_log, time, time_log); + println!( + "{}({} {:.1}) - {:.6}({:.1})", + n, polygons_count, count_log, time, time_log + ); } -} \ No newline at end of file +} diff --git a/performance/rust_app/src/test/test_1_not_overlap.rs b/performance/rust_app/src/test/test_1_not_overlap.rs index 844b78c1..d447e08f 100644 --- a/performance/rust_app/src/test/test_1_not_overlap.rs +++ b/performance/rust_app/src/test/test_1_not_overlap.rs @@ -1,68 +1,137 @@ -use std::time::Instant; +use crate::test::util::{OverlayInt, Util}; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::Overlay; use i_overlay::core::overlay_rule::OverlayRule; use i_overlay::core::solver::Solver; use i_overlay::i_float::int::point::IntPoint; -use crate::test::util::Util; +use std::time::Instant; pub(crate) struct NotOverlapTest; /* +test 1 +NotOverlapTest +Union: + +i16 + +multithreading on + +5 - 0.000001 +25 - 0.000005 +113 - 0.000024 +481 - 0.000138 +1985 - 0.000908 +8065 - 0.002176 +32513 - 0.008377 +130561 - 0.034438 +523265 - 0.158055 +2095105 - 0.687777 + +multithreading off + +5 - 0.000001 +25 - 0.000005 +113 - 0.000024 +481 - 0.000136 +1985 - 0.000901 +8065 - 0.002461 +32513 - 0.011272 +130561 - 0.048214 +523265 - 0.220364 +2095105 - 0.927191 -// 1 -// Union: +i32 multithreading on -5 - 0.000003 -25 - 0.000016 -113 - 0.000066 -481 - 0.000371 -1985 - 0.001692 -8065 - 0.005704 -32513 - 0.026937 -130561 - 0.106385 -523265 - 0.549426 -2095105 - 2.380725 -8384513 - 10.040016 +5 - 0.000001 +25 - 0.000005 +113 - 0.000024 +481 - 0.000132 +1985 - 0.000908 +8065 - 0.002322 +32513 - 0.008956 +130561 - 0.036901 +523265 - 0.177118 +2095105 - 0.745187 +8384513 - 3.282416 multithreading off -5 - 0.000003 -25 - 0.000013 -113 - 0.000064 -481 - 0.000368 -1985 - 0.001671 -8065 - 0.006311 -32513 - 0.030795 -130561 - 0.133008 -523265 - 0.680722 -2095105 - 3.050487 -8384513 - 12.764988 +5 - 0.000001 +25 - 0.000005 +113 - 0.000024 +481 - 0.000133 +1985 - 0.000912 +8065 - 0.002725 +32513 - 0.012319 +130561 - 0.053420 +523265 - 0.240664 +2095105 - 1.037521 +8384513 - 4.580672 + +i64 + +multithreading on + +5 - 0.000001 +25 - 0.000006 +113 - 0.000028 +481 - 0.000151 +1985 - 0.001053 +8065 - 0.002579 +32513 - 0.010409 +130561 - 0.044266 +523265 - 0.217189 +2095105 - 0.920391 +8384513 - 3.948376 + +multithreading off + +5 - 0.000001 +25 - 0.000006 +113 - 0.000027 +481 - 0.000152 +1985 - 0.001046 +8065 - 0.003178 +32513 - 0.014719 +130561 - 0.065100 +523265 - 0.301577 +2095105 - 1.300260 +8384513 - 5.667204 */ // A grid of not overlapping squares. impl NotOverlapTest { - pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64, simple_geometry: bool) { // 1000 - let subj_paths = Util::many_squares(IntPoint::new(0, 0), 10, 30, n); - let clip_paths = Util::many_squares(IntPoint::new(15, 15), 10, 30, n - 1); + pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { + // 1000 + if Util::skip_if_out_of_range::(n, 30 * n + 10) { + return; + } + + let subj_paths = Util::many_squares( + IntPoint::new(I::ZERO, I::ZERO), + I::from_usize(10), + I::from_usize(30), + n, + ); + let clip_paths = Util::many_squares( + IntPoint::new(I::from_usize(15), I::from_usize(15)), + I::from_usize(10), + I::from_usize(30), + n - 1, + ); let it_count = ((scale / (n as f64)) as usize).max(1); - let sq_it_count= it_count * it_count; + let sq_it_count = it_count * it_count; let start = Instant::now(); - if simple_geometry { - // for _i in 0..sq_it_count { - // let _ = Overlay::with_contours(&subj_paths, &clip_paths) - // .overlay_45geom_with_min_area_and_solver(rule, FillRule::NonZero, 0, solver); - // } - } else { - for _i in 0..sq_it_count { - let _ = Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) + for _i in 0..sq_it_count { + let _ = + Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) .overlay(rule, FillRule::NonZero); - } } let duration = start.elapsed(); @@ -72,4 +141,4 @@ impl NotOverlapTest { println!("{:.1} - {:.6}", polygons_count, time); } -} \ No newline at end of file +} diff --git a/performance/rust_app/src/test/test_2_lines_net.rs b/performance/rust_app/src/test/test_2_lines_net.rs index 14142cb6..3d98e92d 100644 --- a/performance/rust_app/src/test/test_2_lines_net.rs +++ b/performance/rust_app/src/test/test_2_lines_net.rs @@ -1,96 +1,127 @@ -use std::time::Instant; +use crate::test::util::{OverlayInt, Util}; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::Overlay; use i_overlay::core::overlay_rule::OverlayRule; use i_overlay::core::solver::Solver; -use crate::test::util::Util; +use std::time::Instant; pub(crate) struct LinesNetTest; /* +test 2 +LinesNetTest +Intersection: + +i16 + +multithreading on + +4 - 0.000002 +8 - 0.000006 +16 - 0.000021 +32 - 0.000092 +64 - 0.000403 +128 - 0.001716 +256 - 0.007047 +512 - 0.030516 +1024 - 0.130099 +2048 - 0.569064 + +multithreading off + +4 - 0.000002 +8 - 0.000006 +16 - 0.000020 +32 - 0.000091 +64 - 0.000392 +128 - 0.001717 +256 - 0.008280 +512 - 0.037633 +1024 - 0.168466 +2048 - 0.751282 -// 2 -// Intersection: +i32 multithreading on -4 - 0.000005 -8 - 0.000019 -16 - 0.000057 -32 - 0.000227 -64 - 0.000989 -128 - 0.004524 -256 - 0.021169 -512 - 0.093545 -1024 - 0.407744 -2048 - 1.617059 -4096 - 6.436703 +4 - 0.000002 +8 - 0.000006 +16 - 0.000020 +32 - 0.000087 +64 - 0.000423 +128 - 0.001829 +256 - 0.007510 +512 - 0.032208 +1024 - 0.145159 +2048 - 0.622211 +4096 - 2.687778 multithreading off -4 - 0.000005 -8 - 0.000016 -16 - 0.000053 -32 - 0.000218 -64 - 0.000981 -128 - 0.004478 -256 - 0.021773 -512 - 0.105066 -1024 - 0.469201 -2048 - 1.982992 -4096 - 8.215361 - -geom multithreading off - -4 - 0.000006 -8 - 0.000016 -16 - 0.000050 -32 - 0.000196 -64 - 0.001032 -128 - 0.003914 -256 - 0.018113 -512 - 0.088561 -1024 - 0.371023 -2048 - 1.676831 -4096 - 7.055219 - -// geom swipe line - -4 - 0.000005 -8 - 0.000014 -16 - 0.000050 -32 - 0.000191 -64 - 0.000852 -128 - 0.003730 -256 - 0.017368 -512 - 0.082651 -1024 - 0.379062 -2048 - 1.638863 -4096 - 6.566427 +4 - 0.000002 +8 - 0.000006 +16 - 0.000020 +32 - 0.000087 +64 - 0.000425 +128 - 0.001791 +256 - 0.008733 +512 - 0.039941 +1024 - 0.181488 +2048 - 0.806123 +4096 - 3.557761 + +i64 + +multithreading on + +4 - 0.000002 +8 - 0.000007 +16 - 0.000025 +32 - 0.000114 +64 - 0.000497 +128 - 0.002118 +256 - 0.008971 +512 - 0.041422 +1024 - 0.181670 +2048 - 0.780686 +4096 - 3.220613 + +multithreading off + +4 - 0.000002 +8 - 0.000007 +16 - 0.000025 +32 - 0.000113 +64 - 0.000486 +128 - 0.002161 +256 - 0.010305 +512 - 0.048515 +1024 - 0.219835 +2048 - 0.991209 +4096 - 4.420209 */ // A grid is formed by the intersection of a set of vertical and horizontal lines. impl LinesNetTest { - pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64, simple_geometry: bool) { // 500 - let subj_paths = Util::many_lines_x(20, n); - let clip_paths = Util::many_lines_y(20, n); + pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { + // 500 + if Util::skip_if_out_of_range::(n, 10 * n + 10) { + return; + } + + let subj_paths = Util::many_lines_x(I::from_usize(20), n); + let clip_paths = Util::many_lines_y(I::from_usize(20), n); let it_count = ((scale / (n as f64)) as usize).max(1); - let sq_it_count= it_count * it_count; + let sq_it_count = it_count * it_count; let start = Instant::now(); - if simple_geometry { - // for _ in 0..sq_it_count { - // let _ = Overlay::with_contours(&subj_paths, &clip_paths) - // .overlay_45geom_with_min_area_and_solver(rule, FillRule::NonZero, 0, solver); - // } - } else { - for _ in 0..sq_it_count { - let _ = Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) + for _ in 0..sq_it_count { + let _ = + Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) .overlay(rule, FillRule::NonZero); - } } let duration = start.elapsed(); @@ -100,4 +131,4 @@ impl LinesNetTest { println!("{} - {:.6}", polygons_count, time); } -} \ No newline at end of file +} diff --git a/performance/rust_app/src/test/test_3_spiral.rs b/performance/rust_app/src/test/test_3_spiral.rs index 6ed07638..7c2e8b8f 100644 --- a/performance/rust_app/src/test/test_3_spiral.rs +++ b/performance/rust_app/src/test/test_3_spiral.rs @@ -1,75 +1,176 @@ -use std::time::Instant; +use crate::test::util::{OverlayInt, Util}; use i_overlay::core::fill_rule::FillRule; -use i_overlay::float::simplify::SimplifyShape; -use crate::test::util::Util; +use i_overlay::core::overlay_rule::OverlayRule; +use i_overlay::core::solver::Solver; +use i_overlay::float::overlay::FloatOverlay; +use i_overlay::i_float::float::point::FloatPoint; +use std::time::Instant; pub(crate) struct SpiralTest; /* +test 3 +SpiralTest +Intersection: + +i16 + +multithreading on + +2 - 0.000001 +4 - 0.000002 +8 - 0.000005 +16 - 0.000007 +32 - 0.000015 +64 - 0.000031 +128 - 0.000076 +256 - 0.000279 +512 - 0.000764 +1024 - 0.001962 +2048 - 0.003950 +4096 - 0.004374 +8192 - 0.008446 +16384 - 0.017347 +32768 - 0.033183 +65536 - 0.065167 +131072 - 0.131789 +262144 - 0.301777 +524288 - 0.456391 + +multithreading off -// 3 -// Intersection: +2 - 0.000001 +4 - 0.000002 +8 - 0.000005 +16 - 0.000007 +32 - 0.000015 +64 - 0.000031 +128 - 0.000086 +256 - 0.000251 +512 - 0.000698 +1024 - 0.001818 +2048 - 0.004124 +4096 - 0.005286 +8192 - 0.010566 +16384 - 0.022061 +32768 - 0.045513 +65536 - 0.094961 +131072 - 0.201415 +262144 - 0.431597 +524288 - 0.666660 +i32 multithreading on -2 - 0.000002 -4 - 0.000005 -8 - 0.000010 -16 - 0.000022 -32 - 0.000053 -64 - 0.000138 -128 - 0.000318 -256 - 0.000698 -512 - 0.001734 -1024 - 0.003680 -2048 - 0.008326 -4096 - 0.011125 -8192 - 0.019165 -16384 - 0.041279 -32768 - 0.082171 -65536 - 0.179237 -131072 - 0.341959 -262144 - 0.785235 -524288 - 1.345209 -1048576 - 3.039988 +2 - 0.000001 +4 - 0.000002 +8 - 0.000005 +16 - 0.000010 +32 - 0.000020 +64 - 0.000042 +128 - 0.000104 +256 - 0.000245 +512 - 0.000748 +1024 - 0.001984 +2048 - 0.004483 +4096 - 0.006270 +8192 - 0.009434 +16384 - 0.019526 +32768 - 0.034397 +65536 - 0.074229 +131072 - 0.146095 +262144 - 0.350941 +524288 - 0.744520 multithreading off -2 - 0.000003 -4 - 0.000005 -8 - 0.000010 -16 - 0.000022 -32 - 0.000054 -64 - 0.000138 -128 - 0.000316 -256 - 0.000727 -512 - 0.001767 -1024 - 0.003605 -2048 - 0.008468 -4096 - 0.010696 -8192 - 0.019332 -16384 - 0.041967 -32768 - 0.078037 -65536 - 0.175368 -131072 - 0.323782 -262144 - 0.823820 -524288 - 1.425306 -1048576 - 3.029298 +2 - 0.000001 +4 - 0.000002 +8 - 0.000005 +16 - 0.000010 +32 - 0.000021 +64 - 0.000043 +128 - 0.000092 +256 - 0.000298 +512 - 0.000790 +1024 - 0.002041 +2048 - 0.004207 +4096 - 0.007974 +8192 - 0.012796 +16384 - 0.028001 +32768 - 0.050997 +65536 - 0.113348 +131072 - 0.223653 +262144 - 0.541526 +524288 - 1.034178 + +i64 + +multithreading on + +2 - 0.000001 +4 - 0.000003 +8 - 0.000008 +16 - 0.000016 +32 - 0.000033 +64 - 0.000069 +128 - 0.000154 +256 - 0.000380 +512 - 0.001008 +1024 - 0.002594 +2048 - 0.005705 +4096 - 0.005941 +8192 - 0.010722 +16384 - 0.023228 +32768 - 0.042546 +65536 - 0.091817 +131072 - 0.192015 +262144 - 0.449105 +524288 - 0.922172 + +multithreading off + +2 - 0.000001 +4 - 0.000003 +8 - 0.000007 +16 - 0.000016 +32 - 0.000034 +64 - 0.000071 +128 - 0.000159 +256 - 0.000376 +512 - 0.001018 +1024 - 0.002658 +2048 - 0.005736 +4096 - 0.009687 +8192 - 0.017713 +16384 - 0.037901 +32768 - 0.075060 +65536 - 0.165686 +131072 - 0.336338 +262144 - 0.780771 +524288 - 1.536626 + */ // Two irregular self-intersecting polygons are generated, the vertices of which are defined by a fixed radius and angle. impl SpiralTest { - pub(crate) fn run(n: usize, scale: f64) { // 1000 + pub(crate) fn run(n: usize, solver: Solver, scale: f64) { + // 1000 let subj_path = Util::spiral(n, 100.0); let it_count = ((scale / (n as f64)) as usize).max(1); - let sq_it_count= it_count * it_count; + let sq_it_count = it_count * it_count; let start = Instant::now(); for _ in 0..sq_it_count { - let _ = subj_path.simplify_shape(FillRule::NonZero); + let _ = FloatOverlay::, I>::from_subj_custom( + &subj_path, + Default::default(), + solver, + ) + .overlay(OverlayRule::Subject, FillRule::NonZero); } let duration = start.elapsed(); @@ -79,4 +180,4 @@ impl SpiralTest { println!("{} - {:.6}", polygons_count, time); } -} \ No newline at end of file +} diff --git a/performance/rust_app/src/test/test_4_windows.rs b/performance/rust_app/src/test/test_4_windows.rs index c72e9afb..5c460c40 100644 --- a/performance/rust_app/src/test/test_4_windows.rs +++ b/performance/rust_app/src/test/test_4_windows.rs @@ -1,67 +1,120 @@ -use std::time::Instant; +use crate::test::util::{OverlayInt, Util}; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::Overlay; use i_overlay::core::overlay_rule::OverlayRule; use i_overlay::core::solver::Solver; use i_overlay::i_float::int::point::IntPoint; -use crate::test::util::Util; +use std::time::Instant; pub(crate) struct WindowsTest; /* -// 4 -// Difference: +test 4 +WindowsTest +Difference: + +i16 + +// multithreading on +8 - 0.000003 +32 - 0.000009 +128 - 0.000038 +512 - 0.000204 +2048 - 0.001129 +8192 - 0.002633 +32768 - 0.010163 +131072 - 0.045737 +524288 - 0.199035 +2097152 - 0.909131 + +// multithreading off +8 - 0.000003 +32 - 0.000009 +128 - 0.000038 +512 - 0.000205 +2048 - 0.001127 +8192 - 0.003188 +32768 - 0.013917 +131072 - 0.063098 +524288 - 0.277322 +2097152 - 1.211561 + +i32 // multithreading on -8 - 0.000006 -32 - 0.000024 -128 - 0.000102 -512 - 0.000554 -2048 - 0.002276 -8192 - 0.007199 -32768 - 0.036992 -131072 - 0.159541 -524288 - 0.701553 -2097152 - 2.941974 -8388608 - 12.287573 +8 - 0.000002 +32 - 0.000009 +128 - 0.000038 +512 - 0.000194 +2048 - 0.001138 +8192 - 0.002809 +32768 - 0.010892 +131072 - 0.049875 +524288 - 0.224817 +2097152 - 1.007965 // multithreading off -8 - 0.000006 -32 - 0.000022 -128 - 0.000099 -512 - 0.000546 -2048 - 0.002241 -8192 - 0.008248 -32768 - 0.042241 -131072 - 0.202890 -524288 - 0.880672 -2097152 - 3.708358 -8388608 - 15.611615 +8 - 0.000003 +32 - 0.000008 +128 - 0.000038 +512 - 0.000195 +2048 - 0.001133 +8192 - 0.003421 +32768 - 0.015584 +131072 - 0.069925 +524288 - 0.312258 +2097152 - 1.382459 + +i64 + +// multithreading on +8 - 0.000003 +32 - 0.000010 +128 - 0.000043 +512 - 0.000224 +2048 - 0.001340 +8192 - 0.003138 +32768 - 0.012617 +131072 - 0.061721 +524288 - 0.267889 +2097152 - 1.175990 + +// multithreading off +8 - 0.000003 +32 - 0.000010 +128 - 0.000043 +512 - 0.000225 +2048 - 0.001338 +8192 - 0.004013 +32768 - 0.018626 +131072 - 0.088967 +524288 - 0.384434 +2097152 - 1.709374 */ // A grid of square frames, each with a smaller square cutout in the center. impl WindowsTest { - pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64, simple_geometry: bool) { // 500 - let offset = 30; - let x = (n as i32) * offset / 2; + pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { + // 500 + if Util::skip_if_out_of_range::(n, 15 * n + 20) { + return; + } + + let offset = I::from_usize(30); + let x = I::from_usize(n) * offset / I::TWO; let origin = IntPoint::new(-x, -x); - let (subj_paths, clip_paths) = Util::many_windows(origin, 20, 10, offset, n); + let (subj_paths, clip_paths) = + Util::many_windows(origin, I::from_usize(20), I::from_usize(10), offset, n); let it_count = ((scale / (n as f64)) as usize).max(1); - let sq_it_count= it_count * it_count; + let sq_it_count = it_count * it_count; let start = Instant::now(); - if simple_geometry { - // for _ in 0..sq_it_count { - // let _ = Overlay::with_contours(&subj_paths, &clip_paths) - // .overlay_45geom_with_min_area_and_solver(rule, FillRule::NonZero, 0, solver); - // } - } else { - for _ in 0..sq_it_count { - let _ = Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) + for _ in 0..sq_it_count { + let _ = + Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) .overlay(rule, FillRule::NonZero); - } } let duration = start.elapsed(); @@ -71,4 +124,4 @@ impl WindowsTest { println!("{} - {:.6}", polygons_count, time); } -} \ No newline at end of file +} diff --git a/performance/rust_app/src/test/test_5_nested_squares.rs b/performance/rust_app/src/test/test_5_nested_squares.rs index e8c8b9e0..de2565a4 100644 --- a/performance/rust_app/src/test/test_5_nested_squares.rs +++ b/performance/rust_app/src/test/test_5_nested_squares.rs @@ -1,77 +1,143 @@ -use std::time::Instant; +use crate::test::util::{OverlayInt, Util}; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::Overlay; use i_overlay::core::overlay_rule::OverlayRule; use i_overlay::core::solver::Solver; -use crate::test::util::Util; +use std::time::Instant; pub(crate) struct CrossTest; /* +test 5 +CrossTest +Union: + +i16 + +// multithreading on +4 - 0.000004 +8 - 0.000007 +16 - 0.000015 +32 - 0.000034 +64 - 0.000083 +128 - 0.000228 +256 - 0.000698 +512 - 0.002015 +1024 - 0.005271 +2048 - 0.007070 +4096 - 0.058529 + +// multithreading off +4 - 0.000004 +8 - 0.000007 +16 - 0.000015 +32 - 0.000034 +64 - 0.000082 +128 - 0.000224 +256 - 0.000651 +512 - 0.002065 +1024 - 0.005356 +2048 - 0.009238 +4096 - 0.057300 -// 5 -// Union: +i32 // multithreading on -4 - 0.000011 -8 - 0.000023 -16 - 0.000042 -32 - 0.000091 -64 - 0.000220 -128 - 0.000625 -256 - 0.002053 -512 - 0.005073 -1024 - 0.013714 -2048 - 0.021411 -4096 - 0.048197 -8192 - 0.157209 -16384 - 0.347116 -32768 - 1.280415 -65536 - 2.305738 -131072 - 9.871802 -262144 - 16.526387 +4 - 0.000004 +8 - 0.000008 +16 - 0.000015 +32 - 0.000033 +64 - 0.000080 +128 - 0.000214 +256 - 0.000672 +512 - 0.002109 +1024 - 0.005426 +2048 - 0.007757 +4096 - 0.014683 +8192 - 0.046014 +16384 - 0.087646 +32768 - 0.322707 +65536 - 0.653741 +131072 - 2.410910 // multithreading off -4 - 0.000010 -8 - 0.000018 -16 - 0.000039 -32 - 0.000086 -64 - 0.000215 -128 - 0.000620 -256 - 0.002037 -512 - 0.004784 -1024 - 0.013536 -2048 - 0.031879 -4096 - 0.066320 -8192 - 0.253849 -16384 - 0.448397 -32768 - 1.870073 -65536 - 3.989876 -131072 - 15.874618 -262144 - 31.306648 +4 - 0.000004 +8 - 0.000007 +16 - 0.000015 +32 - 0.000033 +64 - 0.000079 +128 - 0.000213 +256 - 0.000659 +512 - 0.002060 +1024 - 0.005223 +2048 - 0.011609 +4096 - 0.023309 +8192 - 0.081856 +16384 - 0.181416 +32768 - 0.667394 +65536 - 1.401006 +131072 - 5.445065 + +i64 + +// multithreading on +4 - 0.000004 +8 - 0.000009 +16 - 0.000018 +32 - 0.000040 +64 - 0.000097 +128 - 0.000253 +256 - 0.000756 +512 - 0.002833 +1024 - 0.007585 +2048 - 0.009880 +4096 - 0.018457 +8192 - 0.059750 +16384 - 0.122264 +32768 - 0.431346 +65536 - 0.933473 +131072 - 3.822923 + +// multithreading off + +4 - 0.000005 +8 - 0.000008 +16 - 0.000018 +32 - 0.000040 +64 - 0.000096 +128 - 0.000250 +256 - 0.000760 +512 - 0.002837 +1024 - 0.007613 +2048 - 0.015553 +4096 - 0.031292 +8192 - 0.113196 +16384 - 0.245353 +32768 - 0.961019 +65536 - 2.059438 +131072 - 8.138039 */ // A series of concentric squares, each progressively larger than the last. impl CrossTest { - pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64, simple_geometry: bool) { // 500 - let (subj_paths, clip_paths) = Util::concentric_squares(4, n); + pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { + // 500 + if Util::skip_if_out_of_range::(n, 8 * n + 8) { + return; + } + + let (subj_paths, clip_paths) = Util::concentric_squares(I::from_usize(4), n); let it_count = ((scale / (n as f64)) as usize).max(1); - let sq_it_count= it_count * it_count; + let sq_it_count = it_count * it_count; let start = Instant::now(); - if simple_geometry { - // for _ in 0..sq_it_count { - // let _ = Overlay::with_contours(&subj_paths, &clip_paths) - // .overlay_45geom_with_min_area_and_solver(rule, FillRule::NonZero, 0, solver); - // } - } else { - for _ in 0..sq_it_count { - let _ = Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) + for _ in 0..sq_it_count { + let _ = + Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) .overlay(rule, FillRule::NonZero); - } } let duration = start.elapsed(); let time = duration.as_secs_f64() / sq_it_count as f64; @@ -80,4 +146,4 @@ impl CrossTest { println!("{} - {:.6}", polygons_count, time); } -} \ No newline at end of file +} diff --git a/performance/rust_app/src/test/test_6_corrosion.rs b/performance/rust_app/src/test/test_6_corrosion.rs index b1ea260e..b7d7c951 100644 --- a/performance/rust_app/src/test/test_6_corrosion.rs +++ b/performance/rust_app/src/test/test_6_corrosion.rs @@ -1,47 +1,100 @@ +use crate::test::util::OverlayInt; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay_rule::OverlayRule; -use std::f64::consts::PI; -use std::time::Instant; use i_overlay::core::solver::Solver; use i_overlay::float::overlay::FloatOverlay; +use std::f64::consts::PI; +use std::time::Instant; pub(crate) struct CorrosionTest; /* +test 6 +CorrosionTest +Difference: + +i16 + +// multithreading on +1 - 0.000006 +2 - 0.000018 +4 - 0.000082 +8 - 0.000578 +16 - 0.003789 +32 - 0.007703 +64 - 0.030151 +128 - 0.134113 +256 - 0.553680 +512 - 2.020489 + +// multithreading off +1 - 0.000006 +2 - 0.000018 +4 - 0.000082 +8 - 0.000583 +16 - 0.003799 +32 - 0.009568 +64 - 0.041341 +128 - 0.209341 +256 - 0.878897 +512 - 2.869231 + +i32 -// 6 -// Difference: +// multithreading on +1 - 0.000007 +2 - 0.000024 +4 - 0.000103 +8 - 0.000645 +16 - 0.004095 +32 - 0.008387 +64 - 0.033293 +128 - 0.133794 +256 - 0.594231 +512 - 2.297538 + +// multithreading off +1 - 0.000007 +2 - 0.000023 +4 - 0.000104 +8 - 0.000636 +16 - 0.004134 +32 - 0.011785 +64 - 0.050564 +128 - 0.199536 +256 - 0.812732 +512 - 3.383901 + +i64 // multithreading on -1 - 0.000010 -2 - 0.000061 -4 - 0.000364 -8 - 0.001869 -16 - 0.004424 -32 - 0.017941 -64 - 0.079459 -128 - 0.326245 -256 - 1.313516 -512 - 5.392524 -1024 - 22.228494 +1 - 0.000009 +2 - 0.000036 +4 - 0.000158 +8 - 0.000914 +16 - 0.005314 +32 - 0.010206 +64 - 0.042668 +128 - 0.175621 +256 - 0.737713 +512 - 3.101692 // multithreading off -1 - 0.000010 -2 - 0.000064 -4 - 0.000381 -8 - 0.001897 -16 - 0.005981 -32 - 0.023521 -64 - 0.106998 -128 - 0.439589 -256 - 1.756946 -512 - 7.186862 -1024 - 30.818670 +1 - 0.000009 +2 - 0.000036 +4 - 0.000159 +8 - 0.000918 +16 - 0.005234 +32 - 0.016559 +64 - 0.072355 +128 - 0.304704 +256 - 1.280368 +512 - 5.351266 */ // A series of concentric squares, each progressively larger than the last. impl CorrosionTest { - pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { + pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { // 500 let (subj_paths, clip_paths) = Self::geometry(100.0, n); @@ -51,7 +104,12 @@ impl CorrosionTest { let start = Instant::now(); for _ in 0..sq_it_count { - let mut overlay = FloatOverlay::with_subj_and_clip_custom(&subj_paths, &clip_paths, Default::default(), solver); + let mut overlay = FloatOverlay::<[f64; 2], I>::from_subj_and_clip_custom( + &subj_paths, + &clip_paths, + Default::default(), + solver, + ); let _res = overlay.overlay(rule, FillRule::NonZero); } let duration = start.elapsed(); diff --git a/performance/rust_app/src/test/test_7_concentric.rs b/performance/rust_app/src/test/test_7_concentric.rs index 2087b413..680c7d6f 100644 --- a/performance/rust_app/src/test/test_7_concentric.rs +++ b/performance/rust_app/src/test/test_7_concentric.rs @@ -1,61 +1,102 @@ +use crate::test::util::OverlayInt; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay_rule::OverlayRule; -use std::f64::consts::PI; -use std::time::Instant; use i_overlay::core::solver::Solver; use i_overlay::float::overlay::FloatOverlay; +use std::f64::consts::PI; +use std::time::Instant; pub(crate) struct ConcentricTest; /* +test 7 +ConcentricTest +Difference: -// 7 -// Difference: +i16 + +// multithreading on +1 - 0.000007 +2 - 0.000020 +4 - 0.000082 +8 - 0.000610 +16 - 0.005260 +32 - 0.013028 +64 - 0.053713 +128 - 0.318682 +256 - 1.384741 +512 - 6.981723 + +// multithreading off +1 - 0.000007 +2 - 0.000020 +4 - 0.000083 +8 - 0.000569 +16 - 0.005350 +32 - 0.009203 +64 - 0.035059 +128 - 0.170760 +256 - 0.712446 +512 - 3.317967 + +i32 + +// multithreading on +1 - 0.000010 +2 - 0.000028 +4 - 0.000113 +8 - 0.000660 +16 - 0.004171 +32 - 0.008281 +64 - 0.034357 +128 - 0.134210 +256 - 0.563524 +512 - 2.367582 + +// multithreading off +1 - 0.000010 +2 - 0.000029 +4 - 0.000112 +8 - 0.000709 +16 - 0.004211 +32 - 0.012234 +64 - 0.052943 +128 - 0.223251 +256 - 0.955805 +512 - 3.692530 + +i64 // multithreading on 1 - 0.000016 -2 - 0.000086 -4 - 0.000407 -8 - 0.001839 -16 - 0.008672 -32 - 0.028811 -64 - 0.090131 -128 - 0.368713 -256 - 1.456724 -512 - 6.657090 -1024 - 31.702080 +2 - 0.000049 +4 - 0.000189 +8 - 0.000955 +16 - 0.005389 +32 - 0.010158 +64 - 0.042141 +128 - 0.171089 +256 - 0.726401 +512 - 3.116469 // multithreading off -1 - 0.000015 -2 - 0.000086 -4 - 0.000402 -8 - 0.001852 -16 - 0.008529 -32 - 0.035403 -64 - 0.122799 -128 - 0.504404 -256 - 2.038331 -512 - 10.096450 -1024 - 50.159095 - - -1 - 0.000015 -2 - 0.000086 -4 - 0.000399 -8 - 0.001818 -16 - 0.007582 -32 - 0.036797 -64 - 0.133616 -128 - 0.536633 -256 - 2.157370 -512 - 10.505163 +1 - 0.000016 +2 - 0.000049 +4 - 0.000186 +8 - 0.001010 +16 - 0.005452 +32 - 0.017694 +64 - 0.075916 +128 - 0.325436 +256 - 1.386162 +512 - 5.722743 */ // A series of concentric squares, each progressively larger than the last. impl ConcentricTest { - pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { + pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { let (subj_paths, clip_paths) = Self::geometry(100.0, n); let it_count = ((scale / (n as f64)) as usize).max(1); @@ -64,7 +105,12 @@ impl ConcentricTest { let start = Instant::now(); for _ in 0..sq_it_count { - let mut overlay = FloatOverlay::with_subj_and_clip_custom(&subj_paths, &clip_paths, Default::default(), solver); + let mut overlay = FloatOverlay::<[f64; 2], I>::from_subj_and_clip_custom( + &subj_paths, + &clip_paths, + Default::default(), + solver, + ); let _res = overlay.overlay(rule, FillRule::NonZero); } let duration = start.elapsed(); diff --git a/performance/rust_app/src/test/test_8_wind_mill.rs b/performance/rust_app/src/test/test_8_wind_mill.rs index e2c8617c..c3182724 100644 --- a/performance/rust_app/src/test/test_8_wind_mill.rs +++ b/performance/rust_app/src/test/test_8_wind_mill.rs @@ -1,63 +1,115 @@ -use std::time::Instant; +use crate::test::util::{OverlayInt, Util}; use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay::Overlay; use i_overlay::core::overlay_rule::OverlayRule; use i_overlay::core::solver::Solver; +use i_overlay::i_float::int::number::int::IntNumber; use i_overlay::i_float::int::point::IntPoint; use i_overlay::i_shape::int::shape::IntContour; +use std::time::Instant; pub(crate) struct WindMillTest; /* -// 7 -// Difference: +test 8 +WindMillTest +Difference: + +i16 + +// multithreading on +1 - 0.000004 +2 - 0.000012 +4 - 0.000049 +8 - 0.000239 +16 - 0.001321 +32 - 0.003630 +64 - 0.013392 +128 - 0.055149 +256 - 0.245865 + +// multithreading off +1 - 0.000004 +2 - 0.000012 +4 - 0.000049 +8 - 0.000241 +16 - 0.001326 +32 - 0.004098 +64 - 0.018836 +128 - 0.080683 +256 - 0.375508 + +i32 // multithreading on -1 - 0.000007 -2 - 0.000028 -4 - 0.000136 -8 - 0.000702 -16 - 0.003199 -32 - 0.010574 -64 - 0.049129 -128 - 0.202190 -256 - 0.887956 -512 - 3.830878 -1024 - 15.643612 +1 - 0.000004 +2 - 0.000013 +4 - 0.000048 +8 - 0.000229 +16 - 0.001354 +32 - 0.003816 +64 - 0.015072 +128 - 0.061520 +256 - 0.280049 +512 - 1.243484 // multithreading off -1 - 0.000007 -2 - 0.000027 -4 - 0.000136 -8 - 0.000708 -16 - 0.003072 -32 - 0.010975 -64 - 0.054829 -128 - 0.243694 -256 - 1.180819 -512 - 4.130558 -1024 - 17.840079 +1 - 0.000004 +2 - 0.000012 +4 - 0.000049 +8 - 0.000231 +16 - 0.001354 +32 - 0.004408 +64 - 0.021355 +128 - 0.090989 +256 - 0.416754 +512 - 1.820984 + +i64 + +// multithreading on +1 - 0.000005 +2 - 0.000014 +4 - 0.000058 +8 - 0.000284 +16 - 0.001662 +32 - 0.004458 +64 - 0.018935 +128 - 0.077800 +256 - 0.349065 +512 - 1.508555 + +// multithreading off +1 - 0.000005 +2 - 0.000014 +4 - 0.000059 +8 - 0.000281 +16 - 0.001637 +32 - 0.005425 +64 - 0.026436 +128 - 0.115288 +256 - 0.513712 +512 - 2.269812 + */ impl WindMillTest { - pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64, simple_geometry: bool) { - let (subj_paths, clip_paths) = Self::geometry(80, n); + pub(crate) fn run(n: usize, rule: OverlayRule, solver: Solver, scale: f64) { + if Util::skip_if_out_of_range::(n, 80 * n + 80) { + return; + } + + let (subj_paths, clip_paths) = Self::geometry(I::from_usize(80), n); let it_count = ((scale / (n as f64)) as usize).max(1); let sq_it_count = it_count * it_count; let start = Instant::now(); - if simple_geometry { - // for _ in 0..sq_it_count { - // let _ = Overlay::with_contours(&subj_paths, &clip_paths) - // .overlay_45geom_with_min_area_and_solver(rule, FillRule::NonZero, 0, solver); - // } - } else { - for _ in 0..sq_it_count { - let _ = Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) + for _ in 0..sq_it_count { + let _ = + Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) .overlay(rule, FillRule::NonZero); - } } let duration = start.elapsed(); @@ -66,25 +118,26 @@ impl WindMillTest { println!("{} - {:.6}", n, time); } - pub(crate) fn validate(n: usize, rule: OverlayRule, solver: Solver) { - let (subj_paths, clip_paths) = Self::geometry(80, n); + pub(crate) fn validate(n: usize, rule: OverlayRule, solver: Solver) { + let (subj_paths, clip_paths) = Self::geometry(I::from_usize(80), n); - let res = Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) - .overlay(rule, FillRule::NonZero); + let res = + Overlay::with_contours_custom(&subj_paths, &clip_paths, Default::default(), solver) + .overlay(rule, FillRule::NonZero); assert_eq!(res.len(), n * n); println!("result validation PASS"); } - fn geometry(size: i32, count: usize) -> (Vec, Vec) { + fn geometry(size: I, count: usize) -> (Vec>, Vec>) { let mut subj_paths = Vec::with_capacity(4 * count * count); let mut clip_paths = Vec::with_capacity(4 * count * count); - let a = size / 8; + let a = size / I::from_usize(8); - let mut x = size / 2; + let mut x = size / I::TWO; for _ in 0..count { - let mut y = size / 2; + let mut y = size / I::TWO; for _ in 0..count { let (subj, clip) = Self::shapes(IntPoint::new(x, y), a); @@ -100,57 +153,62 @@ impl WindMillTest { (subj_paths, clip_paths) } - fn shapes(center: IntPoint, a: i32) -> (Vec, Vec) { + fn shapes(center: IntPoint, a: I) -> (Vec>, Vec>) { + let i0 = I::from_usize(0); + let i1 = I::from_usize(1); + let i2 = I::from_usize(2); + let i3 = I::from_usize(3); + let i4 = I::from_usize(4); let clip_paths = vec![ vec![ - IntPoint::new(-3 * a + center.x, 1 * a + center.y), - IntPoint::new(-3 * a + center.x, 3 * a + center.y), - IntPoint::new(-1 * a + center.x, 3 * a + center.y), - IntPoint::new(-1 * a + center.x, 1 * a + center.y), + IntPoint::new(-i3 * a + center.x, i1 * a + center.y), + IntPoint::new(-i3 * a + center.x, i3 * a + center.y), + IntPoint::new(-i1 * a + center.x, i3 * a + center.y), + IntPoint::new(-i1 * a + center.x, i1 * a + center.y), ], vec![ - IntPoint::new(1 * a + center.x, 2 * a + center.y), - IntPoint::new(1 * a + center.x, 4 * a + center.y), - IntPoint::new(3 * a + center.x, 4 * a + center.y), - IntPoint::new(3 * a + center.x, 2 * a + center.y), + IntPoint::new(i1 * a + center.x, i2 * a + center.y), + IntPoint::new(i1 * a + center.x, i4 * a + center.y), + IntPoint::new(i3 * a + center.x, i4 * a + center.y), + IntPoint::new(i3 * a + center.x, i2 * a + center.y), ], vec![ - IntPoint::new(-2 * a + center.x, -3 * a + center.y), - IntPoint::new(-2 * a + center.x, -1 * a + center.y), - IntPoint::new(0 * a + center.x, -1 * a + center.y), - IntPoint::new(0 * a + center.x, -3 * a + center.y), + IntPoint::new(-i2 * a + center.x, -i3 * a + center.y), + IntPoint::new(-i2 * a + center.x, -i1 * a + center.y), + IntPoint::new(i0 * a + center.x, -i1 * a + center.y), + IntPoint::new(i0 * a + center.x, -i3 * a + center.y), ], vec![ - IntPoint::new(2 * a + center.x, -2 * a + center.y), - IntPoint::new(2 * a + center.x, 0 * a + center.y), - IntPoint::new(4 * a + center.x, 0 * a + center.y), - IntPoint::new(4 * a + center.x, -2 * a + center.y), + IntPoint::new(i2 * a + center.x, -i2 * a + center.y), + IntPoint::new(i2 * a + center.x, i0 * a + center.y), + IntPoint::new(i4 * a + center.x, i0 * a + center.y), + IntPoint::new(i4 * a + center.x, -i2 * a + center.y), ], ]; let subj_paths = vec![ vec![ - IntPoint::new(0 * a + center.x, 0 * a + center.y), - IntPoint::new(-3 * a + center.x, 0 * a + center.y), - IntPoint::new(0 * a + center.x, 3 * a + center.y), + IntPoint::new(i0 * a + center.x, i0 * a + center.y), + IntPoint::new(-i3 * a + center.x, i0 * a + center.y), + IntPoint::new(i0 * a + center.x, i3 * a + center.y), ], vec![ - IntPoint::new(0 * a + center.x, 1 * a + center.y), - IntPoint::new(0 * a + center.x, 4 * a + center.y), - IntPoint::new(3 * a + center.x, 1 * a + center.y), + IntPoint::new(i0 * a + center.x, i1 * a + center.y), + IntPoint::new(i0 * a + center.x, i4 * a + center.y), + IntPoint::new(i3 * a + center.x, i1 * a + center.y), ], vec![ - IntPoint::new(1 * a + center.x, 0 * a + center.y), - IntPoint::new(1 * a + center.x, -3 * a + center.y), - IntPoint::new(-2 * a + center.x, 0 * a + center.y), + IntPoint::new(i1 * a + center.x, i0 * a + center.y), + IntPoint::new(i1 * a + center.x, -i3 * a + center.y), + IntPoint::new(-i2 * a + center.x, i0 * a + center.y), ], vec![ - IntPoint::new(1 * a + center.x, 1 * a + center.y), - IntPoint::new(4 * a + center.x, 1 * a + center.y), - IntPoint::new(1 * a + center.x, -2 * a + center.y), + IntPoint::new(i1 * a + center.x, i1 * a + center.y), + IntPoint::new(i4 * a + center.x, i1 * a + center.y), + IntPoint::new(i1 * a + center.x, -i2 * a + center.y), ], ]; (subj_paths, clip_paths) } -} \ No newline at end of file +} diff --git a/performance/rust_app/src/test/util.rs b/performance/rust_app/src/test/util.rs index 2c18ae11..08e6da44 100644 --- a/performance/rust_app/src/test/util.rs +++ b/performance/rust_app/src/test/util.rs @@ -1,20 +1,47 @@ -use std::f64::consts::PI; +use i_key_sort::sort::key::SortKey; use i_overlay::i_float::float::point::FloatPoint; +use i_overlay::i_float::int::number::int::IntNumber; use i_overlay::i_float::int::point::IntPoint; use i_overlay::i_shape::base::data::Contour; use i_overlay::i_shape::int::path::IntPath; +use i_tree::{Expiration, LayoutNumber}; +use std::f64::consts::PI; pub(super) struct Util; +pub(crate) trait OverlayInt: IntNumber + Expiration + LayoutNumber + SortKey {} + +impl OverlayInt for I where I: IntNumber + Expiration + LayoutNumber + SortKey {} + impl Util { + pub(super) fn skip_if_out_of_range(n: usize, max_abs_coord: usize) -> bool { + let max = I::MAX.to_usize(); + if max_abs_coord <= max { + return false; + } - pub(super) fn many_squares(start: IntPoint, size: i32, offset: i32, n: usize) -> Vec { + println!( + "{} - skipped (requires coordinate {}, i{} max {})", + n, + max_abs_coord, + I::BITS, + max + ); + true + } + + pub(super) fn many_squares( + start: IntPoint, + size: I, + offset: I, + n: usize, + ) -> Vec> { let mut result = Vec::with_capacity(n * n); let mut y = start.y; for _ in 0..n { let mut x = start.x; for _ in 0..n { - let path: IntPath = vec![ + let path: IntPath = vec![ IntPoint::new(x, y), IntPoint::new(x, y + size), IntPoint::new(x + size, y + size), @@ -29,17 +56,22 @@ impl Util { result } - - pub(super) fn many_windows(start: IntPoint, a: i32, b: i32, offset: i32, n: usize) -> (Vec, Vec) { + pub(super) fn many_windows( + start: IntPoint, + a: I, + b: I, + offset: I, + n: usize, + ) -> (Vec>, Vec>) { let mut boundaries = Vec::with_capacity(n * n); let mut holes = Vec::with_capacity(n * n); let mut y = start.y; - let c = (a - b) / 2; + let c = (a - b) / I::TWO; let d = b + c; for _ in 0..n { let mut x = start.x; for _ in 0..n { - let boundary: IntPath = vec![ + let boundary: IntPath = vec![ IntPoint::new(x, y), IntPoint::new(x, y + a), IntPoint::new(x + a, y + a), @@ -47,7 +79,7 @@ impl Util { ]; boundaries.push(boundary); - let hole: IntPath = vec![ + let hole: IntPath = vec![ IntPoint::new(x + c, y + c), IntPoint::new(x + c, y + d), IntPoint::new(x + d, y + d), @@ -63,19 +95,22 @@ impl Util { (boundaries, holes) } - pub(super) fn concentric_squares(a: i32, n: usize) -> (Vec, Vec) { + pub(super) fn concentric_squares( + a: I, + n: usize, + ) -> (Vec>, Vec>) { let mut vert = Vec::with_capacity(2 * n); let mut horz = Vec::with_capacity(2 * n); - let s = 2 * a; + let s = I::TWO * a; let mut r = s; for _ in 0..n { - let hz_top: IntPath = vec![ + let hz_top: IntPath = vec![ IntPoint::new(-r, r - a), IntPoint::new(-r, r), IntPoint::new(r, r), IntPoint::new(r, r - a), ]; - let hz_bot: IntPath = vec![ + let hz_bot: IntPath = vec![ IntPoint::new(-r, -r), IntPoint::new(-r, -r + a), IntPoint::new(r, -r + a), @@ -84,13 +119,13 @@ impl Util { horz.push(hz_top); horz.push(hz_bot); - let vt_left: IntPath = vec![ + let vt_left: IntPath = vec![ IntPoint::new(-r, -r), IntPoint::new(-r, r), IntPoint::new(-r + a, r), IntPoint::new(-r + a, -r), ]; - let vt_right: IntPath = vec![ + let vt_right: IntPath = vec![ IntPoint::new(r - a, -r), IntPoint::new(r - a, r), IntPoint::new(r, r), @@ -105,13 +140,13 @@ impl Util { (vert, horz) } - pub(super) fn many_lines_x(a: i32, n: usize) -> Vec { - let w = a / 2; - let s = a * (n as i32) / 2; - let mut x = -s + w / 2; + pub(super) fn many_lines_x(a: I, n: usize) -> Vec> { + let w = a / I::TWO; + let s = a * I::from_usize(n) / I::TWO; + let mut x = -s + w / I::TWO; let mut result = Vec::with_capacity(n); for _ in 0..n { - let path: IntPath = vec![ + let path: IntPath = vec![ IntPoint::new(x, -s), IntPoint::new(x, s), IntPoint::new(x + w, s), @@ -124,13 +159,13 @@ impl Util { result } - pub(super) fn many_lines_y(a: i32, n: usize) -> Vec { - let h = a / 2; - let s = a * (n as i32) / 2; - let mut y = -s + h / 2; + pub(super) fn many_lines_y(a: I, n: usize) -> Vec> { + let h = a / I::TWO; + let s = a * I::from_usize(n) / I::TWO; + let mut y = -s + h / I::TWO; let mut result = Vec::with_capacity(n); for _ in 0..n { - let path: IntPath = vec![ + let path: IntPath = vec![ IntPoint::new(-s, y), IntPoint::new(s, y), IntPoint::new(s, y - h), @@ -163,9 +198,15 @@ impl Util { r - 0.2 * radius }; - let p = FloatPoint { x: rr * sx, y: rr * sy }; + let p = FloatPoint { + x: rr * sx, + y: rr * sy, + }; let n = (p - p0).normalize(); - let t = FloatPoint { x: w * -n.y, y: w * n.x }; + let t = FloatPoint { + x: w * -n.y, + y: w * n.x, + }; a_path.push(p0 + t); a_path.push(p + t); @@ -182,4 +223,4 @@ impl Util { a_path } -} \ No newline at end of file +}