diff --git a/iTriangle/src/float/locator.rs b/iTriangle/src/float/locator.rs index d44037c..254834f 100644 --- a/iTriangle/src/float/locator.rs +++ b/iTriangle/src/float/locator.rs @@ -3,9 +3,9 @@ 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, IntTriangulation}, + float::triangulation::Triangulation, int::triangulation::IndexType, location::PointLocationInTriangulation, }; @@ -23,12 +23,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) } } diff --git a/iTriangle/src/int/locator.rs b/iTriangle/src/int/locator.rs index 9e1c16f..63de362 100644 --- a/iTriangle/src/int/locator.rs +++ b/iTriangle/src/int/locator.rs @@ -1,6 +1,7 @@ use alloc::vec; use alloc::vec::Vec; -use i_overlay::i_float::triangle::Triangle; +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::{ @@ -12,51 +13,56 @@ 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. - _ => {} - } - - continue; - } +impl IntPointInTriangulationLocator for I +where + I: Iterator + Clone, +{ + fn locate_points(&self, points: &[IntPoint]) -> Vec { + locate_points_in_triangles(self.clone(), points) + } +} - if !Triangle::is_contain_point(point, vertex0, vertex1, vertex2) { - continue; - } +fn locate_points_in_triangles( + triangles: impl Iterator, + 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); - 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. - _ => {} - } + 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); - continue; - } + 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; + } - match &result[point_index] { + 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 +72,97 @@ 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)] +struct IndexedPoint { + index: usize, + point: IntPoint, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum PointLocationInTriangle { + Outside, + Inside, + OnEdge, + OnVertex, +} + +trait IntPointInTriangleLocator { + fn locate_point(&self, point: IntPoint) -> PointLocationInTriangle; - result + fn boundary(&self) -> IntRect; +} + +impl IntPointInTriangleLocator for [IntPoint; 3] { + #[inline] + fn locate_point(&self, point: IntPoint) -> PointLocationInTriangle { + let [p0, p1, p2] = *self; + + 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 + } + } + + #[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 { + #[inline] + pub fn locate_points(&self, points: &[IntPoint]) -> Vec { + locate_points_in_triangles(self.triangles(), points) } } @@ -84,13 +175,14 @@ impl IntPointInTriangulationLocator for IntTriangulation { #[cfg(test)] mod tests { + use crate::int::triangulator::IntTriangulator; + use crate::int::validation::Validation; + use crate::{int::triangulation::IntTriangulation, location::PointLocationInTriangulation}; use alloc::vec; + use alloc::vec::Vec; + use i_overlay::core::overlay::IntOverlayOptions; use i_overlay::i_shape::int::IntPoint; - - use crate::{ - int::triangulation::IntTriangulation, - location::{PointLocationInTriangulation, TriangleIndex}, - }; + use i_overlay::i_shape::int_path; fn square_triangulation() -> IntTriangulation { IntTriangulation { @@ -118,32 +210,120 @@ 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] + 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_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); + + 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)); + } + } + } + + 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"); + } + } } } diff --git a/iTriangle/src/int/triangulation.rs b/iTriangle/src/int/triangulation.rs index 9caabb1..0a0a051 100644 --- a/iTriangle/src/int/triangulation.rs +++ b/iTriangle/src/int/triangulation.rs @@ -1,6 +1,7 @@ use crate::advanced::delaunay::IntDelaunay; 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; @@ -79,6 +80,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, @@ -184,6 +222,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); @@ -213,6 +263,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); }