type V2 = [f64; 2];
fn v2_sub(a: V2, b: V2) -> V2 {
[a[0] - b[0], a[1] - b[1]]
}
fn v2_cross(a: V2, b: V2) -> f64 {
a[0] * b[1] - a[1] * b[0]
}
fn signed_area(a: V2, b: V2, c: V2) -> f64 {
0.5 * ((b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]))
}
fn point_in_triangle(p: V2, a: V2, b: V2, c: V2) -> bool {
let area = signed_area(a, b, c).abs();
let a1 = signed_area(p, b, c).abs();
let a2 = signed_area(a, p, c).abs();
let a3 = signed_area(a, b, p).abs();
(a1 + a2 + a3 - area).abs() < 1e-12 * area.max(1e-12)
}
fn is_ccw(poly: &[V2]) -> bool {
let n = poly.len();
if n < 3 {
return false;
}
let mut sum = 0.0;
for i in 0..n {
let j = (i + 1) % n;
sum += (poly[j][0] - poly[i][0]) * (poly[j][1] + 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]];
let cross = v2_cross(v2_sub(b, a), v2_sub(c, a));
if cross <= 0.0 {
continue; }
let mut is_ear = true;
for j in 0..m {
if j == prev || j == i || j == next {
continue;
}
if point_in_triangle(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);
}
}