use crate::polygon::PolygonType;
use crate::wgs84::Wgs84;
use crate::wgs84_polygon::Wgs84Polygon;
#[derive(Debug, Clone, Copy)]
struct PartitionVertex {
is_active: bool,
is_convex: bool,
is_ear: bool,
p: Wgs84,
angle: f64,
previous: usize,
next: usize,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct PolygonTriangulation;
impl PolygonTriangulation {
pub fn new() -> Self {
PolygonTriangulation
}
pub fn triangulate_by_ear_clipping(&self, polygon: &Wgs84Polygon) -> Wgs84Polygon {
let mut result = Wgs84Polygon::with_type(PolygonType::TriangleList);
if polygon.polygon_type() != PolygonType::SimplePolygon || polygon.vertices().len() < 3 {
return Wgs84Polygon::with_type(PolygonType::Unknown);
}
let num_vertices = polygon.vertices().len();
if num_vertices == 3 {
result.add_vertices(polygon.vertices());
return result;
}
let mut vertices: Vec<PartitionVertex> = (0..num_vertices)
.map(|i| PartitionVertex {
is_active: true,
is_convex: false,
is_ear: false,
p: polygon.get(i),
angle: 0.0,
next: if i == num_vertices - 1 { 0 } else { i + 1 },
previous: if i == 0 { num_vertices - 1 } else { i - 1 },
})
.collect();
for i in 0..num_vertices {
Self::update_vertex(i, &mut vertices, num_vertices);
}
let mut ear = 0usize;
for i in 0..(num_vertices - 3) {
let mut ear_found = false;
for j in 0..num_vertices {
if !vertices[j].is_active || !vertices[j].is_ear {
continue;
}
if !ear_found {
ear_found = true;
ear = j;
} else if vertices[j].angle > vertices[ear].angle {
ear = j;
}
}
if !ear_found {
return Wgs84Polygon::with_type(PolygonType::Unknown);
}
let ear_prev = vertices[ear].previous;
let ear_next = vertices[ear].next;
result.add_vertex(vertices[ear_prev].p);
result.add_vertex(vertices[ear].p);
result.add_vertex(vertices[ear_next].p);
vertices[ear].is_active = false;
vertices[ear_prev].next = ear_next;
vertices[ear_next].previous = ear_prev;
if i == num_vertices - 4 {
break;
}
Self::update_vertex(ear_prev, &mut vertices, num_vertices);
Self::update_vertex(ear_next, &mut vertices, num_vertices);
}
for i in 0..num_vertices {
if vertices[i].is_active {
let prev = vertices[i].previous;
let next = vertices[i].next;
result.add_vertex(vertices[prev].p);
result.add_vertex(vertices[i].p);
result.add_vertex(vertices[next].p);
break;
}
}
result
}
fn normalize(p: Wgs84) -> Wgs84 {
let n = (p.lon * p.lon + p.lat * p.lat).sqrt();
if n != 0.0 {
Wgs84::new(p.lon / n, p.lat / n)
} else {
Wgs84::new(0.0, 0.0)
}
}
fn is_convex(p1: Wgs84, p2: Wgs84, p3: Wgs84) -> bool {
(p3.lat - p1.lat) * (p2.lon - p1.lon) - (p3.lon - p1.lon) * (p2.lat - p1.lat) > 0.0
}
fn is_inside(p1: Wgs84, p2: Wgs84, p3: Wgs84, p: Wgs84) -> bool {
!Self::is_convex(p1, p, p2) && !Self::is_convex(p2, p, p3) && !Self::is_convex(p3, p, p1)
}
fn update_vertex(v_idx: usize, vertices: &mut [PartitionVertex], num_vertices: usize) {
let prev_idx = vertices[v_idx].previous;
let next_idx = vertices[v_idx].next;
let v_p = vertices[v_idx].p;
let v1_p = vertices[prev_idx].p;
let v3_p = vertices[next_idx].p;
let is_convex = Self::is_convex(v1_p, v_p, v3_p);
let vec1 = Self::normalize(v1_p - v_p);
let vec3 = Self::normalize(v3_p - v_p);
let angle = vec1.lon * vec3.lon + vec1.lat * vec3.lat;
let mut is_ear = false;
if is_convex {
is_ear = true;
for vert in vertices.iter().take(num_vertices) {
let ip = vert.p;
if ip.lon == v_p.lon && ip.lat == v_p.lat {
continue;
}
if ip.lon == v1_p.lon && ip.lat == v1_p.lat {
continue;
}
if ip.lon == v3_p.lon && ip.lat == v3_p.lat {
continue;
}
if Self::is_inside(v1_p, v_p, v3_p, ip) {
is_ear = false;
break;
}
}
}
let v = &mut vertices[v_idx];
v.is_convex = is_convex;
v.angle = angle;
v.is_ear = is_ear;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pts(coords: &[(f64, f64)]) -> Vec<Wgs84> {
coords.iter().map(|&(x, y)| Wgs84::new(x, y)).collect()
}
#[test]
fn triangle_passthrough() {
let t = PolygonTriangulation::new();
let poly = Wgs84Polygon::from_vertices(pts(&[(0.0, 0.0), (4.0, 0.0), (0.0, 4.0)]));
let out = t.triangulate_by_ear_clipping(&poly);
assert_eq!(out.polygon_type(), PolygonType::TriangleList);
assert_eq!(out.vertices().len(), 3);
}
#[test]
fn convex_quad() {
let t = PolygonTriangulation;
let poly =
Wgs84Polygon::from_vertices(pts(&[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)]));
let out = t.triangulate_by_ear_clipping(&poly);
assert_eq!(out.polygon_type(), PolygonType::TriangleList);
assert_eq!(out.vertices().len(), 6);
}
#[test]
fn concave_with_reflex_vertex() {
let t = PolygonTriangulation::new();
let poly = Wgs84Polygon::from_vertices(pts(&[
(0.0, 0.0),
(4.0, 0.0),
(2.0, 2.0),
(4.0, 4.0),
(0.0, 4.0),
]));
let out = t.triangulate_by_ear_clipping(&poly);
assert_eq!(out.polygon_type(), PolygonType::TriangleList);
assert_eq!(out.vertices().len(), 9);
}
#[test]
fn too_few_vertices_is_unknown() {
let t = PolygonTriangulation::new();
let poly = Wgs84Polygon::from_vertices(pts(&[(0.0, 0.0), (1.0, 0.0)]));
let out = t.triangulate_by_ear_clipping(&poly);
assert_eq!(out.polygon_type(), PolygonType::Unknown);
assert!(out.is_empty());
}
#[test]
fn wrong_type_is_unknown() {
let t = PolygonTriangulation::new();
let poly = Wgs84Polygon::with_type_and_vertices(
PolygonType::TriangleList,
pts(&[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)]),
);
let out = t.triangulate_by_ear_clipping(&poly);
assert_eq!(out.polygon_type(), PolygonType::Unknown);
}
#[test]
fn regular_pentagon_uses_angle_tiebreak() {
let t = PolygonTriangulation::new();
let mut coords = Vec::new();
for i in 0..5 {
let a = 2.0 * std::f64::consts::PI * (i as f64) / 5.0;
coords.push((a.cos(), a.sin()));
}
let poly = Wgs84Polygon::from_vertices(pts(&coords));
let out = t.triangulate_by_ear_clipping(&poly);
assert_eq!(out.polygon_type(), PolygonType::TriangleList);
assert_eq!(out.vertices().len(), 9);
}
#[test]
fn clockwise_polygon_finds_no_ear() {
let t = PolygonTriangulation::new();
let poly =
Wgs84Polygon::from_vertices(pts(&[(0.0, 0.0), (0.0, 4.0), (4.0, 4.0), (4.0, 0.0)]));
let out = t.triangulate_by_ear_clipping(&poly);
assert_eq!(out.polygon_type(), PolygonType::Unknown);
assert!(out.is_empty());
}
#[test]
fn coincident_vertex_zero_length_edge() {
let t = PolygonTriangulation::new();
let poly =
Wgs84Polygon::from_vertices(pts(&[(0.0, 0.0), (0.0, 0.0), (4.0, 0.0), (0.0, 4.0)]));
let out = t.triangulate_by_ear_clipping(&poly);
assert_eq!(out.polygon_type(), PolygonType::TriangleList);
assert_eq!(out.vertices().len(), 6);
}
}