From ccdf4cca7eec6cb7957f88e800737cad792418d8 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Wed, 6 May 2026 18:55:14 +0300 Subject: [PATCH 1/4] switch to iterator api --- iTriangle/src/int/locator.rs | 147 ++++++++++++++++++++--------- iTriangle/src/int/triangulation.rs | 88 +++++++++++++++++ 2 files changed, 189 insertions(+), 46 deletions(-) diff --git a/iTriangle/src/int/locator.rs b/iTriangle/src/int/locator.rs index 9e1c16f..0a56716 100644 --- a/iTriangle/src/int/locator.rs +++ b/iTriangle/src/int/locator.rs @@ -1,6 +1,5 @@ use alloc::vec; use alloc::vec::Vec; -use i_overlay::i_float::triangle::Triangle; use i_overlay::i_shape::int::IntPoint; use crate::{ @@ -12,51 +11,38 @@ pub trait IntPointInTriangulationLocator { fn locate_points(&self, points: &[IntPoint]) -> Vec; } -impl IntTriangulation { - pub fn locate_points(&self, points: &[IntPoint]) -> Vec { - let mut result = vec![PointLocationInTriangulation::Outside; points.len()]; - - for (index, triangle) in self.indices.chunks_exact(3).enumerate() { - let vertex0 = self.points[triangle[0].into_usize()]; - let vertex1 = self.points[triangle[1].into_usize()]; - let vertex2 = self.points[triangle[2].into_usize()]; - let triangle_index = TriangleIndex::new(index); - - for (point_index, &point) in points.iter().enumerate() { - if point == vertex0 || point == vertex1 || point == vertex2 { - match &mut result[point_index] { - PointLocationInTriangulation::Outside => { - result[point_index] = - PointLocationInTriangulation::OnVertex(vec![triangle_index]); - } - PointLocationInTriangulation::OnVertex(hits) => { - hits.push(triangle_index); - } - // Shouldn't happen. - _ => {} - } +impl IntPointInTriangulationLocator for I +where + I: Iterator + Clone, +{ + fn locate_points(&self, points: &[IntPoint]) -> Vec { + locate_points_in_triangles(self.clone(), points) + } +} - continue; - } - - if !Triangle::is_contain_point(point, vertex0, vertex1, vertex2) { - continue; - } - - if Triangle::is_contain_point_exclude_borders(point, vertex0, vertex1, vertex2) { - match &result[point_index] { - PointLocationInTriangulation::Outside => { - result[point_index] = - PointLocationInTriangulation::InsideTriangle(triangle_index); - } - // Shouldn't happen. - _ => {} - } +fn locate_points_in_triangles( + triangles: impl Iterator, + points: &[IntPoint], +) -> Vec { + let mut result = vec![PointLocationInTriangulation::Outside; points.len()]; - continue; - } + for (index, triangle) in triangles.enumerate() { + let triangle_index = TriangleIndex::new(index); - match &result[point_index] { + for (point_index, &point) in points.iter().enumerate() { + match triangle.locate_point(point) { + PointLocationInTriangle::Outside => {} + PointLocationInTriangle::Inside => match &result[point_index] { + PointLocationInTriangulation::Outside => { + result[point_index] = + PointLocationInTriangulation::InsideTriangle(triangle_index); + } + // Shouldn't happen. + _ => { + panic!("Expected outside triangle"); + } + }, + PointLocationInTriangle::OnEdge => match &result[point_index] { PointLocationInTriangulation::Outside => { result[point_index] = PointLocationInTriangulation::OnExteriorEdge(triangle_index); @@ -66,12 +52,81 @@ impl IntTriangulation { PointLocationInTriangulation::OnInteriorEdge(*i, triangle_index); } // Shouldn't happen. - _ => {} - } + _ => { + panic!("More than 2 triangles for one edge"); + } + }, + PointLocationInTriangle::OnVertex => match &mut result[point_index] { + PointLocationInTriangulation::Outside => { + result[point_index] = + PointLocationInTriangulation::OnVertex(vec![triangle_index]); + } + PointLocationInTriangulation::OnVertex(hits) => { + hits.push(triangle_index); + } + // Shouldn't happen. + _ => { + panic!("Point must be only on Vertex"); + } + }, } } + } + + result +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum PointLocationInTriangle { + Outside, + Inside, + OnEdge, + OnVertex, +} + +trait IntPointInTriangleLocator { + fn locate_point(&self, point: IntPoint) -> PointLocationInTriangle; +} + +impl IntPointInTriangleLocator for [IntPoint; 3] { + #[inline] + fn locate_point(&self, point: IntPoint) -> PointLocationInTriangle { + let [p0, p1, p2] = *self; - result + if point == p0 || point == p1 || point == p2 { + return PointLocationInTriangle::OnVertex; + } + + let px = point.x as i64; + let py = point.y as i64; + let x0 = p0.x as i64; + let y0 = p0.y as i64; + let x1 = p1.x as i64; + let y1 = p1.y as i64; + let x2 = p2.x as i64; + let y2 = p2.y as i64; + + let q0 = (px - x1) * (y0 - y1) - (py - y1) * (x0 - x1); + let q1 = (px - x2) * (y1 - y2) - (py - y2) * (x1 - x2); + let q2 = (px - x0) * (y2 - y0) - (py - y0) * (x2 - x0); + + let has_neg = q0 < 0 || q1 < 0 || q2 < 0; + let has_pos = q0 > 0 || q1 > 0 || q2 > 0; + + if has_neg && has_pos { + PointLocationInTriangle::Outside + } else if q0 == 0 || q1 == 0 || q2 == 0 { + PointLocationInTriangle::OnEdge + } else { + PointLocationInTriangle::Inside + } + } +} + +impl IntTriangulation { + #[inline] + pub fn locate_points(&self, points: &[IntPoint]) -> Vec { + locate_points_in_triangles(self.triangles(), points) } } diff --git a/iTriangle/src/int/triangulation.rs b/iTriangle/src/int/triangulation.rs index a787b1c..24807cf 100644 --- a/iTriangle/src/int/triangulation.rs +++ b/iTriangle/src/int/triangulation.rs @@ -1,5 +1,6 @@ use crate::geom::triangle::IntTriangle; use alloc::vec::Vec; +use core::iter::FusedIterator; use i_overlay::i_float::int::point::IntPoint; use i_overlay::i_float::triangle::Triangle; use i_overlay::i_shape::util::reserve::Reserve; @@ -78,6 +79,43 @@ pub struct IntTriangulation { pub indices: Vec, } +/// Iterator over resolved triangles in a flat [`IntTriangulation`]. +/// +/// Each item contains the three triangle points addressed by one consecutive +/// triple in the triangulation index buffer. +#[derive(Clone)] +pub struct IntTriangleIterator<'a, I> { + points: &'a [IntPoint], + indices: core::slice::ChunksExact<'a, I>, +} + +impl Iterator for IntTriangleIterator<'_, I> { + type Item = [IntPoint; 3]; + + #[inline] + fn next(&mut self) -> Option { + let indices = self.indices.next()?; + let a = self.points[indices[0].into_usize()]; + let b = self.points[indices[1].into_usize()]; + let c = self.points[indices[2].into_usize()]; + Some([a, b, c]) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.indices.size_hint() + } +} + +impl ExactSizeIterator for IntTriangleIterator<'_, I> { + #[inline] + fn len(&self) -> usize { + self.indices.len() + } +} + +impl FusedIterator for IntTriangleIterator<'_, I> {} + /// A int triangle mesh produced by the triangulation process. /// /// This is the low-level output containing full triangle and vertex data, @@ -183,6 +221,18 @@ impl IntTriangulation { self.points.extend_from_slice(&other.points) } + /// Iterates over resolved triangle points. + /// + /// The iterator walks `indices` in exact triples and yields the matching + /// `[IntPoint; 3]` for each triangle. + #[inline] + pub fn triangles(&self) -> IntTriangleIterator<'_, I> { + IntTriangleIterator { + points: &self.points, + indices: self.indices.chunks_exact(3), + } + } + #[inline] pub fn reserve_and_clear(&mut self, new_len: usize) { self.points.reserve_capacity(new_len); @@ -202,6 +252,44 @@ 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); } From e962b7097d24e2469ce118b90153d64ec27369fc Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Wed, 6 May 2026 19:08:29 +0300 Subject: [PATCH 2/4] update float api to use iter logic --- iTriangle/src/float/locator.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/iTriangle/src/float/locator.rs b/iTriangle/src/float/locator.rs index d44037c..16f805e 100644 --- a/iTriangle/src/float/locator.rs +++ b/iTriangle/src/float/locator.rs @@ -5,9 +5,10 @@ use i_overlay::{i_float::adapter::FloatPointAdapter, i_shape::float::adapter::Pa use crate::{ float::triangulation::Triangulation, - int::triangulation::{IndexType, IntTriangulation}, + int::triangulation::IndexType, location::PointLocationInTriangulation, }; +use crate::int::locator::IntPointInTriangulationLocator; pub trait PointInTriangulationLocator

{ fn locate_points(&self, points: &[P]) -> Vec @@ -23,12 +24,16 @@ impl Triangulation { { let adapter = FloatPointAdapter::with_iter(self.points.iter().chain(points.iter())); - let int_triangulation = IntTriangulation { - points: self.points.to_int(&adapter), - indices: self.indices.clone(), - }; + let int_points = points.to_int(&adapter); - int_triangulation.locate_points(&points.to_int(&adapter)) + let triangles = self.indices.chunks_exact(3).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()]); + [a, b, c] + }); + + triangles.locate_points(&int_points) } } From a5044222650390a187d53a23f3c7c5b56ee929f7 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Thu, 7 May 2026 11:00:52 +0300 Subject: [PATCH 3/4] add sort --- iTriangle/src/float/locator.rs | 5 +-- iTriangle/src/int/locator.rs | 71 +++++++++++++++++++++++++++++-- iTriangle/src/int/triangulator.rs | 1 + 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/iTriangle/src/float/locator.rs b/iTriangle/src/float/locator.rs index 16f805e..254834f 100644 --- a/iTriangle/src/float/locator.rs +++ b/iTriangle/src/float/locator.rs @@ -3,12 +3,11 @@ use i_overlay::i_float::float::compatible::FloatPointCompatible; use i_overlay::i_float::float::number::FloatNumber; use i_overlay::{i_float::adapter::FloatPointAdapter, i_shape::float::adapter::PathToInt}; +use crate::int::locator::IntPointInTriangulationLocator; use crate::{ - float::triangulation::Triangulation, - int::triangulation::IndexType, + float::triangulation::Triangulation, int::triangulation::IndexType, location::PointLocationInTriangulation, }; -use crate::int::locator::IntPointInTriangulationLocator; pub trait PointInTriangulationLocator

{ fn locate_points(&self, points: &[P]) -> Vec diff --git a/iTriangle/src/int/locator.rs b/iTriangle/src/int/locator.rs index 0a56716..61e1e86 100644 --- a/iTriangle/src/int/locator.rs +++ b/iTriangle/src/int/locator.rs @@ -1,5 +1,7 @@ use alloc::vec; use alloc::vec::Vec; +use i_key_sort::sort::two_keys::TwoKeysSort; +use i_overlay::i_float::int::rect::IntRect; use i_overlay::i_shape::int::IntPoint; use crate::{ @@ -25,11 +27,29 @@ fn locate_points_in_triangles( points: &[IntPoint], ) -> Vec { let mut result = vec![PointLocationInTriangulation::Outside; points.len()]; + let mut sorted_points: Vec<_> = points + .iter() + .enumerate() + .map(|(index, &point)| IndexedPoint { index, point }) + .collect(); + sorted_points.sort_by_two_keys(false, |p| p.point.x, |p| p.point.y); for (index, triangle) in triangles.enumerate() { let triangle_index = TriangleIndex::new(index); + let rect = triangle.boundary(); + let min = IntPoint::new(rect.min_x, rect.min_y); + let max = IntPoint::new(rect.max_x, rect.max_y); + let start = sorted_points.partition_point(|p| p.point < min); + + for &IndexedPoint { + index: point_index, + point, + } in sorted_points[start..].iter().take_while(|p| p.point <= max) + { + if point.y < rect.min_y || point.y > rect.max_y { + continue; + } - for (point_index, &point) in points.iter().enumerate() { match triangle.locate_point(point) { PointLocationInTriangle::Outside => {} PointLocationInTriangle::Inside => match &result[point_index] { @@ -76,6 +96,12 @@ fn locate_points_in_triangles( result } +#[derive(Clone, Copy)] +struct IndexedPoint { + index: usize, + point: IntPoint, +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum PointLocationInTriangle { Outside, @@ -86,6 +112,8 @@ enum PointLocationInTriangle { trait IntPointInTriangleLocator { fn locate_point(&self, point: IntPoint) -> PointLocationInTriangle; + + fn boundary(&self) -> IntRect; } impl IntPointInTriangleLocator for [IntPoint; 3] { @@ -121,6 +149,14 @@ impl IntPointInTriangleLocator for [IntPoint; 3] { PointLocationInTriangle::Inside } } + + #[inline] + fn boundary(&self) -> IntRect { + let mut rect = IntRect::with_point(self[0]); + rect.unsafe_add_point(&self[1]); + rect.unsafe_add_point(&self[2]); + rect + } } impl IntTriangulation { @@ -139,13 +175,18 @@ impl IntPointInTriangulationLocator for IntTriangulation { #[cfg(test)] mod tests { - use alloc::vec; - use i_overlay::i_shape::int::IntPoint; - use crate::{ int::triangulation::IntTriangulation, location::{PointLocationInTriangulation, TriangleIndex}, }; + use alloc::vec; + use i_overlay::core::fill_rule::FillRule::NonZero; + use i_overlay::core::overlay::IntOverlayOptions; + use i_overlay::i_shape::int::IntPoint; + use i_overlay::i_shape::int_path; + use crate::int::triangulatable::IntTriangulatable; + use crate::int::triangulator::IntTriangulator; + use crate::int::validation::Validation; fn square_triangulation() -> IntTriangulation { IntTriangulation { @@ -201,4 +242,26 @@ mod tests { PointLocationInTriangulation::Outside )); } + + #[test] + fn test_two_stacked_squares() { + let path = int_path![[0, 8], [0, 4], [0, 0], [4, 0], [4, 4], [4, 8]]; + let validation = Validation { + fill_rule: Default::default(), + options: IntOverlayOptions::keep_all_points(), + }; + let mut triangulator = IntTriangulator::::new(32, validation, Default::default()); + triangulator.delaunay = true; + let triangulation = triangulator.triangulate_contour(&path); + + let points_to_locate = int_path![[0, 8], [0, 4], [0, 0], [4, 0], [4, 4], [4, 8]]; + + let locations = triangulation.locate_points(&points_to_locate); + + assert!(matches!( + locations[0].clone(), + PointLocationInTriangulation::OnVertex(vec) if vec[0].index() == 0 + )); + + } } diff --git a/iTriangle/src/int/triangulator.rs b/iTriangle/src/int/triangulator.rs index 3129d8e..deefd8e 100644 --- a/iTriangle/src/int/triangulator.rs +++ b/iTriangle/src/int/triangulator.rs @@ -137,6 +137,7 @@ impl IntTriangulator { self.triangulator .contour_into_net_triangulation(contour, None, &mut raw); } + triangulation.fill_with_raw(&raw); self.raw_buffer = Some(raw); } else if self.earcut && contour.is_earcut_compatible() { From 59e14e44c071dcf9269d2469f783cf846f2e8d63 Mon Sep 17 00:00:00 2001 From: Nail Sharipov Date: Thu, 7 May 2026 13:02:32 +0300 Subject: [PATCH 4/4] refactor tests --- iTriangle/src/int/locator.rs | 144 +++++++++++++++++++++++++---------- 1 file changed, 103 insertions(+), 41 deletions(-) diff --git a/iTriangle/src/int/locator.rs b/iTriangle/src/int/locator.rs index 61e1e86..63de362 100644 --- a/iTriangle/src/int/locator.rs +++ b/iTriangle/src/int/locator.rs @@ -175,18 +175,14 @@ impl IntPointInTriangulationLocator for IntTriangulation { #[cfg(test)] mod tests { - use crate::{ - int::triangulation::IntTriangulation, - location::{PointLocationInTriangulation, TriangleIndex}, - }; + use crate::int::triangulator::IntTriangulator; + use crate::int::validation::Validation; + use crate::{int::triangulation::IntTriangulation, location::PointLocationInTriangulation}; use alloc::vec; - use i_overlay::core::fill_rule::FillRule::NonZero; + use alloc::vec::Vec; use i_overlay::core::overlay::IntOverlayOptions; use i_overlay::i_shape::int::IntPoint; use i_overlay::i_shape::int_path; - use crate::int::triangulatable::IntTriangulatable; - use crate::int::triangulator::IntTriangulator; - use crate::int::validation::Validation; fn square_triangulation() -> IntTriangulation { IntTriangulation { @@ -214,33 +210,12 @@ mod tests { let locations = triangulation.locate_points(&points_to_locate); - assert!(matches!( - locations[0], - PointLocationInTriangulation::InsideTriangle(t) if t == TriangleIndex::new(0) - )); - assert!(matches!( - locations[1], - PointLocationInTriangulation::InsideTriangle(t) if t == TriangleIndex::new(1) - )); - assert!(matches!( - locations[2], - PointLocationInTriangulation::OnInteriorEdge(a, b) - if a == TriangleIndex::new(0) && b == TriangleIndex::new(1) - )); - assert!(matches!( - locations[3], - PointLocationInTriangulation::OnExteriorEdge(t) - if t == TriangleIndex::new(0) - )); - assert!(matches!( - locations[4].clone(), - PointLocationInTriangulation::OnVertex(triangles) - if triangles.as_slice() == [TriangleIndex::new(0), TriangleIndex::new(1)] - )); - assert!(matches!( - locations[5], - PointLocationInTriangulation::Outside - )); + locations[0].assert_inside(0); + locations[1].assert_inside(1); + locations[2].assert_on_edge(&[0, 1]); + locations[3].assert_on_edge(&[0]); + locations[4].assert_on_vertex(&[0, 1]); + assert_eq!(locations[5], PointLocationInTriangulation::Outside); } #[test] @@ -254,14 +229,101 @@ mod tests { triangulator.delaunay = true; let triangulation = triangulator.triangulate_contour(&path); - let points_to_locate = int_path![[0, 8], [0, 4], [0, 0], [4, 0], [4, 4], [4, 8]]; + let points_on_vertex = int_path![[0, 8], [0, 4], [0, 0], [4, 0], [4, 4], [4, 8]]; + let locations_on_vertex = triangulation.locate_points(&points_on_vertex); - let locations = triangulation.locate_points(&points_to_locate); + locations_on_vertex[0].assert_on_vertex(&[2, 3]); + locations_on_vertex[1].assert_on_vertex(&[0, 1, 2]); + locations_on_vertex[2].assert_on_vertex(&[0]); + locations_on_vertex[3].assert_on_vertex(&[0, 1]); + locations_on_vertex[4].assert_on_vertex(&[1, 2, 3]); + locations_on_vertex[5].assert_on_vertex(&[3]); + + let points_on_edge = int_path![ + [0, 2], + [0, 6], + [2, 0], + [2, 2], + [2, 4], + [2, 6], + [2, 8], + [4, 2], + [4, 6] + ]; + let locations_on_edge = triangulation.locate_points(&points_on_edge); + + locations_on_edge[0].assert_on_edge(&[0]); + locations_on_edge[1].assert_on_edge(&[2]); + locations_on_edge[2].assert_on_edge(&[0]); + locations_on_edge[3].assert_on_edge(&[0, 1]); + locations_on_edge[4].assert_on_edge(&[1, 2]); + locations_on_edge[5].assert_on_edge(&[2, 3]); + locations_on_edge[6].assert_on_edge(&[3]); + locations_on_edge[7].assert_on_edge(&[1]); + locations_on_edge[8].assert_on_edge(&[3]); + + let points_inside = int_path![[1, 1], [3, 3], [1, 5], [3, 7]]; + let locations_inside = triangulation.locate_points(&points_inside); + + locations_inside[0].assert_inside(0); + locations_inside[1].assert_inside(1); + locations_inside[2].assert_inside(2); + locations_inside[3].assert_inside(3); + + 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) { + points_outside.push(IntPoint::new(x, y)); + } + } + } - assert!(matches!( - locations[0].clone(), - PointLocationInTriangulation::OnVertex(vec) if vec[0].index() == 0 - )); + let locations_inside = triangulation.locate_points(&points_outside); + for location in locations_inside { + assert_eq!(location, PointLocationInTriangulation::Outside); + } + } + + impl PointLocationInTriangulation { + fn assert_on_vertex(&self, triangles: &[usize]) { + if let PointLocationInTriangulation::OnVertex(vec) = self { + let mut vertex_triangles: Vec<_> = vec.iter().map(|e| e.index()).collect(); + vertex_triangles.sort(); + + let mut template_triangles = triangles.to_vec(); + template_triangles.sort(); + + assert_eq!(vertex_triangles, template_triangles); + } else { + assert!(false, "not on Vertex"); + } + } + + fn assert_on_edge(&self, triangles: &[usize]) { + match self { + PointLocationInTriangulation::OnExteriorEdge(triangle) => { + assert_eq!(triangles[0], triangle.index()) + } + PointLocationInTriangulation::OnInteriorEdge(t0, t1) => { + assert!( + t0.index() == triangles[0] && t1.index() == triangles[1] + || t0.index() == triangles[1] && t1.index() == triangles[0] + ); + } + _ => { + assert!(false, "not on Edge"); + } + } + } + + fn assert_inside(&self, index: usize) { + if let PointLocationInTriangulation::InsideTriangle(triangle) = self { + assert_eq!(triangle.index(), index); + } else { + assert!(false, "not Inside"); + } + } } }