From 70fd7e18207384be0a6b5e0be67abcaaf4701512 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Sun, 13 Sep 2026 21:45:28 +0300 Subject: [PATCH 1/2] uint sqr length result --- iTriangle/src/advanced/relax.rs | 14 +++++++------- iTriangle/src/float/triangulator.rs | 2 +- iTriangle/src/float/uniform.rs | 2 +- iTriangle/src/tessellation/split.rs | 2 +- iTriangle/src/tessellation/uniform.rs | 12 ++++++------ 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/iTriangle/src/advanced/relax.rs b/iTriangle/src/advanced/relax.rs index e6f42fd..eb1113e 100644 --- a/iTriangle/src/advanced/relax.rs +++ b/iTriangle/src/advanced/relax.rs @@ -360,7 +360,7 @@ fn displacement_is_safe( } let sqr_displacement = displacement.sqr_length(); - if sqr_displacement <= I::Wide::ZERO { + if sqr_displacement <= I::WideUInt::ZERO { return false; } @@ -378,13 +378,13 @@ fn displacement_is_safe( .sqr_distance(b) .max(b.sqr_distance(c)) .max(c.sqr_distance(a)); - if max_sqr_edge <= I::Wide::ZERO { + if max_sqr_edge <= I::WideUInt::ZERO { return false; } let left = ::Product::multiply( - sqr_displacement.to_uint(), - max_sqr_edge.to_uint(), + sqr_displacement, + max_sqr_edge, ); // |d| < h_min / 4, where h_min = area_two / longest_edge. @@ -409,12 +409,12 @@ fn displacement_is_safe( left16 < right } -fn is_within_tolerance(sqr_distance: I::Wide, tolerance: I::WideUInt) -> bool { - if sqr_distance < I::Wide::ZERO { +fn is_within_tolerance(sqr_distance: I::WideUInt, tolerance: I::WideUInt) -> bool { + if sqr_distance < I::WideUInt::ZERO { return false; } - let distance = ::Product::from_uint(sqr_distance.to_uint()); + let distance = ::Product::from_uint(sqr_distance); let tolerance = ::Product::multiply(tolerance, tolerance); distance <= tolerance } diff --git a/iTriangle/src/float/triangulator.rs b/iTriangle/src/float/triangulator.rs index 5a30061..bccf010 100644 --- a/iTriangle/src/float/triangulator.rs +++ b/iTriangle/src/float/triangulator.rs @@ -6,7 +6,7 @@ use i_overlay::core::integer::OverlayInt; use i_overlay::core::solver::Solver; use i_overlay::i_float::float::compatible::FloatPointCompatible; use i_overlay::i_shape::flat::buffer::FlatContoursBuffer; -use i_overlay::i_shape::source::resource::ShapeResource; +use i_overlay::i_shape::source::float::resource::ShapeResource; /// A reusable triangulator that converts float-based shapes into triangle meshes. pub struct Triangulator diff --git a/iTriangle/src/float/uniform.rs b/iTriangle/src/float/uniform.rs index 981b9a2..6810cc4 100644 --- a/iTriangle/src/float/uniform.rs +++ b/iTriangle/src/float/uniform.rs @@ -7,7 +7,7 @@ use i_overlay::i_float::float::number::FloatNumber; use i_overlay::i_float::float::rect::FloatRect; use i_overlay::i_shape::float::adapter::PathToInt; use i_overlay::i_shape::int::shape::IntShape; -use i_overlay::i_shape::source::resource::ShapeResource; +use i_overlay::i_shape::source::float::resource::ShapeResource; /// Float wrapper for the integer uniform triangulation pipeline. /// diff --git a/iTriangle/src/tessellation/split.rs b/iTriangle/src/tessellation/split.rs index aa35dcd..38939b7 100644 --- a/iTriangle/src/tessellation/split.rs +++ b/iTriangle/src/tessellation/split.rs @@ -73,7 +73,7 @@ fn extract( contour: &mut IntContour, ) { let ab = b - a; - let sqr_len = ab.sqr_length().to_uint(); + let sqr_len = ab.sqr_length(); if sqr_len <= sqr_radius { contour.push(b); return; diff --git a/iTriangle/src/tessellation/uniform.rs b/iTriangle/src/tessellation/uniform.rs index 005ee00..5f204f1 100644 --- a/iTriangle/src/tessellation/uniform.rs +++ b/iTriangle/src/tessellation/uniform.rs @@ -208,22 +208,22 @@ fn is_close_to_edge( let length_sqr = ab.sqr_length(); let clearance_sqr = clearance * clearance; - if length_sqr <= I::Wide::ZERO { - return ap.sqr_length().to_uint() <= clearance_sqr; + if length_sqr <= I::WideUInt::ZERO { + return ap.sqr_length() <= clearance_sqr; } let projection = ap.dot_product(ab); if projection <= I::Wide::ZERO { - return ap.sqr_length().to_uint() <= clearance_sqr; + return ap.sqr_length() <= clearance_sqr; } - if projection >= length_sqr { - return (point - edge.b).sqr_length().to_uint() <= clearance_sqr; + if projection.to_uint() >= length_sqr { + return (point - edge.b).sqr_length() <= clearance_sqr; } let cross = ab.cross_product(ap).unsigned_abs(); let distance_product = ::Product::multiply(cross, cross); let limit_product = - ::Product::multiply(clearance_sqr, length_sqr.to_uint()); + ::Product::multiply(clearance_sqr, length_sqr); distance_product <= limit_product } From 9156b97ed1f9455290d1ed5e7d6d1ea85ef81618 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Sat, 19 Sep 2026 18:07:23 +0300 Subject: [PATCH 2/2] up version and fix clippy and fmt --- DebugApp/uniform_grid/src/main.rs | 3 +- iTriangle/Cargo.toml | 4 +- iTriangle/examples/eagle_svg.rs | 2 +- iTriangle/src/advanced/delaunay.rs | 8 +- iTriangle/src/advanced/relax.rs | 5 +- iTriangle/src/float/custom.rs | 40 ++-- iTriangle/src/float/locator.rs | 9 +- iTriangle/src/float/mod.rs | 11 + iTriangle/src/float/triangulatable.rs | 40 ++-- iTriangle/src/float/triangulation.rs | 2 +- iTriangle/src/float/triangulator.rs | 29 ++- iTriangle/src/float/unchecked.rs | 42 ++-- iTriangle/src/float/uniform.rs | 6 +- iTriangle/src/int/binder.rs | 20 +- iTriangle/src/int/earcut/earcut_64.rs | 74 +++---- iTriangle/src/int/locator.rs | 8 +- .../src/int/monotone/flat/triangulator.rs | 12 +- .../src/int/monotone/net/triangulator.rs | 36 ++-- iTriangle/src/int/triangulatable.rs | 4 +- iTriangle/src/int/triangulation.rs | 80 ++++---- iTriangle/src/int/triangulator.rs | 4 +- iTriangle/src/tessellation/uniform.rs | 9 +- iTriangle/tests/doc_tests.rs | 2 +- .../tests/float_coordinate_contract_tests.rs | 191 ++++++++++++++++++ iTriangle/tests/float_tests.rs | 19 +- iTriangle/tests/relax_stress.rs | 4 +- performance/rust_app/src/test/runner.rs | 6 +- 27 files changed, 447 insertions(+), 223 deletions(-) create mode 100644 iTriangle/tests/float_coordinate_contract_tests.rs diff --git a/DebugApp/uniform_grid/src/main.rs b/DebugApp/uniform_grid/src/main.rs index 2188b53..d07ee66 100644 --- a/DebugApp/uniform_grid/src/main.rs +++ b/DebugApp/uniform_grid/src/main.rs @@ -412,8 +412,9 @@ fn build_mesh_result( // Reproduce the public float wrapper's single conversion into the integer pipeline. let rect = FloatRect::with_iter(shape.iter().flatten()) + .map_err(|error| format!("invalid input bounds: {error:?}"))? .ok_or_else(|| "input shape is empty".to_owned())?; - let adapter = FloatPointAdapter::::new(rect); + let adapter = FloatPointAdapter::::new_conservative(rect); let int_edge_length = adapter.round_len_to_int(edge_length); if int_edge_length <= 1 { return Err("edge_length is below integer adapter precision".to_owned()); diff --git a/iTriangle/Cargo.toml b/iTriangle/Cargo.toml index fa9155a..4b6b76b 100644 --- a/iTriangle/Cargo.toml +++ b/iTriangle/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "i_triangle" -version = "0.48.0" +version = "0.49.0" edition = "2021" authors = ["Nail Sharipov "] description = "Polygon Triangulation Library: Efficient Delaunay Triangulation for Complex Shapes." @@ -20,7 +20,7 @@ serde = ["dep:serde", "i_overlay/serde"] [dependencies] serde = { version = "^1.0", default-features = false, features = ["derive"], optional = true } -i_overlay = { version = "^8.1.0"} +i_overlay = { version = "^9.0.0"} i_tree = "~0.19.0" i_key_sort = "~0.11.0" diff --git a/iTriangle/examples/eagle_svg.rs b/iTriangle/examples/eagle_svg.rs index c0da252..9feca72 100644 --- a/iTriangle/examples/eagle_svg.rs +++ b/iTriangle/examples/eagle_svg.rs @@ -64,7 +64,7 @@ fn tessellation_svg( append_shape_fill(&mut svg, shape); let mut edges = BTreeSet::new(); - for triangle in triangulation.indices.chunks_exact(3) { + for triangle in triangulation.indices.as_chunks::<3>().0 { for (a, b) in [ (triangle[0], triangle[1]), (triangle[1], triangle[2]), diff --git a/iTriangle/src/advanced/delaunay.rs b/iTriangle/src/advanced/delaunay.rs index 45dfac1..726176c 100644 --- a/iTriangle/src/advanced/delaunay.rs +++ b/iTriangle/src/advanced/delaunay.rs @@ -339,7 +339,7 @@ mod tests { let p = IntPoint::new(0, -4); let is_flip_not_required = DelaunayCondition::is_flip_not_required(p, a, b, c); - assert_eq!(is_flip_not_required, true); + assert!(is_flip_not_required); } #[test] @@ -351,7 +351,7 @@ mod tests { let p = IntPoint::new(0, -2); let is_flip_not_required = DelaunayCondition::is_flip_not_required(p, a, b, c); - assert_eq!(is_flip_not_required, true); + assert!(is_flip_not_required); } #[test] @@ -362,7 +362,7 @@ mod tests { let p = IntPoint::new(0, -1); let is_flip_not_required = DelaunayCondition::is_flip_not_required(p, a, b, c); - assert_eq!(is_flip_not_required, false); + assert!(!is_flip_not_required); } #[test] @@ -373,7 +373,7 @@ mod tests { let p = IntPoint::new(0, -1); let is_flip_not_required = DelaunayCondition::is_flip_not_required(p, a, b, c); - assert_eq!(is_flip_not_required, false); + assert!(!is_flip_not_required); } #[test] diff --git a/iTriangle/src/advanced/relax.rs b/iTriangle/src/advanced/relax.rs index eb1113e..7998602 100644 --- a/iTriangle/src/advanced/relax.rs +++ b/iTriangle/src/advanced/relax.rs @@ -382,10 +382,7 @@ fn displacement_is_safe( return false; } - let left = ::Product::multiply( - sqr_displacement, - max_sqr_edge, - ); + let left = ::Product::multiply(sqr_displacement, max_sqr_edge); // |d| < h_min / 4, where h_min = area_two / longest_edge. // Squaring and rearranging avoids both division and square roots: diff --git a/iTriangle/src/float/custom.rs b/iTriangle/src/float/custom.rs index df67986..0a8e5e4 100644 --- a/iTriangle/src/float/custom.rs +++ b/iTriangle/src/float/custom.rs @@ -51,14 +51,14 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_path(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_path(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).custom_triangulate(validation); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -71,8 +71,8 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_path(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_path(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -81,7 +81,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -95,14 +95,14 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_paths(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).custom_triangulate(validation); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -115,8 +115,8 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_paths(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -125,7 +125,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -139,14 +139,16 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_list_of_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = + FloatRect::with_list_of_paths(self).expect("Invalid triangulation bounds") + { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).custom_triangulate(validation); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -159,8 +161,10 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_list_of_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = + FloatRect::with_list_of_paths(self).expect("Invalid triangulation bounds") + { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -169,7 +173,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } diff --git a/iTriangle/src/float/locator.rs b/iTriangle/src/float/locator.rs index dc3012f..65e4572 100644 --- a/iTriangle/src/float/locator.rs +++ b/iTriangle/src/float/locator.rs @@ -1,9 +1,10 @@ use alloc::vec::Vec; use i_key_sort::sort::key::SortKey; +use i_overlay::i_float::adapter::FloatPointAdapter; use i_overlay::i_float::float::compatible::FloatPointCompatible; use i_overlay::i_float::float::number::FloatNumber; use i_overlay::i_float::int::number::int::IntNumber; -use i_overlay::{i_float::adapter::FloatPointAdapter, i_shape::float::adapter::PathToInt}; +use i_overlay::i_shape::float::adapter::PathToInt; use crate::int::locator::IntPointInTriangulationLocator; use crate::{ @@ -27,11 +28,13 @@ impl Triangulation { where P: FloatPointCompatible, { - let adapter = FloatPointAdapter::::with_iter(self.points.iter().chain(points.iter())); + let adapter = FloatPointAdapter::::with_iter_conservative( + self.points.iter().chain(points.iter()), + ); let int_points = points.to_int(&adapter); - let triangles = self.indices.chunks_exact(3).map(|triangle| { + let triangles = self.indices.as_chunks::<3>().0.iter().map(|triangle| { let a = adapter.float_to_int(&self.points[triangle[0].into_usize()]); let b = adapter.float_to_int(&self.points[triangle[1].into_usize()]); let c = adapter.float_to_int(&self.points[triangle[2].into_usize()]); diff --git a/iTriangle/src/float/mod.rs b/iTriangle/src/float/mod.rs index a7e8726..933fa04 100644 --- a/iTriangle/src/float/mod.rs +++ b/iTriangle/src/float/mod.rs @@ -1,3 +1,14 @@ +//! Floating-point triangulation uses the same coordinate contract as iOverlay. +//! +//! Coordinates must be finite, with absolute values at most `2^60` for `f32` +//! or `2^500` for `f64`. Invalid input bounds panic; empty geometry produces +//! an empty mesh. Automatic conversion uses the conservative adapter constructors, +//! reserving `I::BITS - 3` coordinate bits for integer differences, products, and +//! rounding, including unchecked APIs. +//! Unchecked APIs skip topology validation. Reusable `Triangulator` methods +//! validate individual points only in debug builds and final bounds in all builds; +//! callers must ensure every input point satisfies the coordinate contract. + pub mod builder; pub mod centroid_net; pub mod circumcenter; diff --git a/iTriangle/src/float/triangulatable.rs b/iTriangle/src/float/triangulatable.rs index fac90ef..b2444a4 100644 --- a/iTriangle/src/float/triangulatable.rs +++ b/iTriangle/src/float/triangulatable.rs @@ -52,14 +52,14 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_path(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_path(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).triangulate(); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -68,8 +68,8 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_path(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_path(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -78,7 +78,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -92,14 +92,14 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_paths(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).triangulate(); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -108,8 +108,8 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_paths(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -118,7 +118,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -132,14 +132,16 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_list_of_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = + FloatRect::with_list_of_paths(self).expect("Invalid triangulation bounds") + { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).triangulate(); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -148,8 +150,10 @@ where where I: OverlayInt, { - if let Some(rect) = FloatRect::with_list_of_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = + FloatRect::with_list_of_paths(self).expect("Invalid triangulation bounds") + { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -158,7 +162,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } diff --git a/iTriangle/src/float/triangulation.rs b/iTriangle/src/float/triangulation.rs index 6a1da69..45e8cd1 100644 --- a/iTriangle/src/float/triangulation.rs +++ b/iTriangle/src/float/triangulation.rs @@ -183,6 +183,6 @@ mod tests { assert_eq!(triangulation.points.len(), 4); assert_eq!(triangulation.indices.len(), 6); - triangulation.validate(40.0, 0.000_0001); + triangulation.validate(40.0, 0.000_000_1); } } diff --git a/iTriangle/src/float/triangulator.rs b/iTriangle/src/float/triangulator.rs index bccf010..7b9a571 100644 --- a/iTriangle/src/float/triangulator.rs +++ b/iTriangle/src/float/triangulator.rs @@ -100,11 +100,14 @@ where R: ShapeResource

+ ?Sized, P: FloatPointCompatible, { + let adapter = self + .flat_buffer + .get_or_insert_with(Default::default) + .set_with_resource(resource) + .expect("Invalid triangulation bounds"); let mut flat_buffer = self.flat_buffer.take().unwrap_or_default(); let mut int_buffer = self.int_buffer.take().unwrap_or_default(); - let adapter = flat_buffer.set_with_resource(resource); - self.int_triangulator .triangulate_flat_into(&mut flat_buffer, &mut int_buffer); @@ -134,9 +137,13 @@ where R: ShapeResource

+ ?Sized, P: FloatPointCompatible, { + let adapter = self + .flat_buffer + .get_or_insert_with(Default::default) + .set_with_resource(resource) + .expect("Invalid triangulation bounds"); let mut flat_buffer = self.flat_buffer.take().unwrap_or_default(); let mut int_buffer = self.int_buffer.take().unwrap_or_default(); - let adapter = flat_buffer.set_with_resource(resource); self.int_triangulator .triangulate_flat_into(&mut flat_buffer, &mut int_buffer); @@ -166,9 +173,13 @@ where R: ShapeResource

+ ?Sized, P: FloatPointCompatible, { - let mut flat_buffer = self.flat_buffer.take().unwrap_or_default(); + let adapter = self + .flat_buffer + .get_or_insert_with(Default::default) + .set_with_resource(resource) + .expect("Invalid triangulation bounds"); + let flat_buffer = self.flat_buffer.take().unwrap_or_default(); let mut int_buffer = self.int_buffer.take().unwrap_or_default(); - let adapter = flat_buffer.set_with_resource(resource); self.int_triangulator .uncheck_triangulate_flat_into(&flat_buffer, &mut int_buffer); @@ -205,9 +216,13 @@ where R: ShapeResource

+ ?Sized, P: FloatPointCompatible, { - let mut flat_buffer = self.flat_buffer.take().unwrap_or_default(); + let adapter = self + .flat_buffer + .get_or_insert_with(Default::default) + .set_with_resource(resource) + .expect("Invalid triangulation bounds"); + let flat_buffer = self.flat_buffer.take().unwrap_or_default(); let mut int_buffer = self.int_buffer.take().unwrap_or_default(); - let adapter = flat_buffer.set_with_resource(resource); self.int_triangulator .uncheck_triangulate_flat_into(&flat_buffer, &mut int_buffer); diff --git a/iTriangle/src/float/unchecked.rs b/iTriangle/src/float/unchecked.rs index 4356aaa..7188310 100644 --- a/iTriangle/src/float/unchecked.rs +++ b/iTriangle/src/float/unchecked.rs @@ -13,7 +13,7 @@ use i_tree::Expiration; /// A trait for triangulating already valid float-based geometry. /// -/// Skips all validation for performance. Ideal when input is generated programmatically. +/// Skips topology validation for performance. Coordinate bounds are still validated. /// /// # Safety Requirements /// - Outer contours must be counter-clockwise @@ -52,14 +52,14 @@ where where I: IntNumber + Expiration + SortKey, { - if let Some(rect) = FloatRect::with_path(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_path(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).uncheck_triangulate(); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -71,8 +71,8 @@ where where I: IntNumber + Expiration + SortKey, { - if let Some(rect) = FloatRect::with_path(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_path(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -81,7 +81,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -95,14 +95,14 @@ where where I: IntNumber + Expiration + SortKey, { - if let Some(rect) = FloatRect::with_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_paths(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).uncheck_triangulate(); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -114,8 +114,8 @@ where where I: IntNumber + Expiration + SortKey, { - if let Some(rect) = FloatRect::with_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = FloatRect::with_paths(self).expect("Invalid triangulation bounds") { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -124,7 +124,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -138,14 +138,16 @@ where where I: IntNumber + Expiration + SortKey, { - if let Some(rect) = FloatRect::with_list_of_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = + FloatRect::with_list_of_paths(self).expect("Invalid triangulation bounds") + { + let adapter = FloatPointAdapter::::new_conservative(rect); let raw = self.to_int(&adapter).uncheck_triangulate(); RawTriangulation { raw, adapter } } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } @@ -157,8 +159,10 @@ where where I: IntNumber + Expiration + SortKey, { - if let Some(rect) = FloatRect::with_list_of_paths(self) { - let adapter = FloatPointAdapter::::new(rect); + if let Some(rect) = + FloatRect::with_list_of_paths(self).expect("Invalid triangulation bounds") + { + let adapter = FloatPointAdapter::::new_conservative(rect); let float_points = points.to_int(&adapter); let raw = self .to_int(&adapter) @@ -167,7 +171,7 @@ where } else { RawTriangulation { raw: RawIntTriangulation::default(), - adapter: FloatPointAdapter::::new(FloatRect::zero()), + adapter: FloatPointAdapter::::new_conservative(FloatRect::zero()), } } } diff --git a/iTriangle/src/float/uniform.rs b/iTriangle/src/float/uniform.rs index 6810cc4..41550f2 100644 --- a/iTriangle/src/float/uniform.rs +++ b/iTriangle/src/float/uniform.rs @@ -4,7 +4,6 @@ use i_overlay::core::integer::OverlayInt; use i_overlay::i_float::adapter::FloatPointAdapter; use i_overlay::i_float::float::compatible::FloatPointCompatible; use i_overlay::i_float::float::number::FloatNumber; -use i_overlay::i_float::float::rect::FloatRect; use i_overlay::i_shape::float::adapter::PathToInt; use i_overlay::i_shape::int::shape::IntShape; use i_overlay::i_shape::source::float::resource::ShapeResource; @@ -59,9 +58,8 @@ where "edge_length must be finite and positive" ); - let rect = - FloatRect::with_iter(self.iter_paths().flatten()).unwrap_or_else(FloatRect::zero); - let adapter = FloatPointAdapter::::new(rect); + let adapter = + FloatPointAdapter::::with_iter_conservative(self.iter_paths().flatten()); let int_edge_length = adapter.round_len_to_int(edge_length); assert!( int_edge_length > I::ONE, diff --git a/iTriangle/src/int/binder.rs b/iTriangle/src/int/binder.rs index 7897542..c0912e4 100644 --- a/iTriangle/src/int/binder.rs +++ b/iTriangle/src/int/binder.rs @@ -212,7 +212,7 @@ mod tests { #[test] fn test_0() { - let shapes = vec![vec![path(&[[0, 0], [10, 0], [10, 10], [0, 10]])]]; + let shapes = [vec![path(&[[0, 0], [10, 0], [10, 10], [0, 10]])]]; let groups = shapes.group_by_shapes(&[ IntPoint::new(5, 5), @@ -225,7 +225,7 @@ mod tests { #[test] fn test_1() { - let shapes = vec![ + let shapes = [ vec![path(&[[0, 0], [10, 0], [10, 10], [0, 10]])], vec![path(&[[20, 0], [30, 0], [30, 10], [20, 10]])], ]; @@ -243,7 +243,7 @@ mod tests { #[test] fn test_2() { - let shapes = vec![ + let shapes = [ vec![path(&[[0, 0], [10, 0], [10, 10], [0, 10]])], vec![path(&[[0, 20], [10, 20], [10, 30], [0, 30]])], vec![path(&[[0, 40], [10, 40], [10, 50], [0, 50]])], @@ -267,7 +267,7 @@ mod tests { #[test] fn test_3() { - let shapes = vec![vec![path(&[[-10, 0], [0, -10], [10, 0], [0, 10]])]]; + let shapes = [vec![path(&[[-10, 0], [0, -10], [10, 0], [0, 10]])]]; let groups = shapes.group_by_shapes(&[IntPoint::new(-3, 7)]); @@ -276,7 +276,7 @@ mod tests { #[test] fn test_4() { - let shapes = vec![vec![path(&[[3, -2], [-5, 3], [0, -1], [-2, -3]])]]; + let shapes = [vec![path(&[[3, -2], [-5, 3], [0, -1], [-2, -3]])]]; let groups = shapes.group_by_shapes(&[IntPoint::new(0, -1)]); assert_eq!(groups[0].len(), 0); @@ -284,7 +284,7 @@ mod tests { #[test] fn test_5() { - let shapes = vec![vec![path(&[[-1, 2], [-5, -2], [2, -2], [3, 4]])]]; + let shapes = [vec![path(&[[-1, 2], [-5, -2], [2, -2], [3, 4]])]]; let groups = shapes.group_by_shapes(&[IntPoint::new(1, 5)]); assert_eq!(groups[0].len(), 0); @@ -292,7 +292,7 @@ mod tests { #[test] fn test_6() { - let shapes = vec![vec![path(&[[-5, 0], [0, -5], [5, 0], [0, 5]])]]; + let shapes = [vec![path(&[[-5, 0], [0, -5], [5, 0], [0, 5]])]]; let groups = shapes.group_by_shapes(&[IntPoint::new(0, 0), IntPoint::new(0, 0)]); assert_eq!(groups[0].len(), 1); @@ -300,7 +300,7 @@ mod tests { #[test] fn test_7() { - let shapes = vec![vec![path(&[[-5, 0], [0, -5], [5, 0], [0, 5]])]]; + let shapes = [vec![path(&[[-5, 0], [0, -5], [5, 0], [0, 5]])]]; let groups = shapes.group_by_shapes(&[IntPoint::new(-4, 3), IntPoint::new(0, 3)]); assert_eq!(groups[0].len(), 1); @@ -308,7 +308,7 @@ mod tests { #[test] fn test_8() { - let shapes = vec![vec![path(&[ + let shapes = [vec![path(&[ [1, 0], [-4, -2], [3, 0], @@ -323,7 +323,7 @@ mod tests { #[test] fn test_9() { - let shapes = vec![vec![path(&[[-10, -10], [10, -10], [10, 10], [-10, 10]])]]; + let shapes = [vec![path(&[[-10, -10], [10, -10], [10, 10], [-10, 10]])]]; let groups = shapes.group_by_shapes(&[ IntPoint::new(-10, 10), IntPoint::new(-10, 5), diff --git a/iTriangle/src/int/earcut/earcut_64.rs b/iTriangle/src/int/earcut/earcut_64.rs index 1d833d0..e094028 100644 --- a/iTriangle/src/int/earcut/earcut_64.rs +++ b/iTriangle/src/int/earcut/earcut_64.rs @@ -674,7 +674,7 @@ mod tests { contour[0], contour[1], contour[2], contour[3], 0b1111, false, ); - assert_eq!(queue.is_none(), true); + assert!(queue.is_none()); } #[test] @@ -694,7 +694,7 @@ mod tests { contour[0], contour[1], contour[2], contour[3], 0b1111, false, ); - assert_eq!(queue.is_none(), true); + assert!(queue.is_none()); } #[test] @@ -716,7 +716,7 @@ mod tests { ) .unwrap(); - assert_eq!(queue.is_empty(), true); + assert!(queue.is_empty()); } #[test] @@ -738,7 +738,7 @@ mod tests { ) .unwrap(); - assert_eq!(queue.is_empty(), true); + assert!(queue.is_empty()); } #[test] @@ -760,7 +760,7 @@ mod tests { ) .unwrap(); - assert_eq!(queue.is_empty(), false); + assert!(!queue.is_empty()); } // find_point @@ -791,121 +791,121 @@ mod tests { // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(0, -20), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 2); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(0, -5), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 2); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(10, -10), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 2); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(15, -15), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 2); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(0, -1), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 3); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(0, 0), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 3); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(10, 0), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 3); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(15, 0), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 3); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(20, 0), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 3); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(0, 5), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 4); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(0, 10), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 4); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(15, 15), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 4); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(10, 10), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 4); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(-5, 10), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 5); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(-5, 12), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 5); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(0, 15), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 5); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(0, 20), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 5); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(-10, 10), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 6); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(-15, 15), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 6); // ---- let mut ear = new_ear(); let inner = ear.cut(IntPoint::new(-10, 8), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 6); } @@ -928,7 +928,7 @@ mod tests { indices: 0b011111, }; let inner = ear.cut(IntPoint::new(5, 10), &contour); - assert_eq!(inner, true); + assert!(inner); assert_eq!(ear.active_index, 3); } @@ -956,7 +956,7 @@ mod tests { indices: 0b11111, }; let inner = ear.cut(IntPoint::new(10, -10), &contour); - assert_eq!(inner, false); + assert!(!inner); assert_eq!(ear.active_index, 2); } @@ -1566,7 +1566,7 @@ mod tests { { if let Some(contour) = first.first() { if !contour.is_empty() { - single_test(&contour); + single_test(contour); } } } @@ -1582,7 +1582,7 @@ mod tests { { if let Some(contour) = first.first() { if !contour.is_empty() { - single_test(&contour); + single_test(contour); } } } @@ -1598,7 +1598,7 @@ mod tests { { if let Some(contour) = first.first() { if !contour.is_empty() { - single_test(&contour); + single_test(contour); } } } @@ -1614,7 +1614,7 @@ mod tests { { if let Some(contour) = first.first() { if !contour.is_empty() { - single_test(&contour); + single_test(contour); } } } @@ -1630,8 +1630,8 @@ mod tests { { if let Some(contour) = first.first() { let n = contour.len(); - if 3 <= n && n <= 64 { - single_test(&contour); + if (3..=64).contains(&n) { + single_test(contour); } } } @@ -1647,8 +1647,8 @@ mod tests { { if let Some(contour) = first.first() { let n = contour.len(); - if 3 <= n && n <= 64 { - single_test(&contour); + if (3..=64).contains(&n) { + single_test(contour); } } } @@ -1664,8 +1664,8 @@ mod tests { { if let Some(contour) = first.first() { let n = contour.len(); - if 3 <= n && n <= 64 { - single_test(&contour); + if (3..=64).contains(&n) { + single_test(contour); } } } diff --git a/iTriangle/src/int/locator.rs b/iTriangle/src/int/locator.rs index b17d0de..9825517 100644 --- a/iTriangle/src/int/locator.rs +++ b/iTriangle/src/int/locator.rs @@ -273,7 +273,7 @@ mod tests { let mut points_outside = Vec::new(); for x in -10..=10 { for y in -10..=10 { - if (x < 0 || x > 4) && (y < 0 || y > 8) { + if !(0..=4).contains(&x) && !(0..=8).contains(&y) { points_outside.push(IntPoint::new(x, y)); } } @@ -297,7 +297,7 @@ mod tests { assert_eq!(vertex_triangles, template_triangles); } else { - assert!(false, "not on Vertex"); + panic!("not on Vertex"); } } @@ -313,7 +313,7 @@ mod tests { ); } _ => { - assert!(false, "not on Edge"); + panic!("not on Edge"); } } } @@ -322,7 +322,7 @@ mod tests { if let PointLocationInTriangulation::InsideTriangle(triangle) = self { assert_eq!(triangle.index(), index); } else { - assert!(false, "not Inside"); + panic!("not Inside"); } } } diff --git a/iTriangle/src/int/monotone/flat/triangulator.rs b/iTriangle/src/int/monotone/flat/triangulator.rs index 3476a08..8fd943c 100644 --- a/iTriangle/src/int/monotone/flat/triangulator.rs +++ b/iTriangle/src/int/monotone/flat/triangulator.rs @@ -830,7 +830,7 @@ mod tests { { let shape_area = first.area_two(); - MonotoneTriangulator::default().shape_into_flat_triangulation(&first, &mut raw); + MonotoneTriangulator::default().shape_into_flat_triangulation(first, &mut raw); raw.validate(shape_area); }; @@ -849,7 +849,7 @@ mod tests { { let shape_area = first.area_two(); - MonotoneTriangulator::default().shape_into_flat_triangulation(&first, &mut raw); + MonotoneTriangulator::default().shape_into_flat_triangulation(first, &mut raw); raw.validate(shape_area); }; @@ -868,7 +868,7 @@ mod tests { { let shape_area = first.area_two(); - MonotoneTriangulator::default().shape_into_flat_triangulation(&first, &mut raw); + MonotoneTriangulator::default().shape_into_flat_triangulation(first, &mut raw); raw.validate(shape_area); }; @@ -887,7 +887,7 @@ mod tests { { let shape_area = first.area_two(); - MonotoneTriangulator::default().shape_into_flat_triangulation(&first, &mut raw); + MonotoneTriangulator::default().shape_into_flat_triangulation(first, &mut raw); raw.validate(shape_area); }; @@ -906,7 +906,7 @@ mod tests { { let shape_area = first.area_two(); - MonotoneTriangulator::default().shape_into_flat_triangulation(&first, &mut raw); + MonotoneTriangulator::default().shape_into_flat_triangulation(first, &mut raw); raw.validate(shape_area); }; @@ -929,7 +929,7 @@ mod tests { { let shape_area = first.area_two(); - MonotoneTriangulator::default().shape_into_flat_triangulation(&first, &mut raw); + MonotoneTriangulator::default().shape_into_flat_triangulation(first, &mut raw); raw.validate(shape_area); }; diff --git a/iTriangle/src/int/monotone/net/triangulator.rs b/iTriangle/src/int/monotone/net/triangulator.rs index a63fa43..1bc702b 100644 --- a/iTriangle/src/int/monotone/net/triangulator.rs +++ b/iTriangle/src/int/monotone/net/triangulator.rs @@ -1255,7 +1255,7 @@ mod tests { let shape = vec![path(&[[1, 0], [-4, -2], [3, 0], [5, 1], [4, 1], [-4, -1]])]; let points = vec![IntPoint::new(0, 3), IntPoint::new(4, 3)]; let shape_area = shape.area_two(); - let group = vec![shape.clone()].group_by_shapes(&points); + let group = [shape.clone()].group_by_shapes(&points); let mut raw = RawIntTriangulation::default(); MonotoneTriangulator::default().shape_into_net_triangulation( @@ -1275,7 +1275,7 @@ mod tests { let shape = vec![path(&[[-1, 2], [-5, -2], [2, -2], [3, 4]])]; let points = vec![IntPoint::new(1, 5)]; let shape_area = shape.area_two(); - let group = vec![shape.clone()].group_by_shapes(&points); + let group = [shape.clone()].group_by_shapes(&points); let mut raw = RawIntTriangulation::default(); MonotoneTriangulator::default().shape_into_net_triangulation( @@ -1302,8 +1302,7 @@ mod tests { let shape_area = first.area_two(); let mut raw = RawIntTriangulation::default(); - MonotoneTriangulator::default() - .shape_into_net_triangulation(&first, None, &mut raw); + MonotoneTriangulator::default().shape_into_net_triangulation(first, None, &mut raw); raw.validate(); assert_eq!(raw.area_two(), shape_area); @@ -1323,8 +1322,7 @@ mod tests { let shape_area = first.area_two(); let mut raw = RawIntTriangulation::default(); - MonotoneTriangulator::default() - .shape_into_net_triangulation(&first, None, &mut raw); + MonotoneTriangulator::default().shape_into_net_triangulation(first, None, &mut raw); raw.validate(); assert_eq!(raw.area_two(), shape_area); @@ -1344,8 +1342,7 @@ mod tests { let shape_area = first.area_two(); let mut raw = RawIntTriangulation::default(); - MonotoneTriangulator::default() - .shape_into_net_triangulation(&first, None, &mut raw); + MonotoneTriangulator::default().shape_into_net_triangulation(first, None, &mut raw); raw.validate(); assert_eq!(raw.area_two(), shape_area); @@ -1365,8 +1362,7 @@ mod tests { let shape_area = first.area_two(); let mut raw = RawIntTriangulation::default(); - MonotoneTriangulator::default() - .shape_into_net_triangulation(&first, None, &mut raw); + MonotoneTriangulator::default().shape_into_net_triangulation(first, None, &mut raw); raw.validate(); assert_eq!(raw.area_two(), shape_area); @@ -1386,8 +1382,7 @@ mod tests { let shape_area = first.area_two(); let mut raw = RawIntTriangulation::default(); - MonotoneTriangulator::default() - .shape_into_net_triangulation(&first, None, &mut raw); + MonotoneTriangulator::default().shape_into_net_triangulation(first, None, &mut raw); raw.validate(); assert_eq!(raw.area_two(), shape_area); @@ -1411,8 +1406,7 @@ mod tests { let shape_area = first.area_two(); let mut raw = RawIntTriangulation::default(); - MonotoneTriangulator::default() - .shape_into_net_triangulation(&first, None, &mut raw); + MonotoneTriangulator::default().shape_into_net_triangulation(first, None, &mut raw); raw.validate(); assert_eq!(raw.area_two(), shape_area); @@ -1441,7 +1435,7 @@ mod tests { #[test] fn test_random_7() { - let shapes = vec![vec![path(&[[-5, 0], [0, -5], [5, 0], [0, 5]])]]; + let shapes = [vec![path(&[[-5, 0], [0, -5], [5, 0], [0, 5]])]]; let shape_area = shapes.area_two(); for _ in 0..random_cases(20_000) { let points = random_points(8, 2); @@ -1469,7 +1463,7 @@ mod tests { .simplify(FillRule::NonZero, IntOverlayOptions::keep_all_points()) .first() { - let shapes = vec![first.clone()]; + let shapes = [first.clone()]; let shape_area = shapes.area_two(); let group = shapes.group_by_shapes(&points); @@ -1497,7 +1491,7 @@ mod tests { .simplify(FillRule::NonZero, IntOverlayOptions::keep_all_points()) .first() { - let shapes = vec![first.clone()]; + let shapes = [first.clone()]; let shape_area = shapes.area_two(); let group = shapes.group_by_shapes(&points); @@ -1525,7 +1519,7 @@ mod tests { .simplify(FillRule::NonZero, IntOverlayOptions::keep_all_points()) .first() { - let shapes = vec![first.clone()]; + let shapes = [first.clone()]; let shape_area = shapes.area_two(); let group = shapes.group_by_shapes(&points); @@ -1558,11 +1552,11 @@ mod tests { { let shape_area = first.area_two(); - let group = vec![first.clone()].group_by_shapes(&points); + let group = [first.clone()].group_by_shapes(&points); let mut raw = RawIntTriangulation::default(); MonotoneTriangulator::default().shape_into_net_triangulation( - &first, + first, Some(&group[0]), &mut raw, ); @@ -1596,6 +1590,6 @@ mod tests { points.insert(IntPoint { x, y }); } - points.iter().map(|p| p).copied().collect() + points.iter().copied().collect() } } diff --git a/iTriangle/src/int/triangulatable.rs b/iTriangle/src/int/triangulatable.rs index fb67f3e..f944f21 100644 --- a/iTriangle/src/int/triangulatable.rs +++ b/iTriangle/src/int/triangulatable.rs @@ -135,7 +135,9 @@ mod tests { .unwrap() as u16; triangulation .indices - .chunks_exact(3) + .as_chunks::<3>() + .0 + .iter() .any(|triangle| triangle.contains(&a) && triangle.contains(&b)) } diff --git a/iTriangle/src/int/triangulation.rs b/iTriangle/src/int/triangulation.rs index 9474e12..9236164 100644 --- a/iTriangle/src/int/triangulation.rs +++ b/iTriangle/src/int/triangulation.rs @@ -98,7 +98,7 @@ impl Default for IntTriangulation { #[derive(Clone)] pub struct IntTriangleIterator<'a, I: IntNumber, N> { points: &'a [IntPoint], - indices: core::slice::ChunksExact<'a, N>, + indices: core::slice::Iter<'a, [N; 3]>, } impl Iterator for IntTriangleIterator<'_, I, N> { @@ -259,7 +259,7 @@ impl IntTriangulation { pub fn triangles(&self) -> IntTriangleIterator<'_, I, N> { IntTriangleIterator { points: &self.points, - indices: self.indices.chunks_exact(3), + indices: self.indices.as_chunks::<3>().0.iter(), } } @@ -292,44 +292,6 @@ impl IntTriangulation { } } -#[cfg(test)] -mod tests { - use super::IntTriangulation; - use alloc::{vec, vec::Vec}; - use i_overlay::i_float::int::point::IntPoint; - - #[test] - fn triangles_iterates_resolved_points() { - let triangulation = IntTriangulation { - points: vec![ - IntPoint::new(0, 0), - IntPoint::new(10, 0), - IntPoint::new(10, 10), - IntPoint::new(0, 10), - ], - indices: vec![0_u16, 1, 2, 0, 2, 3], - }; - - let triangles: Vec<_> = triangulation.triangles().collect(); - - assert_eq!( - triangles, - vec![ - [ - IntPoint::new(0, 0), - IntPoint::new(10, 0), - IntPoint::new(10, 10), - ], - [ - IntPoint::new(0, 0), - IntPoint::new(10, 10), - IntPoint::new(0, 10), - ], - ] - ); - } -} - pub(crate) trait IndicesBuilder { fn feed_indices(&self, max_count: usize, indices: &mut Vec); } @@ -425,3 +387,41 @@ impl IntTriangulation { assert!(s == shape_x2_area); } } + +#[cfg(test)] +mod tests { + use super::IntTriangulation; + use alloc::{vec, vec::Vec}; + use i_overlay::i_float::int::point::IntPoint; + + #[test] + fn triangles_iterates_resolved_points() { + let triangulation = IntTriangulation { + points: vec![ + IntPoint::new(0, 0), + IntPoint::new(10, 0), + IntPoint::new(10, 10), + IntPoint::new(0, 10), + ], + indices: vec![0_u16, 1, 2, 0, 2, 3], + }; + + let triangles: Vec<_> = triangulation.triangles().collect(); + + assert_eq!( + triangles, + vec![ + [ + IntPoint::new(0, 0), + IntPoint::new(10, 0), + IntPoint::new(10, 10), + ], + [ + IntPoint::new(0, 0), + IntPoint::new(10, 10), + IntPoint::new(0, 10), + ], + ] + ); + } +} diff --git a/iTriangle/src/int/triangulator.rs b/iTriangle/src/int/triangulator.rs index e207881..b21b5f1 100644 --- a/iTriangle/src/int/triangulator.rs +++ b/iTriangle/src/int/triangulator.rs @@ -101,7 +101,7 @@ where #[inline] pub fn triangulate_shapes(&mut self, shapes: &IntShapes) -> IntTriangulation { - let simple = self.overlay.simplify_shapes(shapes, self.fill_rule); + let simple = self.overlay.simplify_source(shapes, self.fill_rule); self.uncheck_triangulate_shapes(&simple) } @@ -111,7 +111,7 @@ where shapes: &IntShapes, triangulation: &mut IntTriangulation, ) { - let simple = self.overlay.simplify_shapes(shapes, self.fill_rule); + let simple = self.overlay.simplify_source(shapes, self.fill_rule); self.uncheck_triangulate_shapes_into(&simple, triangulation); } diff --git a/iTriangle/src/tessellation/uniform.rs b/iTriangle/src/tessellation/uniform.rs index 5f204f1..bea33ee 100644 --- a/iTriangle/src/tessellation/uniform.rs +++ b/iTriangle/src/tessellation/uniform.rs @@ -222,8 +222,7 @@ fn is_close_to_edge( let cross = ab.cross_product(ap).unsigned_abs(); let distance_product = ::Product::multiply(cross, cross); - let limit_product = - ::Product::multiply(clearance_sqr, length_sqr); + let limit_product = ::Product::multiply(clearance_sqr, length_sqr); distance_product <= limit_product } @@ -237,7 +236,7 @@ mod tests { #[test] fn square_grid_has_staggered_rows() { - let contour = vec![ + let contour = [ IntPoint::new(0, 0), IntPoint::new(100, 0), IntPoint::new(100, 100), @@ -256,7 +255,7 @@ mod tests { #[test] fn shape_grid_excludes_hole() { - let shape = vec![ + let shape = [ vec![ IntPoint::new(0, 0), IntPoint::new(100, 0), @@ -281,7 +280,7 @@ mod tests { #[test] fn grid_removes_points_in_edge_influence_across_cell_boundary() { - let contour = vec![ + let contour = [ IntPoint::new(0, 0), IntPoint::new(100, 0), IntPoint::new(100, 100), diff --git a/iTriangle/tests/doc_tests.rs b/iTriangle/tests/doc_tests.rs index f163f94..b52554e 100644 --- a/iTriangle/tests/doc_tests.rs +++ b/iTriangle/tests/doc_tests.rs @@ -13,7 +13,7 @@ mod tests { #[test] fn test_0() { - let shape = vec![ + let shape = [ vec![ // body [0.0, 20.0], // 0 diff --git a/iTriangle/tests/float_coordinate_contract_tests.rs b/iTriangle/tests/float_coordinate_contract_tests.rs new file mode 100644 index 0000000..91fe471 --- /dev/null +++ b/iTriangle/tests/float_coordinate_contract_tests.rs @@ -0,0 +1,191 @@ +use i_triangle::float::custom::CustomTriangulatable; +use i_triangle::float::triangulatable::Triangulatable; +use i_triangle::float::triangulation::Triangulation; +use i_triangle::float::triangulator::Triangulator; +use i_triangle::float::unchecked::UncheckedTriangulatable; +use i_triangle::float::uniform::UniformTriangulatable; +use i_triangle::i_overlay::core::integer::OverlayInt; +use i_triangle::i_overlay::i_float::float::number::FloatNumber; +use std::panic::{catch_unwind, AssertUnwindSafe}; + +fn check_coordinate_budget(radius: F) { + let contour = vec![ + [-radius, -radius], + [radius, -radius], + [radius, radius], + [-radius, radius], + ]; + let direct = contour.triangulate_as::(); + let limit = I::ONE << (I::BITS - 3); + for point in direct.raw.points() { + assert!(point.x >= -limit && point.x <= limit); + assert!(point.y >= -limit && point.y <= limit); + } + let expected = direct.to_triangulation::(); + assert_eq!(expected.indices.len(), 6); + + let mut triangulator = Triangulator::::default(); + let buffered = triangulator.triangulate(&contour); + let unchecked = triangulator.uncheck_triangulate(&contour); + let custom = contour + .custom_triangulate_as::(Default::default()) + .to_triangulation::(); + let unchecked_direct = contour + .unchecked_triangulate_as::() + .to_triangulation::(); + for mesh in [buffered, unchecked, custom, unchecked_direct] { + assert_eq!(mesh.indices.len(), expected.indices.len()); + assert_eq!(mesh.points.len(), expected.points.len()); + for point in mesh.points { + assert!(point[0].is_finite() && point[1].is_finite()); + assert!(expected.points.contains(&point)); + } + } +} + +#[test] +fn all_engines_share_the_coordinate_budget_at_rounding_and_scalar_limits() { + // Just below a power of two: the default adapter can round beyond the budget. + for radius in [1.999_999_f32, f32::MAX_COORDINATE, f32::MIN_POSITIVE] { + check_coordinate_budget::<_, i16>(radius); + check_coordinate_budget::<_, i32>(radius); + check_coordinate_budget::<_, i64>(radius); + } + for radius in [ + 1.999_999_999_999_999_f64, + f64::MAX_COORDINATE, + f64::MIN_POSITIVE, + ] { + check_coordinate_budget::<_, i16>(radius); + check_coordinate_budget::<_, i32>(radius); + check_coordinate_budget::<_, i64>(radius); + } +} + +#[test] +fn invalid_coordinates_panic_in_all_float_entry_points() { + for invalid in [ + f64::NAN, + f64::INFINITY, + f64::NEG_INFINITY, + f64::MAX_COORDINATE * 2.0, + ] { + // Interior NaN must not disappear during min/max bounds accumulation. + let contour = vec![[0.0, 0.0], [invalid, 1.0], [2.0, 2.0], [0.0, 2.0]]; + let shape = vec![contour.clone()]; + let shapes = [shape.clone()]; + assert!(catch_unwind(|| shape.triangulate()).is_err()); + assert!(catch_unwind(|| shape.custom_triangulate(Default::default())).is_err()); + assert!(catch_unwind(|| shape.unchecked_triangulate()).is_err()); + assert!(catch_unwind(|| shapes.triangulate()).is_err()); + assert!(catch_unwind(|| shapes.custom_triangulate(Default::default())).is_err()); + assert!(catch_unwind(|| shapes.unchecked_triangulate()).is_err()); + assert!(catch_unwind(|| contour.triangulate()).is_err()); + assert!(catch_unwind(|| contour.custom_triangulate(Default::default())).is_err()); + assert!(catch_unwind(|| contour.unchecked_triangulate()).is_err()); + assert!(catch_unwind(|| contour.uniform_triangulate(1.0)).is_err()); + let mesh = Triangulation { + points: vec![[0.0, 0.0], [2.0, 0.0], [0.0, 2.0]], + indices: vec![0u32, 1, 2], + }; + assert!(catch_unwind(|| mesh.locate_points::(&[[invalid, 1.0]])).is_err()); + } +} + +#[test] +fn reusable_triangulator_preserves_output_on_invalid_input_and_recovers() { + let valid = vec![[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]]; + let invalid = vec![[0.0, 0.0], [f64::MAX_COORDINATE * 2.0, 1.0], [2.0, 2.0]]; + let mut triangulator = Triangulator::::default(); + let mut output = triangulator.triangulate(&valid); + let previous = output.clone(); + for unchecked in [false, true] { + assert!(catch_unwind(AssertUnwindSafe(|| { + if unchecked { + triangulator.uncheck_triangulate_into(&invalid, &mut output); + } else { + triangulator.triangulate_into(&invalid, &mut output); + } + })) + .is_err()); + assert_eq!(output.points, previous.points); + assert_eq!(output.indices, previous.indices); + if unchecked { + triangulator.uncheck_triangulate_into(&valid, &mut output); + } else { + triangulator.triangulate_into(&valid, &mut output); + } + output.validate(16.0, 1e-10); + } +} + +#[test] +fn empty_input_clears_reused_output() { + let empty: Vec<[f64; 2]> = Vec::new(); + assert!(empty.triangulate().raw.points().is_empty()); + assert!(empty + .custom_triangulate(Default::default()) + .raw + .points() + .is_empty()); + assert!(empty.unchecked_triangulate().raw.points().is_empty()); + assert!(empty + .uniform_triangulate(2.0) + .to_triangulation::() + .indices + .is_empty()); + let mut triangulator = Triangulator::::default(); + let valid = [[0.0, 0.0], [4.0, 0.0], [0.0, 4.0]]; + let mut output = triangulator.triangulate(&valid); + triangulator.triangulate_into(&empty, &mut output); + assert!(output.points.is_empty() && output.indices.is_empty()); + triangulator.uncheck_triangulate_into(&valid, &mut output); + triangulator.uncheck_triangulate_into(&empty, &mut output); + assert!(output.points.is_empty() && output.indices.is_empty()); +} + +#[test] +fn self_intersections_match_overlay_on_the_same_integer_grid() { + use i_triangle::i_overlay::core::fill_rule::FillRule; + use i_triangle::i_overlay::float::simplify::SimplifyShape; + use i_triangle::i_overlay::i_shape::float::area::Area; + + // With i16 the intersection rounding changes area by more than the old + // test tolerance relative to i32. The reference must use the selected engine. + fn check() { + let contour = [ + [-5.0f32, -2.0], + [4.0, 5.0], + [-3.0, 1.0], + [0.0, 3.0], + [0.0, -3.0], + [0.0, 4.0], + ]; + let area = contour.simplify_shape_as::(FillRule::NonZero).area(); + let mut triangulator = Triangulator::::default(); + for delaunay in [false, true] { + triangulator.delaunay(delaunay); + triangulator.triangulate(&contour).validate(area, 0.000_01); + } + contour + .triangulate_as::() + .to_triangulation::() + .validate(area, 0.000_01); + } + check::(); + check::(); + check::(); +} + +#[test] +fn nested_empty_contours_clear_buffers_without_leaving_stale_geometry() { + let empty: Vec>> = vec![vec![vec![]], vec![vec![], vec![]]]; + let mut triangulator = Triangulator::::default(); + let valid = [[0.0, 0.0], [4.0, 0.0], [0.0, 4.0]]; + let mut output = triangulator.triangulate(&valid); + triangulator.triangulate_into(&empty, &mut output); + assert!(output.points.is_empty() && output.indices.is_empty()); + triangulator.uncheck_triangulate_into(&valid, &mut output); + triangulator.uncheck_triangulate_into(&empty, &mut output); + assert!(output.points.is_empty() && output.indices.is_empty()); +} diff --git a/iTriangle/tests/float_tests.rs b/iTriangle/tests/float_tests.rs index 1159cd7..5d76278 100644 --- a/iTriangle/tests/float_tests.rs +++ b/iTriangle/tests/float_tests.rs @@ -10,6 +10,7 @@ mod tests { use i_triangle::float::triangulator::Triangulator; use rand::RngExt; + // Reference geometry must use the same integer grid as the triangulator. trait TestInt: OverlayInt {} impl TestInt for I {} @@ -71,7 +72,7 @@ mod tests { triangulator.delaunay(true); let t1 = triangulator.triangulate(&contour); - let area = contour.simplify_shape(FillRule::NonZero).area(); + let area = contour.simplify_shape_as::(FillRule::NonZero).area(); t0.validate(area, 0.001); t1.validate(area, 0.001); @@ -93,7 +94,7 @@ mod tests { [0.0, 3.0], ]; - let simple = contour.simplify_shape(FillRule::NonZero); + let simple = contour.simplify_shape_as::(FillRule::NonZero); let area = simple.area(); let mut triangulator = Triangulator::::default(); @@ -126,7 +127,7 @@ mod tests { triangulator.delaunay(true); let t1 = triangulator.triangulate(&contour); - let area = contour.simplify_shape(FillRule::NonZero).area(); + let area = contour.simplify_shape_as::(FillRule::NonZero).area(); t0.validate(area, 0.001); t1.validate(area, 0.001); @@ -145,7 +146,7 @@ mod tests { for _ in 0..random_cases(20_000) { let contour = random(8, 5); - let area = contour.simplify_shape(FillRule::NonZero).area(); + let area = contour.simplify_shape_as::(FillRule::NonZero).area(); triangulator.delaunay(false); triangulator.triangulate_into(&contour, &mut t); @@ -170,7 +171,7 @@ mod tests { for _ in 0..random_cases(20_000) { let contour = random(10, 6); - let area = contour.simplify_shape(FillRule::NonZero).area(); + let area = contour.simplify_shape_as::(FillRule::NonZero).area(); triangulator.delaunay(false); triangulator.triangulate_into(&contour, &mut t); @@ -195,7 +196,7 @@ mod tests { for _ in 0..random_cases(20_000) { let contour = random(10, 12); - let area = contour.simplify_shape(FillRule::NonZero).area(); + let area = contour.simplify_shape_as::(FillRule::NonZero).area(); triangulator.delaunay(false); triangulator.triangulate_into(&contour, &mut t); @@ -220,7 +221,7 @@ mod tests { for _ in 0..random_cases(10_000) { let contour = random(20, 20); - let area = contour.simplify_shape(FillRule::NonZero).area(); + let area = contour.simplify_shape_as::(FillRule::NonZero).area(); triangulator.delaunay(false); triangulator.triangulate_into(&contour, &mut t); @@ -245,7 +246,7 @@ mod tests { for _ in 0..random_cases(1_000) { let contour = random(30, 50); - let area = contour.simplify_shape(FillRule::NonZero).area(); + let area = contour.simplify_shape_as::(FillRule::NonZero).area(); triangulator.delaunay(false); triangulator.triangulate_into(&contour, &mut t); @@ -275,7 +276,7 @@ mod tests { shape.push(random(30, 5)); } - let area = shape.simplify_shape(FillRule::NonZero).area(); + let area = shape.simplify_shape_as::(FillRule::NonZero).area(); triangulator.delaunay(false); triangulator.triangulate_into(&shape, &mut t); diff --git a/iTriangle/tests/relax_stress.rs b/iTriangle/tests/relax_stress.rs index 9f1830b..0c90f95 100644 --- a/iTriangle/tests/relax_stress.rs +++ b/iTriangle/tests/relax_stress.rs @@ -106,7 +106,7 @@ fn boundary_vertices(point_count: usize, indices: &[u32], neighbors: &[[usize; 3 assert_eq!(neighbors.len(), triangle_count); let mut result = vec![false; point_count]; - for (triangle_index, triangle) in indices.chunks_exact(3).enumerate() { + for (triangle_index, triangle) in indices.as_chunks::<3>().0.iter().enumerate() { for edge in 0..3 { if neighbors[triangle_index][edge] >= triangle_count { result[triangle[(edge + 1) % 3] as usize] = true; @@ -119,7 +119,7 @@ fn boundary_vertices(point_count: usize, indices: &[u32], neighbors: &[[usize; 3 fn mesh_area(case: usize, points: &[[f64; 2]], indices: &[u32]) -> f64 { let mut area_two = 0.0; - for triangle in indices.chunks_exact(3) { + for triangle in indices.as_chunks::<3>().0 { let a = points[triangle[0] as usize]; let b = points[triangle[1] as usize]; let c = points[triangle[2] as usize]; diff --git a/performance/rust_app/src/test/runner.rs b/performance/rust_app/src/test/runner.rs index ed3e8be..c38addf 100644 --- a/performance/rust_app/src/test/runner.rs +++ b/performance/rust_app/src/test/runner.rs @@ -4,7 +4,7 @@ use i_triangle::float::triangulation::Triangulation; use i_triangle::float::triangulator::Triangulator; use i_triangle::i_overlay::i_float::float::compatible::FloatPointCompatible; use i_triangle::i_overlay::i_float::float::number::FloatNumber; -use i_triangle::i_overlay::i_shape::source::resource::ShapeResource; +use i_triangle::i_overlay::i_shape::source::float::resource::ShapeResource; use crate::test::test::TestData; pub(crate) struct Runner; @@ -13,8 +13,8 @@ impl Runner { pub(crate) fn run_triangle(resource: &R, test: &TestData, delaunay: bool, earcut: bool) -> usize where - R: ShapeResource + ?Sized, - P: FloatPointCompatible, + R: ShapeResource

+ ?Sized, + P: FloatPointCompatible, T: FloatNumber, { let start = Instant::now();