├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── editor ├── Cargo.toml ├── src │ ├── app │ │ ├── design.rs │ │ ├── main.rs │ │ ├── mod.rs │ │ └── triangle │ │ │ ├── content.rs │ │ │ ├── control.rs │ │ │ ├── mod.rs │ │ │ └── workspace.rs │ ├── compat │ │ ├── convert.rs │ │ ├── mod.rs │ │ └── point.rs │ ├── data │ │ ├── mod.rs │ │ ├── resource.rs │ │ └── triangle.rs │ ├── draw │ │ ├── mod.rs │ │ └── path.rs │ ├── geom │ │ ├── camera.rs │ │ ├── mod.rs │ │ └── vector.rs │ ├── lib.rs │ ├── main.rs │ ├── mesh │ │ ├── mod.rs │ │ └── path_builder.rs │ ├── mesh_viewer │ │ ├── color.rs │ │ ├── mod.rs │ │ └── widget.rs │ ├── path_editor │ │ ├── color.rs │ │ ├── mod.rs │ │ ├── state.rs │ │ └── widget.rs │ ├── sheet │ │ ├── mod.rs │ │ ├── state.rs │ │ └── widget.rs │ └── web.rs └── tests │ └── triangle │ ├── test_0.json │ ├── test_1.json │ ├── test_10.json │ ├── test_11.json │ ├── test_12.json │ ├── test_13.json │ ├── test_14.json │ ├── test_15.json │ ├── test_16.json │ ├── test_17.json │ ├── test_18.json │ ├── test_19.json │ ├── test_2.json │ ├── test_20.json │ ├── test_21.json │ ├── test_22.json │ ├── test_23.json │ ├── test_24.json │ ├── test_25.json │ ├── test_26.json │ ├── test_27.json │ ├── test_28.json │ ├── test_29.json │ ├── test_3.json │ ├── test_30.json │ ├── test_31.json │ ├── test_32.json │ ├── test_33.json │ ├── test_34.json │ ├── test_35.json │ ├── test_36.json │ ├── test_37.json │ ├── test_38.json │ ├── test_39.json │ ├── test_4.json │ ├── test_40.json │ ├── test_5.json │ ├── test_6.json │ ├── test_7.json │ ├── test_8.json │ └── test_9.json ├── iTriangle ├── Cargo.toml ├── README.md ├── readme │ ├── cheese_example.svg │ ├── eagle_centroid.svg │ ├── eagle_tessellation.svg │ ├── eagle_triangles_extra_points.svg │ ├── star_polygon.svg │ ├── star_triangle.svg │ └── triangulation_process.gif ├── src │ ├── advanced │ │ ├── bitset.rs │ │ ├── buffer.rs │ │ ├── centroid.rs │ │ ├── convex.rs │ │ ├── delaunay.rs │ │ ├── mod.rs │ │ └── triangulation.rs │ ├── float │ │ ├── builder.rs │ │ ├── centroid_net.rs │ │ ├── circumcenter.rs │ │ ├── convex.rs │ │ ├── custom.rs │ │ ├── delaunay.rs │ │ ├── mod.rs │ │ ├── triangulatable.rs │ │ ├── triangulation.rs │ │ ├── triangulator.rs │ │ └── unchecked.rs │ ├── geom │ │ ├── mod.rs │ │ ├── point.rs │ │ └── triangle.rs │ ├── index.rs │ ├── int │ │ ├── binder.rs │ │ ├── custom.rs │ │ ├── earcut │ │ │ ├── earcut_64.rs │ │ │ ├── flat.rs │ │ │ ├── mod.rs │ │ │ ├── net.rs │ │ │ └── util.rs │ │ ├── meta.rs │ │ ├── mod.rs │ │ ├── monotone │ │ │ ├── chain │ │ │ │ ├── builder.rs │ │ │ │ ├── mod.rs │ │ │ │ └── vertex.rs │ │ │ ├── flat │ │ │ │ ├── mod.rs │ │ │ │ ├── section.rs │ │ │ │ └── triangulator.rs │ │ │ ├── mod.rs │ │ │ ├── net │ │ │ │ ├── mod.rs │ │ │ │ ├── phantom.rs │ │ │ │ ├── section.rs │ │ │ │ └── triangulator.rs │ │ │ ├── triangulator.rs │ │ │ └── v_segment.rs │ │ ├── solver.rs │ │ ├── triangulatable.rs │ │ ├── triangulation.rs │ │ ├── triangulator.rs │ │ ├── unchecked.rs │ │ └── validation.rs │ ├── lib.rs │ └── tessellation │ │ ├── circumcenter.rs │ │ ├── mod.rs │ │ └── split.rs └── tests │ ├── doc_tests.rs │ └── float_tests.rs ├── performance └── rust_app │ ├── Cargo.toml │ ├── readme.txt │ └── src │ ├── main.rs │ ├── test │ ├── mod.rs │ ├── runner.rs │ ├── test.rs │ ├── test_0_star.rs │ ├── test_1_spiral.rs │ ├── test_2_star_with_hole.rs │ └── test_3_star_with_8_holes.rs │ └── util │ ├── args.rs │ ├── mod.rs │ └── star_builder.rs └── readme ├── cheese_example.svg ├── eagle_centroid.svg ├── eagle_tessellation.svg ├── eagle_triangles_extra_points.svg ├── star_polygon.svg ├── star_triangle.svg └── triangulation_process.gif /.gitattributes: -------------------------------------------------------------------------------- 1 | /performance/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # IntelliJ IDEA specific 17 | .idea/ 18 | *.iml 19 | 20 | # Visual Studio Code specific 21 | .vscode/ 22 | 23 | # macOS system files 24 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 iShape-Rust 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iTriangle/README.md -------------------------------------------------------------------------------- /editor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "editor" 3 | version = "0.1.0" 4 | edition = "2024" 5 | publish = false 6 | 7 | 8 | [dependencies] 9 | iced = { git = "https://github.com/iced-rs/iced", branch = "master", features = ["wgpu", "advanced", "fira-sans"] } 10 | serde = { version = "^1.0", features = ["derive"] } 11 | serde_json = "^1.0" 12 | 13 | wasm-bindgen = "~0.2.95" 14 | 15 | log = "0.4.22" 16 | console_log = "^1.0.0" 17 | console_error_panic_hook = "^0" 18 | 19 | #i_mesh = { path = "../../iMesh/iMesh" } 20 | #i_triangle = { path = "../iTriangle", default-features = true, features = ["serde"] } 21 | i_mesh = "^0.3.0" 22 | i_triangle = { version = "^0.35.0", features = ["serde"] } -------------------------------------------------------------------------------- /editor/src/app/design.rs: -------------------------------------------------------------------------------- 1 | use iced::widget::button; 2 | use iced::widget::container; 3 | use iced::widget::rule; 4 | use iced::widget::text; 5 | use iced::{Background, border, Color, Padding, Theme}; 6 | 7 | 8 | pub(super) struct Design { 9 | pub(super) action_separator: f32, 10 | } 11 | 12 | impl Design { 13 | pub(crate) fn subject_color() -> Color { 14 | Color::from_rgb8(255, 51, 51) 15 | } 16 | 17 | pub(crate) fn clip_color() -> Color { 18 | Color::from_rgb8(26, 142, 255) 19 | } 20 | 21 | pub(crate) fn negative_color() -> Color { 22 | if iced::Theme::default().extended_palette().is_dark { 23 | Color::from_rgb8(224, 224, 224) 24 | } else { 25 | Color::from_rgb8(32, 32, 32) 26 | } 27 | } 28 | 29 | pub(crate) fn accent_color() -> Color { 30 | Color::from_rgb8(255, 140, 0) 31 | } 32 | 33 | pub(crate) fn both_color() -> Color { 34 | Color::from_rgb8(76, 217, 100) 35 | } 36 | 37 | pub(super) fn new() -> Self { 38 | Self { 39 | action_separator: 3.0 40 | } 41 | } 42 | 43 | pub(super) fn action_padding(&self) -> Padding { 44 | Padding { 45 | top: self.action_separator, 46 | right: 2.0 * self.action_separator, 47 | bottom: self.action_separator, 48 | left: 2.0 * self.action_separator, 49 | } 50 | } 51 | } 52 | 53 | // Sidebar 54 | 55 | pub(super) fn style_sidebar_button(theme: &Theme, status: button::Status) -> button::Style { 56 | let palette = theme.extended_palette(); 57 | let base = button::Style { 58 | background: Some(Background::Color(palette.primary.strong.color)), 59 | text_color: palette.primary.strong.text, 60 | border: border::rounded(6), 61 | ..button::Style::default() 62 | }; 63 | 64 | match status { 65 | button::Status::Pressed => base, 66 | button::Status::Hovered => button::Style { 67 | background: Some(Background::Color(palette.background.weak.color.scale_alpha(0.2))), 68 | ..base 69 | }, 70 | button::Status::Disabled | button::Status::Active => button::Style { 71 | background: Some(Background::Color(Color::TRANSPARENT)), 72 | ..base 73 | }, 74 | } 75 | } 76 | 77 | pub(super) fn style_sidebar_button_selected(theme: &Theme, status: button::Status) -> button::Style { 78 | let palette = theme.extended_palette(); 79 | let base = button::Style { 80 | background: Some(Background::Color(palette.primary.strong.color)), 81 | text_color: palette.primary.strong.text, 82 | border: border::rounded(6), 83 | ..button::Style::default() 84 | }; 85 | 86 | match status { 87 | button::Status::Pressed | button::Status::Active => base, 88 | button::Status::Hovered => button::Style { 89 | background: Some(Background::Color(palette.primary.base.color)), 90 | ..base 91 | }, 92 | button::Status::Disabled => button::Style { 93 | background: Some(Background::Color(Color::TRANSPARENT)), 94 | ..base 95 | }, 96 | } 97 | } 98 | 99 | pub(super) fn style_sidebar_text(theme: &Theme) -> text::Style { 100 | let palette = theme.palette(); 101 | text::Style { 102 | color: Some(palette.text.scale_alpha(0.7)) 103 | } 104 | } 105 | 106 | pub(super) fn style_sidebar_text_selected(theme: &Theme) -> text::Style { 107 | let palette = theme.palette(); 108 | text::Style { 109 | color: Some(palette.text) 110 | } 111 | } 112 | 113 | pub(super) fn style_sidebar_background(theme: &Theme) -> container::Style { 114 | container::Style::default().background(theme.extended_palette().background.weak.color.scale_alpha(0.1)) 115 | } 116 | 117 | pub(super) fn style_separator(theme: &Theme) -> rule::Style { 118 | let color = if theme.extended_palette().is_dark { 119 | Color::from_rgba(0.0, 0.0, 0.0, 0.8) 120 | } else { 121 | Color::from_rgba(1.0, 1.0, 1.0, 0.8) 122 | }; 123 | 124 | rule::Style { 125 | color, 126 | width: 1, 127 | radius: border::Radius::new(0), 128 | fill_mode: rule::FillMode::Padded(0), 129 | } 130 | } 131 | 132 | pub(super) fn style_sheet_background(theme: &Theme) -> container::Style { 133 | if theme.extended_palette().is_dark { 134 | container::Style::default().background(Color::BLACK.scale_alpha(0.4)) 135 | } else { 136 | container::Style::default().background(Color::WHITE.scale_alpha(0.4)) 137 | } 138 | } -------------------------------------------------------------------------------- /editor/src/app/main.rs: -------------------------------------------------------------------------------- 1 | use iced::Subscription; 2 | use iced::keyboard::Key::Named as NamedBox; 3 | use iced::event::{self, Event as MainEvent}; 4 | use iced::widget::{Space, vertical_rule}; 5 | use iced::{Alignment, Element, Length}; 6 | use iced::advanced::graphics::futures::keyboard; 7 | use iced::keyboard::{Event as KeyboardEvent, Key}; 8 | use iced::keyboard::key::Named; 9 | use iced::widget::{Button, Column, Container, Row, Text}; 10 | use crate::app::triangle::content::TriangleMessage; 11 | use crate::app::triangle::content::TriangleState; 12 | 13 | 14 | use crate::app::design::style_separator; 15 | use crate::app::design::{style_sidebar_button, style_sidebar_button_selected, Design}; 16 | use crate::data::resource::AppResource; 17 | 18 | pub struct EditorApp { 19 | main_actions: Vec, 20 | pub(super) state: MainState, 21 | pub(super) app_resource: AppResource, 22 | pub(super) design: Design, 23 | } 24 | 25 | pub(super) struct MainState { 26 | selected_action: MainAction, 27 | pub(super) triangle: TriangleState, 28 | } 29 | 30 | #[derive(Debug, Clone, PartialEq)] 31 | pub(crate) enum MainAction { 32 | Intersect 33 | } 34 | 35 | impl MainAction { 36 | fn title(&self) -> &str { 37 | match self { 38 | MainAction::Intersect => "Triangle", 39 | } 40 | } 41 | } 42 | 43 | #[derive(Debug, Clone)] 44 | pub(crate) enum MainMessage { 45 | ActionSelected(MainAction), 46 | NextTest, 47 | PrevTest, 48 | } 49 | 50 | #[derive(Debug, Clone)] 51 | pub(crate) enum AppMessage { 52 | Main(MainMessage), 53 | Triangle(TriangleMessage), 54 | EventOccurred(MainEvent), 55 | } 56 | 57 | impl EditorApp { 58 | 59 | pub fn new(mut app_resource: AppResource) -> Self { 60 | Self { 61 | main_actions: vec![MainAction::Intersect], 62 | state: MainState { 63 | selected_action: MainAction::Intersect, 64 | triangle: TriangleState::new(&mut app_resource.triangle), 65 | }, 66 | app_resource, 67 | design: Design::new(), 68 | } 69 | } 70 | 71 | pub fn update(&mut self, message: AppMessage) { 72 | match message { 73 | AppMessage::Main(msg) => self.update_main(msg), 74 | AppMessage::Triangle(msg) => self.triangle_update(msg), 75 | _ => {} 76 | } 77 | } 78 | 79 | pub fn subscription(&self) -> Subscription { 80 | keyboard::on_key_press(|key, _mods| { 81 | match key.as_ref() { 82 | Key::Named(Named::ArrowDown) => { 83 | Some(AppMessage::Main(MainMessage::NextTest)) 84 | } 85 | Key::Named(Named::ArrowUp) => { 86 | Some(AppMessage::Main(MainMessage::PrevTest)) 87 | } 88 | _ => None, 89 | } 90 | }) 91 | } 92 | 93 | fn update_main(&mut self, message: MainMessage) { 94 | match message { 95 | MainMessage::ActionSelected(action) => { 96 | self.state.selected_action = action; 97 | match self.state.selected_action { 98 | MainAction::Intersect => self.triangle_init(), 99 | } 100 | } 101 | MainMessage::NextTest => self.triangle_next_test(), 102 | MainMessage::PrevTest => self.triangle_prev_test(), 103 | } 104 | } 105 | 106 | pub fn view(&self) -> Element { 107 | let content = Row::new() 108 | .push(Container::new(self.main_navigation()) 109 | .width(Length::Fixed(160.0)) 110 | .height(Length::Shrink) 111 | .align_x(Alignment::Start)); 112 | 113 | let content = match self.state.selected_action { 114 | MainAction::Intersect => { 115 | content 116 | .push( 117 | vertical_rule(1).style(style_separator) 118 | ) 119 | .push(self.triangle_content()) 120 | } 121 | }; 122 | 123 | content.height(Length::Fill).into() 124 | } 125 | 126 | fn main_navigation(&self) -> Column { 127 | self.main_actions.iter().fold( 128 | Column::new().push(Space::new(Length::Fill, Length::Fixed(2.0))), 129 | |column, item| { 130 | let is_selected = self.state.selected_action.eq(item); 131 | column.push( 132 | Container::new( 133 | Button::new(Text::new(item.title())) 134 | .width(Length::Fill) 135 | .on_press(AppMessage::Main(MainMessage::ActionSelected(item.clone()))) 136 | .style(if is_selected { style_sidebar_button_selected } else { style_sidebar_button }) 137 | ).padding(self.design.action_padding()) 138 | ) 139 | }, 140 | ) 141 | } 142 | } -------------------------------------------------------------------------------- /editor/src/app/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod main; 2 | mod design; 3 | mod triangle; -------------------------------------------------------------------------------- /editor/src/app/triangle/control.rs: -------------------------------------------------------------------------------- 1 | use crate::app::triangle::content::TriangleMessage; 2 | use crate::app::main::{EditorApp, AppMessage}; 3 | use iced::{Alignment, Length}; 4 | use iced::widget::{Column, Container, pick_list, Row, Text, slider}; 5 | 6 | impl EditorApp { 7 | pub(crate) fn triangle_control(&self) -> Column { 8 | let mode_pick_list = 9 | Row::new() 10 | .push(Text::new("Mode:") 11 | .width(Length::Fixed(90.0)) 12 | .height(Length::Fill) 13 | .align_y(Alignment::Center)) 14 | .push( 15 | Container::new( 16 | pick_list( 17 | &ModeOption::ALL[..], 18 | Some(self.state.triangle.mode), 19 | on_select_mode, 20 | ).width(Length::Fixed(160.0)) 21 | ) 22 | .height(Length::Fill) 23 | .align_y(Alignment::Center) 24 | ).height(Length::Fixed(40.0)); 25 | 26 | let mut columns = Column::new() 27 | .push(mode_pick_list); 28 | 29 | if self.state.triangle.mode == ModeOption::Tessellation || self.state.triangle.mode == ModeOption::CentroidNet { 30 | let radius_list = Row::new() 31 | .push( 32 | Text::new("Max Radius:") 33 | .width(Length::Fixed(120.0)) 34 | .height(Length::Fill) 35 | .align_y(Alignment::Center), 36 | ) 37 | .push( 38 | Container::new( 39 | slider(2.0f64..=10.0f64, self.state.triangle.radius, on_update_radius).step(0.1f32) 40 | ) 41 | .width(410) 42 | .height(Length::Fill) 43 | .align_y(Alignment::Center), 44 | ) 45 | .height(Length::Fixed(40.0)); 46 | let area_list = Row::new() 47 | .push( 48 | Text::new("Max Area:") 49 | .width(Length::Fixed(120.0)) 50 | .height(Length::Fill) 51 | .align_y(Alignment::Center), 52 | ) 53 | .push( 54 | Container::new( 55 | slider(10.0f64..=10000.0f64, self.state.triangle.max_area, on_update_area).step(0.1f32) 56 | ) 57 | .width(410) 58 | .height(Length::Fill) 59 | .align_y(Alignment::Center), 60 | ) 61 | .height(Length::Fixed(40.0)); 62 | columns = columns.push(radius_list); 63 | columns = columns.push(area_list); 64 | } 65 | 66 | 67 | columns 68 | } 69 | } 70 | 71 | fn on_update_radius(value: f64) -> AppMessage { 72 | AppMessage::Triangle(TriangleMessage::RadiusUpdated(value)) 73 | } 74 | 75 | fn on_update_area(value: f64) -> AppMessage { 76 | AppMessage::Triangle(TriangleMessage::AreaUpdated(value)) 77 | } 78 | 79 | fn on_select_mode(option: ModeOption) -> AppMessage { 80 | AppMessage::Triangle(TriangleMessage::ModeSelected(option)) 81 | } 82 | 83 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 84 | pub(crate) enum ModeOption { 85 | #[default] 86 | Raw, 87 | Delaunay, 88 | Convex, 89 | Tessellation, 90 | CentroidNet, 91 | } 92 | 93 | impl ModeOption { 94 | const ALL: [ModeOption; 5] = [ 95 | ModeOption::Raw, 96 | ModeOption::Delaunay, 97 | ModeOption::Convex, 98 | ModeOption::Tessellation, 99 | ModeOption::CentroidNet, 100 | ]; 101 | } 102 | 103 | impl std::fmt::Display for ModeOption { 104 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 105 | write!( 106 | f, 107 | "{}", 108 | match self { 109 | ModeOption::Raw => "Raw", 110 | ModeOption::Delaunay => "Delaunay", 111 | ModeOption::Convex => "Convex", 112 | ModeOption::Tessellation => "Tessellation", 113 | ModeOption::CentroidNet => "CentroidNet", 114 | } 115 | ) 116 | } 117 | } -------------------------------------------------------------------------------- /editor/src/app/triangle/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod content; 2 | mod workspace; 3 | mod control; -------------------------------------------------------------------------------- /editor/src/app/triangle/workspace.rs: -------------------------------------------------------------------------------- 1 | use i_triangle::i_overlay::i_shape::int::path::IntPath; 2 | use i_triangle::i_overlay::i_shape::int::shape::IntContour; 3 | use i_triangle::int::triangulation::IntTriangulation; 4 | use crate::geom::camera::Camera; 5 | use crate::sheet::widget::SheetWidget; 6 | use crate::app::triangle::content::TriangleMessage; 7 | use crate::app::design::{style_sheet_background, Design}; 8 | use crate::app::main::{EditorApp, AppMessage}; 9 | use iced::widget::Stack; 10 | use iced::widget::Container; 11 | use iced::{Color, Length, Padding, Size, Vector}; 12 | use crate::app::triangle::control::ModeOption; 13 | use crate::draw::path::PathWidget; 14 | use crate::mesh_viewer::widget::MeshViewerWidget; 15 | use crate::path_editor::widget::{PathEditorUpdateEvent, PathEditorWidget}; 16 | 17 | pub(crate) struct WorkspaceState { 18 | pub(crate) camera: Camera, 19 | pub(crate) paths: Vec, 20 | pub(crate) triangulations: Vec>, 21 | pub(crate) polygons: Vec, 22 | } 23 | 24 | impl EditorApp { 25 | pub(crate) fn triangle_workspace(&self) -> Container { 26 | Container::new({ 27 | let mut stack = Stack::new(); 28 | stack = stack.push( 29 | Container::new(SheetWidget::new( 30 | self.state.triangle.workspace.camera, 31 | Design::negative_color().scale_alpha(0.5), 32 | on_update_size, 33 | on_update_zoom, 34 | on_update_drag, 35 | )) 36 | .width(Length::Fill) 37 | .height(Length::Fill) 38 | ); 39 | for (id, curve) in self.state.triangle.workspace.paths.iter().enumerate() { 40 | stack = stack.push( 41 | Container::new(PathEditorWidget::new( 42 | id, 43 | curve, 44 | self.state.triangle.workspace.camera, 45 | on_update_anchor 46 | )) 47 | .width(Length::Fill) 48 | .height(Length::Fill) 49 | ); 50 | } 51 | match self.state.triangle.mode { 52 | ModeOption::Delaunay | ModeOption::Raw | ModeOption::Tessellation => { 53 | for triangulation in self.state.triangle.workspace.triangulations.iter() { 54 | stack = stack.push( 55 | Container::new(MeshViewerWidget::new( 56 | triangulation, 57 | self.state.triangle.workspace.camera 58 | )) 59 | .width(Length::Fill) 60 | .height(Length::Fill) 61 | ); 62 | } 63 | } 64 | ModeOption::Convex | ModeOption::CentroidNet => { 65 | stack = stack.push( 66 | Container::new(PathWidget::with_paths( 67 | &self.state.triangle.workspace.polygons, 68 | self.state.triangle.workspace.camera, 69 | Color::from_rgb8(100, 255, 100), 70 | 4.0, 71 | false 72 | )) 73 | .width(Length::Fill) 74 | .height(Length::Fill) 75 | ); 76 | 77 | } 78 | } 79 | stack.push( 80 | Container::new(self.triangle_control()) 81 | .width(Length::Shrink) 82 | .height(Length::Shrink) 83 | .padding(Padding::new(8.0)) 84 | ) 85 | }) 86 | .style(style_sheet_background) 87 | } 88 | 89 | pub(super) fn triangle_update_anchor(&mut self, update: PathEditorUpdateEvent) { 90 | self.state.triangle.triangle_update_point(update); 91 | } 92 | 93 | pub(super) fn triangle_update_zoom(&mut self, camera: Camera) { 94 | self.state.triangle.workspace.camera = camera; 95 | } 96 | 97 | pub(super) fn triangle_update_drag(&mut self, new_pos: Vector) { 98 | self.state.triangle.workspace.camera.pos = new_pos; 99 | } 100 | 101 | pub(super) fn triangle_update_radius(&mut self, radius: f64) { 102 | self.state.triangle.triangle_update_radius(radius); 103 | } 104 | pub(super) fn triangle_update_area(&mut self, area: f64) { 105 | self.state.triangle.triangle_update_area(area); 106 | } 107 | } 108 | 109 | fn on_update_anchor(event: PathEditorUpdateEvent) -> AppMessage { 110 | AppMessage::Triangle(TriangleMessage::PathEdited(event)) 111 | } 112 | 113 | fn on_update_size(size: Size) -> AppMessage { 114 | AppMessage::Triangle(TriangleMessage::WorkspaceSized(size)) 115 | } 116 | 117 | fn on_update_zoom(zoom: Camera) -> AppMessage { 118 | AppMessage::Triangle(TriangleMessage::WorkspaceZoomed(zoom)) 119 | } 120 | 121 | fn on_update_drag(drag: Vector) -> AppMessage { 122 | AppMessage::Triangle(TriangleMessage::WorkspaceDragged(drag)) 123 | } 124 | 125 | impl Default for WorkspaceState { 126 | fn default() -> Self { 127 | WorkspaceState { camera: Camera::empty(), paths: vec![], triangulations: vec![], polygons: vec![] } 128 | } 129 | } -------------------------------------------------------------------------------- /editor/src/compat/convert.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait Convert { 2 | fn convert(&self) -> T; 3 | } -------------------------------------------------------------------------------- /editor/src/compat/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod convert; 2 | pub(crate) mod point; -------------------------------------------------------------------------------- /editor/src/compat/point.rs: -------------------------------------------------------------------------------- 1 | use i_triangle::i_overlay::i_float::float::point::FloatPoint; 2 | use i_triangle::i_overlay::i_float::int::point::IntPoint; 3 | use iced::Vector; 4 | use crate::compat::convert::Convert; 5 | 6 | impl Convert> for IntPoint { 7 | fn convert(&self) -> FloatPoint { 8 | FloatPoint::new(self.x as f32, self.y as f32) 9 | } 10 | } 11 | 12 | impl Convert> for Vector { 13 | fn convert(&self) -> FloatPoint { 14 | FloatPoint::new(self.x, self.y) 15 | } 16 | } 17 | 18 | impl Convert> for IntPoint { 19 | fn convert(&self) -> Vector { 20 | Vector::new(self.x as f32, self.y as f32) 21 | } 22 | } 23 | 24 | impl Convert> for FloatPoint { 25 | fn convert(&self) -> Vector { 26 | Vector::new(self.x, self.y) 27 | } 28 | } -------------------------------------------------------------------------------- /editor/src/data/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod resource; 2 | pub mod triangle; -------------------------------------------------------------------------------- /editor/src/data/resource.rs: -------------------------------------------------------------------------------- 1 | use crate::data::triangle::TriangleResource; 2 | 3 | pub struct AppResource { 4 | pub(crate) triangle: TriangleResource, 5 | } 6 | 7 | impl AppResource { 8 | #[cfg(not(target_arch = "wasm32"))] 9 | pub fn with_paths(triangle: &str) -> Self { 10 | Self { 11 | triangle: TriangleResource::with_path(triangle), 12 | } 13 | } 14 | 15 | #[cfg(target_arch = "wasm32")] 16 | pub fn with_content(triangle: String) -> Self { 17 | Self { 18 | triangle: TriangleResource::with_content(triangle) 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /editor/src/data/triangle.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::PathBuf; 3 | use i_triangle::i_overlay::i_shape::int::path::IntPath; 4 | use serde::Deserialize; 5 | 6 | #[derive(Debug, Clone, Deserialize)] 7 | pub(crate) struct TriangleTest { 8 | pub(crate) paths: Vec, 9 | } 10 | 11 | impl TriangleTest { 12 | fn load(index: usize, folder: &str) -> Option { 13 | let file_name = format!("test_{}.json", index); 14 | let mut path_buf = PathBuf::from(folder); 15 | path_buf.push(file_name); 16 | 17 | match path_buf.canonicalize() { 18 | Ok(full_path) => println!("Full path: {}", full_path.display()), 19 | Err(e) => eprintln!("Error: {}", e), 20 | } 21 | let data = match std::fs::read_to_string(path_buf.as_path()) { 22 | Ok(data) => { 23 | data 24 | } 25 | Err(e) => { 26 | eprintln!("{:?}", e); 27 | return None; 28 | } 29 | }; 30 | 31 | let result: Result = serde_json::from_str(&data); 32 | match result { 33 | Ok(test) => Some(test), 34 | Err(e) => { 35 | eprintln!("Failed to parse JSON: {}", e); 36 | None 37 | } 38 | } 39 | } 40 | 41 | fn tests_count(folder: &str) -> usize { 42 | let folder_path = PathBuf::from(folder); 43 | match folder_path.canonicalize() { 44 | Ok(full_path) => println!("Full path: {}", full_path.display()), 45 | Err(e) => eprintln!("Error: {}", e), 46 | } 47 | 48 | match std::fs::read_dir(folder_path) { 49 | Ok(entries) => { 50 | entries 51 | .filter_map(|entry| { 52 | entry.ok().and_then(|e| { 53 | let path = e.path(); 54 | if path.extension()?.to_str()? == "json" { 55 | Some(()) 56 | } else { 57 | None 58 | } 59 | }) 60 | }) 61 | .count() 62 | } 63 | Err(e) => { 64 | eprintln!("Failed to read directory: {}", e); 65 | 0 66 | } 67 | } 68 | } 69 | } 70 | 71 | pub(crate) struct TriangleResource { 72 | folder: Option, 73 | pub(crate) count: usize, 74 | pub(crate) tests: HashMap 75 | } 76 | 77 | impl TriangleResource { 78 | 79 | #[cfg(not(target_arch = "wasm32"))] 80 | pub(crate) fn with_path(folder: &str) -> Self { 81 | let count = TriangleTest::tests_count(folder); 82 | Self { count, folder: Some(folder.to_string()), tests: Default::default() } 83 | } 84 | 85 | #[cfg(target_arch = "wasm32")] 86 | pub(crate) fn with_content(content: String) -> Self { 87 | let tests_vec: Vec = serde_json::from_str(&content).unwrap_or_else(|e| { 88 | eprintln!("Failed to parse JSON content: {}", e); 89 | vec![] 90 | }); 91 | 92 | let tests: HashMap = tests_vec 93 | .into_iter() 94 | .enumerate() // Assign indices 95 | .collect(); 96 | 97 | let count = tests.len(); 98 | Self { 99 | count, 100 | folder: None, 101 | tests, 102 | } 103 | } 104 | 105 | pub(crate) fn load(&mut self, index: usize) -> Option { 106 | if self.count <= index { 107 | return None; 108 | } 109 | if let Some(test) = self.tests.get(&index) { 110 | return Some(test.clone()) 111 | } 112 | 113 | let folder = if let Some(folder) = &self.folder { folder } else { return None; }; 114 | let test = TriangleTest::load(index, folder.as_str())?; 115 | 116 | self.tests.insert(index, test.clone()); 117 | 118 | Some(test) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /editor/src/draw/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod path; -------------------------------------------------------------------------------- /editor/src/draw/path.rs: -------------------------------------------------------------------------------- 1 | use i_mesh::path::butt::ButtStrokeBuilder; 2 | use i_mesh::path::style::StrokeStyle; 3 | use i_triangle::float::builder::TriangulationBuilder; 4 | use i_triangle::float::triangulation::Triangulation; 5 | use i_triangle::i_overlay::i_float::float::point::FloatPoint; 6 | use i_triangle::i_overlay::i_float::int::point::IntPoint; 7 | use i_triangle::i_overlay::i_shape::int::path::{IntPath, IntPaths}; 8 | use iced::advanced::layout::{self, Layout}; 9 | use iced::advanced::{Clipboard, renderer, Shell}; 10 | use iced::advanced::widget::{Tree, Widget}; 11 | use iced::{Event, event, mouse, Color, Vector, Transformation}; 12 | use iced::{Element, Length, Rectangle, Renderer, Size, Theme}; 13 | use iced::advanced::graphics::color::pack; 14 | use iced::advanced::graphics::Mesh; 15 | use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; 16 | use crate::geom::camera::Camera; 17 | use crate::geom::vector::VectorExt; 18 | 19 | pub(crate) struct PathWidget { 20 | stroke: Option, 21 | } 22 | 23 | impl PathWidget { 24 | pub(crate) fn with_paths(paths: &IntPaths, camera: Camera, stroke_color: Color, stroke_width: f32, arrows: bool) -> Self { 25 | let offset = Self::offset_for_paths(paths, camera); 26 | let stroke = Self::stroke_mesh_for_paths(paths, camera, offset, stroke_color, stroke_width, arrows); 27 | Self { 28 | stroke, 29 | } 30 | } 31 | 32 | fn stroke_mesh_for_paths(paths: &IntPaths, camera: Camera, offset: Vector, color: Color, width: f32, arrows: bool) -> Option { 33 | if paths.is_empty() { 34 | return None; 35 | } 36 | 37 | let mut builder = TriangulationBuilder::default(); 38 | 39 | for path in paths.iter() { 40 | Self::append_path(&mut builder, camera, path, width, arrows); 41 | } 42 | 43 | let s = if arrows { 44 | 2.5 * width 45 | } else { 46 | 0.5 * width 47 | }; 48 | 49 | let offset = Vector::new(offset.x - s, offset.y - s); 50 | 51 | let triangulation = builder.build(); 52 | 53 | Self::stroke_mesh_for_triangulation(triangulation, offset, color) 54 | } 55 | 56 | fn stroke_mesh_for_triangulation(triangulation: Triangulation, usize>, offset: Vector, color: Color) -> Option { 57 | if triangulation.indices.is_empty() { 58 | return None; 59 | } 60 | let color_pack = pack(color); 61 | let vertices = triangulation.points.iter().map(|&p| { 62 | SolidVertex2D { position: [p.x - offset.x, p.y - offset.y], color: color_pack } 63 | }).collect(); 64 | 65 | let indices = triangulation.indices.iter().map(|&i| i as u32).collect(); 66 | 67 | Some(Mesh::Solid { 68 | buffers: Indexed { vertices, indices }, 69 | transformation: Transformation::translate(offset.x, offset.y), 70 | clip_bounds: Rectangle::INFINITE, 71 | }) 72 | } 73 | 74 | fn offset_for_paths(paths: &IntPaths, camera: Camera) -> Vector { 75 | if paths.is_empty() { 76 | return Vector::new(0.0, 0.0); 77 | } 78 | 79 | let mut min_x = i32::MAX; 80 | let mut max_y = i32::MIN; 81 | 82 | for p in paths.iter().flatten() { 83 | min_x = min_x.min(p.x); 84 | max_y = max_y.max(p.y); 85 | } 86 | 87 | camera.int_world_to_view(IntPoint::new(min_x, max_y)) 88 | } 89 | 90 | fn append_path(builder: &mut TriangulationBuilder, usize>, camera: Camera, path: &IntPath, width: f32, arrows: bool) { 91 | let stroke_builder = ButtStrokeBuilder::new(StrokeStyle::with_width(width)); 92 | let screen_path: Vec> = path.iter().map(|&p| { 93 | let v = camera.int_world_to_view(p); 94 | FloatPoint::new(v.x, v.y) 95 | }).collect(); 96 | 97 | let sub_triangulation = stroke_builder 98 | .build_closed_path_mesh::, usize>(&screen_path); 99 | builder.append(sub_triangulation); 100 | 101 | let r2 = 2.0 * width; 102 | let r4 = 4.0 * width; 103 | 104 | if arrows { 105 | let mut a = screen_path[0]; 106 | for &b in screen_path.iter().skip(1) { 107 | let m = (a + b) * 0.5; 108 | let n = (b - a).normalize(); 109 | let m0 = m - n * r4; 110 | let t0 = FloatPoint::new(-n.y, n.x) * r2; 111 | let t1 = FloatPoint::new(n.y, -n.x) * r2; 112 | let v0 = m0 + t0; 113 | let v1 = m0 + t1; 114 | 115 | let arrow_triangulation = stroke_builder.build_closed_path_mesh(&[v0, m, v1]); 116 | builder.append(arrow_triangulation); 117 | 118 | a = b; 119 | } 120 | } 121 | } 122 | } 123 | 124 | impl Widget for PathWidget { 125 | fn size(&self) -> Size { 126 | Size { 127 | width: Length::Fill, 128 | height: Length::Fill, 129 | } 130 | } 131 | 132 | fn layout( 133 | &self, 134 | _tree: &mut Tree, 135 | _renderer: &Renderer, 136 | limits: &layout::Limits, 137 | ) -> layout::Node { 138 | layout::Node::new(limits.max()) 139 | } 140 | 141 | fn draw( 142 | &self, 143 | _tree: &Tree, 144 | renderer: &mut Renderer, 145 | _theme: &Theme, 146 | _style: &renderer::Style, 147 | layout: Layout<'_>, 148 | _cursor: mouse::Cursor, 149 | _viewport: &Rectangle, 150 | ) { 151 | use iced::advanced::graphics::mesh::Renderer as _; 152 | use iced::advanced::Renderer as _; 153 | 154 | let offset = Vector::point(layout.position()); 155 | if let Some(mesh) = &self.stroke { 156 | renderer.with_translation(offset, |renderer| { 157 | renderer.draw_mesh(mesh.clone()) 158 | }); 159 | } 160 | } 161 | } 162 | 163 | impl<'a, Message: 'a> From for Element<'a, Message> { 164 | fn from(editor: PathWidget) -> Self { 165 | Self::new(editor) 166 | } 167 | } -------------------------------------------------------------------------------- /editor/src/geom/camera.rs: -------------------------------------------------------------------------------- 1 | use i_triangle::i_overlay::i_float::int::point::IntPoint; 2 | use i_triangle::i_overlay::i_float::int::rect::IntRect; 3 | use iced::{Size, Vector}; 4 | 5 | #[derive(Debug, Clone, Copy)] 6 | pub(crate) struct Camera { 7 | pub(crate) scale: f32, 8 | pub(crate) i_scale: f32, 9 | pub(crate) size: Size, 10 | pub(crate) pos: Vector, 11 | } 12 | 13 | impl Camera { 14 | 15 | pub(crate) fn empty() -> Self { 16 | Self { scale: 0.0, i_scale: 0.0, size: Size::ZERO, pos: Vector::new(0.0 ,0.0) } 17 | } 18 | 19 | pub(crate) fn is_empty(&self) -> bool { 20 | self.scale < 0.000_000_000_1 21 | } 22 | 23 | pub(crate) fn set_scale(&mut self, scale: f32) { 24 | self.scale = scale; 25 | self.i_scale = 1.0 / scale; 26 | } 27 | 28 | pub(crate) fn is_not_empty(&self) -> bool { 29 | self.scale > 0.0 30 | } 31 | 32 | pub(crate) fn new(rect: IntRect, size: Size) -> Self { 33 | let w_pow = rect.width().ilog2() as usize; 34 | let h_pow = rect.height().ilog2() as usize; 35 | 36 | let width = (1 << w_pow) as f32; 37 | let height = (1 << h_pow) as f32; 38 | let sw = size.width / width; 39 | let sh = size.height / height; 40 | 41 | let scale = 0.25 * sw.min(sh); 42 | let i_scale = 1.0 / scale; 43 | let x = 0.5 * (rect.min_x + rect.max_x) as f32; 44 | let y = 0.5 * (rect.min_y + rect.max_y) as f32; 45 | let pos = Vector::new(x, y); 46 | 47 | Camera { scale, i_scale, size, pos } 48 | } 49 | 50 | #[inline] 51 | pub(crate) fn int_world_to_screen(&self, view_left_top: Vector, world: IntPoint) -> Vector { 52 | let x = self.scale * (world.x as f32 - self.pos.x) + view_left_top.x + 0.5 * self.size.width; 53 | let y = self.scale * (self.pos.y - world.y as f32) + view_left_top.y + 0.5 * self.size.height; 54 | Vector { x, y } 55 | } 56 | 57 | #[inline] 58 | pub(crate) fn world_to_screen(&self, view_left_top: Vector, world: Vector) -> Vector { 59 | let x = self.scale * (world.x - self.pos.x) + view_left_top.x + 0.5 * self.size.width; 60 | let y = self.scale * (self.pos.y - world.y) + view_left_top.y + 0.5 * self.size.height; 61 | Vector { x, y } 62 | } 63 | 64 | #[inline] 65 | pub(crate) fn int_world_to_view(&self, world: IntPoint) -> Vector { 66 | self.world_to_view(Vector::new(world.x as f32, world.y as f32)) 67 | } 68 | 69 | #[inline] 70 | pub(crate) fn world_to_view(&self, world: Vector) -> Vector { 71 | let x = self.scale * (world.x - self.pos.x) + 0.5 * self.size.width; 72 | let y = self.scale * (self.pos.y - world.y) + 0.5 * self.size.height; 73 | Vector { x, y } 74 | } 75 | 76 | #[inline] 77 | pub(crate) fn view_to_world(&self, view: Vector) -> Vector { 78 | let x = self.i_scale * (view.x - 0.5 * self.size.width) + self.pos.x; 79 | let y = self.i_scale * (0.5 * self.size.height - view.y) + self.pos.y; 80 | Vector { x, y } 81 | } 82 | 83 | #[inline] 84 | pub(crate) fn view_distance_to_world(&self, view_distance: Vector) -> Vector { 85 | let x = view_distance.x * self.i_scale; 86 | let y = -view_distance.y * self.i_scale; 87 | Vector { x, y } 88 | } 89 | } -------------------------------------------------------------------------------- /editor/src/geom/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod camera; 2 | pub(crate) mod vector; -------------------------------------------------------------------------------- /editor/src/geom/vector.rs: -------------------------------------------------------------------------------- 1 | use i_triangle::i_overlay::i_float::int::point::IntPoint; 2 | use iced::{Point, Vector}; 3 | 4 | pub(crate) trait VectorExt { 5 | fn round(&self) -> IntPoint; 6 | fn point(point: Point) -> Self; 7 | } 8 | 9 | impl VectorExt for Vector { 10 | fn round(&self) -> IntPoint { 11 | IntPoint::new( 12 | self.x.round() as i32, 13 | self.y.round() as i32, 14 | ) 15 | } 16 | fn point(point: Point) -> Self { 17 | Self { x: point.x, y: point.y } 18 | } 19 | } -------------------------------------------------------------------------------- /editor/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod web; 2 | mod data; 3 | mod app; 4 | mod draw; 5 | mod sheet; 6 | mod geom; 7 | mod path_editor; 8 | mod mesh; 9 | mod compat; 10 | mod mesh_viewer; -------------------------------------------------------------------------------- /editor/src/main.rs: -------------------------------------------------------------------------------- 1 | mod data; 2 | mod app; 3 | mod draw; 4 | mod sheet; 5 | mod geom; 6 | mod path_editor; 7 | mod compat; 8 | mod mesh; 9 | mod mesh_viewer; 10 | 11 | use iced::application; 12 | use crate::app::main::EditorApp; 13 | use crate::data::resource::AppResource; 14 | 15 | #[cfg(not(target_arch = "wasm32"))] 16 | fn main() -> iced::Result { 17 | run_desktop() 18 | } 19 | 20 | #[cfg(not(target_arch = "wasm32"))] 21 | fn run_desktop() -> iced::Result { 22 | let app_initializer = move || { 23 | let app = EditorApp::new(AppResource::with_paths( 24 | "./tests/triangle", 25 | )); 26 | (app, iced::Task::none()) 27 | }; 28 | 29 | application(app_initializer, EditorApp::update, EditorApp::view) 30 | .resizable(true) 31 | .centered() 32 | .title("iTriangle Editor") 33 | .subscription(EditorApp::subscription) 34 | .run() 35 | } -------------------------------------------------------------------------------- /editor/src/mesh/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod path_builder; -------------------------------------------------------------------------------- /editor/src/mesh/path_builder.rs: -------------------------------------------------------------------------------- 1 | use i_mesh::path::butt::ButtStrokeBuilder; 2 | use i_mesh::path::style::StrokeStyle; 3 | use i_triangle::float::builder::TriangulationBuilder; 4 | use i_triangle::i_overlay::i_float::float::point::FloatPoint; 5 | use i_triangle::i_overlay::i_float::int::point::IntPoint; 6 | use iced::{Color, Rectangle, Transformation}; 7 | use iced::advanced::graphics::color::pack; 8 | use iced::advanced::graphics::Mesh; 9 | use iced::advanced::graphics::mesh::{Indexed, SolidVertex2D}; 10 | use crate::compat::convert::Convert; 11 | use crate::geom::camera::Camera; 12 | 13 | pub(crate) struct PathBuilder { 14 | camera: Camera, 15 | offset: FloatPoint, 16 | builder: TriangulationBuilder, usize>, 17 | } 18 | 19 | impl PathBuilder { 20 | 21 | #[inline] 22 | pub(crate) fn new(camera: Camera, offset: FloatPoint) -> Self { 23 | Self { camera, offset, builder: TriangulationBuilder::default() } 24 | } 25 | 26 | #[inline] 27 | pub(crate) fn add_int_segment(&mut self, a: IntPoint, b: IntPoint, width: f32) { 28 | let p0 = self.camera.world_to_screen(self.offset.convert(), a.convert()).convert(); 29 | let p1 = self.camera.world_to_screen(self.offset.convert(), b.convert()).convert(); 30 | 31 | let stroke_builder = ButtStrokeBuilder::new(StrokeStyle::with_width(width)); 32 | let sub_triangulation = stroke_builder.build_open_path_mesh(&[p0, p1]); 33 | self.builder.append(sub_triangulation); 34 | } 35 | 36 | #[inline] 37 | pub(crate) fn add_paths(&mut self, points: &[IntPoint], closed: bool, width: f32) { 38 | let float_points: Vec<_> = points.iter() 39 | .map(|p| { 40 | let screen = self.camera.world_to_screen(self.offset.convert(), p.convert()); 41 | screen.convert() 42 | }).collect(); 43 | 44 | let stroke_builder = ButtStrokeBuilder::new(StrokeStyle::with_width(width)); 45 | let sub_triangulation = if closed { 46 | stroke_builder.build_closed_path_mesh(&float_points) 47 | } else { 48 | stroke_builder.build_open_path_mesh(&float_points) 49 | }; 50 | self.builder.append(sub_triangulation); 51 | } 52 | 53 | 54 | pub(crate) fn into_mesh(self, color: Color) -> Option { 55 | let triangulation = self.builder.build(); 56 | if triangulation.indices.is_empty() { 57 | return None; 58 | } 59 | let color_pack = pack(color); 60 | let vertices = triangulation.points.iter().map(|&p| { 61 | let dp = p - self.offset; 62 | SolidVertex2D { position: [dp.x, dp.y], color: color_pack } 63 | }).collect(); 64 | 65 | let indices = triangulation.indices.iter().map(|&i| i as u32).collect(); 66 | 67 | Some(Mesh::Solid { 68 | buffers: Indexed { vertices, indices }, 69 | transformation: Transformation::translate(self.offset.x, self.offset.y), 70 | clip_bounds: Rectangle::INFINITE, 71 | }) 72 | } 73 | } -------------------------------------------------------------------------------- /editor/src/mesh_viewer/color.rs: -------------------------------------------------------------------------------- 1 | use iced::{Color, Theme}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub(crate) struct MeshViewerColorSchema { 5 | pub(crate) main: Color, 6 | } 7 | 8 | impl MeshViewerColorSchema { 9 | 10 | pub(crate) fn with_theme(theme: Theme) -> Self { 11 | let palette = theme.extended_palette(); 12 | 13 | if palette.is_dark { 14 | Self { 15 | main: Color::from_rgb8(255, 100, 100), 16 | } 17 | } else { 18 | Self { 19 | main: Color::from_rgb8(255, 100, 100), 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /editor/src/mesh_viewer/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod color; 2 | pub(crate) mod widget; -------------------------------------------------------------------------------- /editor/src/mesh_viewer/widget.rs: -------------------------------------------------------------------------------- 1 | use i_triangle::int::triangulation::IntTriangulation; 2 | use crate::compat::convert::Convert; 3 | use crate::geom::camera::Camera; 4 | use crate::mesh::path_builder::PathBuilder; 5 | use iced::advanced::widget::Tree; 6 | use iced::advanced::{Layout, Widget, layout, renderer}; 7 | use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector, mouse}; 8 | use crate::mesh_viewer::color::MeshViewerColorSchema; 9 | 10 | pub(crate) struct MeshViewerWidget<'a> { 11 | pub(super) triangulation: &'a IntTriangulation, 12 | pub(super) camera: Camera, 13 | pub(super) schema: MeshViewerColorSchema, 14 | } 15 | 16 | impl<'a> MeshViewerWidget<'a> { 17 | pub(crate) fn new( 18 | triangulation: &'a IntTriangulation, 19 | camera: Camera, 20 | ) -> Self { 21 | Self { 22 | triangulation, 23 | camera, 24 | schema: MeshViewerColorSchema::with_theme(Theme::default()) 25 | } 26 | } 27 | } 28 | 29 | impl Widget for MeshViewerWidget<'_> { 30 | 31 | fn size(&self) -> Size { 32 | Size { 33 | width: Length::Fill, 34 | height: Length::Fill, 35 | } 36 | } 37 | 38 | fn layout( 39 | &self, 40 | _tree: &mut Tree, 41 | _renderer: &Renderer, 42 | limits: &layout::Limits, 43 | ) -> layout::Node { 44 | layout::Node::new(limits.max()) 45 | } 46 | 47 | fn draw( 48 | &self, 49 | _tree: &Tree, 50 | renderer: &mut Renderer, 51 | _theme: &Theme, 52 | _style: &renderer::Style, 53 | layout: Layout<'_>, 54 | _cursor: mouse::Cursor, 55 | _viewport: &Rectangle, 56 | ) { 57 | use iced::advanced::Renderer as _; 58 | use iced::advanced::graphics::mesh::Renderer as _; 59 | 60 | let offset = layout.position(); 61 | let offset_vec = Vector::new(offset.x, offset.y); 62 | 63 | let mut contour_builder = PathBuilder::new(self.camera, offset_vec.convert()); 64 | let mut i = 0; 65 | while i < self.triangulation.indices.len() { 66 | let ai = self.triangulation.indices[i]; 67 | let bi = self.triangulation.indices[i + 1]; 68 | let ci = self.triangulation.indices[i+ 2]; 69 | 70 | let a = self.triangulation.points[ai]; 71 | let b = self.triangulation.points[bi]; 72 | let c = self.triangulation.points[ci]; 73 | contour_builder.add_int_segment(a, b, 1.0); 74 | contour_builder.add_int_segment(b, c, 1.0); 75 | contour_builder.add_int_segment(c, a, 1.0); 76 | i += 3; 77 | } 78 | if let Some(mesh) = contour_builder.into_mesh(self.schema.main) { 79 | renderer.with_translation(Vector::new(0.0, 0.0), |renderer| renderer.draw_mesh(mesh)); 80 | } 81 | } 82 | } 83 | 84 | impl<'a, Message: 'a> From> for Element<'a, Message> { 85 | fn from(editor: MeshViewerWidget<'a>) -> Self { 86 | Self::new(editor) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /editor/src/path_editor/color.rs: -------------------------------------------------------------------------------- 1 | use iced::{Color, Theme}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub(crate) struct PathEditorColorSchema { 5 | pub(crate) main: Color, 6 | pub(crate) drag: Color, 7 | pub(crate) hover: Color, 8 | } 9 | 10 | impl PathEditorColorSchema { 11 | 12 | pub(crate) fn with_theme(theme: Theme) -> Self { 13 | let palette = theme.extended_palette(); 14 | 15 | if palette.is_dark { 16 | Self { 17 | main: Color::WHITE, 18 | drag: palette.primary.base.color, 19 | hover: palette.primary.weak.color 20 | } 21 | } else { 22 | Self { 23 | main: Color::BLACK, 24 | drag: palette.primary.base.color, 25 | hover: palette.primary.weak.color 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /editor/src/path_editor/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod state; 2 | pub(crate) mod widget; 3 | pub(crate) mod color; -------------------------------------------------------------------------------- /editor/src/path_editor/widget.rs: -------------------------------------------------------------------------------- 1 | use i_triangle::i_overlay::i_float::int::point::IntPoint; 2 | use i_triangle::i_overlay::i_shape::int::path::IntPath; 3 | use crate::compat::convert::Convert; 4 | use crate::geom::camera::Camera; 5 | use crate::mesh::path_builder::PathBuilder; 6 | use crate::path_editor::color::PathEditorColorSchema; 7 | use crate::path_editor::state::{ActivePoint, MeshCache, PathEditorState, SelectState}; 8 | use iced::advanced::graphics::Mesh; 9 | use iced::advanced::widget::tree::State; 10 | use iced::advanced::widget::{Tree, tree}; 11 | use iced::advanced::{Clipboard, Layout, Shell, Widget, layout, renderer}; 12 | use iced::{ 13 | Color, Element, Event, Length, Point, Rectangle, Renderer, Size, Theme, Vector, event, mouse, 14 | }; 15 | 16 | pub(crate) struct PathEditorWidget<'a, Message> { 17 | pub(super) id: usize, 18 | pub(super) path: &'a IntPath, 19 | pub(super) camera: Camera, 20 | pub(super) schema: PathEditorColorSchema, 21 | pub(super) mesh_radius: f32, 22 | pub(super) hover_radius: f32, 23 | pub(super) split_factor: usize, 24 | on_update: Box Message + 'a>, 25 | } 26 | 27 | #[derive(Debug, Clone)] 28 | pub(crate) struct PathEditorUpdateEvent { 29 | pub(crate) curve_index: usize, 30 | pub(crate) point_index: usize, 31 | pub(crate) point: IntPoint, 32 | } 33 | 34 | impl<'a, Message> PathEditorWidget<'a, Message> { 35 | pub(crate) fn new( 36 | id: usize, 37 | path: &'a IntPath, 38 | camera: Camera, 39 | on_update: impl Fn(PathEditorUpdateEvent) -> Message + 'a, 40 | ) -> Self { 41 | Self { 42 | id, 43 | path, 44 | camera, 45 | mesh_radius: 4.0, 46 | hover_radius: 12.0, 47 | split_factor: 5, 48 | schema: PathEditorColorSchema::with_theme(Theme::default()), 49 | on_update: Box::new(on_update), 50 | } 51 | } 52 | 53 | pub(crate) fn set_schema(mut self, schema: PathEditorColorSchema) -> Self { 54 | self.schema = schema; 55 | self 56 | } 57 | } 58 | 59 | impl Widget for PathEditorWidget<'_, Message> { 60 | fn tag(&self) -> tree::Tag { 61 | tree::Tag::of::() 62 | } 63 | 64 | fn state(&self) -> State { 65 | State::new(PathEditorState::default()) 66 | } 67 | 68 | fn size(&self) -> Size { 69 | Size { 70 | width: Length::Fill, 71 | height: Length::Fill, 72 | } 73 | } 74 | 75 | fn layout( 76 | &self, 77 | tree: &mut Tree, 78 | _renderer: &Renderer, 79 | limits: &layout::Limits, 80 | ) -> layout::Node { 81 | if let State::Some(state_box) = &mut tree.state { 82 | state_box 83 | .downcast_mut::() 84 | .unwrap() 85 | .update_mesh(self.mesh_radius, self.schema) 86 | }; 87 | 88 | layout::Node::new(limits.max()) 89 | } 90 | 91 | fn update( 92 | &mut self, 93 | tree: &mut Tree, 94 | event: &Event, 95 | layout: Layout<'_>, 96 | cursor: mouse::Cursor, 97 | _renderer: &Renderer, 98 | _clipboard: &mut dyn Clipboard, 99 | shell: &mut Shell<'_, Message>, 100 | _viewport: &Rectangle, 101 | ) { 102 | let bounds = layout.bounds(); 103 | 104 | let mouse_event = if let Event::Mouse(mouse_event) = event { 105 | mouse_event 106 | } else { 107 | return; 108 | }; 109 | 110 | let state = tree.state.downcast_mut::(); 111 | match mouse_event { 112 | mouse::Event::CursorMoved { position } => { 113 | if bounds.contains(*position) { 114 | let view_cursor = *position - bounds.position(); 115 | if let Some(updated_point) = state.mouse_move(&*self, view_cursor) { 116 | shell.publish((self.on_update)(updated_point)); 117 | shell.capture_event(); 118 | } 119 | } 120 | } 121 | mouse::Event::ButtonPressed(mouse::Button::Left) => { 122 | let position = cursor.position().unwrap_or(Point::ORIGIN); 123 | if bounds.contains(position) { 124 | let view_cursor = position - bounds.position(); 125 | if state.mouse_press(&*self, view_cursor) { 126 | shell.capture_event(); 127 | } 128 | } 129 | } 130 | mouse::Event::ButtonReleased(mouse::Button::Left) => { 131 | let position = cursor.position().unwrap_or(Point::ORIGIN); 132 | let view_cursor = position - bounds.position(); 133 | if state.mouse_release(&*self, view_cursor) { 134 | shell.capture_event(); 135 | } 136 | } 137 | _ => {} 138 | } 139 | } 140 | 141 | fn draw( 142 | &self, 143 | tree: &Tree, 144 | renderer: &mut Renderer, 145 | _theme: &Theme, 146 | _style: &renderer::Style, 147 | layout: Layout<'_>, 148 | _cursor: mouse::Cursor, 149 | _viewport: &Rectangle, 150 | ) { 151 | let state = tree.state.downcast_ref::(); 152 | 153 | let mesh_cache = if let Some(mesh) = &state.mesh_cache { 154 | mesh 155 | } else { 156 | return; 157 | }; 158 | 159 | use iced::advanced::Renderer as _; 160 | use iced::advanced::graphics::mesh::Renderer as _; 161 | 162 | let offset = layout.position(); 163 | let offset_vec = Vector::new(offset.x, offset.y); 164 | let radius_offset = offset - Point::new(self.mesh_radius, self.mesh_radius); 165 | 166 | let mut contour_builder = PathBuilder::new(self.camera, offset_vec.convert()); 167 | contour_builder.add_paths(&self.path, true, 4.0); 168 | if let Some(mesh) = contour_builder.into_mesh(Color ::from_rgba(1.0, 1.0, 1.0, 0.2)) { 169 | renderer.with_translation(Vector::new(0.0, 0.0), |renderer| renderer.draw_mesh(mesh)); 170 | } 171 | 172 | for (index, point) in self.path.iter().enumerate() { 173 | let main_screen = self.camera.world_to_screen(radius_offset, point.convert()); 174 | 175 | if let Some(active) = &state.active_point { 176 | let mesh = if active.index == index { 177 | mesh_cache.active_point(active) 178 | } else { 179 | mesh_cache.point.clone() 180 | }; 181 | 182 | renderer.with_translation(main_screen, |renderer| renderer.draw_mesh(mesh)); 183 | } 184 | } 185 | } 186 | } 187 | 188 | impl MeshCache { 189 | fn active_point(&self, point: &ActivePoint) -> Mesh { 190 | match point.select_state { 191 | SelectState::Hover => self.hover.clone(), 192 | SelectState::Drag(_) => self.hover.clone(), 193 | } 194 | } 195 | } 196 | 197 | impl<'a, Message: 'a> From> for Element<'a, Message> { 198 | fn from(editor: PathEditorWidget<'a, Message>) -> Self { 199 | Self::new(editor) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /editor/src/sheet/mod.rs: -------------------------------------------------------------------------------- 1 | mod state; 2 | pub(crate) mod widget; -------------------------------------------------------------------------------- /editor/src/sheet/state.rs: -------------------------------------------------------------------------------- 1 | use iced::{Size, Vector}; 2 | use iced::mouse::ScrollDelta; 3 | use crate::geom::camera::Camera; 4 | 5 | struct Drag { 6 | start_screen: Vector, 7 | start_world: Vector, 8 | } 9 | 10 | enum DragState { 11 | Drag(Drag), 12 | None, 13 | } 14 | 15 | pub(super) struct SheetState { 16 | drag_state: DragState, 17 | } 18 | 19 | impl SheetState { 20 | pub(super) fn mouse_press(&mut self, camera: Camera, view_cursor: Vector) { 21 | self.drag_state = DragState::Drag(Drag { start_screen: view_cursor, start_world: camera.pos }); 22 | } 23 | 24 | pub(super) fn mouse_release(&mut self) { 25 | self.drag_state = DragState::None; 26 | } 27 | 28 | pub(super) fn mouse_move(&mut self, camera: Camera, view_cursor: Vector) -> Option> { 29 | if let DragState::Drag(drag) = &self.drag_state { 30 | let translate = drag.start_screen - view_cursor; 31 | let world_dist = camera.view_distance_to_world(translate); 32 | let new_pos = Vector::new( 33 | drag.start_world.x + world_dist.x, 34 | drag.start_world.y + world_dist.y, 35 | ); 36 | Some(new_pos) 37 | } else { 38 | None 39 | } 40 | } 41 | 42 | pub(super) fn mouse_wheel_scrolled(&mut self, camera: Camera, viewport_size: Size, delta: ScrollDelta, view_cursor: Vector) -> Option { 43 | if let ScrollDelta::Pixels { x: _ , y } = delta { 44 | 45 | let s = 1.0 + y / viewport_size.height; 46 | let mut new_camera = camera; 47 | new_camera.set_scale(s * camera.scale); 48 | 49 | let world_pos = camera.view_to_world(view_cursor); 50 | let new_view_pos = new_camera.world_to_view(world_pos); 51 | 52 | let view_distance = view_cursor - new_view_pos; 53 | let world_distance = new_camera.view_distance_to_world(view_distance); 54 | 55 | new_camera.pos = new_camera.pos - world_distance; 56 | 57 | Some(new_camera) 58 | } else { 59 | None 60 | } 61 | } 62 | } 63 | 64 | impl Default for SheetState { 65 | fn default() -> Self { 66 | Self { 67 | drag_state: DragState::None, 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /editor/src/web.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "wasm32")] 2 | mod wasm { 3 | use std::panic; 4 | use wasm_bindgen::prelude::*; 5 | use iced::{application}; 6 | 7 | use log::info; 8 | use crate::app::main::EditorApp; 9 | use crate::data::resource::AppResource; 10 | 11 | #[wasm_bindgen] 12 | pub struct WebApp; 13 | 14 | #[wasm_bindgen] 15 | impl WebApp { 16 | #[wasm_bindgen(constructor)] 17 | pub fn create() -> Self { 18 | Self {} 19 | } 20 | 21 | #[wasm_bindgen] 22 | pub fn start(&self, triangle_data: String) { 23 | panic::set_hook(Box::new(console_error_panic_hook::hook)); 24 | console_log::init_with_level(log::Level::Debug).expect("error initializing log"); 25 | info!("Starting application..."); 26 | 27 | let app_resource = AppResource::with_content(triangle_data); 28 | 29 | let app_initializer = move || { 30 | info!("wasm init"); 31 | let app = EditorApp::with_resource(AppResource::with_content(&triangle_data)); 32 | 33 | (app, iced::Task::none()) 34 | }; 35 | 36 | application(app_initializer, EditorApp::update, EditorApp::view) 37 | .resizable(true) 38 | .centered() 39 | .title("iTriangle Editor") 40 | .subscription(EditorApp::subscription) 41 | .run() 42 | .unwrap(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /editor/tests/triangle/test_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [[ 3 | [0, 0], [100, 0], [100, 100], [0, 100] 4 | ]] 5 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[0, 0], [20, 0], [20, 20], [0, 20]], 4 | [[5, 5], [5, 15], [15, 15], [15, 5]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_10.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-10, 5], [-10, -5], [0, 0], [-5, -10], [5, -10], [0, 0], [10, -5], [10, 5], [0, 0], [5, 10], [-5, 10], [0, 0]] 4 | ] 5 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_11.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-15, -15], [15, -15], [15, 15], [-15, 15]], 4 | [[-10, -5], [-10, 5], [0, 0]], 5 | [[5, -10], [-5, -10], [0, 0]], 6 | [[10, 5], [10, -5], [0, 0]], 7 | [[-5, 10], [5, 10], [0, 0]] 8 | ] 9 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_12.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[0, 0], [-10, -5], [-5, -10], [0, 0], [5, -10], [10, -5], [0, 0], [10, 5], [5, 10], [0, 0], [-5, 10], [-10, 5]] 4 | ] 5 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_13.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[0, 0], [5, 0], [5, 5], [10, 5], [10, 5], [10, 0], [15, 0], [15, 5], [10, 5], [10, 10], [15, 10], [15, 15], [10, 15], [10, 10], [5, 10], [5, 15], [0, 15], [0, 10], [5, 10], [5, 5], [0, 5]] 4 | ] 5 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_14.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-5, -5], [20, -5], [20, 20], [-5, 20]], 4 | [[0, 0], [0, 5], [5, 5], [5, 0]], 5 | [[0, 10], [0, 15], [5, 15], [5, 10]], 6 | [[10, 0], [10, 5], [15, 5], [15, 0]], 7 | [[10, 10], [10, 15], [15, 15], [15, 10]], 8 | [[5, 5], [5, 10], [10, 10], [10, 5]] 9 | ] 10 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_15.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[0, 5], [5, 5], [5, 0], [10, 0], [10, 5], [15, 5], [15, 10], [10, 10], [10, 15], [5, 15], [5, 10], [0, 10]], 4 | [[5, 5], [5, 10], [10, 10], [10, 5]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_16.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-5, -5], [20, -5], [20, 20], [-5, 20]], 4 | [[0, 5], [0, 10], [5, 10], [5, 5]], 5 | [[10, 5], [10, 10], [15, 10], [15, 5]], 6 | [[5, 10], [5, 15], [10, 15], [10, 10]], 7 | [[5, 0], [5, 5], [10, 5], [10, 0]] 8 | ] 9 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_17.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-5, 0], [-10, -5], [-5, -10], [0, -5], [5, -10], [0, -5], [5, -10], [10, -5], [5, 0], [10, 5], [5, 10], [0, 5], [-5, 10], [-10, 5]], 4 | [[-5, 0], [0, 5], [5, 0], [0, -5]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_18.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-15, 10], [-15, -10], [15, -10], [15, 10]], 4 | [[-10, 0], [-5, 5], [0, 0], [5, 5], [10, 0], [5, -5], [0, 0], [-5, -5]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_19.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-10, 15], [-10, -15], [10, -15], [10, 15]], 4 | [[0, 0], [-5, 5], [0, 10], [5, 5], [0, 0], [5, -5], [0, -10], [-5, -5]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [[ 3 | [ 4 | -20, 5 | -20 6 | ], 7 | [ 8 | -10, 9 | 0 10 | ], 11 | [ 12 | -20, 13 | 20 14 | ], 15 | [ 16 | -5, 17 | 20 18 | ], 19 | [ 20 | 0, 21 | 10 22 | ], 23 | [ 24 | 5, 25 | 20 26 | ], 27 | [ 28 | 20, 29 | 20 30 | ], 31 | [ 32 | 10, 33 | 0 34 | ], 35 | [ 36 | 20, 37 | -20 38 | ], 39 | [ 40 | 5, 41 | -20 42 | ], 43 | [ 44 | 0, 45 | -10 46 | ], 47 | [ 48 | -5, 49 | -20 50 | ] 51 | ]] 52 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_20.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [ 5 | -10, 6 | -10 7 | ], 8 | [ 9 | -10, 10 | 10 11 | ], 12 | [ 13 | -5, 14 | 10 15 | ], 16 | [ 17 | 0, 18 | 5 19 | ], 20 | [ 21 | -5, 22 | 0 23 | ], 24 | [ 25 | 0, 26 | -5 27 | ], 28 | [ 29 | 5, 30 | 0 31 | ], 32 | [ 33 | 0, 34 | 5 35 | ], 36 | [ 37 | 5, 38 | 10 39 | ], 40 | [ 41 | 10, 42 | 10 43 | ], 44 | [ 45 | 10, 46 | -10 47 | ] 48 | ] 49 | ] 50 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_21.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [ 5 | -10, 6 | -10 7 | ], 8 | [ 9 | -10, 10 | 10 11 | ], 12 | [ 13 | 10, 14 | 10 15 | ], 16 | [ 17 | 10, 18 | -10 19 | ], 20 | [ 21 | 5, 22 | -10 23 | ], 24 | [ 25 | 0, 26 | -5 27 | ], 28 | [ 29 | 5, 30 | 0 31 | ], 32 | [ 33 | 0, 34 | 5 35 | ], 36 | [ 37 | -5, 38 | 0 39 | ], 40 | [ 41 | 0, 42 | -5 43 | ], 44 | [ 45 | -5, 46 | -10 47 | ] 48 | ] 49 | ] 50 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_22.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-5, 0], [-10, -5], [-10, -10], [-5, -10], [0, -5], [5, -10], [0, -5], [5, -10], [10, -10], [10, -5], [5, 0], [10, 5], [10, 10], [5, 10], [0, 5], [-5, 10], [-10, 10], [-10, 5], [-5, 0], [0, 5], [5, 0], [0, -5]] 4 | ] 5 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_23.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [ 5 | -10, 6 | -10 7 | ], 8 | [ 9 | -10, 10 | 10 11 | ], 12 | [ 13 | 10, 14 | 10 15 | ], 16 | [ 17 | 0, 18 | 5 19 | ], 20 | [ 21 | 5, 22 | 0 23 | ], 24 | [ 25 | 10, 26 | 10 27 | ], 28 | [ 29 | 10, 30 | -10 31 | ] 32 | ] 33 | ] 34 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_24.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [ 5 | -10, 6 | -10 7 | ], 8 | [ 9 | -10, 10 | 10 11 | ], 12 | [ 13 | 10, 14 | 10 15 | ], 16 | [ 17 | 10, 18 | -10 19 | ], 20 | [ 21 | 5, 22 | 0 23 | ], 24 | [ 25 | 0, 26 | -5 27 | ], 28 | [ 29 | 10, 30 | -10 31 | ] 32 | ] 33 | ] 34 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_25.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-10, -10], [-5, 0], [0, -5], [-10, -10], [10, -10], [10, 10], [-10, 10]] 4 | ] 5 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_26.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [ 5 | -10, 6 | -10 7 | ], 8 | [ 9 | -10, 10 | 10 11 | ], 12 | [ 13 | -5, 14 | 0 15 | ], 16 | [ 17 | 0, 18 | 5 19 | ], 20 | [ 21 | -10, 22 | 10 23 | ], 24 | [ 25 | 10, 26 | 10 27 | ], 28 | [ 29 | 10, 30 | -10 31 | ] 32 | ] 33 | ] 34 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_27.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [-10, -10], [0, -10], [10, -10], [10, 0], [10, 10], [0, 10], [-10, 10], [-10, 0] 5 | ] 6 | ] 7 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_28.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-30, -30], [0, -15], [30, -30], [15, 0], [30, 30], [0, 15], [-30, 30], [-15, 0]], 4 | [[-20, 20], [0, 10], [20, 20], [10, 0], [20, -20], [0, -10], [-20, -20], [-10, 0]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_29.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-10, 8], [-7, 6], [-6, 2], [-8, -2], [-13, -4], [-16, -3], [-18, 0], [-25, -7], [-14, -15], [0, -18], [14, -15], [26, -7], [17, 1], [13, -1], [9, 1], [7, 6], [8, 10], [0, 20]], 4 | [[5, -2], [7, -5], [5, -9], [2, -11], [-2, -9], [-4, -5], [-2, -2], [2, 0]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [[ 3 | [0, 0], [5, 0], [5, 5], [10, 5], [10, 0], [15, 0], [15, 15], [10, 15], [10, 10], [5, 10], [5, 15], [0, 15] 4 | ]] 5 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_30.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[46, 9], [29, 12], [14, 9], [22, 22], [26, 39], [12, 29], [3, 16], [0, 32], [-9, 46], [-12, 29], [-9, 14], [-22, 22], [-39, 26], [-29, 12], [-16, 3], [-32, 0], [-46, -9], [-29, -12], [-14, -9], [-22, -22], [-26, -39], [-12, -29], [-3, -16], [0, -32], [9, -46], [12, -29], [9, -14], [22, -22], [39, -26], [29, -12], [16, -3], [32, 0]], 4 | [[16, 0], [8, -1], [14, -6], [19, -13], [11, -11], [4, -7], [6, -14], [4, -23], [0, -16], [-1, -8], [-6, -14], [-13, -19], [-11, -11], [-7, -4], [-14, -6], [-23, -4], [-16, 0], [-8, 1], [-14, 6], [-19, 13], [-11, 11], [-4, 7], [-6, 14], [-4, 23], [0, 16], [1, 8], [6, 14], [13, 19], [11, 11], [7, 4], [14, 6], [23, 4]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_31.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[41, 8], [29, 12], [17, 11], [22, 22], [23, 35], [12, 29], [4, 21], [0, 32], [-8, 41], [-12, 29], [-11, 17], [-22, 22], [-35, 23], [-29, 12], [-21, 4], [-32, 0], [-41, -8], [-29, -12], [-17, -11], [-22, -22], [-23, -35], [-12, -29], [-4, -21], [0, -32], [8, -41], [12, -29], [11, -17], [22, -22], [35, -23], [29, -12], [21, -4], [32, 0]], 4 | [[16, 0], [10, -2], [14, -6], [17, -11], [11, -11], [5, -8], [6, -14], [4, -20], [0, -16], [-2, -10], [-6, -14], [-11, -17], [-11, -11], [-8, -5], [-14, -6], [-20, -4], [-16, 0], [-10, 2], [-14, 6], [-17, 11], [-11, 11], [-5, 8], [-6, 14], [-4, 20], [0, 16], [2, 10], [6, 14], [11, 17], [11, 11], [8, 5], [14, 6], [20, 4]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_32.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[2, -19], [4, -19], [3, -17], [6, -18], [9, -16], [7, -15], [10, -15], [12, -13], [10, -12], [13, -12], [15, -10], [14, -10], [16, -9], [17, -7], [12, -6], [7, -4], [7, -1], [8, 0], [8, 0], [10, -1], [9, 1], [10, 0], [12, -1], [12, 1], [13, 0], [16, -2], [16, 0], [18, -1], [18, 1], [21, 0], [20, 1], [23, 0], [23, 2], [26, 1], [25, 3], [29, 2], [28, 4], [32, 3], [31, 6], [35, 6], [34, 7], [37, 7], [35, 9], [38, 8], [41, 9], [39, 10], [42, 10], [44, 12], [41, 13], [45, 13], [47, 15], [42, 16], [46, 17], [48, 19], [44, 19], [40, 20], [42, 20], [46, 23], [48, 25], [42, 23], [36, 22], [38, 23], [39, 26], [39, 30], [37, 26], [28, 22], [30, 24], [30, 26], [28, 24], [21, 22], [11, 13], [8, 12], [6, 12], [4, 12], [2, 14], [0, 15], [-2, 14], [-4, 12], [-6, 12], [-8, 12], [-11, 13], [-21, 22], [-28, 24], [-30, 26], [-30, 24], [-28, 22], [-37, 26], [-39, 30], [-39, 26], [-38, 23], [-36, 22], [-42, 23], [-48, 25], [-46, 23], [-42, 20], [-40, 20], [-44, 19], [-48, 19], [-46, 17], [-42, 16], [-47, 15], [-45, 13], [-41, 13], [-44, 12], [-42, 10], [-39, 10], [-41, 9], [-38, 8], [-35, 9], [-37, 7], [-34, 7], [-35, 6], [-31, 6], [-32, 3], [-28, 4], [-29, 2], [-25, 3], [-26, 1], [-23, 2], [-23, 0], [-20, 1], [-21, 0], [-18, 1], [-18, -1], [-16, 0], [-16, -2], [-13, 0], [-12, 1], [-12, -1], [-10, 0], [-9, 1], [-10, -1], [-8, 0], [-8, 0], [-7, -1], [-7, -4], [-12, -6], [-17, -7], [-16, -9], [-14, -10], [-15, -10], [-13, -12], [-10, -12], [-12, -13], [-10, -15], [-7, -15], [-9, -16], [-6, -18], [-3, -17], [-4, -19], [-2, -19], [0, -18]], 4 | [[-1, 8], [-1, 7], [-2, 8], [-2, 9]], 5 | [[2, 9], [2, 8], [1, 7], [1, 8]], 6 | [[0, 7], [1, 6], [1, 6], [3, 6], [1, 5], [0, 2], [-1, 5], [-3, 6], [-1, 6], [-1, 6]] 7 | ] 8 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_36.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [80, 0], [156, 31], [73, 30], [133, 88], [56, 56], [88, 133], [30, 73], [31, 156], [0, 80], [-31, 156], [-30, 73], [-88, 133], [-56, 56], [-133, 88], [-73, 30], [-156, 31], [-80, 0], [-156, -31], [-73, -30], [-133, -88], [-56, -56], [-88, -133], [-30, -73], [-31, -156], [0, -80], [31, -156], [30, -73], [88, -133], [56, -56], [133, -88], [73, -30], [156, -31] 5 | ] 6 | ] 7 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_37.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [0, 0], [20, 0], [20, 30], [-20, 30], [-20, -20], [40, -20], [40, 50], [-40, 50], [-40, -40], [60, -40], [60, 70], [-60, 70], [-60, -60], [80, -60], [80, 90], [-80, 90], [-80, -80], [100, -80], [100, 110], [-100, 110], [-100, -100], [120, -100], [120, 130], [-120, 130], [-110, 120], [110, 120], [110, -90], [-90, -90], [-90, 100], [90, 100], [90, -70], [-70, -70], [-70, 80], [70, 80], [70, -50], [-50, -50], [-50, 60], [50, 60], [50, -30], [-30, -30], [-30, 40], [30, 40], [30, -10], [-10, -10], [-10, 20], [10, 20], [10, 10], [0, 10] 5 | ] 6 | ] 7 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_38.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [120, 0], [78, 15], [36, 15], [66, 44], [84, 84], [44, 66], [15, 36], [15, 78], [0, 120], [-15, 78], [-15, 36], [-44, 66], [-84, 84], [-66, 44], [-36, 15], [-78, 15], [-120, 0], [-78, -15], [-36, -15], [-66, -44], [-84, -84], [-44, -66], [-15, -36], [-15, -78], [0, -120], [15, -78], [15, -36], [44, -66], [84, -84], [66, -44], [36, -15], [78, -15] 5 | ], 6 | [ 7 | [60, 0], [39, -7], [18, -7], [33, -22], [42, -42], [22, -33], [7, -18], [7, -39], [0, -60], [-7, -39], [-7, -18], [-22, -33], [-42, -42], [-33, -22], [-18, -7], [-39, -7], [-60, 0], [-39, 7], [-18, 7], [-33, 22], [-42, 42], [-22, 33], [-7, 18], [-7, 39], [0, 60], [7, 39], [7, 18], [22, 33], [42, 42], [33, 22], [18, 7], [39, 7] 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_39.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [104, 0], [96, 9], [78, 15], [60, 18], [51, 21], [55, 29], [66, 44], [74, 61], [73, 73], [61, 74], [44, 66], [29, 55], [21, 51], [18, 60], [15, 78], [9, 96], [0, 104], [-9, 96], [-15, 78], [-18, 60], [-21, 51], [-29, 55], [-44, 66], [-61, 74], [-73, 73], [-74, 61], [-66, 44], [-55, 29], [-51, 21], [-60, 18], [-78, 15], [-96, 9], [-104, 0], [-96, -9], [-78, -15], [-60, -18], [-51, -21], [-55, -29], [-66, -44], [-74, -61], [-73, -73], [-61, -74], [-44, -66], [-29, -55], [-21, -51], [-18, -60], [-15, -78], [-9, -96], [0, -104], [9, -96], [15, -78], [18, -60], [21, -51], [29, -55], [44, -66], [61, -74], [73, -73], [74, -61], [66, -44], [55, -29], [51, -21], [60, -18], [78, -15], [96, -9] 5 | ], 6 | [ 7 | [52, 0], [48, -4], [39, -7], [30, -9], [25, -10], [27, -14], [33, -22], [37, -30], [36, -36], [30, -37], [22, -33], [14, -27], [10, -25], [9, -30], [7, -39], [4, -48], [0, -52], [-4, -48], [-7, -39], [-9, -30], [-10, -25], [-14, -27], [-22, -33], [-30, -37], [-36, -36], [-37, -30], [-33, -22], [-27, -14], [-25, -10], [-30, -9], [-39, -7], [-48, -4], [-52, 0], [-48, 4], [-39, 7], [-30, 9], [-25, 10], [-27, 14], [-33, 22], [-37, 30], [-36, 36], [-30, 37], [-22, 33], [-14, 27], [-10, 25], [-9, 30], [-7, 39], [-4, 48], [0, 52], [4, 48], [7, 39], [9, 30], [10, 25], [14, 27], [22, 33], [30, 37], [36, 36], [37, 30], [33, 22], [27, 14], [25, 10], [30, 9], [39, 7], [48, 4] 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths" : [ 3 | [[-15, 0], [0, -15], [15, 0], [0, 15]], 4 | [[-5, 0], [0, 5], [5, 0], [0, -5]]] 5 | } 6 | -------------------------------------------------------------------------------- /editor/tests/triangle/test_40.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [104, 0], [102, 5], [96, 9], [88, 13], [78, 15], [68, 17], [60, 18], [54, 19], [51, 21], [52, 24], [55, 29], [60, 36], [66, 44], [71, 53], [74, 61], [75, 68], [73, 73], [68, 75], [61, 74], [53, 71], [44, 66], [36, 60], [29, 55], [24, 52], [21, 51], [19, 54], [18, 60], [17, 68], [15, 78], [13, 88], [9, 96], [5, 102], [0, 104], [-5, 102], [-9, 96], [-13, 88], [-15, 78], [-17, 68], [-18, 60], [-19, 54], [-21, 51], [-24, 52], [-29, 55], [-36, 60], [-44, 66], [-53, 71], [-61, 74], [-68, 75], [-73, 73], [-75, 68], [-74, 61], [-71, 53], [-66, 44], [-60, 36], [-55, 29], [-52, 24], [-51, 21], [-54, 19], [-60, 18], [-68, 17], [-78, 15], [-88, 13], [-96, 9], [-102, 5], [-104, 0], [-102, -5], [-96, -9], [-88, -13], [-78, -15], [-68, -17], [-60, -18], [-54, -19], [-51, -21], [-52, -24], [-55, -29], [-60, -36], [-66, -44], [-71, -53], [-74, -61], [-75, -68], [-73, -73], [-68, -75], [-61, -74], [-53, -71], [-44, -66], [-36, -60], [-29, -55], [-24, -52], [-21, -51], [-19, -54], [-18, -60], [-17, -68], [-15, -78], [-13, -88], [-9, -96], [-5, -102], [0, -104], [5, -102], [9, -96], [13, -88], [15, -78], [17, -68], [18, -60], [19, -54], [21, -51], [24, -52], [29, -55], [36, -60], [44, -66], [53, -71], [61, -74], [68, -75], [73, -73], [75, -68], [74, -61], [71, -53], [66, -44], [60, -36], [55, -29], [52, -24], [51, -21], [54, -19], [60, -18], [68, -17], [78, -15], [88, -13], [96, -9], [102, -5]], 5 | [ 6 | [13, 50], [6, 47], [9, 40], [2, 43], [0, 37], [-2, 43], [-9, 40], [-6, 47], [-13, 49], [-6, 52], [-9, 59], [-2, 56], [0, 63], [2, 56], [9, 59], [6, 52] 7 | ], 8 | [ 9 | [48, 35], [41, 32], [44, 26], [38, 28], [35, 22], [32, 28], [26, 26], [28, 32], [22, 35], [28, 38], [26, 44], [32, 41], [35, 48], [38, 41], [44, 44], [41, 38] 10 | ], 11 | [ 12 | [63, 0], [56, -2], [59, -9], [52, -6], [50, -13], [47, -6], [40, -9], [43, -2], [37, 0], [43, 2], [40, 9], [47, 6], [50, 12], [52, 6], [59, 9], [56, 2] 13 | ], 14 | [ 15 | [48, -35], [41, -38], [44, -44], [38, -41], [35, -48], [32, -41], [26, -44], [28, -38], [22, -35], [28, -32], [26, -26], [32, -28], [35, -22], [38, -28], [44, -26], [41, -32] 16 | ], 17 | [ 18 | [12, -50], [6, -52], [9, -59], [2, -56], [0, -63], [-2, -56], [-9, -59], [-6, -52], [-13, -50], [-6, -47], [-9, -40], [-2, -43], [0, -37], [2, -43], [9, -40], [6, -47] 19 | ], 20 | [ 21 | [-22, -35], [-28, -38], [-26, -44], [-32, -41], [-35, -48], [-38, -41], [-44, -44], [-41, -38], [-48, -35], [-41, -32], [-44, -26], [-38, -28], [-35, -22], [-32, -28], [-26, -26], [-28, -32] 22 | ], 23 | [ 24 | [-37, 0], [-43, -2], [-40, -9], [-47, -6], [-50, -12], [-52, -6], [-59, -9], [-56, -2], [-63, 0], [-56, 2], [-59, 9], [-52, 6], [-50, 13], [-47, 6], [-40, 9], [-43, 2] 25 | ], 26 | [ 27 | [-22, 35], [-28, 32], [-26, 26], [-32, 28], [-35, 22], [-38, 28], [-44, 26], [-41, 32], [-48, 35], [-41, 38], [-44, 44], [-38, 41], [-35, 48], [-32, 41], [-26, 44], [-28, 38] 28 | ] 29 | ] 30 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths" : [ 3 | [[-20, -20], [20, -20], [20, 20], [-20, 20]], 4 | [[-10, -10], [-10, 10], [10, 10], [10, -10]]] 5 | } 6 | -------------------------------------------------------------------------------- /editor/tests/triangle/test_6.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-10, -5], [-5, -10], [5, -10], [10, -5], [10, 5], [5, 10], [-5, 10], [-10, 5]], 4 | [[-5, 0], [0, 5], [5, 0], [0, -5]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_7.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [[-10, -10], [0, -5], [10, -10], [10, 10], [0, 5], [-10, 10]], 4 | [[-5, 0], [0, 5], [5, 0], [0, -5]] 5 | ] 6 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_8.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [0, 0], [-5, -5], [-5, -10], [5, -10], [5, -5], [0, 0], [5, 5], [5, 10], [-5, 10], [-5, 5] 5 | ] 6 | ] 7 | } -------------------------------------------------------------------------------- /editor/tests/triangle/test_9.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ 3 | [ 4 | [ 5 | -10, 6 | -5 7 | ], 8 | [ 9 | -10, 10 | 5 11 | ], 12 | [ 13 | -5, 14 | 5 15 | ], 16 | [ 17 | 0, 18 | 0 19 | ], 20 | [ 21 | 5, 22 | 5 23 | ], 24 | [ 25 | 10, 26 | 5 27 | ], 28 | [ 29 | 10, 30 | -5 31 | ], 32 | [ 33 | 5, 34 | -5 35 | ], 36 | [ 37 | 0, 38 | 0 39 | ], 40 | [ 41 | -5, 42 | -5 43 | ] 44 | ] 45 | ] 46 | } -------------------------------------------------------------------------------- /iTriangle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "i_triangle" 3 | version = "0.36.1" 4 | edition = "2021" 5 | authors = ["Nail Sharipov "] 6 | description = "Polygon Triangulation Library: Efficient Delaunay Triangulation for Complex Shapes." 7 | license = "MIT" 8 | repository = "https://github.com/iShape-Rust/iTriangle" 9 | 10 | keywords = ["triangulation", "delaunay", "earcut", "monotone", "convex" ] 11 | 12 | categories = ["algorithms", "graphics", "game-development", "science::geo", "no-std"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [features] 17 | glam = ["i_overlay/glam"] 18 | serde = ["dep:serde", "i_overlay/serde"] 19 | 20 | [dependencies] 21 | serde = { version = "^1.0", default-features = false, features = ["derive"], optional = true } 22 | 23 | #i_overlay = { path = "../../iOverlay/iOverlay" } 24 | #i_tree = { path = "../../iTree" } 25 | #i_key_sort = { path = "../../iKeySort" } 26 | 27 | i_overlay = "~4.0.2" 28 | i_tree = "~0.16.0" 29 | i_key_sort = "~0.6.0" 30 | 31 | 32 | 33 | [dev-dependencies] 34 | rand = { version = "~0.9", features = ["alloc"] } -------------------------------------------------------------------------------- /iTriangle/README.md: -------------------------------------------------------------------------------- 1 | # iTriangle 2 | 3 | 4 | [![crates.io version](https://img.shields.io/crates/v/i_triangle.svg)](https://crates.io/crates/i_triangle) 5 | ![Stability](https://img.shields.io/badge/tested-10⁹+_random_cases-green) 6 | [![docs.rs docs](https://docs.rs/i_triangle/badge.svg)](https://docs.rs/i_triangle) 7 | 8 | A fast, stable, and robust 2d triangulation library for rust — tested on over **10⁹ randomized inputs**. 9 | 10 | *For detailed performance benchmarks, check out the* [Performance Comparison](https://ishape-rust.github.io/iShape-js/triangle/performance/performance.html) 11 | 12 | 13 | 14 | ## Delaunay 15 | 16 | 17 | ## Convex polygons 18 | 19 | 20 | ## Steiner points 21 | 22 | 23 | ## Tessellation 24 | 25 | 26 | ## Centroid net 27 | 28 | 29 | ## Features 30 | 31 | - **Raw Triangulation** - Fast and simple triangulation of polygons with or without holes. 32 | - **Delaunay Triangulation** - Efficient and robust implementation for generating Delaunay triangulations. 33 | - **Self-Intersection Handling** – Fully supports self-intersecting polygons with automatic resolution. 34 | - **Adaptive Tessellation** - Refine Delaunay triangles using circumcenters for better shape quality. 35 | - **Convex Decomposition** - Convert triangulation into convex polygons. 36 | - **Centroidal Polygon Net**: Build per-vertex dual polygons using triangle centers and edge midpoints. 37 | - **Steiner Points**: Add custom inner points to influence triangulation. 38 | - **GPU-Friendly Layout**: Triangles and vertices are naturally ordered by X due to the sweep-line algorithm, improving cache locality for rendering. 39 | 40 | ## Reliability 41 | 42 | - Extremely Stable: The core triangulation and Delaunay algorithms have been tested against over 1 billion randomized polygon samples. 43 | - Uses pure integer math to avoid floating-point precision issues. 44 | - Designed for use in CAD, EDA, game engines, and any application where robustness is critical. 45 | 46 | ## Demo 47 | 48 | - [Triangulation](https://ishape-rust.github.io/iShape-js/triangle/triangulation.html) 49 | - [Tessellation](https://ishape-rust.github.io/iShape-js/triangle/tessellation.html) 50 | 51 | ## Documentation 52 | - [Delaunay](https://ishape-rust.github.io/iShape-js/triangle/delaunay.html) 53 | 54 | ## Getting Started 55 | 56 | Add to your `Cargo.toml`: 57 | ``` 58 | [dependencies] 59 | i_triangle = "^0.36.0" 60 | ``` 61 | 62 | --- 63 | 64 | ## Example: Single Shape Triangulation 65 | 66 | 67 | 68 | ```rust 69 | use i_triangle::float::triangulatable::Triangulatable; 70 | use i_triangle::float::triangulation::Triangulation; 71 | 72 | let shape = vec![ 73 | vec![ 74 | // body 75 | [0.0, 20.0], // 0 76 | [-10.0, 8.0], // 1 77 | [-7.0, 6.0], // 2 78 | [-6.0, 2.0], // 3 79 | [-8.0, -2.0], // 4 80 | [-13.0, -4.0], // 5 81 | [-16.0, -3.0], // 6 82 | [-18.0, 0.0], // 7 83 | [-25.0, -7.0], // 8 84 | [-14.0, -15.0], // 9 85 | [0.0, -18.0], // 10 86 | [14.0, -15.0], // 11 87 | [26.0, -7.0], // 12 88 | [17.0, 1.0], // 13 89 | [13.0, -1.0], // 14 90 | [9.0, 1.0], // 15 91 | [7.0, 6.0], // 16 92 | [8.0, 10.0], // 17 93 | ], 94 | vec![ 95 | // hole 96 | [2.0, 0.0], // 0 97 | [5.0, -2.0], // 1 98 | [7.0, -5.0], // 2 99 | [5.0, -9.0], // 3 100 | [2.0, -11.0], // 4 101 | [-2.0, -9.0], // 5 102 | [-4.0, -5.0], // 6 103 | [-2.0, -2.0], // 7 104 | ], 105 | ]; 106 | 107 | let triangulation = shape.triangulate().to_triangulation::(); 108 | 109 | println!("points: {:?}", triangulation.points); 110 | println!("indices: {:?}", triangulation.indices); 111 | 112 | let delaunay_triangulation: Triangulation<[f64; 2], u16> = 113 | shape.triangulate().into_delaunay().to_triangulation(); 114 | 115 | println!("points: {:?}", delaunay_triangulation.points); 116 | println!("indices: {:?}", delaunay_triangulation.indices); 117 | 118 | let convex_polygons = shape.triangulate().into_delaunay().to_convex_polygons(); 119 | 120 | println!("convex polygons: {:?}", convex_polygons); 121 | 122 | let tessellation: Triangulation<[f64; 2], u16> = shape 123 | .triangulate() 124 | .into_delaunay() 125 | .refine_with_circumcenters_by_obtuse_angle(0.0) 126 | .to_triangulation(); 127 | 128 | println!("points: {:?}", tessellation.points); 129 | println!("indices: {:?}", tessellation.indices); 130 | 131 | let centroids = shape 132 | .triangulate() 133 | .into_delaunay() 134 | .refine_with_circumcenters_by_obtuse_angle(0.0) 135 | .to_centroid_net(0.0); 136 | 137 | println!("centroids: {:?}", centroids); 138 | ``` 139 | 140 | > 💡 Output: Triangle indices and vertices, where all triangles oriented in a **counter-clockwise** direction.. 141 | 142 | --- 143 | 144 | ## Example: Triangulating Multiple Shapes Efficiently 145 | 146 | If you need triangulate many shapes it's more efficient way is to use Triangulator 147 | ```rust 148 | let contours = random_contours(100); 149 | 150 | let mut triangulator = Triangulator::::default(); 151 | 152 | // Enable Delaunay refinement 153 | triangulator.delaunay(true); 154 | 155 | // Use fast Earcut solver for contours with ≤ 64 points 156 | triangulator.earcut(true); 157 | 158 | let mut triangulation = Triangulation::with_capacity(100); 159 | 160 | for contour in contours.iter() { 161 | // Triangulate using self-intersection resolver 162 | triangulator.triangulate_into(contour, &mut triangulation); 163 | 164 | println!("points: {:?}", triangulation.points); 165 | println!("indices: {:?}", triangulation.indices); 166 | } 167 | ``` 168 | -------------------------------------------------------------------------------- /iTriangle/readme/triangulation_process.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iShape-Rust/iTriangle/18d46589ef3b45f5f0112af3396e416601fd525b/iTriangle/readme/triangulation_process.gif -------------------------------------------------------------------------------- /iTriangle/src/advanced/bitset.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec; 2 | use alloc::vec::Vec; 3 | 4 | pub struct IndexBitSet { 5 | chunks: Vec, 6 | } 7 | 8 | impl IndexBitSet { 9 | 10 | #[inline] 11 | pub fn with_size(count: usize) -> Self { 12 | let len = count >> 6; 13 | Self { 14 | chunks: vec![0; len], 15 | } 16 | } 17 | 18 | #[inline] 19 | pub fn clear_and_resize(&mut self, count: usize) { 20 | self.chunks.clear(); 21 | let new_len = count >> 6; 22 | self.chunks.resize(new_len, 0); 23 | } 24 | 25 | #[inline] 26 | pub fn insert(&mut self, index: usize) { 27 | let chunk_index = index >> 6; 28 | if chunk_index >= self.chunks.len() { 29 | self.chunks.resize(chunk_index + 1, 0); 30 | } 31 | let bit_index = 63 & index; 32 | self.chunks[chunk_index] |= 1 << bit_index; 33 | } 34 | 35 | #[inline] 36 | pub fn remove(&mut self, index: usize) { 37 | let chunk_index = index >> 6; 38 | if chunk_index < self.chunks.len() { 39 | let bit_index = 63 & index; 40 | self.chunks[chunk_index] &= !(1 << bit_index); 41 | } 42 | } 43 | 44 | #[inline] 45 | pub fn read_and_clean(&mut self, indices: &mut Vec) { 46 | let count = self.chunks.iter().map(|ch| ch.count_ones() as usize).sum::(); 47 | indices.clear(); 48 | if count == 0 { 49 | return; 50 | } 51 | 52 | let additional = count.saturating_sub(indices.capacity()); 53 | if additional > 0 { 54 | indices.reserve(additional); 55 | } 56 | 57 | for (chunk_index, chunk) in self.chunks.iter_mut().enumerate() { 58 | if *chunk == 0 { 59 | continue; 60 | } 61 | let mut bits = *chunk; 62 | while bits != 0 { 63 | let bit = bits.trailing_zeros() as usize; 64 | indices.push((chunk_index << 6) + bit); 65 | bits &= !(1 << bit); 66 | } 67 | *chunk = 0; 68 | } 69 | } 70 | 71 | #[inline] 72 | pub fn is_empty(&self) -> bool { 73 | !self.chunks.iter().any(|&ch|ch != 0) 74 | } 75 | 76 | } 77 | 78 | impl Default for IndexBitSet { 79 | fn default() -> Self { 80 | Self { 81 | chunks: vec![0; 8], 82 | } 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | extern crate std; 89 | 90 | use alloc::vec; 91 | use super::*; 92 | use std::collections::HashSet; 93 | use rand::{Rng, SeedableRng}; 94 | use rand::rngs::StdRng; 95 | 96 | #[test] 97 | fn test_empty() { 98 | let mut set = IndexBitSet::default(); 99 | let mut out = Vec::new(); 100 | set.read_and_clean(&mut out); 101 | assert!(out.is_empty()); 102 | } 103 | 104 | #[test] 105 | fn test_single_value() { 106 | let mut set = IndexBitSet::default(); 107 | set.insert(42); 108 | let mut out = Vec::new(); 109 | set.read_and_clean(&mut out); 110 | assert_eq!(out, vec![42]); 111 | } 112 | 113 | #[test] 114 | fn test_duplicates() { 115 | let mut set = IndexBitSet::default(); 116 | set.insert(10); 117 | set.insert(10); 118 | set.insert(10); 119 | let mut out = Vec::new(); 120 | set.read_and_clean(&mut out); 121 | assert_eq!(out, vec![10]); 122 | } 123 | 124 | #[test] 125 | fn test_ordered_values() { 126 | let mut set = IndexBitSet::default(); 127 | for i in 0..100 { 128 | set.insert(i); 129 | } 130 | let mut out = Vec::new(); 131 | set.read_and_clean(&mut out); 132 | let expected: Vec<_> = (0..100).collect(); 133 | assert_eq!(out, expected); 134 | } 135 | 136 | #[test] 137 | fn test_random_comparison_with_hashset() { 138 | let mut rng = StdRng::seed_from_u64(12345); 139 | let mut set = IndexBitSet::default(); 140 | let mut hash = HashSet::new(); 141 | 142 | for _ in 0..10_000 { 143 | let v = rng.random_range(0..10_000); 144 | set.insert(v); 145 | hash.insert(v); 146 | } 147 | 148 | let mut out = Vec::new(); 149 | set.read_and_clean(&mut out); 150 | let set_from_index_set: HashSet<_> = out.into_iter().collect(); 151 | 152 | assert_eq!(set_from_index_set, hash); 153 | } 154 | 155 | #[test] 156 | fn test_reuse_and_clean() { 157 | let mut set = IndexBitSet::default(); 158 | let mut out = Vec::new(); 159 | 160 | set.insert(1); 161 | set.insert(2); 162 | set.read_and_clean(&mut out); 163 | assert_eq!(out.len(), 2); 164 | 165 | // Second call should be empty now 166 | set.read_and_clean(&mut out); 167 | assert!(out.is_empty()); 168 | 169 | // Reuse 170 | set.insert(5); 171 | set.insert(10); 172 | set.read_and_clean(&mut out); 173 | let out_set: HashSet<_> = out.iter().cloned().collect(); 174 | assert_eq!(out_set, HashSet::from([5, 10])); 175 | } 176 | 177 | #[test] 178 | fn test_remove() { 179 | let mut set = IndexBitSet::default(); 180 | set.insert(15); 181 | set.insert(100); 182 | set.insert(200); 183 | 184 | set.remove(100); 185 | let mut out = Vec::new(); 186 | set.read_and_clean(&mut out); 187 | 188 | let expected = vec![15, 200]; 189 | assert_eq!(out, expected); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /iTriangle/src/advanced/buffer.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use crate::advanced::bitset::IndexBitSet; 3 | 4 | #[derive(Default)] 5 | pub struct DelaunayBuffer { 6 | pub(crate) bitset: Option, 7 | pub(crate) indices: Option> 8 | } 9 | 10 | impl DelaunayBuffer { 11 | #[inline] 12 | pub fn new() -> Self { 13 | Self { bitset: None, indices: None } 14 | } 15 | } -------------------------------------------------------------------------------- /iTriangle/src/advanced/centroid.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec; 2 | use alloc::vec::Vec; 3 | use crate::advanced::delaunay::IntDelaunay; 4 | use crate::geom::triangle::IntTriangle; 5 | use i_overlay::i_float::int::point::IntPoint; 6 | use i_overlay::i_shape::int::area::Area; 7 | use i_overlay::i_shape::int::shape::IntContour; 8 | 9 | impl IntDelaunay { 10 | 11 | /// Constructs a centroid-based polygonal net from the Delaunay triangulation. 12 | /// Each polygon surrounds a vertex using adjacent triangle centers and edge midpoints. 13 | /// 14 | /// This is similar to a centroidal Voronoi diagram. 15 | /// 16 | /// # Parameters 17 | /// - `min_area`: minimum polygon area to emit 18 | /// 19 | /// # Returns 20 | /// A list of `IntContour` objects forming closed or convex polygonal regions. 21 | pub fn centroid_net(&self, min_area: u64) -> Vec { 22 | let two_area = min_area << 1; 23 | let n = self.triangles.len(); 24 | 25 | let mut visited_index = vec![false; self.points.len()]; 26 | let mut result = Vec::with_capacity(self.points.len() / 4); 27 | 28 | for triangle_index in 0..n { 29 | for v in self.triangles[triangle_index].vertices.iter() { 30 | if visited_index[v.index] { 31 | continue; 32 | } 33 | visited_index[v.index] = true; 34 | 35 | // go in counter-clockwise direction first 36 | 37 | let mut contour = IntContour::with_capacity(16); 38 | let mut t = &self.triangles[triangle_index]; 39 | let (mut next_index, mut mid) = t.left_neighbor_and_mid_edge(v.index); 40 | contour.push(t.center()); 41 | contour.push(mid); 42 | while next_index < self.triangles.len() && next_index != triangle_index { 43 | t = &self.triangles[next_index]; 44 | (next_index, mid) = t.left_neighbor_and_mid_edge(v.index); 45 | contour.push(t.center()); 46 | contour.push(mid); 47 | } 48 | 49 | if next_index == triangle_index { 50 | // it's a closed contour 51 | result.add_area_check(contour, two_area); 52 | continue; 53 | } 54 | 55 | // collect other part in clockwise direction 56 | 57 | let mut start_contour = Vec::with_capacity(8); 58 | t = &self.triangles[triangle_index]; 59 | let (mut next_index, mut mid) = t.right_neighbor_and_mid_edge(v.index); 60 | start_contour.push(mid); 61 | while next_index < self.triangles.len() { 62 | t = &self.triangles[next_index]; 63 | (next_index, mid) = t.right_neighbor_and_mid_edge(v.index); 64 | start_contour.push(t.center()); 65 | start_contour.push(mid); 66 | } 67 | 68 | start_contour.reverse(); // make it counter-clockwise 69 | start_contour.append(&mut contour); 70 | start_contour.push(v.point); 71 | 72 | result.add_area_check(start_contour, two_area); 73 | } 74 | } 75 | 76 | result 77 | } 78 | } 79 | 80 | trait SafeAdd { 81 | fn add_area_check(&mut self, contour: IntContour, two_area: u64); 82 | } 83 | 84 | impl SafeAdd for Vec { 85 | fn add_area_check(&mut self, contour: IntContour, two_area: u64) { 86 | if two_area == 0 || contour.area_two().unsigned_abs() > two_area { 87 | self.push(contour); 88 | } 89 | } 90 | } 91 | 92 | impl IntTriangle { 93 | #[inline] 94 | fn right_neighbor_and_mid_edge(&self, vertex_index: usize) -> (usize, IntPoint) { 95 | if self.vertices[0].index == vertex_index { 96 | let neighbor = self.neighbors[2]; 97 | let mid = middle(self.vertices[0].point, self.vertices[1].point); 98 | (neighbor, mid) 99 | } else if self.vertices[1].index == vertex_index { 100 | let neighbor = self.neighbors[0]; 101 | let mid = middle(self.vertices[1].point, self.vertices[2].point); 102 | (neighbor, mid) 103 | } else { 104 | let neighbor = self.neighbors[1]; 105 | let mid = middle(self.vertices[2].point, self.vertices[0].point); 106 | (neighbor, mid) 107 | } 108 | } 109 | 110 | #[inline] 111 | fn left_neighbor_and_mid_edge(&self, vertex_index: usize) -> (usize, IntPoint) { 112 | if self.vertices[0].index == vertex_index { 113 | let neighbor = self.neighbors[1]; 114 | let mid = middle(self.vertices[0].point, self.vertices[2].point); 115 | (neighbor, mid) 116 | } else if self.vertices[1].index == vertex_index { 117 | let neighbor = self.neighbors[2]; 118 | let mid = middle(self.vertices[1].point, self.vertices[0].point); 119 | (neighbor, mid) 120 | } else { 121 | let neighbor = self.neighbors[0]; 122 | let mid = middle(self.vertices[2].point, self.vertices[1].point); 123 | (neighbor, mid) 124 | } 125 | } 126 | 127 | #[inline] 128 | fn center(&self) -> IntPoint { 129 | let a = self.vertices[0].point; 130 | let b = self.vertices[1].point; 131 | let c = self.vertices[2].point; 132 | 133 | let x = a.x as i64 + b.x as i64 + c.x as i64; 134 | let y = a.y as i64 + b.y as i64 + c.y as i64; 135 | 136 | IntPoint::new((x / 3) as i32, (y / 3) as i32) 137 | } 138 | } 139 | 140 | #[inline] 141 | fn middle(a: IntPoint, b: IntPoint) -> IntPoint { 142 | let x = a.x as i64 + b.x as i64; 143 | let y = a.y as i64 + b.y as i64; 144 | IntPoint::new((x / 2) as i32, (y / 2) as i32) 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use alloc::vec; 150 | use i_overlay::i_float::int::point::IntPoint; 151 | use crate::int::triangulatable::IntTriangulatable; 152 | 153 | #[test] 154 | fn test_0() { 155 | let contour = vec![ 156 | IntPoint::new(0, 0), 157 | IntPoint::new(10, 0), 158 | IntPoint::new(10, 10), 159 | IntPoint::new(0, 10), 160 | ]; 161 | 162 | let centroids = contour.triangulate_with_steiner_points(&[IntPoint::new(5, 5)]) 163 | .into_delaunay().centroid_net(0); 164 | assert_eq!(centroids.len(), 5); 165 | } 166 | } -------------------------------------------------------------------------------- /iTriangle/src/advanced/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bitset; 2 | pub mod buffer; 3 | pub mod centroid; 4 | pub mod convex; 5 | pub mod delaunay; 6 | pub mod triangulation; 7 | -------------------------------------------------------------------------------- /iTriangle/src/advanced/triangulation.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use i_overlay::i_float::int::point::IntPoint; 3 | use crate::advanced::delaunay::IntDelaunay; 4 | use crate::int::triangulation::{IndexType, IntTriangulation}; 5 | 6 | impl IntDelaunay { 7 | #[inline] 8 | pub fn points(&self) -> &Vec { 9 | &self.points 10 | } 11 | 12 | #[inline] 13 | pub fn triangle_indices(&self) -> Vec { 14 | let mut result = Vec::with_capacity(3 * self.triangles.len()); 15 | for t in &self.triangles { 16 | let v = &t.vertices; 17 | let i0 = I::try_from(v[0].index).unwrap_or(I::ZERO); 18 | let i1 = I::try_from(v[1].index).unwrap_or(I::ZERO); 19 | let i2 = I::try_from(v[2].index).unwrap_or(I::ZERO); 20 | 21 | result.extend_from_slice(&[i0, i1, i2]); 22 | } 23 | result 24 | } 25 | 26 | #[inline] 27 | pub fn into_triangulation(self) -> IntTriangulation { 28 | IntTriangulation { 29 | indices: self.triangle_indices(), 30 | points: self.points, 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /iTriangle/src/float/builder.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use crate::float::triangulation::Triangulation; 3 | use crate::int::triangulation::IndexType; 4 | 5 | pub struct TriangulationBuilder { 6 | points: Vec

, 7 | indices: Vec, 8 | } 9 | 10 | impl TriangulationBuilder { 11 | /// Appends another `Triangulation` to the builder. 12 | /// 13 | /// This method correctly offsets the indices of the appended triangulation 14 | /// based on the current number of points in the builder. 15 | pub fn append(&mut self, triangulation: Triangulation) -> &mut Self { 16 | let points_count = self.points.len() + triangulation.points.len(); 17 | if points_count > I::MAX { 18 | panic!( 19 | "Index type `{}` cannot hold {} points", 20 | core::any::type_name::(), 21 | points_count 22 | ); 23 | } 24 | 25 | let offset = I::try_from(self.points.len()).unwrap_or(I::ZERO); 26 | self.points.extend(triangulation.points); 27 | self.indices 28 | .extend(triangulation.indices.iter().map(|&i|i.add(offset))); 29 | self 30 | } 31 | 32 | /// Builds and returns the final `Triangulation`. 33 | pub fn build(self) -> Triangulation { 34 | Triangulation { 35 | points: self.points, 36 | indices: self.indices, 37 | } 38 | } 39 | } 40 | 41 | impl Default for TriangulationBuilder { 42 | fn default() -> Self { 43 | Self { 44 | points: Vec::new(), 45 | indices: Vec::new(), 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /iTriangle/src/float/centroid_net.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use crate::float::delaunay::Delaunay; 3 | use i_overlay::i_float::float::compatible::FloatPointCompatible; 4 | use i_overlay::i_float::float::number::FloatNumber; 5 | use i_overlay::i_shape::base::data::Contour; 6 | use i_overlay::i_shape::float::adapter::ShapeToFloat; 7 | 8 | impl, T: FloatNumber> Delaunay { 9 | #[inline] 10 | pub fn to_centroid_net(&self, min_area: T) -> Vec> { 11 | let int_area = self.adapter.sqr_float_to_int(min_area); 12 | self.delaunay.centroid_net(int_area).to_float(&self.adapter) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /iTriangle/src/float/circumcenter.rs: -------------------------------------------------------------------------------- 1 | use crate::float::delaunay::Delaunay; 2 | use i_overlay::i_float::float::compatible::FloatPointCompatible; 3 | use i_overlay::i_float::float::number::FloatNumber; 4 | 5 | impl, T: FloatNumber> Delaunay { 6 | #[inline] 7 | pub fn refine_with_circumcenters(mut self, min_area: T) -> Self { 8 | self.refine_with_circumcenters_mut(min_area); 9 | self 10 | } 11 | 12 | #[inline] 13 | pub fn refine_with_circumcenters_by_obtuse_angle(mut self, min_area: T) -> Self { 14 | self.refine_with_circumcenters_by_obtuse_angle_mut(min_area); 15 | self 16 | } 17 | 18 | #[inline] 19 | pub fn refine_with_circumcenters_mut(&mut self, min_area: T) { 20 | let int_area = self.adapter.sqr_float_to_int(min_area); 21 | self.delaunay.refine_with_circumcenters_mut(int_area); 22 | } 23 | 24 | #[inline] 25 | pub fn refine_with_circumcenters_by_obtuse_angle_mut(&mut self, min_area: T) { 26 | let int_area = self.adapter.sqr_float_to_int(min_area); 27 | self.delaunay.refine_with_circumcenters_by_obtuse_angle_mut(int_area); 28 | } 29 | } -------------------------------------------------------------------------------- /iTriangle/src/float/convex.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use crate::float::delaunay::Delaunay; 3 | use i_overlay::i_float::float::compatible::FloatPointCompatible; 4 | use i_overlay::i_float::float::number::FloatNumber; 5 | use i_overlay::i_shape::base::data::Contour; 6 | use i_overlay::i_shape::float::adapter::ShapeToFloat; 7 | 8 | impl, T: FloatNumber> Delaunay { 9 | /// Groups triangles into non-overlapping convex polygons in counter-clockwise order. 10 | /// 11 | /// Returns a list of float-based [`Contour

`]s. 12 | #[inline] 13 | pub fn to_convex_polygons(&self) -> Vec> { 14 | self.delaunay.to_convex_polygons().to_float(&self.adapter) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iTriangle/src/float/custom.rs: -------------------------------------------------------------------------------- 1 | use crate::int::custom::IntCustomTriangulatable; 2 | use i_overlay::i_float::adapter::FloatPointAdapter; 3 | use i_overlay::i_float::float::compatible::FloatPointCompatible; 4 | use i_overlay::i_float::float::number::FloatNumber; 5 | use i_overlay::i_float::float::rect::FloatRect; 6 | use i_overlay::i_shape::base::data::{Contour, Shape}; 7 | use i_overlay::i_shape::float::adapter::{PathToInt, ShapeToInt, ShapesToInt}; 8 | use i_overlay::i_shape::float::rect::RectInit; 9 | use crate::float::triangulation::RawTriangulation; 10 | use crate::int::triangulation::RawIntTriangulation; 11 | use crate::int::validation::Validation; 12 | 13 | /// A trait for triangulating float geometry with user-defined validation rules. 14 | /// 15 | /// Accepts a custom [`Validation`] object for tuning fill rule, min area, etc. 16 | pub trait CustomTriangulatable, T: FloatNumber> { 17 | 18 | /// Performs triangulation using the specified [`Validation`] settings. 19 | fn custom_triangulate(&self, validation: Validation) -> RawTriangulation; 20 | 21 | /// Performs triangulation with Steiner points and a custom [`Validation`] config. 22 | fn custom_triangulate_with_steiner_points( 23 | &self, 24 | points: &[P], 25 | validation: Validation, 26 | ) -> RawTriangulation; 27 | } 28 | 29 | impl, T: FloatNumber> CustomTriangulatable for Contour

{ 30 | fn custom_triangulate(&self, validation: Validation) -> RawTriangulation { 31 | if let Some(rect) = FloatRect::with_path(self) { 32 | let adapter = FloatPointAdapter::::new(rect); 33 | let raw = self.to_int(&adapter).custom_triangulate(validation); 34 | RawTriangulation { raw, adapter } 35 | } else { 36 | RawTriangulation { 37 | raw: RawIntTriangulation::default(), 38 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 39 | } 40 | } 41 | } 42 | 43 | fn custom_triangulate_with_steiner_points( 44 | &self, 45 | points: &[P], 46 | validation: Validation, 47 | ) -> RawTriangulation { 48 | if let Some(rect) = FloatRect::with_path(self) { 49 | let adapter = FloatPointAdapter::::new(rect); 50 | let float_points = points.to_int(&adapter); 51 | let raw = self 52 | .to_int(&adapter) 53 | .custom_triangulate_with_steiner_points(&float_points, validation); 54 | RawTriangulation { raw, adapter } 55 | } else { 56 | RawTriangulation { 57 | raw: RawIntTriangulation::default(), 58 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 59 | } 60 | } 61 | } 62 | } 63 | 64 | impl, T: FloatNumber> CustomTriangulatable for [Contour

] { 65 | fn custom_triangulate(&self, validation: Validation) -> RawTriangulation { 66 | if let Some(rect) = FloatRect::with_paths(self) { 67 | let adapter = FloatPointAdapter::::new(rect); 68 | let raw = self.to_int(&adapter).custom_triangulate(validation); 69 | RawTriangulation { raw, adapter } 70 | } else { 71 | RawTriangulation { 72 | raw: RawIntTriangulation::default(), 73 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 74 | } 75 | } 76 | } 77 | 78 | fn custom_triangulate_with_steiner_points( 79 | &self, 80 | points: &[P], 81 | validation: Validation, 82 | ) -> RawTriangulation { 83 | if let Some(rect) = FloatRect::with_paths(self) { 84 | let adapter = FloatPointAdapter::::new(rect); 85 | let float_points = points.to_int(&adapter); 86 | let raw = self 87 | .to_int(&adapter) 88 | .custom_triangulate_with_steiner_points(&float_points, validation); 89 | RawTriangulation { raw, adapter } 90 | } else { 91 | RawTriangulation { 92 | raw: RawIntTriangulation::default(), 93 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 94 | } 95 | } 96 | } 97 | } 98 | 99 | impl, T: FloatNumber> CustomTriangulatable for [Shape

] { 100 | fn custom_triangulate(&self, validation: Validation) -> RawTriangulation { 101 | if let Some(rect) = FloatRect::with_list_of_paths(self) { 102 | let adapter = FloatPointAdapter::::new(rect); 103 | let raw = self.to_int(&adapter).custom_triangulate(validation); 104 | RawTriangulation { raw, adapter } 105 | } else { 106 | RawTriangulation { 107 | raw: RawIntTriangulation::default(), 108 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 109 | } 110 | } 111 | } 112 | 113 | fn custom_triangulate_with_steiner_points( 114 | &self, 115 | points: &[P], 116 | validation: Validation, 117 | ) -> RawTriangulation { 118 | if let Some(rect) = FloatRect::with_list_of_paths(self) { 119 | let adapter = FloatPointAdapter::::new(rect); 120 | let float_points = points.to_int(&adapter); 121 | let raw = self 122 | .to_int(&adapter) 123 | .custom_triangulate_with_steiner_points(&float_points, validation); 124 | RawTriangulation { raw, adapter } 125 | } else { 126 | RawTriangulation { 127 | raw: RawIntTriangulation::default(), 128 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /iTriangle/src/float/delaunay.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use crate::advanced::delaunay::IntDelaunay; 3 | use i_overlay::i_float::adapter::FloatPointAdapter; 4 | use i_overlay::i_float::float::compatible::FloatPointCompatible; 5 | use i_overlay::i_float::float::number::FloatNumber; 6 | use i_overlay::i_shape::float::adapter::PathToFloat; 7 | use crate::float::triangulation::{RawTriangulation, Triangulation}; 8 | use crate::int::triangulation::IndexType; 9 | 10 | /// A Delaunay-refined triangle mesh with float-mapped geometry. 11 | /// 12 | /// Produced from [`Triangulation::into_delaunay`] by applying edge flips 13 | /// to satisfy the Delaunay condition. 14 | pub struct Delaunay, T: FloatNumber> { 15 | pub(super) delaunay: IntDelaunay, 16 | pub(super) adapter: FloatPointAdapter, 17 | } 18 | 19 | impl, T: FloatNumber> RawTriangulation { 20 | #[inline] 21 | pub fn into_delaunay(self) -> Delaunay { 22 | Delaunay { 23 | delaunay: self.raw.into_delaunay(), 24 | adapter: self.adapter, 25 | } 26 | } 27 | } 28 | 29 | impl, T: FloatNumber> Delaunay { 30 | /// Returns the float-mapped vertex positions in the triangulation. 31 | #[inline] 32 | pub fn points(&self) -> Vec

{ 33 | self.delaunay.points.to_float(&self.adapter) 34 | } 35 | 36 | /// Returns indices forming counter-clockwise triangles. 37 | #[inline] 38 | pub fn triangle_indices(&self) -> Vec { 39 | self.delaunay.triangle_indices() 40 | } 41 | 42 | /// Converts this refined mesh into a flat float [`Triangulation`]. 43 | #[inline] 44 | pub fn to_triangulation(&self) -> Triangulation { 45 | Triangulation { 46 | indices: self.triangle_indices(), 47 | points: self.points(), 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /iTriangle/src/float/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod builder; 2 | pub mod centroid_net; 3 | pub mod circumcenter; 4 | pub mod convex; 5 | pub mod custom; 6 | pub mod delaunay; 7 | pub mod triangulatable; 8 | pub mod triangulation; 9 | pub mod triangulator; 10 | pub mod unchecked; 11 | -------------------------------------------------------------------------------- /iTriangle/src/float/triangulatable.rs: -------------------------------------------------------------------------------- 1 | use crate::int::triangulatable::IntTriangulatable; 2 | use crate::int::triangulation::RawIntTriangulation; 3 | use i_overlay::i_float::adapter::FloatPointAdapter; 4 | use i_overlay::i_float::float::compatible::FloatPointCompatible; 5 | use i_overlay::i_float::float::number::FloatNumber; 6 | use i_overlay::i_float::float::rect::FloatRect; 7 | use i_overlay::i_shape::base::data::{Contour, Shape}; 8 | use i_overlay::i_shape::float::adapter::{PathToInt, ShapeToInt, ShapesToInt}; 9 | use i_overlay::i_shape::float::rect::RectInit; 10 | use crate::float::triangulation::RawTriangulation; 11 | 12 | /// A trait for triangulating float-based geometry with default validation. 13 | /// 14 | /// Automatically converts the input to integer space, applies validation, 15 | /// and returns a float-mapped result. 16 | /// 17 | /// # Implemented For 18 | /// - `Contour

` 19 | /// - `[Contour

]` 20 | /// - `[Shape

]` 21 | pub trait Triangulatable, T: FloatNumber> { 22 | /// Triangulates the shape(s) using the default [`Triangulator`] configuration. 23 | /// 24 | /// Validation includes contour simplification, direction correction, and area filtering. 25 | fn triangulate(&self) -> RawTriangulation; 26 | 27 | /// Triangulates the shape(s) and inserts the given Steiner points. 28 | /// 29 | /// Points must lie strictly within the interior of the geometry. 30 | fn triangulate_with_steiner_points(&self, points: &[P]) -> RawTriangulation; 31 | } 32 | 33 | impl, T: FloatNumber> Triangulatable for [P] { 34 | fn triangulate(&self) -> RawTriangulation { 35 | if let Some(rect) = FloatRect::with_path(self) { 36 | let adapter = FloatPointAdapter::::new(rect); 37 | let raw = self.to_int(&adapter).triangulate(); 38 | RawTriangulation { raw, adapter } 39 | } else { 40 | RawTriangulation { 41 | raw: RawIntTriangulation::default(), 42 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 43 | } 44 | } 45 | } 46 | 47 | fn triangulate_with_steiner_points(&self, points: &[P]) -> RawTriangulation { 48 | if let Some(rect) = FloatRect::with_path(self) { 49 | let adapter = FloatPointAdapter::::new(rect); 50 | let float_points = points.to_int(&adapter); 51 | let raw = self 52 | .to_int(&adapter) 53 | .triangulate_with_steiner_points(&float_points); 54 | RawTriangulation { raw, adapter } 55 | } else { 56 | RawTriangulation { 57 | raw: RawIntTriangulation::default(), 58 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 59 | } 60 | } 61 | } 62 | } 63 | 64 | impl, T: FloatNumber> Triangulatable for [Contour

] { 65 | fn triangulate(&self) -> RawTriangulation { 66 | if let Some(rect) = FloatRect::with_paths(self) { 67 | let adapter = FloatPointAdapter::::new(rect); 68 | let raw = self.to_int(&adapter).triangulate(); 69 | RawTriangulation { raw, adapter } 70 | } else { 71 | RawTriangulation { 72 | raw: RawIntTriangulation::default(), 73 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 74 | } 75 | } 76 | } 77 | 78 | fn triangulate_with_steiner_points(&self, points: &[P]) -> RawTriangulation { 79 | if let Some(rect) = FloatRect::with_paths(self) { 80 | let adapter = FloatPointAdapter::::new(rect); 81 | let float_points = points.to_int(&adapter); 82 | let raw = self 83 | .to_int(&adapter) 84 | .triangulate_with_steiner_points(&float_points); 85 | RawTriangulation { raw, adapter } 86 | } else { 87 | RawTriangulation { 88 | raw: RawIntTriangulation::default(), 89 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 90 | } 91 | } 92 | } 93 | } 94 | 95 | impl, T: FloatNumber> Triangulatable for [Shape

] { 96 | fn triangulate(&self) -> RawTriangulation { 97 | if let Some(rect) = FloatRect::with_list_of_paths(self) { 98 | let adapter = FloatPointAdapter::::new(rect); 99 | let raw = self.to_int(&adapter).triangulate(); 100 | RawTriangulation { raw, adapter } 101 | } else { 102 | RawTriangulation { 103 | raw: RawIntTriangulation::default(), 104 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 105 | } 106 | } 107 | } 108 | 109 | fn triangulate_with_steiner_points(&self, points: &[P]) -> RawTriangulation { 110 | if let Some(rect) = FloatRect::with_list_of_paths(self) { 111 | let adapter = FloatPointAdapter::::new(rect); 112 | let float_points = points.to_int(&adapter); 113 | let raw = self 114 | .to_int(&adapter) 115 | .triangulate_with_steiner_points(&float_points); 116 | RawTriangulation { raw, adapter } 117 | } else { 118 | RawTriangulation { 119 | raw: RawIntTriangulation::default(), 120 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 121 | } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /iTriangle/src/float/triangulation.rs: -------------------------------------------------------------------------------- 1 | use crate::int::triangulation::{IndexType, IntTriangulation, RawIntTriangulation}; 2 | use alloc::vec::Vec; 3 | use i_overlay::i_float::adapter::FloatPointAdapter; 4 | use i_overlay::i_float::float::compatible::FloatPointCompatible; 5 | use i_overlay::i_float::float::number::FloatNumber; 6 | use i_overlay::i_shape::float::adapter::PathToFloat; 7 | use i_overlay::i_shape::util::reserve::Reserve; 8 | 9 | /// A triangulation result based on integer computation, with float mapping. 10 | /// 11 | /// Internally uses an [`Triangulation`] for performance and robustness, 12 | /// and maps results back to user-provided float types via a [`FloatPointAdapter`]. 13 | /// 14 | /// # Parameters 15 | /// - `P`: Float point type (e.g., `Vec2`, `[f32; 2]`, etc.) 16 | /// - `T`: Float scalar type (e.g., `f32`, `f64`) 17 | pub struct RawTriangulation, T: FloatNumber> { 18 | pub raw: RawIntTriangulation, 19 | pub adapter: FloatPointAdapter, 20 | } 21 | 22 | /// A flat triangulation result consisting of float points and triangle indices. 23 | /// 24 | /// Useful for rendering, exporting, or post-processing the mesh in float space. 25 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 26 | #[derive(Debug, Clone)] 27 | pub struct Triangulation { 28 | pub points: Vec

, 29 | pub indices: Vec, 30 | } 31 | 32 | impl, T: FloatNumber> RawTriangulation { 33 | /// Returns the float-mapped points used in the triangulation. 34 | /// 35 | /// The points are guaranteed to match the input shape geometry within adapter precision. 36 | #[inline] 37 | pub fn points(&self) -> Vec

{ 38 | self.raw.points.to_float(&self.adapter) 39 | } 40 | 41 | /// Returns the triangle indices for the mesh, ordered counter-clockwise. 42 | #[inline] 43 | pub fn triangle_indices(&self) -> Vec { 44 | self.raw.triangle_indices() 45 | } 46 | 47 | /// Converts this flat triangulation into a flat [`Triangulation`] (points + indices). 48 | #[inline] 49 | pub fn to_triangulation(&self) -> Triangulation { 50 | Triangulation { 51 | indices: self.triangle_indices(), 52 | points: self.points(), 53 | } 54 | } 55 | } 56 | 57 | impl Triangulation { 58 | #[inline] 59 | pub fn with_capacity(capacity: usize) -> Self { 60 | Self { 61 | points: Vec::with_capacity(capacity), 62 | indices: Vec::with_capacity(3 * capacity), 63 | } 64 | } 65 | 66 | #[inline] 67 | pub fn set_with_int( 68 | &mut self, 69 | triangulation: &IntTriangulation, 70 | adapter: &FloatPointAdapter, 71 | ) where 72 | P: FloatPointCompatible, 73 | T: FloatNumber, 74 | { 75 | self.points.clear(); 76 | self.points 77 | .reserve_capacity(triangulation.points.capacity()); 78 | self.points 79 | .extend(triangulation.points.iter().map(|p| adapter.int_to_float(p))); 80 | 81 | self.indices.clear(); 82 | self.indices.extend_from_slice(&triangulation.indices); 83 | } 84 | } 85 | 86 | impl IntTriangulation { 87 | #[inline] 88 | pub fn into_float, T: FloatNumber>( 89 | self, 90 | adapter: &FloatPointAdapter, 91 | ) -> Triangulation { 92 | let points = self 93 | .points 94 | .iter() 95 | .map(|p| adapter.int_to_float(p)) 96 | .collect(); 97 | Triangulation { 98 | points, 99 | indices: self.indices, 100 | } 101 | } 102 | 103 | #[inline] 104 | pub fn to_float, T: FloatNumber>( 105 | &self, 106 | adapter: &FloatPointAdapter, 107 | ) -> Triangulation { 108 | let points = self 109 | .points 110 | .iter() 111 | .map(|p| adapter.int_to_float(p)) 112 | .collect(); 113 | Triangulation { 114 | points, 115 | indices: self.indices.clone(), 116 | } 117 | } 118 | } 119 | 120 | impl Triangulation { 121 | 122 | pub fn validate(&self, shape_area: T, epsilon: T) 123 | where 124 | P: FloatPointCompatible, 125 | { 126 | let mut s = T::from_float(0.0); 127 | let mut i = 0; 128 | let neg_eps = -epsilon; 129 | while i < self.indices.len() { 130 | let ai = self.indices[i]; 131 | i += 1; 132 | let bi = self.indices[i]; 133 | i += 1; 134 | let ci = self.indices[i]; 135 | i += 1; 136 | 137 | let a = &self.points[ai.into_usize()]; 138 | let b = &self.points[bi.into_usize()]; 139 | let c = &self.points[ci.into_usize()]; 140 | 141 | let abc = Self::triangle_area_x2(a, b, c); 142 | 143 | // check points direction by its area. 144 | // Since it's a float point operation in degenerate case it can be near 0 value 145 | assert!(abc > neg_eps); 146 | 147 | s = s + abc; 148 | } 149 | 150 | s = T::from_float(0.5) * s; 151 | 152 | let eps = epsilon * T::from_usize(self.indices.len() / 3); 153 | let delta = (shape_area - s).abs(); 154 | 155 | assert!(delta <= eps); 156 | } 157 | 158 | fn triangle_area_x2(a: &P, b: &P, c: &P) -> T 159 | where 160 | P: FloatPointCompatible, 161 | { 162 | let ax = a.x(); 163 | let ay = a.y(); 164 | let bx = b.x(); 165 | let by = b.y(); 166 | let cx = c.x(); 167 | let cy = c.y(); 168 | 169 | let v0x = ax - bx; 170 | let v0y = ay - by; 171 | let v1x = ax - cx; 172 | let v1y = ay - cy; 173 | 174 | v0x * v1y - v0y * v1x 175 | } 176 | } 177 | 178 | #[cfg(test)] 179 | mod tests { 180 | use crate::float::triangulator::Triangulator; 181 | 182 | #[test] 183 | fn test_0() { 184 | let rect = [[0.0, 0.0], [5.0, 0.0], [5.0, 8.0], [0.0, 8.0]]; 185 | 186 | let triangulation = Triangulator::::default().triangulate(&rect); 187 | assert_eq!(triangulation.points.len(), 4); 188 | assert_eq!(triangulation.indices.len(), 6); 189 | 190 | triangulation.validate(40.0, 0.000_0001); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /iTriangle/src/float/unchecked.rs: -------------------------------------------------------------------------------- 1 | use crate::float::triangulation::RawTriangulation; 2 | use crate::int::triangulation::RawIntTriangulation; 3 | use crate::int::unchecked::IntUncheckedTriangulatable; 4 | use i_overlay::i_float::adapter::FloatPointAdapter; 5 | use i_overlay::i_float::float::compatible::FloatPointCompatible; 6 | use i_overlay::i_float::float::number::FloatNumber; 7 | use i_overlay::i_float::float::rect::FloatRect; 8 | use i_overlay::i_shape::base::data::{Contour, Shape}; 9 | use i_overlay::i_shape::float::adapter::{PathToInt, ShapeToInt, ShapesToInt}; 10 | use i_overlay::i_shape::float::rect::RectInit; 11 | 12 | /// A trait for triangulating already valid float-based geometry. 13 | /// 14 | /// Skips all validation for performance. Ideal when input is generated programmatically. 15 | /// 16 | /// # Safety Requirements 17 | /// - Outer contours must be counter-clockwise 18 | /// - Holes must be clockwise 19 | /// - Steiner points must lie strictly within the shape 20 | pub trait UncheckedTriangulatable, T: FloatNumber> { 21 | /// Triangulates float geometry without validation or simplification. 22 | fn unchecked_triangulate(&self) -> RawTriangulation; 23 | /// Same as `unchecked_triangulate`, but inserts user-defined Steiner points. 24 | fn unchecked_triangulate_with_steiner_points(&self, points: &[P]) -> RawTriangulation; 25 | } 26 | 27 | impl, T: FloatNumber> UncheckedTriangulatable for [P] { 28 | fn unchecked_triangulate(&self) -> RawTriangulation { 29 | if let Some(rect) = FloatRect::with_path(self) { 30 | let adapter = FloatPointAdapter::::new(rect); 31 | let raw = self.to_int(&adapter).uncheck_triangulate(); 32 | RawTriangulation { raw, adapter } 33 | } else { 34 | RawTriangulation { 35 | raw: RawIntTriangulation::default(), 36 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 37 | } 38 | } 39 | } 40 | 41 | fn unchecked_triangulate_with_steiner_points(&self, points: &[P]) -> RawTriangulation { 42 | if let Some(rect) = FloatRect::with_path(self) { 43 | let adapter = FloatPointAdapter::::new(rect); 44 | let float_points = points.to_int(&adapter); 45 | let raw = self 46 | .to_int(&adapter) 47 | .uncheck_triangulate_with_steiner_points(&float_points); 48 | RawTriangulation { raw, adapter } 49 | } else { 50 | RawTriangulation { 51 | raw: RawIntTriangulation::default(), 52 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 53 | } 54 | } 55 | } 56 | } 57 | 58 | impl, T: FloatNumber> UncheckedTriangulatable for [Contour

] { 59 | fn unchecked_triangulate(&self) -> RawTriangulation { 60 | if let Some(rect) = FloatRect::with_paths(self) { 61 | let adapter = FloatPointAdapter::::new(rect); 62 | let raw = self.to_int(&adapter).uncheck_triangulate(); 63 | RawTriangulation { raw, adapter } 64 | } else { 65 | RawTriangulation { 66 | raw: RawIntTriangulation::default(), 67 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 68 | } 69 | } 70 | } 71 | 72 | fn unchecked_triangulate_with_steiner_points(&self, points: &[P]) -> RawTriangulation { 73 | if let Some(rect) = FloatRect::with_paths(self) { 74 | let adapter = FloatPointAdapter::::new(rect); 75 | let float_points = points.to_int(&adapter); 76 | let raw = self 77 | .to_int(&adapter) 78 | .uncheck_triangulate_with_steiner_points(&float_points); 79 | RawTriangulation { raw, adapter } 80 | } else { 81 | RawTriangulation { 82 | raw: RawIntTriangulation::default(), 83 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 84 | } 85 | } 86 | } 87 | } 88 | 89 | impl, T: FloatNumber> UncheckedTriangulatable for [Shape

] { 90 | fn unchecked_triangulate(&self) -> RawTriangulation { 91 | if let Some(rect) = FloatRect::with_list_of_paths(self) { 92 | let adapter = FloatPointAdapter::::new(rect); 93 | let raw = self.to_int(&adapter).uncheck_triangulate(); 94 | RawTriangulation { raw, adapter } 95 | } else { 96 | RawTriangulation { 97 | raw: RawIntTriangulation::default(), 98 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 99 | } 100 | } 101 | } 102 | 103 | fn unchecked_triangulate_with_steiner_points(&self, points: &[P]) -> RawTriangulation { 104 | if let Some(rect) = FloatRect::with_list_of_paths(self) { 105 | let adapter = FloatPointAdapter::::new(rect); 106 | let float_points = points.to_int(&adapter); 107 | let raw = self 108 | .to_int(&adapter) 109 | .uncheck_triangulate_with_steiner_points(&float_points); 110 | RawTriangulation { raw, adapter } 111 | } else { 112 | RawTriangulation { 113 | raw: RawIntTriangulation::default(), 114 | adapter: FloatPointAdapter::::new(FloatRect::zero()), 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /iTriangle/src/geom/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod point; 2 | pub mod triangle; -------------------------------------------------------------------------------- /iTriangle/src/geom/point.rs: -------------------------------------------------------------------------------- 1 | use i_overlay::i_float::int::point::IntPoint; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct IndexPoint { 5 | pub index: usize, 6 | pub point: IntPoint, 7 | } 8 | 9 | impl IndexPoint { 10 | #[inline] 11 | pub fn new(index: usize, point: IntPoint) -> Self { 12 | Self { index, point } 13 | } 14 | 15 | #[inline] 16 | pub const fn empty() -> Self { 17 | Self { 18 | index: usize::MAX, 19 | point: IntPoint::ZERO, 20 | } 21 | } 22 | } 23 | 24 | impl Default for IndexPoint { 25 | #[inline] 26 | fn default() -> Self { 27 | IndexPoint::empty() 28 | } 29 | } -------------------------------------------------------------------------------- /iTriangle/src/geom/triangle.rs: -------------------------------------------------------------------------------- 1 | use crate::geom::point::IndexPoint; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Abc { 5 | pub v0: ABCVertex, 6 | pub v1: ABCVertex, 7 | pub v2: ABCVertex, 8 | } 9 | 10 | #[derive(Debug, Clone, Copy)] 11 | pub struct ABCVertex { 12 | pub vertex: IndexPoint, 13 | pub position: usize, 14 | pub neighbor: usize, 15 | } 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct IntTriangle { 19 | pub vertices: [IndexPoint; 3], 20 | pub neighbors: [usize; 3], 21 | } 22 | 23 | impl IntTriangle { 24 | #[inline] 25 | pub fn abc(a: IndexPoint, b: IndexPoint, c: IndexPoint) -> Self { 26 | Self { 27 | vertices: [a, b, c], 28 | neighbors: [usize::MAX; 3], 29 | } 30 | } 31 | 32 | #[inline] 33 | pub fn set_neighbor(&mut self, order: usize, neighbor: usize) { 34 | debug_assert!(order < 3); 35 | unsafe { 36 | *self.neighbors.get_unchecked_mut(order) = neighbor; 37 | } 38 | } 39 | 40 | #[inline] 41 | pub fn remove_neighbor(&mut self, order: usize) { 42 | debug_assert!(order < 3); 43 | unsafe { 44 | *self.neighbors.get_unchecked_mut(order) = usize::MAX; 45 | } 46 | } 47 | 48 | #[inline] 49 | pub fn other_vertex(&self, a: usize, b: usize) -> usize { 50 | if self.vertices[0].index != a && self.vertices[0].index != b { 51 | 0 52 | } else if self.vertices[1].index != a && self.vertices[1].index != b { 53 | 1 54 | } else { 55 | 2 56 | } 57 | } 58 | 59 | pub fn opposite(&self, neighbor: usize) -> usize { 60 | #[cfg(debug_assertions)] 61 | { 62 | for i in 0..3 { 63 | if self.neighbors[i] == neighbor { 64 | return i; 65 | } 66 | } 67 | 68 | panic!("Neighbor is not present"); 69 | } 70 | 71 | #[cfg(not(debug_assertions))] 72 | { 73 | for i in 0..2 { 74 | if self.neighbors[i] == neighbor { 75 | return i; 76 | } 77 | } 78 | 79 | 2 80 | } 81 | } 82 | 83 | #[inline] 84 | pub(crate) fn abc_by_neighbor(&self, neighbor: usize) -> Abc { 85 | if neighbor == self.neighbors[0] { 86 | self.abc_by_a() 87 | } else if neighbor == self.neighbors[1] { 88 | self.abc_by_b() 89 | } else { 90 | self.abc_by_c() 91 | } 92 | } 93 | 94 | #[inline] 95 | pub(crate) fn abc_by_a(&self) -> Abc { 96 | let a = ABCVertex { 97 | vertex: self.vertices[0], 98 | position: 0, 99 | neighbor: self.neighbors[0], 100 | }; 101 | let b = ABCVertex { 102 | vertex: self.vertices[1], 103 | position: 1, 104 | neighbor: self.neighbors[1], 105 | }; 106 | let c = ABCVertex { 107 | vertex: self.vertices[2], 108 | position: 2, 109 | neighbor: self.neighbors[2], 110 | }; 111 | Abc { v0: a, v1: b, v2: c } 112 | } 113 | 114 | #[inline] 115 | pub(crate) fn abc_by_b(&self) -> Abc { 116 | let a = ABCVertex { 117 | vertex: self.vertices[1], 118 | position: 1, 119 | neighbor: self.neighbors[1], 120 | }; 121 | let b = ABCVertex { 122 | vertex: self.vertices[2], 123 | position: 2, 124 | neighbor: self.neighbors[2], 125 | }; 126 | let c = ABCVertex { 127 | vertex: self.vertices[0], 128 | position: 0, 129 | neighbor: self.neighbors[0], 130 | }; 131 | Abc { v0: a, v1: b, v2: c } 132 | } 133 | 134 | #[inline] 135 | pub(crate) fn abc_by_c(&self) -> Abc { 136 | let a = ABCVertex { 137 | vertex: self.vertices[2], 138 | position: 2, 139 | neighbor: self.neighbors[2], 140 | }; 141 | let b = ABCVertex { 142 | vertex: self.vertices[0], 143 | position: 0, 144 | neighbor: self.neighbors[0], 145 | }; 146 | let c = ABCVertex { 147 | vertex: self.vertices[1], 148 | position: 1, 149 | neighbor: self.neighbors[1], 150 | }; 151 | Abc { v0: a, v1: b, v2: c } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /iTriangle/src/index.rs: -------------------------------------------------------------------------------- 1 | pub(crate) const NIL_INDEX: usize = usize::MAX; 2 | 3 | pub(crate) trait Index { 4 | fn is_not_nil(&self) -> bool; 5 | } 6 | 7 | impl Index for usize { 8 | fn is_not_nil(&self) -> bool { 9 | *self != NIL_INDEX 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /iTriangle/src/int/custom.rs: -------------------------------------------------------------------------------- 1 | use crate::int::solver::ShapesSolver; 2 | use crate::int::solver::{ContourSolver, ShapeSolver}; 3 | use crate::int::triangulation::RawIntTriangulation; 4 | use crate::int::validation::Validation; 5 | use i_overlay::i_float::int::point::IntPoint; 6 | use i_overlay::i_shape::int::shape::{IntContour, IntShape, IntShapes}; 7 | 8 | /// A trait for performing triangulation with custom validation settings. 9 | /// 10 | /// Useful when precise control over fill rule, minimum area, or orientation is needed. 11 | /// Accepts a custom [`Validation`] struct to configure triangulation behavior. 12 | /// 13 | /// # Implemented For 14 | /// - [`IntContour`] 15 | /// - [`IntShape`] 16 | /// - [`IntShapes`] 17 | pub trait IntCustomTriangulatable { 18 | /// Triangulates the shape(s) using the given [`Validation`] settings. 19 | fn custom_triangulate(&self, validation: Validation) -> RawIntTriangulation; 20 | 21 | /// Triangulates the shape(s), injecting Steiner points and using the specified [`Validation`] settings. 22 | fn custom_triangulate_with_steiner_points( 23 | &self, 24 | points: &[IntPoint], 25 | validation: Validation, 26 | ) -> RawIntTriangulation; 27 | } 28 | 29 | impl IntCustomTriangulatable for IntContour { 30 | #[inline] 31 | fn custom_triangulate(&self, validation: Validation) -> RawIntTriangulation { 32 | ContourSolver::triangulate(validation, self) 33 | } 34 | 35 | #[inline] 36 | fn custom_triangulate_with_steiner_points( 37 | &self, 38 | points: &[IntPoint], 39 | validation: Validation, 40 | ) -> RawIntTriangulation { 41 | ContourSolver::triangulate_with_steiner_points(validation, self, points) 42 | } 43 | } 44 | 45 | impl IntCustomTriangulatable for IntShape { 46 | #[inline] 47 | fn custom_triangulate(&self, validation: Validation) -> RawIntTriangulation { 48 | ShapeSolver::triangulate(validation, self) 49 | } 50 | 51 | #[inline] 52 | fn custom_triangulate_with_steiner_points( 53 | &self, 54 | points: &[IntPoint], 55 | validation: Validation, 56 | ) -> RawIntTriangulation { 57 | ShapeSolver::triangulate_with_steiner_points(validation, self, points) 58 | } 59 | } 60 | 61 | impl IntCustomTriangulatable for IntShapes { 62 | #[inline] 63 | fn custom_triangulate(&self, validation: Validation) -> RawIntTriangulation { 64 | ShapesSolver::triangulate(validation, self) 65 | } 66 | 67 | #[inline] 68 | fn custom_triangulate_with_steiner_points( 69 | &self, 70 | points: &[IntPoint], 71 | validation: Validation, 72 | ) -> RawIntTriangulation { 73 | ShapesSolver::triangulate_with_steiner_points(validation, self, points) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /iTriangle/src/int/earcut/flat.rs: -------------------------------------------------------------------------------- 1 | use i_overlay::i_float::int::point::IntPoint; 2 | use crate::int::earcut::earcut_64::{Bit, EarcutStore}; 3 | use crate::int::triangulation::{IndexType, IntTriangulation}; 4 | 5 | pub(super) struct FlatEarcutStore<'a, I> { 6 | triangulation: &'a mut IntTriangulation, 7 | } 8 | 9 | impl<'a, I: IndexType> FlatEarcutStore<'a, I> { 10 | #[inline] 11 | pub(super) fn new(triangulation: &'a mut IntTriangulation) -> Self { 12 | Self { triangulation } 13 | } 14 | } 15 | 16 | impl EarcutStore for FlatEarcutStore<'_, I> { 17 | #[inline] 18 | fn collect_triangles(&mut self, _: &[IntPoint], start: usize, bits: u64, count: u32) { 19 | let mut i = start; 20 | let a = unsafe { I::try_from(i).unwrap_unchecked() }; 21 | i = bits.next_wrapped_index(i); 22 | let mut b = unsafe { I::try_from(i).unwrap_unchecked() }; 23 | 24 | for _ in 0..count { 25 | i = bits.next_wrapped_index(i); 26 | let c = unsafe { I::try_from(i).unwrap_unchecked() }; 27 | self.triangulation.indices.push(a); 28 | self.triangulation.indices.push(b); 29 | self.triangulation.indices.push(c); 30 | 31 | b = c; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /iTriangle/src/int/earcut/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod earcut_64; 2 | mod flat; 3 | mod net; 4 | mod util; -------------------------------------------------------------------------------- /iTriangle/src/int/earcut/net.rs: -------------------------------------------------------------------------------- 1 | use crate::geom::point::IndexPoint; 2 | use crate::geom::triangle::IntTriangle; 3 | use crate::int::earcut::earcut_64::{Bit, EarcutStore}; 4 | use crate::int::triangulation::RawIntTriangulation; 5 | use alloc::vec::Vec; 6 | use i_overlay::i_float::int::point::IntPoint; 7 | 8 | struct TriangleHandler { 9 | triangle: u8, 10 | vertex: u8, 11 | } 12 | 13 | struct EdgeItem { 14 | edge: Edge, 15 | handler: TriangleHandler 16 | } 17 | 18 | #[derive(Clone, Copy, PartialEq, Eq)] 19 | struct Edge { 20 | a: u8, 21 | b: u8, 22 | } 23 | 24 | impl Edge { 25 | #[inline] 26 | fn new(a: usize, b: usize) -> Self { 27 | debug_assert!(a < b); 28 | Self { 29 | a: a as u8, 30 | b: b as u8, 31 | } 32 | } 33 | } 34 | 35 | struct EdgePool { 36 | edges: Vec, 37 | } 38 | 39 | impl EdgePool { 40 | #[inline] 41 | fn insert(&mut self, edge: Edge, handler: TriangleHandler) -> Option { 42 | // if it exists remove and return value 43 | // if not exists save 44 | if let Some(index) = self.edges.iter().position(|it| it.edge == edge) { 45 | Some(self.edges.swap_remove(index).handler) 46 | } else { 47 | let item = EdgeItem { 48 | edge, 49 | handler, 50 | }; 51 | self.edges.push(item); 52 | None 53 | } 54 | } 55 | } 56 | 57 | pub(super) struct NetEarcutStore<'a> { 58 | triangulation: &'a mut RawIntTriangulation, 59 | pool: EdgePool, 60 | last: usize, 61 | } 62 | 63 | impl<'a> NetEarcutStore<'a> { 64 | #[inline] 65 | pub(super) fn new(count: usize, triangulation: &'a mut RawIntTriangulation) -> Self { 66 | Self { 67 | last: count - 1, 68 | triangulation, 69 | pool: EdgePool { 70 | edges: Vec::with_capacity(8), 71 | }, 72 | } 73 | } 74 | } 75 | 76 | impl EarcutStore for NetEarcutStore<'_> { 77 | #[inline] 78 | fn collect_triangles(&mut self, contour: &[IntPoint], start: usize, bits: u64, count: u32) { 79 | let ai = start; 80 | let a = IndexPoint::new(ai, contour[ai]); 81 | 82 | let bi = bits.next_wrapped_index(ai); 83 | let mut b = IndexPoint::new(bi, contour[bi]); 84 | let mut ci = bits.next_wrapped_index(bi); 85 | for _ in 0..count { 86 | 87 | let c = IndexPoint::new(ci, contour[ci]); 88 | 89 | let triangle_index = self.triangulation.triangles.len(); 90 | let mut triangle = IntTriangle::abc(a, b, c); 91 | 92 | triangle.neighbors[0] = self.get_or_put(b.index, c.index, triangle_index, 0); 93 | triangle.neighbors[1] = self.get_or_put(a.index, c.index, triangle_index, 1); 94 | triangle.neighbors[2] = self.get_or_put(a.index, b.index, triangle_index, 2); 95 | 96 | self.triangulation.triangles.push(triangle); 97 | 98 | b = c; 99 | ci = bits.next_wrapped_index(ci); 100 | } 101 | } 102 | } 103 | 104 | impl NetEarcutStore<'_> { 105 | #[inline] 106 | fn get_or_put(&mut self, i0: usize, i1: usize, t: usize, v: usize) -> usize { 107 | // is edge inner or outer 108 | let (a, b) = if i0 < i1 { (i0, i1) } else { (i1, i0) }; 109 | if b - a == 1 || a == 0 && b == self.last { 110 | return usize::MAX; 111 | } 112 | // a < b 113 | 114 | let handler = TriangleHandler { 115 | triangle: t as u8, 116 | vertex: v as u8, 117 | }; 118 | 119 | // if a neighbor exist we should also update it 120 | if let Some(other) = self.pool.insert(Edge::new(a, b), handler) { 121 | let triangle = other.triangle as usize; 122 | self.triangulation.triangles[triangle].neighbors[other.vertex as usize] = t; 123 | triangle 124 | } else { 125 | usize::MAX 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /iTriangle/src/int/earcut/util.rs: -------------------------------------------------------------------------------- 1 | use core::cmp::Ordering; 2 | use i_overlay::i_float::fix_vec::FixVec; 3 | use i_overlay::i_float::int::point::IntPoint; 4 | 5 | #[derive(PartialEq, Eq)] 6 | pub(super) enum ABCExcludeResult { 7 | Inside, 8 | Outside, 9 | OutsideEdge, 10 | } 11 | 12 | // a, b, c - counter clock wised points 13 | pub(super) struct Abc { 14 | a: IntPoint, 15 | b: IntPoint, 16 | c: IntPoint, 17 | ab: FixVec, 18 | bc: FixVec, 19 | ca: FixVec, 20 | } 21 | 22 | impl Abc { 23 | #[inline(always)] 24 | pub(super) fn new(a: IntPoint, b: IntPoint, c: IntPoint) -> Self { 25 | let ab = b.subtract(a); 26 | let bc = c.subtract(b); 27 | let ca = a.subtract(c); 28 | Self { 29 | a, 30 | b, 31 | c, 32 | ab, 33 | bc, 34 | ca, 35 | } 36 | } 37 | 38 | #[inline(always)] 39 | pub(super) fn contains(&self, p: IntPoint) -> bool { 40 | let ap = p.subtract(self.a); 41 | let a_cross = ap.cross_product(self.ab); 42 | if a_cross >= 0 { 43 | return false; 44 | } 45 | 46 | let bp = p.subtract(self.b); 47 | let b_cross = bp.cross_product(self.bc); 48 | if b_cross >= 0 { 49 | return false; 50 | } 51 | 52 | let cp = p.subtract(self.c); 53 | let c_cross = cp.cross_product(self.ca); 54 | 55 | c_cross < 0 56 | } 57 | 58 | #[inline(always)] 59 | pub(super) fn contains_exclude_ca(&self, p: IntPoint) -> ABCExcludeResult { 60 | let ap = p.subtract(self.a); 61 | let a_cross = ap.cross_product(self.ab); 62 | if a_cross >= 0 { 63 | return ABCExcludeResult::Outside; 64 | } 65 | 66 | let bp = p.subtract(self.b); 67 | let b_cross = bp.cross_product(self.bc); 68 | if b_cross >= 0 { 69 | return ABCExcludeResult::Outside; 70 | } 71 | 72 | let cp = p.subtract(self.c); 73 | let c_cross = cp.cross_product(self.ca); 74 | 75 | match c_cross.cmp(&0) { 76 | Ordering::Less => ABCExcludeResult::Inside, 77 | Ordering::Equal => { 78 | if AB::contains(self.a, self.c, p) { 79 | ABCExcludeResult::Inside 80 | } else { 81 | ABCExcludeResult::OutsideEdge 82 | } 83 | } 84 | Ordering::Greater => ABCExcludeResult::OutsideEdge, 85 | } 86 | } 87 | } 88 | 89 | pub(super) struct AB; 90 | 91 | impl AB { 92 | #[inline(always)] 93 | pub(super) fn contains(a: IntPoint, b: IntPoint, p: IntPoint) -> bool { 94 | // a, b, p already on one line 95 | // not including ends 96 | let ap = a.subtract(p); 97 | let bp = b.subtract(p); 98 | 99 | // must have opposite direction 100 | ap.dot_product(bp) < 0 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /iTriangle/src/int/meta.rs: -------------------------------------------------------------------------------- 1 | use i_overlay::i_float::int::point::IntPoint; 2 | use i_overlay::i_shape::flat::buffer::FlatContoursBuffer; 3 | use i_overlay::i_shape::int::shape::IntContour; 4 | 5 | pub(crate) struct MeshMeta { 6 | pub(crate) triangles_count: usize, 7 | pub(crate) vertices_count: usize, 8 | } 9 | 10 | pub(crate) trait TrianglesCount { 11 | fn triangles_count(&self, points_count: usize) -> usize; 12 | } 13 | 14 | impl TrianglesCount for FlatContoursBuffer { 15 | #[inline] 16 | fn triangles_count(&self, points_count: usize) -> usize { 17 | let mut count = 2 * points_count; 18 | count += self.points.len(); 19 | count -= 2 * self.ranges.len(); 20 | count 21 | } 22 | } 23 | 24 | impl TrianglesCount for [IntContour] { 25 | #[inline] 26 | fn triangles_count(&self, points_count: usize) -> usize { 27 | let mut count = 2 * points_count; 28 | for contour in self.iter() { 29 | count += contour.len() - 2; 30 | } 31 | count 32 | } 33 | } 34 | 35 | impl TrianglesCount for [IntPoint] { 36 | #[inline] 37 | fn triangles_count(&self, points_count: usize) -> usize { 38 | self.len() - 2 + 2 * points_count 39 | } 40 | } 41 | 42 | pub(crate) trait MeshMetaProvider { 43 | fn meta(&self, points_count: usize) -> MeshMeta; 44 | } 45 | 46 | impl MeshMetaProvider for [IntPoint] { 47 | #[inline] 48 | fn meta(&self, points_count: usize) -> MeshMeta { 49 | MeshMeta { 50 | triangles_count: self.triangles_count(points_count), 51 | vertices_count: self.len() + points_count, 52 | } 53 | } 54 | } 55 | 56 | impl MeshMetaProvider for [IntContour] { 57 | #[inline] 58 | fn meta(&self, points_count: usize) -> MeshMeta { 59 | let mut triangles_count = 2 * points_count; 60 | let mut vertices_count = points_count; 61 | for contour in self { 62 | triangles_count += contour.len() - 2; 63 | vertices_count += contour.len(); 64 | } 65 | 66 | MeshMeta { 67 | triangles_count, 68 | vertices_count, 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /iTriangle/src/int/mod.rs: -------------------------------------------------------------------------------- 1 | mod binder; 2 | pub mod custom; 3 | pub mod earcut; 4 | mod meta; 5 | pub(crate) mod monotone; 6 | mod solver; 7 | pub mod triangulatable; 8 | pub mod triangulation; 9 | pub mod triangulator; 10 | pub mod unchecked; 11 | pub mod validation; 12 | -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/chain/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod vertex; 2 | pub(crate) mod builder; -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/chain/vertex.rs: -------------------------------------------------------------------------------- 1 | use crate::geom::point::IndexPoint; 2 | use i_key_sort::bin_key::index::BinKey; 3 | use i_key_sort::bin_key::index::BinLayout; 4 | use i_overlay::i_float::int::point::IntPoint; 5 | use i_overlay::i_float::triangle::Triangle; 6 | 7 | #[derive(Debug, Clone, Copy)] 8 | pub(crate) enum VertexType { 9 | Start, 10 | End, 11 | Merge, 12 | Split, 13 | Join, 14 | Steiner, 15 | } 16 | 17 | #[derive(Debug, Clone, Default)] 18 | pub(crate) struct ChainVertex { 19 | pub(crate) index: usize, 20 | pub(crate) this: IntPoint, 21 | pub(crate) next: IntPoint, 22 | pub(crate) prev: IntPoint, 23 | } 24 | 25 | impl ChainVertex { 26 | pub(super) const EMPTY: ChainVertex = ChainVertex { 27 | index: 0, 28 | this: IntPoint::EMPTY, 29 | next: IntPoint::EMPTY, 30 | prev: IntPoint::EMPTY, 31 | }; 32 | 33 | #[inline] 34 | pub(super) fn new(this: IntPoint, next: IntPoint, prev: IntPoint) -> Self { 35 | Self { 36 | index: 0, 37 | this, 38 | next, 39 | prev, 40 | } 41 | } 42 | 43 | #[inline] 44 | pub(crate) fn implant(this: IntPoint) -> Self { 45 | Self { 46 | index: 0, 47 | this, 48 | next: IntPoint::EMPTY, 49 | prev: IntPoint::EMPTY, 50 | } 51 | } 52 | 53 | #[inline] 54 | pub(crate) fn get_type(&self) -> VertexType { 55 | let clock_wise = Triangle::is_clockwise_point(self.prev, self.this, self.next); 56 | if self.prev == IntPoint::EMPTY && self.next == IntPoint::EMPTY { 57 | VertexType::Steiner 58 | } else if self.prev < self.this && self.next < self.this { 59 | if clock_wise { 60 | VertexType::Merge 61 | } else { 62 | VertexType::End 63 | } 64 | } else if self.this < self.next && self.this < self.prev { 65 | if clock_wise { 66 | VertexType::Split 67 | } else { 68 | VertexType::Start 69 | } 70 | } else { 71 | VertexType::Join 72 | } 73 | } 74 | 75 | #[inline] 76 | pub(crate) fn index_point(&self) -> IndexPoint { 77 | IndexPoint::new(self.index, self.this) 78 | } 79 | } 80 | 81 | impl BinKey for ChainVertex { 82 | #[inline] 83 | fn bin_key(&self) -> i32 { 84 | self.this.x 85 | } 86 | 87 | #[inline] 88 | fn bin_index(&self, layout: &BinLayout) -> usize { 89 | layout.index(self.this.x) 90 | } 91 | } -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/flat/mod.rs: -------------------------------------------------------------------------------- 1 | mod section; 2 | pub(crate) mod triangulator; -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/flat/section.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use i_tree::set::sort::KeyValue; 3 | use crate::geom::point::IndexPoint; 4 | use crate::int::monotone::v_segment::VSegment; 5 | 6 | #[derive(Debug, Clone)] 7 | pub(super) struct FlatSection { 8 | pub(super) sort: VSegment, 9 | pub(super) points: Vec, 10 | } 11 | 12 | impl Default for FlatSection { 13 | #[inline] 14 | fn default() -> Self { 15 | Self { 16 | sort: Default::default(), 17 | points: Vec::new(), 18 | } 19 | } 20 | } 21 | 22 | impl KeyValue for FlatSection { 23 | #[inline] 24 | fn key(&self) -> &VSegment { 25 | &self.sort 26 | } 27 | } -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/mod.rs: -------------------------------------------------------------------------------- 1 | mod chain; 2 | pub(crate) mod flat; 3 | mod net; 4 | pub(crate) mod triangulator; 5 | pub(crate) mod v_segment; 6 | -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/net/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod triangulator; 2 | pub mod section; 3 | pub mod phantom; -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/net/phantom.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | #[derive(Copy, Clone)] 4 | pub(crate) struct PhantomHandler { 5 | pub(crate) vertex: usize, 6 | pub(crate) triangle: usize, 7 | } 8 | 9 | pub(crate) struct PhantomEdgePool { 10 | buffer: Vec, 11 | unused: Vec, 12 | } 13 | 14 | impl PhantomEdgePool { 15 | const INIT_LEN: usize = 8; 16 | 17 | const EMPTY: PhantomHandler = PhantomHandler { 18 | vertex: usize::MAX, 19 | triangle: usize::MAX, 20 | }; 21 | 22 | pub(crate) fn new() -> Self { 23 | let mut store = Self { 24 | buffer: Vec::with_capacity(Self::INIT_LEN), 25 | unused: Vec::with_capacity(Self::INIT_LEN), 26 | }; 27 | store.reserve(Self::INIT_LEN); 28 | store 29 | } 30 | 31 | #[inline] 32 | fn reserve(&mut self, length: usize) { 33 | debug_assert!(length > 0); 34 | let n = self.buffer.len(); 35 | self.buffer.reserve(length); 36 | self.buffer.resize(self.buffer.len() + length, Self::EMPTY); 37 | self.unused.reserve(length); 38 | self.unused.extend((n..n + length).rev()); 39 | } 40 | 41 | #[inline] 42 | pub(crate) fn get(&self, index: usize) -> Option { 43 | let item = self.buffer[index]; 44 | if item.triangle == usize::MAX { 45 | None 46 | } else { 47 | Some(item) 48 | } 49 | } 50 | 51 | #[inline] 52 | pub(crate) fn register_phantom_link(&mut self, index: usize, handler: PhantomHandler) { 53 | debug_assert!(self.buffer[index].triangle == usize::MAX); 54 | self.buffer[index] = handler; 55 | } 56 | 57 | #[inline] 58 | pub(crate) fn alloc_phantom_index(&mut self) -> usize { 59 | if self.unused.is_empty() { 60 | self.reserve(self.unused.capacity()); 61 | } 62 | self.unused.pop().unwrap() 63 | } 64 | 65 | #[inline] 66 | pub(crate) fn free_phantom_index(&mut self, index: usize) { 67 | self.buffer[index] = Self::EMPTY; 68 | self.unused.push(index) 69 | } 70 | } -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/net/section.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use crate::geom::point::IndexPoint; 3 | use crate::int::monotone::v_segment::VSegment; 4 | use i_tree::set::sort::KeyValue; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | pub(crate) enum EdgeType { 8 | Regular(usize), // keep index to triangle 9 | Phantom(usize), // keep index to itself(edge) in phantom store 10 | } 11 | #[derive(Debug, Clone, Copy)] 12 | pub(crate) struct TriangleEdge { 13 | pub(crate) a: IndexPoint, 14 | pub(crate) b: IndexPoint, 15 | pub(crate) kind: EdgeType, 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | pub(crate) enum Content { 20 | Point(IndexPoint), 21 | Edges(Vec), 22 | } 23 | 24 | #[derive(Debug, Clone)] 25 | pub(crate) struct Section { 26 | pub(crate) sort: VSegment, 27 | pub(crate) content: Content, 28 | } 29 | 30 | impl Default for Section { 31 | #[inline] 32 | fn default() -> Self { 33 | Self { 34 | sort: Default::default(), 35 | content: Content::Point(IndexPoint::empty()), 36 | } 37 | } 38 | } 39 | 40 | impl KeyValue for Section { 41 | #[inline] 42 | fn key(&self) -> &VSegment { 43 | &self.sort 44 | } 45 | } 46 | 47 | impl TriangleEdge { 48 | #[inline] 49 | pub(crate) fn border(a: IndexPoint, b: IndexPoint) -> Self { 50 | Self { 51 | a, 52 | b, 53 | kind: EdgeType::Regular(usize::MAX), 54 | } 55 | } 56 | 57 | #[inline] 58 | pub(crate) fn phantom(a: IndexPoint, b: IndexPoint, index: usize) -> Self { 59 | Self { 60 | a, 61 | b, 62 | kind: EdgeType::Phantom(index), 63 | } 64 | } 65 | 66 | #[inline] 67 | pub(crate) fn regular(a: IndexPoint, b: IndexPoint, index: usize) -> Self { 68 | Self { 69 | a, 70 | b, 71 | kind: EdgeType::Regular(index), 72 | } 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use crate::geom::point::IndexPoint; 79 | use crate::int::monotone::net::section::{Content, Section, VSegment}; 80 | use i_overlay::i_float::int::point::IntPoint; 81 | use i_tree::set::sort::SetCollection; 82 | use i_tree::set::tree::SetTree; 83 | use i_tree::EMPTY_REF; 84 | use core::cmp::Ordering; 85 | 86 | impl Section { 87 | fn with_sort(sort: VSegment) -> Section { 88 | Section { 89 | sort, 90 | content: Content::Point(IndexPoint::empty()), 91 | } 92 | } 93 | } 94 | 95 | #[test] 96 | fn test_0() { 97 | let vs = VSegment { 98 | a: IntPoint::new(0, 10), 99 | b: IntPoint::new(10, 10), 100 | }; 101 | 102 | let ord0 = vs.is_under_point_order(IntPoint::new(0, 20)); 103 | let ord1 = vs.is_under_point_order(IntPoint::new(5, 20)); 104 | let ord2 = vs.is_under_point_order(IntPoint::new(10, 20)); 105 | 106 | assert_eq!(ord0, Ordering::Less); 107 | assert_eq!(ord1, Ordering::Less); 108 | assert_eq!(ord2, Ordering::Less); 109 | 110 | let ord3 = vs.is_under_point_order(IntPoint::new(0, 10)); 111 | let ord4 = vs.is_under_point_order(IntPoint::new(5, 10)); 112 | let ord5 = vs.is_under_point_order(IntPoint::new(10, 10)); 113 | 114 | assert_eq!(ord3, Ordering::Equal); 115 | assert_eq!(ord4, Ordering::Equal); 116 | assert_eq!(ord5, Ordering::Equal); 117 | 118 | let ord6 = vs.is_under_point_order(IntPoint::new(0, 0)); 119 | let ord7 = vs.is_under_point_order(IntPoint::new(5, 0)); 120 | let ord8 = vs.is_under_point_order(IntPoint::new(10, 0)); 121 | 122 | assert_eq!(ord6, Ordering::Greater); 123 | assert_eq!(ord7, Ordering::Greater); 124 | assert_eq!(ord8, Ordering::Greater); 125 | } 126 | 127 | #[test] 128 | fn test_1() { 129 | let vs0 = VSegment { 130 | a: IntPoint::new(0, 9), 131 | b: IntPoint::new(10, 9), 132 | }; 133 | let vs1 = VSegment { 134 | a: IntPoint::new(0, 6), 135 | b: IntPoint::new(10, 6), 136 | }; 137 | let vs2 = VSegment { 138 | a: IntPoint::new(0, 3), 139 | b: IntPoint::new(10, 3), 140 | }; 141 | let vs3 = VSegment { 142 | a: IntPoint::new(0, 1), 143 | b: IntPoint::new(10, 1), 144 | }; 145 | 146 | let mut sections = SetTree::new(8); 147 | sections.insert(Section::with_sort(vs0)); 148 | sections.insert(Section::with_sort(vs1)); 149 | sections.insert(Section::with_sort(vs2)); 150 | sections.insert(Section::with_sort(vs3)); 151 | 152 | let i0 = sections.first_index_less_by(|s| s.is_under_point_order(IntPoint::new(5, 15))); 153 | let i1 = sections.first_index_less_by(|s| s.is_under_point_order(IntPoint::new(5, 7))); 154 | let i2 = sections.first_index_less_by(|s| s.is_under_point_order(IntPoint::new(5, 4))); 155 | let i3 = sections.first_index_less_by(|s| s.is_under_point_order(IntPoint::new(5, 3))); 156 | let i4 = sections.first_index_less_by(|s| s.is_under_point_order(IntPoint::new(5, 2))); 157 | let i5 = sections.first_index_less_by(|s| s.is_under_point_order(IntPoint::new(5, 0))); 158 | 159 | let r0 = sections.value_by_index(i0); 160 | let r1 = sections.value_by_index(i1); 161 | let r2 = sections.value_by_index(i2); 162 | let r3 = sections.value_by_index(i3); 163 | let r4 = sections.value_by_index(i4); 164 | 165 | assert!(r0.sort.eq(&Section::with_sort(vs0).sort)); 166 | assert!(r1.sort.eq(&Section::with_sort(vs1).sort)); 167 | assert!(r2.sort.eq(&Section::with_sort(vs2).sort)); 168 | assert!(r3.sort.eq(&Section::with_sort(vs2).sort)); 169 | assert!(r4.sort.eq(&Section::with_sort(vs3).sort)); 170 | 171 | assert_eq!(i5, EMPTY_REF); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/triangulator.rs: -------------------------------------------------------------------------------- 1 | use crate::int::meta::TrianglesCount; 2 | use crate::int::monotone::chain::builder::ChainBuilder; 3 | use crate::int::monotone::chain::vertex::ChainVertex; 4 | use crate::int::monotone::flat::triangulator::FlatTriangulation; 5 | use crate::int::monotone::net::triangulator::NetTriangulation; 6 | use crate::int::triangulation::{IndexType, IntTriangulation, RawIntTriangulation}; 7 | use alloc::vec::Vec; 8 | use i_overlay::i_float::int::point::IntPoint; 9 | use i_overlay::i_shape::flat::buffer::FlatContoursBuffer; 10 | use i_overlay::i_shape::int::shape::{IntContour, IntShape}; 11 | 12 | #[derive(Default)] 13 | pub(crate) struct MonotoneTriangulator { 14 | vertices: Option>, 15 | vertices_builder: ChainBuilder, 16 | } 17 | 18 | impl MonotoneTriangulator { 19 | #[inline] 20 | pub(crate) fn shape_into_net_triangulation( 21 | &mut self, 22 | shape: &IntShape, 23 | points: Option<&[IntPoint]>, 24 | triangulation: &mut RawIntTriangulation, 25 | ) { 26 | let points_count = points.map(|points| points.len()).unwrap_or(0); 27 | 28 | let mut vertices = self.vertices.take().unwrap_or_default(); 29 | self.vertices_builder 30 | .shape_to_vertices(shape, points, &mut vertices); 31 | 32 | vertices.net_triangulate_into(shape.triangles_count(points_count), triangulation); 33 | 34 | self.vertices = Some(vertices); 35 | } 36 | 37 | #[inline] 38 | pub(crate) fn contour_into_net_triangulation( 39 | &mut self, 40 | contour: &IntContour, 41 | points: Option<&[IntPoint]>, 42 | triangulation: &mut RawIntTriangulation, 43 | ) { 44 | let points_count = points.map(|points| points.len()).unwrap_or(0); 45 | 46 | let mut vertices = self.vertices.take().unwrap_or_default(); 47 | self.vertices_builder 48 | .contour_to_vertices(contour, points, &mut vertices); 49 | 50 | vertices.net_triangulate_into(contour.triangles_count(points_count), triangulation); 51 | 52 | self.vertices = Some(vertices); 53 | } 54 | 55 | #[inline] 56 | pub(crate) fn flat_into_net_triangulation( 57 | &mut self, 58 | flat: &FlatContoursBuffer, 59 | triangulation: &mut RawIntTriangulation, 60 | ) { 61 | let mut vertices = self.vertices.take().unwrap_or_default(); 62 | self.vertices_builder.flat_to_vertices(flat, &mut vertices); 63 | 64 | vertices.net_triangulate_into(flat.triangles_count(0), triangulation); 65 | 66 | self.vertices = Some(vertices); 67 | } 68 | 69 | #[inline] 70 | pub(crate) fn shape_into_flat_triangulation( 71 | &mut self, 72 | shape: &IntShape, 73 | triangulation: &mut IntTriangulation, 74 | ) { 75 | let mut vertices = self.vertices.take().unwrap_or_default(); 76 | self.vertices_builder 77 | .shape_to_vertices(shape, None, &mut vertices); 78 | 79 | vertices.flat_triangulate_into(shape.triangles_count(0), triangulation); 80 | 81 | self.vertices = Some(vertices); 82 | } 83 | 84 | #[inline] 85 | pub(crate) fn contour_into_flat_triangulation( 86 | &mut self, 87 | contour: &IntContour, 88 | triangulation: &mut IntTriangulation, 89 | ) { 90 | let mut vertices = self.vertices.take().unwrap_or_default(); 91 | self.vertices_builder 92 | .contour_to_vertices(contour, None, &mut vertices); 93 | 94 | vertices.flat_triangulate_into(contour.triangles_count(0), triangulation); 95 | 96 | self.vertices = Some(vertices); 97 | } 98 | 99 | #[inline] 100 | pub(crate) fn flat_into_flat_triangulation( 101 | &mut self, 102 | flat: &FlatContoursBuffer, 103 | triangulation: &mut IntTriangulation, 104 | ) { 105 | let mut vertices = self.vertices.take().unwrap_or_default(); 106 | self.vertices_builder.flat_to_vertices(flat, &mut vertices); 107 | 108 | vertices.flat_triangulate_into(flat.triangles_count(0), triangulation); 109 | 110 | self.vertices = Some(vertices); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /iTriangle/src/int/monotone/v_segment.rs: -------------------------------------------------------------------------------- 1 | use core::cmp::Ordering; 2 | use i_overlay::i_float::int::point::IntPoint; 3 | use i_overlay::i_float::triangle::Triangle; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 6 | pub(crate) struct VSegment { 7 | pub(crate) a: IntPoint, 8 | pub(crate) b: IntPoint, 9 | } 10 | 11 | impl VSegment { 12 | #[inline] 13 | fn is_under_segment_order(&self, other: &VSegment) -> Ordering { 14 | match self.b.cmp(&other.b) { 15 | Ordering::Less => Triangle::clock_order_point(self.b, other.a, other.b), 16 | Ordering::Equal => Triangle::clock_order_point(self.b, self.a, other.a), 17 | Ordering::Greater => Triangle::clock_order_point(other.b, self.b, self.a), 18 | } 19 | } 20 | 21 | #[inline] 22 | pub(crate) fn is_under_point_order(&self, p: IntPoint) -> Ordering { 23 | debug_assert!(self.a.x <= p.x && p.x <= self.b.x); 24 | Triangle::clock_order_point(self.a, p, self.b) 25 | } 26 | } 27 | 28 | impl PartialOrd for VSegment { 29 | #[inline] 30 | fn partial_cmp(&self, other: &Self) -> Option { 31 | Some(self.cmp(other)) 32 | } 33 | } 34 | 35 | impl Ord for VSegment { 36 | #[inline] 37 | fn cmp(&self, other: &Self) -> Ordering { 38 | self.is_under_segment_order(other) 39 | } 40 | } 41 | 42 | impl Default for VSegment { 43 | #[inline] 44 | fn default() -> Self { 45 | Self { 46 | a: IntPoint::ZERO, 47 | b: IntPoint::ZERO, 48 | } 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use core::cmp::Ordering; 55 | use i_overlay::i_float::int::point::IntPoint; 56 | use crate::int::monotone::v_segment::VSegment; 57 | 58 | #[test] 59 | fn test_0() { 60 | let v0 = VSegment { a: IntPoint::new(0, 0), b: IntPoint::new(5, 0) }; 61 | let v1 = VSegment { a: IntPoint::new(0, 0), b: IntPoint::new(5, 5) }; 62 | 63 | assert_eq!(v0.is_under_segment_order(&v1), Ordering::Less); 64 | } 65 | 66 | #[test] 67 | fn test_1() { 68 | let v0 = VSegment { a: IntPoint::new(-2, -2), b: IntPoint::new(5, -2) }; 69 | let v1 = VSegment { a: IntPoint::new(0, 0), b: IntPoint::new(5, 0) }; 70 | 71 | assert_eq!(v0.is_under_segment_order(&v1), Ordering::Less); 72 | } 73 | 74 | #[test] 75 | fn test_2() { 76 | let v0 = VSegment { a: IntPoint::new(-2, -5), b: IntPoint::new(5, 0) }; 77 | let v1 = VSegment { a: IntPoint::new(0, 0), b: IntPoint::new(5, 0) }; 78 | 79 | assert_eq!(v0.is_under_segment_order(&v1), Ordering::Less); 80 | } 81 | 82 | #[test] 83 | fn test_3() { 84 | let v0 = VSegment { a: IntPoint::new(0, -5), b: IntPoint::new(5, 5) }; 85 | let v1 = VSegment { a: IntPoint::new(0, 0), b: IntPoint::new(5, 0) }; 86 | 87 | assert_eq!(v0.is_under_segment_order(&v1), Ordering::Greater); 88 | } 89 | } -------------------------------------------------------------------------------- /iTriangle/src/int/solver.rs: -------------------------------------------------------------------------------- 1 | use crate::int::meta::MeshMetaProvider; 2 | use crate::int::monotone::triangulator::MonotoneTriangulator; 3 | use crate::int::triangulation::RawIntTriangulation; 4 | use crate::int::unchecked::IntUncheckedTriangulatable; 5 | use crate::int::validation::Validation; 6 | use alloc::vec::Vec; 7 | use i_overlay::core::simplify::Simplify; 8 | use i_overlay::i_float::int::point::IntPoint; 9 | use i_overlay::i_shape::int::shape::{IntContour, IntShape, IntShapes}; 10 | 11 | pub(super) struct ShapesSolver; 12 | pub(super) struct ShapeSolver; 13 | pub(super) struct ContourSolver; 14 | 15 | impl ShapesSolver { 16 | #[inline] 17 | pub(super) fn triangulate(validation: Validation, shapes: &IntShapes) -> RawIntTriangulation { 18 | let shapes = shapes.simplify(validation.fill_rule, validation.options); 19 | Self::uncheck_triangulate(&shapes) 20 | } 21 | 22 | pub(super) fn uncheck_triangulate(shapes: &IntShapes) -> RawIntTriangulation { 23 | if shapes.len() <= 1 { 24 | return if let Some(first) = shapes.first() { 25 | first.uncheck_triangulate() 26 | } else { 27 | Default::default() 28 | }; 29 | } 30 | 31 | let mut triangles_count = 0; 32 | let mut points_count = 0; 33 | for shape in shapes.iter() { 34 | let meta = shape.meta(0); 35 | triangles_count += meta.triangles_count; 36 | points_count += meta.vertices_count; 37 | } 38 | 39 | let mut triangles = Vec::with_capacity(triangles_count); 40 | let mut points = Vec::with_capacity(points_count); 41 | 42 | let mut iter = shapes.iter(); 43 | if let Some(first) = iter.next() { 44 | let mut raw_0 = first.uncheck_triangulate(); 45 | triangles.append(&mut raw_0.triangles); 46 | points.append(&mut raw_0.points); 47 | 48 | for shape in iter { 49 | let points_offset = points.len(); 50 | let triangle_offset = triangles.len(); 51 | let mut raw_i = shape.uncheck_triangulate(); 52 | raw_i.shift(points_offset, triangle_offset); 53 | 54 | triangles.append(&mut raw_i.triangles); 55 | points.append(&mut raw_i.points); 56 | } 57 | } 58 | 59 | RawIntTriangulation::new(triangles, points) 60 | } 61 | 62 | #[inline] 63 | pub(super) fn triangulate_with_steiner_points( 64 | validation: Validation, 65 | shapes: &IntShapes, 66 | points: &[IntPoint], 67 | ) -> RawIntTriangulation { 68 | shapes 69 | .simplify(validation.fill_rule, validation.options) 70 | .uncheck_triangulate_with_steiner_points(points) 71 | } 72 | 73 | pub(super) fn uncheck_triangulate_with_steiner_points( 74 | shapes: &IntShapes, 75 | groups: &[Vec], 76 | ) -> RawIntTriangulation { 77 | if shapes.len() <= 1 { 78 | return if let Some(first) = shapes.first() { 79 | first.uncheck_triangulate_with_steiner_points(&groups[0]) 80 | } else { 81 | Default::default() 82 | }; 83 | } 84 | 85 | let mut triangles_count = 0; 86 | let mut points_count = 0; 87 | for (i, shape) in shapes.iter().enumerate() { 88 | let meta = shape.meta(groups[i].len()); 89 | triangles_count += meta.triangles_count; 90 | points_count += meta.vertices_count; 91 | } 92 | 93 | let mut triangles = Vec::with_capacity(triangles_count); 94 | let mut points = Vec::with_capacity(points_count); 95 | 96 | let mut raw_buffer = RawIntTriangulation::default(); 97 | 98 | let mut triangulator = MonotoneTriangulator::default(); 99 | triangulator.shape_into_net_triangulation(&shapes[0], Some(&groups[0]), &mut raw_buffer); 100 | 101 | triangles.extend_from_slice(&raw_buffer.triangles); 102 | points.extend_from_slice(&raw_buffer.points); 103 | 104 | let mut i = 1; 105 | while i < shapes.len() { 106 | let shape = &shapes[i]; 107 | let steiner_points = &groups[i]; 108 | i += 1; 109 | 110 | let points_offset = points.len(); 111 | let triangle_offset = triangles.len(); 112 | 113 | triangulator.shape_into_net_triangulation(shape, Some(steiner_points), &mut raw_buffer); 114 | raw_buffer.shift(points_offset, triangle_offset); 115 | 116 | triangles.extend_from_slice(&raw_buffer.triangles); 117 | points.extend_from_slice(&raw_buffer.points); 118 | } 119 | 120 | RawIntTriangulation::new(triangles, points) 121 | } 122 | } 123 | 124 | impl ShapeSolver { 125 | #[inline] 126 | pub(super) fn triangulate(validation: Validation, shape: &IntShape) -> RawIntTriangulation { 127 | let shapes = shape.simplify(validation.fill_rule, validation.options); 128 | ShapesSolver::uncheck_triangulate(&shapes) 129 | } 130 | 131 | #[inline] 132 | pub(super) fn uncheck_triangulate(shape: &IntShape) -> RawIntTriangulation { 133 | let mut raw = RawIntTriangulation::default(); 134 | MonotoneTriangulator::default().shape_into_net_triangulation(shape, None, &mut raw); 135 | raw 136 | } 137 | 138 | #[inline] 139 | pub(super) fn triangulate_with_steiner_points( 140 | validation: Validation, 141 | shape: &IntShape, 142 | points: &[IntPoint], 143 | ) -> RawIntTriangulation { 144 | shape 145 | .simplify(validation.fill_rule, validation.options) 146 | .uncheck_triangulate_with_steiner_points(points) 147 | } 148 | 149 | #[inline] 150 | pub(super) fn uncheck_triangulate_with_steiner_points( 151 | shape: &IntShape, 152 | points: &[IntPoint], 153 | ) -> RawIntTriangulation { 154 | if shape.len() <= 1 { 155 | return if let Some(first) = shape.first() { 156 | first.uncheck_triangulate_with_steiner_points(points) 157 | } else { 158 | RawIntTriangulation::default() 159 | }; 160 | } 161 | let mut raw = RawIntTriangulation::default(); 162 | MonotoneTriangulator::default().shape_into_net_triangulation(shape, Some(points), &mut raw); 163 | raw 164 | } 165 | } 166 | 167 | impl ContourSolver { 168 | #[inline] 169 | pub(super) fn triangulate(validation: Validation, contour: &IntContour) -> RawIntTriangulation { 170 | contour 171 | .simplify(validation.fill_rule, validation.options) 172 | .uncheck_triangulate() 173 | } 174 | 175 | #[inline] 176 | pub(super) fn uncheck_triangulate(contour: &IntContour) -> RawIntTriangulation { 177 | if contour.len() < 3 { 178 | RawIntTriangulation::default() 179 | } else { 180 | let mut raw = RawIntTriangulation::default(); 181 | MonotoneTriangulator::default().contour_into_net_triangulation(contour, None, &mut raw); 182 | raw 183 | } 184 | } 185 | 186 | #[inline] 187 | pub(super) fn triangulate_with_steiner_points( 188 | validation: Validation, 189 | contour: &IntContour, 190 | points: &[IntPoint], 191 | ) -> RawIntTriangulation { 192 | contour 193 | .simplify(validation.fill_rule, validation.options) 194 | .uncheck_triangulate_with_steiner_points(points) 195 | } 196 | 197 | #[inline] 198 | pub(super) fn uncheck_triangulate_with_steiner_points( 199 | contour: &IntContour, 200 | points: &[IntPoint], 201 | ) -> RawIntTriangulation { 202 | if contour.len() < 3 { 203 | Default::default() 204 | } else { 205 | let mut raw = RawIntTriangulation::default(); 206 | MonotoneTriangulator::default().contour_into_net_triangulation( 207 | contour, 208 | Some(points), 209 | &mut raw, 210 | ); 211 | raw 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /iTriangle/src/int/triangulatable.rs: -------------------------------------------------------------------------------- 1 | use crate::int::triangulation::RawIntTriangulation; 2 | use i_overlay::i_float::int::point::IntPoint; 3 | use i_overlay::i_shape::int::shape::{IntContour, IntShape, IntShapes}; 4 | use crate::int::solver::{ContourSolver, ShapeSolver, ShapesSolver}; 5 | /// A trait for performing triangulation with default validation settings. 6 | /// 7 | /// Provides a simplified interface for converting shapes or contours into triangle meshes. 8 | /// Internally applies the default [`DisposableTriangulator`] settings: 9 | /// - [`FillRule::NonZero`] 10 | /// - Minimum area = `0` 11 | /// - Orientation = counter-clockwise for outer contours, clockwise for holes 12 | /// 13 | /// # Implemented For 14 | /// - [`IntContour`] 15 | /// - [`IntShape`] 16 | /// - [`IntShapes`] 17 | /// 18 | /// # Output 19 | /// Returns an [`RawIntTriangulation`] containing vertex indices and point data. 20 | /// 21 | /// # Steiner Points 22 | /// Use [`triangulate_with_steiner_points`] to inject additional internal points during triangulation. 23 | pub trait IntTriangulatable { 24 | /// Triangulates the shape(s) with automatic validation and cleanup. 25 | /// 26 | /// Uses the default [`DisposableTriangulator`] (non-zero fill rule, zero area threshold). 27 | fn triangulate(&self) -> RawIntTriangulation; 28 | 29 | /// Triangulates the shape(s) with inserted Steiner points. 30 | /// 31 | /// Points must lie within the shape's valid interior area (not on edges). 32 | fn triangulate_with_steiner_points(&self, points: &[IntPoint]) -> RawIntTriangulation; 33 | } 34 | 35 | impl IntTriangulatable for IntContour { 36 | #[inline] 37 | fn triangulate(&self) -> RawIntTriangulation { 38 | ContourSolver::triangulate(Default::default(), self) 39 | } 40 | 41 | #[inline] 42 | fn triangulate_with_steiner_points(&self, points: &[IntPoint]) -> RawIntTriangulation { 43 | ContourSolver::triangulate_with_steiner_points(Default::default(), self, points) 44 | } 45 | } 46 | 47 | impl IntTriangulatable for IntShape { 48 | #[inline] 49 | fn triangulate(&self) -> RawIntTriangulation { 50 | ShapeSolver::triangulate(Default::default(), self) 51 | } 52 | 53 | #[inline] 54 | fn triangulate_with_steiner_points(&self, points: &[IntPoint]) -> RawIntTriangulation { 55 | ShapeSolver::triangulate_with_steiner_points(Default::default(), self, points) 56 | } 57 | } 58 | 59 | impl IntTriangulatable for IntShapes { 60 | #[inline] 61 | fn triangulate(&self) -> RawIntTriangulation { 62 | ShapesSolver::triangulate(Default::default(), self) 63 | } 64 | 65 | #[inline] 66 | fn triangulate_with_steiner_points(&self, points: &[IntPoint]) -> RawIntTriangulation { 67 | ShapesSolver::triangulate_with_steiner_points(Default::default(), self, points) 68 | } 69 | } -------------------------------------------------------------------------------- /iTriangle/src/int/unchecked.rs: -------------------------------------------------------------------------------- 1 | use crate::int::binder::SteinerInference; 2 | use crate::int::solver::{ContourSolver, ShapeSolver, ShapesSolver}; 3 | use crate::int::triangulation::RawIntTriangulation; 4 | use i_overlay::i_float::int::point::IntPoint; 5 | use i_overlay::i_shape::int::shape::{IntContour, IntShape, IntShapes}; 6 | 7 | /// A trait for performing triangulation on already validated geometry. 8 | /// 9 | /// Skips all shape simplification and orientation checks for maximum performance. 10 | /// Useful when the input is known to be valid (e.g., preprocessed or generated data). 11 | /// 12 | /// # ⚠️ Safety Requirements 13 | /// - Outer contours must be in **counter-clockwise** order 14 | /// - Holes must be in **clockwise** order 15 | /// - Shapes must not have self-intersections 16 | /// - Steiner points must be **strictly inside** the shape 17 | /// 18 | /// # Implemented For 19 | /// - [`IntContour`] 20 | /// - [`IntShape`] 21 | /// - [`IntShapes`] 22 | pub trait IntUncheckedTriangulatable { 23 | /// Performs triangulation without applying any shape simplification or validation. 24 | fn uncheck_triangulate(&self) -> RawIntTriangulation; 25 | 26 | /// Performs triangulation without validation, inserting the given Steiner points. 27 | /// 28 | /// Points are grouped and applied based on their target shape. 29 | fn uncheck_triangulate_with_steiner_points(&self, points: &[IntPoint]) -> RawIntTriangulation; 30 | } 31 | 32 | impl IntUncheckedTriangulatable for IntContour { 33 | #[inline] 34 | fn uncheck_triangulate(&self) -> RawIntTriangulation { 35 | ContourSolver::uncheck_triangulate(self) 36 | } 37 | 38 | #[inline] 39 | fn uncheck_triangulate_with_steiner_points(&self, points: &[IntPoint]) -> RawIntTriangulation { 40 | ContourSolver::uncheck_triangulate_with_steiner_points(self, points) 41 | } 42 | } 43 | 44 | impl IntUncheckedTriangulatable for IntShape { 45 | #[inline] 46 | fn uncheck_triangulate(&self) -> RawIntTriangulation { 47 | ShapeSolver::uncheck_triangulate(self) 48 | } 49 | 50 | #[inline] 51 | fn uncheck_triangulate_with_steiner_points(&self, points: &[IntPoint]) -> RawIntTriangulation { 52 | ShapeSolver::uncheck_triangulate_with_steiner_points(self, points) 53 | } 54 | } 55 | 56 | impl IntUncheckedTriangulatable for IntShapes { 57 | #[inline] 58 | fn uncheck_triangulate(&self) -> RawIntTriangulation { 59 | ShapesSolver::uncheck_triangulate(self) 60 | } 61 | 62 | #[inline] 63 | fn uncheck_triangulate_with_steiner_points(&self, points: &[IntPoint]) -> RawIntTriangulation { 64 | let group = self.group_by_shapes(points); 65 | ShapesSolver::uncheck_triangulate_with_steiner_points(self, &group) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /iTriangle/src/int/validation.rs: -------------------------------------------------------------------------------- 1 | use i_overlay::core::fill_rule::FillRule; 2 | use i_overlay::core::overlay::IntOverlayOptions; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Validation { 6 | pub fill_rule: FillRule, 7 | pub options: IntOverlayOptions, 8 | } 9 | 10 | impl Validation { 11 | pub fn with_fill_rule(fill_rule: FillRule) -> Self { 12 | Self { 13 | fill_rule, 14 | options: IntOverlayOptions::keep_output_points(), 15 | } 16 | } 17 | } 18 | 19 | impl Default for Validation { 20 | fn default() -> Self { 21 | Self { 22 | fill_rule: FillRule::NonZero, 23 | options: IntOverlayOptions::keep_output_points(), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /iTriangle/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | extern crate alloc; 3 | 4 | pub mod advanced; 5 | pub mod float; 6 | pub mod geom; 7 | pub mod int; 8 | pub mod tessellation; 9 | mod index; 10 | 11 | pub use i_overlay; -------------------------------------------------------------------------------- /iTriangle/src/tessellation/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod split; 2 | pub mod circumcenter; -------------------------------------------------------------------------------- /iTriangle/src/tessellation/split.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use i_overlay::i_float::int::point::IntPoint; 3 | use i_overlay::i_shape::int::shape::{IntContour, IntShape, IntShapes}; 4 | 5 | pub trait SliceContour { 6 | fn slice_contour(&self, max_edge_length: u32) -> Self; 7 | } 8 | 9 | const SCALE: u32 = 29; 10 | 11 | impl SliceContour for IntContour { 12 | #[inline] 13 | fn slice_contour(&self, max_edge_length: u32) -> Self { 14 | let mut a = if let Some(last) = self.last() { 15 | *last 16 | } else { 17 | return Vec::new(); 18 | }; 19 | 20 | let radius = max_edge_length as u64; 21 | let sqr_radius = radius.pow(2); 22 | 23 | let mut contour = IntContour::with_capacity(2 * self.len()); 24 | 25 | for &b in self.iter() { 26 | extract(a, b, radius, sqr_radius, &mut contour); 27 | a = b; 28 | } 29 | 30 | contour 31 | } 32 | } 33 | 34 | impl SliceContour for IntShape { 35 | #[inline] 36 | fn slice_contour(&self, max_edge_length: u32) -> Self { 37 | let mut shape = Vec::with_capacity(self.len()); 38 | 39 | for contour in self.iter() { 40 | shape.push(contour.slice_contour(max_edge_length)); 41 | } 42 | 43 | shape 44 | } 45 | } 46 | 47 | impl SliceContour for IntShapes { 48 | #[inline] 49 | fn slice_contour(&self, max_edge_length: u32) -> Self { 50 | let mut shapes = Vec::with_capacity(self.len()); 51 | 52 | for shape in self.iter() { 53 | shapes.push(shape.slice_contour(max_edge_length)); 54 | } 55 | 56 | shapes 57 | } 58 | } 59 | 60 | #[inline] 61 | fn extract(a: IntPoint, b: IntPoint, radius: u64, sqr_radius: u64, contour: &mut IntContour) { 62 | let ab = b.subtract(a); 63 | let sqr_len = ab.sqr_length() as u64; 64 | if sqr_len <= sqr_radius { 65 | contour.push(b); 66 | return; 67 | } 68 | let len = sqr_len.isqrt(); 69 | let n = ((len + (radius >> 1)) / radius) as i64; 70 | 71 | if n <= 1 { 72 | contour.push(b); 73 | return; 74 | } 75 | 76 | if n == 2 { 77 | let x = ((a.x as i64 + b.x as i64) / 2) as i32; 78 | let y = ((a.y as i64 + b.y as i64) / 2) as i32; 79 | 80 | contour.push(IntPoint::new(x, y)); 81 | contour.push(b); 82 | return; 83 | } 84 | 85 | 86 | let sx = (ab.x << SCALE) / n; 87 | let sy = (ab.y << SCALE) / n; 88 | 89 | let mut dx = 0i64; 90 | let mut dy = 0i64; 91 | 92 | for _ in 1..n { 93 | dx += sx; 94 | dy += sy; 95 | 96 | let x = a.x + (dx >> SCALE) as i32; 97 | let y = a.y + (dy >> SCALE) as i32; 98 | 99 | contour.push(IntPoint::new(x, y)); 100 | } 101 | contour.push(b); 102 | } 103 | 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use alloc::vec; 108 | use i_overlay::i_float::int::point::IntPoint; 109 | use crate::tessellation::split::SliceContour; 110 | 111 | #[test] 112 | fn test_0() { 113 | let contour = vec![ 114 | IntPoint::new(0, 0), 115 | IntPoint::new(10, 0), 116 | IntPoint::new(10, 10), 117 | IntPoint::new(0, 10), 118 | ]; 119 | 120 | let s0 = contour.slice_contour(8); 121 | assert_eq!(s0.len(), 4); 122 | 123 | let s1 = contour.slice_contour(7); 124 | assert_eq!(s1.len(), 4); 125 | 126 | let s2 = contour.slice_contour(6); 127 | assert_eq!(s2.len(), 8); 128 | 129 | let s3 = contour.slice_contour(5); 130 | assert_eq!(s3.len(), 8); 131 | 132 | let s4 = contour.slice_contour(3); 133 | assert_eq!(s4.len(), 12); 134 | } 135 | } -------------------------------------------------------------------------------- /iTriangle/tests/doc_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use i_triangle::float::triangulatable::Triangulatable; 4 | use i_triangle::float::triangulation::Triangulation; 5 | 6 | #[test] 7 | fn test_0() { 8 | let shape = vec![ 9 | vec![ 10 | // body 11 | [0.0, 20.0], // 0 12 | [-10.0, 8.0], // 1 13 | [-7.0, 6.0], // 2 14 | [-6.0, 2.0], // 3 15 | [-8.0, -2.0], // 4 16 | [-13.0, -4.0], // 5 17 | [-16.0, -3.0], // 6 18 | [-18.0, 0.0], // 7 19 | [-25.0, -7.0], // 8 20 | [-14.0, -15.0], // 9 21 | [0.0, -18.0], // 10 22 | [14.0, -15.0], // 11 23 | [26.0, -7.0], // 12 24 | [17.0, 1.0], // 13 25 | [13.0, -1.0], // 14 26 | [9.0, 1.0], // 15 27 | [7.0, 6.0], // 16 28 | [8.0, 10.0], // 17 29 | ], 30 | vec![ 31 | // hole 32 | [2.0, 0.0], // 0 33 | [5.0, -2.0], // 1 34 | [7.0, -5.0], // 2 35 | [5.0, -9.0], // 3 36 | [2.0, -11.0], // 4 37 | [-2.0, -9.0], // 5 38 | [-4.0, -5.0], // 6 39 | [-2.0, -2.0], // 7 40 | ], 41 | ]; 42 | 43 | let triangulation = shape.triangulate().to_triangulation::(); 44 | 45 | println!("points: {:?}", triangulation.points); 46 | println!("indices: {:?}", triangulation.indices); 47 | 48 | let delaunay_triangulation: Triangulation<[f64; 2], u16> = 49 | shape.triangulate().into_delaunay().to_triangulation(); 50 | 51 | println!("points: {:?}", delaunay_triangulation.points); 52 | println!("indices: {:?}", delaunay_triangulation.indices); 53 | 54 | let convex_polygons = shape.triangulate().into_delaunay().to_convex_polygons(); 55 | 56 | println!("convex polygons: {:?}", convex_polygons); 57 | 58 | let tessellation: Triangulation<[f64; 2], u16> = shape 59 | .triangulate() 60 | .into_delaunay() 61 | .refine_with_circumcenters_by_obtuse_angle(0.0) 62 | .to_triangulation(); 63 | 64 | println!("points: {:?}", tessellation.points); 65 | println!("indices: {:?}", tessellation.indices); 66 | 67 | let centroids = shape 68 | .triangulate() 69 | .into_delaunay() 70 | .refine_with_circumcenters_by_obtuse_angle(0.0) 71 | .to_centroid_net(0.0); 72 | 73 | println!("centroids: {:?}", centroids); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /iTriangle/tests/float_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use i_overlay::core::fill_rule::FillRule; 4 | use i_overlay::float::simplify::SimplifyShape; 5 | use i_overlay::i_shape::base::data::Contour; 6 | use i_overlay::i_shape::float::area::Area; 7 | use i_triangle::float::triangulatable::Triangulatable; 8 | use i_triangle::float::triangulation::Triangulation; 9 | use i_triangle::float::triangulator::Triangulator; 10 | use rand::Rng; 11 | 12 | #[test] 13 | fn test_0() { 14 | let shape = [[ 15 | [1.0, 1.0], 16 | [1.0, 4.0], 17 | [2.0, 4.0], 18 | [2.0, 3.0], 19 | [3.0, 3.0], 20 | [3.0, 1.0], 21 | ] 22 | .to_vec()] 23 | .to_vec(); 24 | 25 | let triangulation = shape.triangulate().to_triangulation::(); 26 | 27 | assert_eq!(triangulation.points.len(), 6); 28 | assert_eq!(triangulation.indices.len(), 12); 29 | } 30 | 31 | #[test] 32 | fn test_1() { 33 | let contour = [ 34 | [0.0, 2.0], 35 | [0.0, 0.0], 36 | [4.0, 2.0], 37 | [4.0, 0.0] 38 | ]; 39 | 40 | let mut triangulator = Triangulator::::default(); 41 | 42 | triangulator.delaunay(false); 43 | let t0 = triangulator.triangulate(&contour); 44 | 45 | triangulator.delaunay(true); 46 | let t1 = triangulator.triangulate(&contour); 47 | 48 | let area = contour.simplify_shape(FillRule::NonZero).area(); 49 | 50 | t0.validate(area, 0.001); 51 | t1.validate(area, 0.001); 52 | } 53 | 54 | #[test] 55 | fn test_2() { 56 | let contour = [ 57 | [0.0, 3.0], 58 | [-4.0, -3.0], 59 | [4.0, -3.0], 60 | [3.0, -3.0], 61 | [0.0, 3.0] 62 | ]; 63 | 64 | let simple = contour.simplify_shape(FillRule::NonZero); 65 | let area = simple.area(); 66 | 67 | let mut triangulator = Triangulator::::default(); 68 | 69 | triangulator.delaunay(false); 70 | let t0 = triangulator.triangulate(&contour); 71 | 72 | triangulator.delaunay(true); 73 | let t1 = triangulator.triangulate(&contour); 74 | 75 | t0.validate(area, 0.001); 76 | t1.validate(area, 0.001); 77 | } 78 | 79 | #[test] 80 | fn test_3() { 81 | let contour = [ 82 | [2.0, 3.0], 83 | [2.0, -2.0], 84 | [0.0, 3.0], 85 | [-1.0, 4.0], 86 | [0.0, 1.0] 87 | ]; 88 | 89 | let mut triangulator = Triangulator::::default(); 90 | 91 | triangulator.delaunay(false); 92 | let t0 = triangulator.triangulate(&contour); 93 | 94 | triangulator.delaunay(true); 95 | let t1 = triangulator.triangulate(&contour); 96 | 97 | let area = contour.simplify_shape(FillRule::NonZero).area(); 98 | 99 | t0.validate(area, 0.001); 100 | t1.validate(area, 0.001); 101 | } 102 | 103 | #[test] 104 | fn test_random_0() { 105 | let mut triangulator = Triangulator::::default(); 106 | let mut t = Triangulation::with_capacity(8); 107 | 108 | for _ in 0..20_000 { 109 | let contour = random(8, 5); 110 | let area = contour.simplify_shape(FillRule::NonZero).area(); 111 | 112 | triangulator.delaunay(false); 113 | triangulator.triangulate_into(&contour, &mut t); 114 | t.validate(area, 0.001); 115 | 116 | triangulator.delaunay(true); 117 | triangulator.triangulate_into(&contour, &mut t); 118 | t.validate(area, 0.001); 119 | } 120 | } 121 | 122 | #[test] 123 | fn test_random_1() { 124 | let mut triangulator = Triangulator::::default(); 125 | let mut t = Triangulation::with_capacity(8); 126 | 127 | for _ in 0..20_000 { 128 | let contour = random(10, 6); 129 | let area = contour.simplify_shape(FillRule::NonZero).area(); 130 | 131 | triangulator.delaunay(false); 132 | triangulator.triangulate_into(&contour, &mut t); 133 | t.validate(area, 0.001); 134 | 135 | triangulator.delaunay(true); 136 | triangulator.triangulate_into(&contour, &mut t); 137 | t.validate(area, 0.001); 138 | } 139 | } 140 | 141 | #[test] 142 | fn test_random_2() { 143 | let mut triangulator = Triangulator::::default(); 144 | let mut t = Triangulation::with_capacity(8); 145 | 146 | for _ in 0..20_000 { 147 | let contour = random(10, 12); 148 | let area = contour.simplify_shape(FillRule::NonZero).area(); 149 | 150 | triangulator.delaunay(false); 151 | triangulator.triangulate_into(&contour, &mut t); 152 | t.validate(area, 0.001); 153 | 154 | triangulator.delaunay(true); 155 | triangulator.triangulate_into(&contour, &mut t); 156 | t.validate(area, 0.001); 157 | } 158 | } 159 | 160 | #[test] 161 | fn test_random_3() { 162 | let mut triangulator = Triangulator::::default(); 163 | let mut t = Triangulation::with_capacity(8); 164 | 165 | for _ in 0..10_000 { 166 | let contour = random(20, 20); 167 | let area = contour.simplify_shape(FillRule::NonZero).area(); 168 | 169 | triangulator.delaunay(false); 170 | triangulator.triangulate_into(&contour, &mut t); 171 | t.validate(area, 0.001); 172 | 173 | triangulator.delaunay(true); 174 | triangulator.triangulate_into(&contour, &mut t); 175 | t.validate(area, 0.001); 176 | } 177 | } 178 | 179 | #[test] 180 | fn test_random_4() { 181 | let mut triangulator = Triangulator::::default(); 182 | let mut t = Triangulation::with_capacity(8); 183 | 184 | for _ in 0..1_000 { 185 | let contour = random(30, 50); 186 | let area = contour.simplify_shape(FillRule::NonZero).area(); 187 | 188 | triangulator.delaunay(false); 189 | triangulator.triangulate_into(&contour, &mut t); 190 | t.validate(area, 0.001); 191 | 192 | triangulator.delaunay(true); 193 | triangulator.triangulate_into(&contour, &mut t); 194 | t.validate(area, 0.001); 195 | } 196 | } 197 | 198 | #[test] 199 | fn test_random_5() { 200 | let mut triangulator = Triangulator::::default(); 201 | let mut t = Triangulation::with_capacity(8); 202 | 203 | for _ in 0..500 { 204 | let main = random(50, 20); 205 | let mut shape = vec![main]; 206 | for _ in 0..10 { 207 | shape.push(random(30, 5)); 208 | } 209 | 210 | let area = shape.simplify_shape(FillRule::NonZero).area(); 211 | 212 | triangulator.delaunay(false); 213 | triangulator.triangulate_into(&shape, &mut t); 214 | t.validate(area, 0.001); 215 | 216 | triangulator.delaunay(true); 217 | triangulator.triangulate_into(&shape, &mut t); 218 | t.validate(area, 0.001); 219 | } 220 | } 221 | 222 | fn random(radius: i32, n: usize) -> Contour<[f32; 2]> { 223 | let a = radius / 2; 224 | let mut points = Vec::with_capacity(n); 225 | let mut rng = rand::rng(); 226 | for _ in 0..n { 227 | let x = rng.random_range(-a..=a) as f32; 228 | let y = rng.random_range(-a..=a) as f32; 229 | points.push([x, y]); 230 | } 231 | 232 | points 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /performance/rust_app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_app" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | #i_triangle = "0.33.0" 8 | i_triangle = { path = "../../iTriangle" } 9 | earcutr = "0.5.0" 10 | -------------------------------------------------------------------------------- /performance/rust_app/readme.txt: -------------------------------------------------------------------------------- 1 | cargo build --release 2 | 3 | to run 4 | 5 | target/release/rust_app --test 0 --complex true 6 | -------------------------------------------------------------------------------- /performance/rust_app/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::test::runner::Runner; 2 | use crate::test::test::TestData; 3 | use crate::test::test_2_star_with_hole::StarWithHoleTest; 4 | use crate::test::test_3_star_with_8_holes::StarWith8HolesTest; 5 | use crate::test::test_1_spiral::SpiralTest; 6 | use crate::test::test_0_star::StarTest; 7 | use crate::util::args::EnvArgs; 8 | 9 | mod test; 10 | mod util; 11 | 12 | fn main() { 13 | #[cfg(debug_assertions)] 14 | { 15 | debug_run(); 16 | } 17 | 18 | #[cfg(not(debug_assertions))] 19 | { 20 | release_run(); 21 | } 22 | } 23 | 24 | #[cfg(not(debug_assertions))] 25 | fn release_run() { 26 | let args = EnvArgs::new(); 27 | let test = args.get_usize("test"); 28 | match test { 29 | 0 => test_0(), 30 | 1 => test_1(), 31 | 2 => test_2(), 32 | 3 => test_3(), 33 | _ => { 34 | panic!("No test found") 35 | } 36 | } 37 | } 38 | 39 | #[cfg(debug_assertions)] 40 | fn debug_run() { 41 | 42 | } 43 | 44 | #[allow(dead_code)] 45 | fn test_0() { 46 | let mut tests = vec![ 47 | TestData::new(8, 10_000), 48 | TestData::new(16, 10_000), 49 | TestData::new(32, 10_000), 50 | TestData::new(64, 10_000), 51 | ]; 52 | 53 | for i in 1..9 { 54 | tests.push(TestData::new(64 << i, 256 >> i)); 55 | } 56 | 57 | let mut s; 58 | 59 | println!("flat + earcut: "); 60 | s = 0; 61 | for t in tests.iter() { 62 | let contour = StarTest::contour(t.count); 63 | s += Runner::run_triangle(&contour, t, false, true); 64 | } 65 | println!("s: {}", s); 66 | 67 | println!("flat + monotone: "); 68 | s = 0; 69 | for t in tests.iter() { 70 | let contour = StarTest::contour(t.count); 71 | s += Runner::run_triangle(&contour, t, false, false); 72 | } 73 | println!("s: {}", s); 74 | 75 | println!("flat + delaunay: "); 76 | s = 0; 77 | for t in tests.iter() { 78 | let contour = StarTest::contour(t.count); 79 | s += Runner::run_triangle(&contour, t, true, true); 80 | } 81 | println!("s: {}", s); 82 | 83 | println!("rust earcut: "); 84 | s = 0; 85 | for t in tests.iter() { 86 | let points = StarTest::points(t.count); 87 | s += Runner::run_earcut(points, t); 88 | } 89 | println!("s: {}", s); 90 | } 91 | 92 | #[allow(dead_code)] 93 | fn test_1() { 94 | let mut tests = vec![ 95 | TestData::new(8, 10_000), 96 | TestData::new(16, 10_000), 97 | TestData::new(32, 10_000), 98 | TestData::new(64, 10_000), 99 | ]; 100 | 101 | for i in 1..9 { 102 | tests.push(TestData::new(64 << i, 256 >> i)); 103 | } 104 | 105 | let mut s; 106 | 107 | println!("flat + earcut: "); 108 | s = 0; 109 | for t in tests.iter() { 110 | let resource = SpiralTest::resource(t.count); 111 | s += Runner::run_triangle(&resource, t, false, true); 112 | } 113 | println!("s: {}", s); 114 | 115 | println!("flat + monotone: "); 116 | s = 0; 117 | for t in tests.iter() { 118 | let resource = SpiralTest::resource(t.count); 119 | s += Runner::run_triangle(&resource, t, false, false); 120 | } 121 | println!("s: {}", s); 122 | 123 | println!("flat + delaunay: "); 124 | s = 0; 125 | for t in tests.iter() { 126 | let resource = SpiralTest::resource(t.count); 127 | s += Runner::run_triangle(&resource, t, true, true); 128 | } 129 | println!("s: {}", s); 130 | 131 | println!("rust earcut: "); 132 | s = 0; 133 | for t in tests.iter() { 134 | let points = SpiralTest::points(t.count); 135 | s += Runner::run_earcut(points, t); 136 | } 137 | println!("s: {}", s); 138 | } 139 | 140 | #[allow(dead_code)] 141 | fn test_2() { 142 | let mut tests = Vec::new(); 143 | 144 | for i in 1..9 { 145 | tests.push(TestData::new(64 << i, 256 >> i)); 146 | } 147 | 148 | let mut s; 149 | 150 | println!("flat + earcut: "); 151 | s = 0; 152 | for t in tests.iter() { 153 | let resource = StarWithHoleTest::resource(t.count); 154 | s += Runner::run_triangle(&resource, t, false, true); 155 | } 156 | println!("s: {}", s); 157 | 158 | println!("flat + monotone: "); 159 | s = 0; 160 | for t in tests.iter() { 161 | let resource = StarWithHoleTest::resource(t.count); 162 | s += Runner::run_triangle(&resource, t, false, false); 163 | } 164 | println!("s: {}", s); 165 | 166 | println!("flat + delaunay: "); 167 | s = 0; 168 | for t in tests.iter() { 169 | let resource = StarWithHoleTest::resource(t.count); 170 | s += Runner::run_triangle(&resource, t, true, true); 171 | } 172 | println!("s: {}", s); 173 | 174 | println!("rust earcut: "); 175 | s = 0; 176 | for t in tests.iter() { 177 | let points = StarWithHoleTest::points(t.count); 178 | s += Runner::run_earcut(points, t); 179 | } 180 | println!("s: {}", s); 181 | } 182 | 183 | #[allow(dead_code)] 184 | fn test_3() { 185 | let mut tests = Vec::new(); 186 | 187 | for i in 1..8 { 188 | tests.push(TestData::new(128 << i, 256 >> i)); 189 | } 190 | 191 | let mut s; 192 | 193 | println!("flat + earcut: "); 194 | s = 0; 195 | for t in tests.iter() { 196 | let resource = StarWith8HolesTest::resource(t.count); 197 | s += Runner::run_triangle(&resource, t, false, true); 198 | } 199 | println!("s: {}", s); 200 | 201 | println!("flat + monotone: "); 202 | s = 0; 203 | for t in tests.iter() { 204 | let resource = StarWith8HolesTest::resource(t.count); 205 | s += Runner::run_triangle(&resource, t, false, false); 206 | } 207 | println!("s: {}", s); 208 | 209 | println!("flat + delaunay: "); 210 | s = 0; 211 | for t in tests.iter() { 212 | let resource = StarWith8HolesTest::resource(t.count); 213 | s += Runner::run_triangle(&resource, t, true, true); 214 | } 215 | println!("s: {}", s); 216 | 217 | println!("rust earcut: "); 218 | s = 0; 219 | for t in tests.iter() { 220 | let points = StarWith8HolesTest::points(t.count); 221 | s += Runner::run_earcut(points, t); 222 | } 223 | println!("s: {}", s); 224 | } -------------------------------------------------------------------------------- /performance/rust_app/src/test/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod test; 2 | pub(crate) mod test_2_star_with_hole; 3 | pub(crate) mod test_3_star_with_8_holes; 4 | pub(crate) mod test_1_spiral; 5 | pub(crate) mod test_0_star; 6 | pub(crate) mod runner; -------------------------------------------------------------------------------- /performance/rust_app/src/test/runner.rs: -------------------------------------------------------------------------------- 1 | use std::hint::black_box; 2 | use std::time::Instant; 3 | use i_triangle::float::triangulation::Triangulation; 4 | use i_triangle::float::triangulator::Triangulator; 5 | use i_triangle::i_overlay::i_float::float::compatible::FloatPointCompatible; 6 | use i_triangle::i_overlay::i_float::float::number::FloatNumber; 7 | use i_triangle::i_overlay::i_shape::source::resource::ShapeResource; 8 | use crate::test::test::TestData; 9 | 10 | pub(crate) struct Runner; 11 | 12 | impl Runner { 13 | 14 | pub(crate) fn run_triangle(resource: &R, test: &TestData, delaunay: bool, earcut: bool) -> usize 15 | where 16 | R: ShapeResource + ?Sized, 17 | P: FloatPointCompatible, 18 | T: FloatNumber, 19 | { 20 | let start = Instant::now(); 21 | 22 | let mut triangulator = Triangulator::::default(); 23 | triangulator.delaunay(delaunay); 24 | triangulator.earcut(earcut); 25 | 26 | let mut sum = 0; 27 | 28 | let mut triangulation = Triangulation::with_capacity(test.count); 29 | 30 | for _ in 0..test.repeat { 31 | black_box(triangulator.uncheck_triangulate_into(resource, &mut triangulation)); 32 | sum += triangulation.indices.len(); 33 | } 34 | 35 | let duration = start.elapsed(); 36 | let time = 1000_000.0 * duration.as_secs_f64() / test.repeat as f64; 37 | 38 | println!("{} - {:.8}", test.count, time); 39 | sum 40 | } 41 | 42 | pub(crate) fn run_earcut(points: Vec, test: &TestData) -> usize { 43 | let start = Instant::now(); 44 | 45 | let mut sum = 0; 46 | 47 | for _ in 0..test.repeat { 48 | let triangulation = black_box(earcutr::earcut(&points, &[], 2)).unwrap(); 49 | sum += triangulation.len(); 50 | } 51 | 52 | let duration = start.elapsed(); 53 | let time = 1000_000.0 * duration.as_secs_f64() / test.repeat as f64; 54 | 55 | println!("{} - {:.8}", test.count, time); 56 | sum 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /performance/rust_app/src/test/test.rs: -------------------------------------------------------------------------------- 1 | pub(crate) struct TestData { 2 | pub(crate) count: usize, 3 | pub(crate) repeat: usize 4 | } 5 | 6 | impl TestData { 7 | pub(crate) fn new(count: usize, repeat: usize) -> Self { 8 | Self { 9 | count, 10 | repeat, 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /performance/rust_app/src/test/test_0_star.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | pub(crate) struct StarTest; 4 | 5 | impl StarTest { 6 | 7 | pub(crate) fn contour(count: usize) -> Vec<[f32; 2]> { 8 | Self::star(count) 9 | } 10 | 11 | pub(crate) fn points(count: usize) -> Vec { 12 | Self::star(count) 13 | .into_iter() 14 | .flat_map(|p| p) 15 | .collect() 16 | } 17 | } 18 | 19 | impl StarTest { 20 | fn star(count: usize) -> Vec<[f32; 2]> { 21 | let r0 = 160.0; 22 | let r1 = 80.0; 23 | 24 | let n = count / 2; 25 | let da = PI / (n as f32); 26 | let mut a = 0.0f32; 27 | 28 | let mut path = Vec::with_capacity(count); 29 | for _ in 0..n { 30 | let (s, c) = a.sin_cos(); 31 | let x = c * r0; 32 | let y = s * r0; 33 | path.push([x, y]); 34 | 35 | a += da; 36 | 37 | let (s, c) = a.sin_cos(); 38 | let x = c * r1; 39 | let y = s * r1; 40 | 41 | path.push([x, y]); 42 | 43 | a += da; 44 | } 45 | 46 | path 47 | } 48 | } -------------------------------------------------------------------------------- /performance/rust_app/src/test/test_1_spiral.rs: -------------------------------------------------------------------------------- 1 | 2 | pub(crate) struct SpiralTest; 3 | 4 | impl SpiralTest { 5 | 6 | pub(crate) fn resource(count: usize) -> Vec<[f32; 2]> { 7 | Self::spiral(count) 8 | } 9 | 10 | pub(crate) fn points(count: usize) -> Vec { 11 | Self::spiral(count) 12 | .into_iter() 13 | .flat_map(|p| p) 14 | .collect() 15 | } 16 | } 17 | 18 | impl SpiralTest { 19 | fn spiral(count: usize) -> Vec<[f32; 2]> { 20 | let s = 10.0; 21 | 22 | let mut path_0: Vec<[f32; 2]> = Vec::with_capacity(count); 23 | let mut path_1: Vec<[f32; 2]> = Vec::with_capacity(count / 2); 24 | 25 | let mut s0 = s; 26 | let mut s1 = 2.0 * s; 27 | 28 | let mut x0 = 0.0f32; 29 | let mut y0 = 0.0f32; 30 | 31 | let mut x1 = 0.0f32; 32 | let mut y1 = 0.0f32; 33 | 34 | y0 += s0; 35 | path_0.push([x0, y0]); 36 | 37 | x0 += s0; 38 | path_0.push([x0, y0]); 39 | 40 | path_1.push([x1, y1]); 41 | 42 | x1 += s1; 43 | path_1.push([x1, y1]); 44 | s1 += s; 45 | 46 | let n = count - 4; 47 | 48 | for i in 0..n / 2 { 49 | match i % 4 { 50 | 0 => { 51 | y0 += s0; 52 | y1 += s1; 53 | } 54 | 1 => { 55 | x0 -= s0; 56 | x1 -= s1; 57 | } 58 | 2 => { 59 | y0 -= s0; 60 | y1 -= s1; 61 | } 62 | _ => { 63 | x0 += s0; 64 | x1 += s1; 65 | } 66 | } 67 | path_0.push([x0, y0]); 68 | path_1.push([x1, y1]); 69 | 70 | s0 += s; 71 | s1 += s; 72 | } 73 | 74 | path_1.extend(path_0.into_iter().rev()); 75 | path_1 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /performance/rust_app/src/test/test_2_star_with_hole.rs: -------------------------------------------------------------------------------- 1 | use crate::util::star_builder::StarBuilder; 2 | 3 | pub(crate) struct StarWithHoleTest; 4 | 5 | impl StarWithHoleTest { 6 | pub(crate) fn resource(count: usize) -> Vec> { 7 | Self::star(count) 8 | } 9 | 10 | pub(crate) fn points(count: usize) -> Vec { 11 | Self::star(count) 12 | .into_iter() 13 | .flatten() 14 | .flat_map(|p| p) 15 | .collect() 16 | } 17 | } 18 | 19 | impl StarWithHoleTest { 20 | fn star(count: usize) -> Vec> { 21 | let corners_count = 8; 22 | let points_per_corner = count / 8; 23 | let main = StarBuilder::generate_star( 24 | 80.0, 25 | 0.3, 26 | points_per_corner, 27 | corners_count, 28 | true, 29 | [0.0, 0.0], 30 | ); 31 | let hole = StarBuilder::generate_star( 32 | 40.0, 33 | 0.3, 34 | points_per_corner, 35 | corners_count, 36 | false, 37 | [0.0, 0.0], 38 | ); 39 | 40 | vec![main, hole] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /performance/rust_app/src/test/test_3_star_with_8_holes.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | use crate::util::star_builder::StarBuilder; 3 | 4 | pub(crate) struct StarWith8HolesTest; 5 | 6 | impl StarWith8HolesTest { 7 | pub(crate) fn resource(count: usize) -> Vec> { 8 | Self::star(count) 9 | } 10 | 11 | pub(crate) fn points(count: usize) -> Vec { 12 | Self::star(count) 13 | .into_iter() 14 | .flatten() 15 | .flat_map(|p| p) 16 | .collect() 17 | } 18 | } 19 | 20 | impl StarWith8HolesTest { 21 | fn star(count: usize) -> Vec> { 22 | let corners_count = 8; 23 | let holes = 8; 24 | let main_points_count = count / 2; 25 | let holes_points_count = count - main_points_count; 26 | let main_points_per_corner = main_points_count / corners_count; 27 | let hole_points_per_corner = holes_points_count / (corners_count * holes); 28 | 29 | let main = StarBuilder::generate_star( 30 | 80.0, 31 | 0.3, 32 | main_points_per_corner, 33 | corners_count, 34 | true, 35 | [0.0, 0.0], 36 | ); 37 | 38 | let mut shape = vec![main]; 39 | 40 | let r = 50.0f32; 41 | let n = holes as f32; 42 | for i in 0..holes { 43 | let a = i as f32 * 2.0 * PI / n; 44 | let sc = a.sin_cos(); 45 | let x = r * sc.1; 46 | let y = r * sc.0; 47 | let hole = StarBuilder::generate_star( 48 | 10.0, 49 | 0.3, 50 | hole_points_per_corner, 51 | corners_count, 52 | false, 53 | [x, y], 54 | ); 55 | shape.push(hole); 56 | } 57 | 58 | shape 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /performance/rust_app/src/util/args.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::env; 3 | 4 | pub(crate) struct EnvArgs { 5 | map: HashMap, 6 | } 7 | #[allow(dead_code)] 8 | impl EnvArgs { 9 | pub(crate) fn new() -> Self { 10 | let args = env::args(); 11 | let mut args_iter = args.peekable(); 12 | let mut args_map = HashMap::new(); 13 | 14 | while let Some(arg) = args_iter.next() { 15 | if arg.starts_with("--") { 16 | let key = arg.trim_start_matches("--").to_owned(); 17 | // If the next argument is also a key, store a boolean flag; otherwise, store the value. 18 | let value = if args_iter.peek().is_some_and(|a| a.starts_with("--")) { 19 | "true".to_string() 20 | } else { 21 | args_iter.next().unwrap() 22 | }; 23 | args_map.insert(key, value); 24 | } 25 | } 26 | 27 | Self { map: args_map } 28 | } 29 | 30 | pub(crate) fn get_usize(&self, name: &str) -> usize { 31 | let value = self.map.get(name).unwrap_or_else(|| panic!("{} is not set", name)); 32 | value 33 | .parse() 34 | .unwrap_or_else(|_| panic!("Unable to {} as an usize", name)) 35 | } 36 | 37 | pub(crate) fn get_bool(&self, name: &str) -> bool { 38 | let value = self.map.get(name).unwrap_or_else(|| panic!("{} is not set", name)); 39 | value 40 | .parse() 41 | .unwrap_or_else(|_| panic!("Unable to {} as a boolean", name)) 42 | } 43 | 44 | pub(crate) fn set_bool(&mut self, name: &str, value: bool) { 45 | self.map.insert(name.to_string(), value.to_string()); 46 | } 47 | 48 | pub(crate) fn set_usize(&mut self, name: &str, value: usize) { 49 | self.map.insert(name.to_string(), value.to_string()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /performance/rust_app/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod args; 2 | pub(crate) mod star_builder; -------------------------------------------------------------------------------- /performance/rust_app/src/util/star_builder.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | pub(crate) struct StarBuilder {} 4 | 5 | impl StarBuilder { 6 | 7 | pub(crate) fn generate_star( 8 | radius: f32, 9 | scale: f32, 10 | points_per_corner: usize, 11 | corners_count: usize, 12 | direction: bool, 13 | center: [f32; 2] 14 | ) -> Vec<[f32; 2]> { 15 | let points_count: usize = points_per_corner * corners_count; 16 | let mut points = Vec::with_capacity(points_count); 17 | let sign = if direction { 1.0 } else { -1.0 }; 18 | let da = sign * 2.0 * PI / points_count as f32; 19 | let w = corners_count as f32; 20 | let mut a = 0.0f32; 21 | 22 | for _ in 0..points_count { 23 | let r = radius * (1.0f32 + scale * (w * a).cos()); 24 | let (sn, cs) = a.sin_cos(); 25 | let x = r * cs + center[0]; 26 | let y = r * sn + center[1]; 27 | 28 | a += da; 29 | 30 | points.push([x, y]); 31 | } 32 | points 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /readme/triangulation_process.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iShape-Rust/iTriangle/18d46589ef3b45f5f0112af3396e416601fd525b/readme/triangulation_process.gif --------------------------------------------------------------------------------