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);
}