use crate::predicates::{is_convex_vertex2d, orient2d, point_in_triangle_2d};
type V2 = [f64; 2];
fn is_ccw(poly: &[V2]) -> bool {
let n = poly.len();
if n < 3 {
return false;
}
let v0 = poly[0];
let mut sum = 0.0f64;
for i in 1..n - 1 {
sum += orient2d(v0, poly[i], poly[i + 1]);
}
sum > 0.0
}
pub fn fan_triangulation(polygon: &[[f64; 2]]) -> Vec<[usize; 3]> {
let n = polygon.len();
if n < 3 {
return Vec::new();
}
let mut tris = Vec::with_capacity(n - 2);
for i in 2..n {
tris.push([0, i - 1, i]);
}
tris
}
pub fn fan_triangulation_3d(polygon: &[[f64; 3]]) -> Vec<[usize; 3]> {
let n = polygon.len();
if n < 3 {
return Vec::new();
}
let mut tris = Vec::with_capacity(n - 2);
for i in 2..n {
tris.push([0, i - 1, i]);
}
tris
}
pub fn ear_clipping(polygon: &[[f64; 2]]) -> Vec<[usize; 3]> {
let n = polygon.len();
if n < 3 {
return Vec::new();
}
if n == 3 {
return vec![[0, 1, 2]];
}
let mut verts: Vec<V2> = polygon.to_vec();
if !is_ccw(&verts) {
verts.reverse();
}
let mut indices: Vec<usize> = (0..n).collect();
let mut triangles = Vec::with_capacity(n - 2);
let mut iter_count = 0;
while indices.len() > 3 {
iter_count += 1;
if iter_count > indices.len() * indices.len() {
break;
}
let m = indices.len();
let mut found_ear = false;
for i in 0..m {
let prev = if i == 0 { m - 1 } else { i - 1 };
let next = (i + 1) % m;
let a = verts[indices[prev]];
let b = verts[indices[i]];
let c = verts[indices[next]];
if !is_convex_vertex2d(a, b, c) {
continue; }
let mut is_ear = true;
for j in 0..m {
if j == prev || j == i || j == next {
continue;
}
if point_in_triangle_2d(verts[indices[j]], a, b, c) {
is_ear = false;
break;
}
}
if is_ear {
triangles.push([indices[prev], indices[i], indices[next]]);
indices.remove(i);
found_ear = true;
break;
}
}
if !found_ear {
break; }
}
if indices.len() == 3 {
triangles.push([indices[0], indices[1], indices[2]]);
}
triangles
}
pub fn ear_clipping_3d(polygon: &[[f64; 3]]) -> Vec<[usize; 3]> {
let n = polygon.len();
if n < 3 {
return Vec::new();
}
if n == 3 {
return vec![[0, 1, 2]];
}
let mut normal = [0.0f64; 3];
for i in 0..n {
let j = (i + 1) % n;
normal[0] += (polygon[i][1] - polygon[j][1]) * (polygon[i][2] + polygon[j][2]);
normal[1] += (polygon[i][2] - polygon[j][2]) * (polygon[i][0] + polygon[j][0]);
normal[2] += (polygon[i][0] - polygon[j][0]) * (polygon[i][1] + polygon[j][1]);
}
let drop_axis = if normal[0].abs() >= normal[1].abs() && normal[0].abs() >= normal[2].abs() {
0 } else if normal[1].abs() >= normal[2].abs() {
1 } else {
2 };
let poly_2d: Vec<V2> = polygon
.iter()
.map(|p| match drop_axis {
0 => [p[1], p[2]],
1 => [p[0], p[2]],
_ => [p[0], p[1]],
})
.collect();
ear_clipping(&poly_2d)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fan_triangulation_quad() {
let quad = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let tris = fan_triangulation(&quad);
assert_eq!(tris.len(), 2);
assert_eq!(tris[0], [0, 1, 2]);
assert_eq!(tris[1], [0, 2, 3]);
}
#[test]
fn fan_triangulation_empty() {
assert_eq!(fan_triangulation(&[]).len(), 0);
assert_eq!(fan_triangulation(&[[0.0, 0.0]]).len(), 0);
}
#[test]
fn ear_clipping_convex_quad() {
let quad = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let tris = ear_clipping(&quad);
assert_eq!(tris.len(), 2);
}
#[test]
fn ear_clipping_concave_l_shape() {
let lshape = [
[0.0, 0.0],
[2.0, 0.0],
[2.0, 0.5],
[1.0, 0.5],
[1.0, 2.0],
[0.0, 2.0],
];
let tris = ear_clipping(&lshape);
assert_eq!(tris.len(), 4);
}
#[test]
fn ear_clipping_3d_square() {
let square = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let tris = ear_clipping_3d(&square);
assert_eq!(tris.len(), 2);
}
#[test]
fn ear_clipping_triangle() {
let tri = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
assert_eq!(ear_clipping(&tri).len(), 1);
}
}