1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
//! Ear-clipping algorithm for creating a triangle mesh from a simple polygon.
//! Based on <https://github.com/ivanfratric/polypartition>, contributed by embotech AG.
use crate::{
math::{Point, Real},
utils::point_in_triangle::{corner_direction, is_point_in_triangle, Orientation},
};
/// The information stored for each vertex in the ear clipping algorithm.
#[derive(Clone, Default)]
struct VertexInfo {
/// Whether the vertex is still active i.e. it has not been clipped yet.
is_active: bool,
/// Whether the vertex is the tip of an ear and should be clipped.
is_ear: bool,
/// How small the angle of the ear is. Ears with a smaller angle are clipped first.
pointiness: Real,
/// The index of the previous vertex.
p_prev: usize,
/// The index of the next vertex.
p_next: usize,
}
/// Updates the fields `pointiness` and `is_ear` for a given vertex index.
fn update_vertex(idx: usize, vertex_info: &mut VertexInfo, points: &[Point<Real>]) -> bool {
// Get the point and its neighbors.
let p = points[idx];
let p1 = points[vertex_info.p_prev];
let p3 = points[vertex_info.p_next];
// Get the pointiness.
let vec1 = (p1 - p).normalize();
let vec3 = (p3 - p).normalize();
vertex_info.pointiness = vec1.dot(&vec3);
if vertex_info.pointiness.is_nan() {
return false;
}
// A point is considered an ear when it is convex and no other points are
// inside the triangle spanned by it and its two neighbors.
let mut error = false;
vertex_info.is_ear = corner_direction(&p1, &p, &p3) == Orientation::Ccw
&& (0..points.len())
.filter(|&i| i != vertex_info.p_prev && i != idx && i != vertex_info.p_next)
.all(|i| {
if let Some(is) = is_point_in_triangle(&points[i], &p1, &p, &p3) {
!is
} else {
error = true;
true
}
});
!error
}
/// Ear clipping triangulation algorithm.
pub(crate) fn triangulate_ear_clipping(vertices: &[Point<Real>]) -> Option<Vec<[u32; 3]>> {
let n_vertices = vertices.len();
// Create a new vector to hold the information about vertices.
let mut vertex_info = vec![VertexInfo::default(); n_vertices];
// Initialize information for each vertex.
let success = vertex_info.iter_mut().enumerate().all(|(i, info)| {
info.is_active = true;
info.p_prev = if i == 0 { n_vertices - 1 } else { i - 1 };
info.p_next = if i == n_vertices - 1 { 0 } else { i + 1 };
update_vertex(i, info, vertices)
});
if !success {
return None;
}
// The output shapes
let mut output_indices = Vec::new();
for i in 0..n_vertices - 3 {
// Search through all active ears and pick out the pointiest.
let maybe_ear = vertex_info
.iter()
.enumerate()
.filter(|(_, info)| info.is_active && info.is_ear)
.max_by(|(_, info1), (_, info2)| {
// The unwrap here is safe since we check for NaN when
// we assign the pointiness value.
info1.pointiness.partial_cmp(&info2.pointiness).unwrap()
});
// If we found an ear, clip it. Else the algorithm failed.
let (ear_i, _) = match maybe_ear {
Some(ear) => ear,
None => return None,
};
// Deactivate the tip of the ear.
vertex_info[ear_i].is_active = false;
// Get the indices of the neighbors.
let VertexInfo { p_prev, p_next, .. } = vertex_info[ear_i];
// Extract the triangle that is the ear and add it to the index buffer.
let triangle_points = [p_prev as u32, ear_i as u32, p_next as u32];
output_indices.push(triangle_points);
// Connect the remaining two vertices.
vertex_info[p_prev].p_next = vertex_info[ear_i].p_next;
vertex_info[p_next].p_prev = vertex_info[ear_i].p_prev;
// Only three vertices remain and those are guaranteed to be convex so
// there is no point in updating the remaining vertex information.
if i == n_vertices - 4 {
break;
};
// Update the info for the remaining two vertices.
if !update_vertex(p_prev, &mut vertex_info[p_prev], vertices)
|| !update_vertex(p_next, &mut vertex_info[p_next], vertices)
{
return None;
}
}
// Add the remaining triangle.
if let Some((i, info)) = vertex_info
.iter()
.enumerate()
.find(|(_, info)| info.is_active)
{
let triangle_points = [info.p_prev as u32, i as u32, info.p_next as u32];
output_indices.push(triangle_points);
}
Some(output_indices)
}
// --- Unit tests ----------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn triangle_ccw() {
let vertices = vec![Point::new(0., 0.), Point::new(1., 0.), Point::new(1., 1.)];
let triangles = triangulate_ear_clipping(&vertices);
assert_eq!(triangles.unwrap(), vec![[2, 0, 1]]);
}
#[test]
fn square_ccw() {
let vertices = vec![
Point::new(0., 0.), // 0
Point::new(1., 0.), // 1
Point::new(1., 1.), // 2
Point::new(0., 1.), // 3
];
let triangles = triangulate_ear_clipping(&vertices);
assert_eq!(triangles.unwrap(), vec![[2, 3, 0], [2, 0, 1]]);
}
#[test]
fn square_cw() {
let vertices = vec![
Point::new(0., 1.), // 0
Point::new(1., 1.), // 1
Point::new(1., 0.), // 2
Point::new(0., 0.), // 3
];
// This fails because we expect counter-clockwise ordering.
let triangles = triangulate_ear_clipping(&vertices);
assert!(triangles.is_none());
}
#[test]
fn square_with_dent() {
let vertices = vec![
Point::new(0., 0.), // 0
Point::new(1., 0.), // 1
Point::new(0.5, 0.5), // 2
Point::new(1., 1.), // 3
Point::new(0., 1.), // 4
];
let triangles = triangulate_ear_clipping(&vertices);
assert_eq!(triangles.unwrap(), vec![[2, 3, 4], [2, 4, 0], [2, 0, 1],]);
}
#[test]
/// Checks the case where the origin is outside the shape.
/// 4-----------------------3
/// | |
/// | |
/// | 7-------0 |
/// | | | |
/// | | ° | |
/// 5-------6 1-------2
fn origin_outside_shape() {
let vertices = vec![
Point::new(2.0, 2.0), // 0
Point::new(2.0, -2.0), // 1
Point::new(4.0, -2.0), // 2
Point::new(4.0, 4.0), // 3
Point::new(-4.0, 4.0), // 4
Point::new(-4.0, -2.0), // 5
Point::new(-2.0, -2.0), // 6
Point::new(-2.0, 2.0), // 7
];
let triangles = triangulate_ear_clipping(&vertices).unwrap();
assert_eq!(
triangles,
vec![
[5, 6, 7],
[4, 5, 7],
[3, 4, 7],
[3, 7, 0],
[2, 3, 0],
[2, 0, 1],
]
);
}
}