Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use [T; 2] as a vertex + some optimizations #11

Merged
merged 6 commits into from
Apr 15, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
improments
ciscorn committed Apr 15, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 13e78bee4ea76e574c6606eeed0b812e74f4afd7
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "earcut"
version = "0.3.6"
version = "0.4.0"
edition = "2021"
description = "A Rust port of the Earcut polygon triangulation library"
authors = ["Taku Fukada <[email protected]>", "MIERUNE Inc. <[email protected]>"]
9 changes: 2 additions & 7 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -4,20 +4,15 @@ use criterion::{criterion_group, criterion_main, Criterion};

use earcut::Earcut;

fn load_fixture(name: &str) -> (Vec<f64>, Vec<usize>) {
fn load_fixture(name: &str) -> (Vec<[f64; 2]>, Vec<usize>) {
// load JSON
type Coords = Vec<Vec<[f64; 2]>>;
let s = fs::read_to_string("./tests/fixtures/".to_string() + name + ".json").unwrap();
let expected = serde_json::from_str::<Coords>(&s).unwrap();

// prepare input
let num_holes = expected.len();
let data = expected
.clone()
.into_iter()
.flatten()
.flatten()
.collect::<Vec<_>>();
let data = expected.clone().into_iter().flatten().collect::<Vec<_>>();
let hole_indices: Vec<_> = expected
.iter()
.map(|x| x.len())
7 changes: 1 addition & 6 deletions examples/example.rs
Original file line number Diff line number Diff line change
@@ -10,12 +10,7 @@ fn load_fixture(name: &str, num_triangles: usize, expected_deviation: f64) {

// prepare input
let num_holes = expected.len();
let vertices = expected
.clone()
.into_iter()
.flatten()
.flatten()
.collect::<Vec<_>>();
let vertices = expected.clone().into_iter().flatten().collect::<Vec<_>>();
let hole_indices: Vec<_> = expected
.into_iter()
.map(|x| x.len() as u32)
106 changes: 51 additions & 55 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -112,7 +112,7 @@ impl<T: Float> Node<T> {

/// Instance of the earcut algorithm.
pub struct Earcut<T: Float> {
data: Vec<T>,
data: Vec<[T; 2]>,
nodes: Vec<Node<T>>,
queue: Vec<NodeIndex>,
}
@@ -146,26 +146,26 @@ impl<T: Float> Earcut<T> {
/// The API is similar to the original JavaScript implementation, except you can provide a vector for the output indices.
pub fn earcut<N: Index>(
&mut self,
data: impl IntoIterator<Item = T>,
data: impl IntoIterator<Item = [T; 2]>,
hole_indices: &[N],
triangles_out: &mut Vec<N>,
) {
self.data.clear();
self.data.extend(data);
triangles_out.clear();
if self.data.len() < 6 {
if self.data.len() < 3 {
return;
}
self.earcut_impl(hole_indices, triangles_out);
}

pub fn earcut_impl<N: Index>(&mut self, hole_indices: &[N], triangles_out: &mut Vec<N>) {
triangles_out.reserve(self.data.len() / 2 + 1);
self.reset(self.data.len() / 2 * 3 / 2);
triangles_out.reserve(self.data.len() + 1);
self.reset(self.data.len() / 2 * 3);

let has_holes = !hole_indices.is_empty();
let outer_len: usize = if has_holes {
hole_indices[0].into_usize() * 2
hole_indices[0].into_usize()
} else {
self.data.len()
};
@@ -187,21 +187,20 @@ impl<T: Float> Earcut<T> {
let mut inv_size = T::zero();

// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
if self.data.len() > 80 * 2 {
let (max_x, max_y) = self.data[2..outer_len].windows(2).fold(
(self.data[0], self.data[1]),
|(ax, ay), b| {
if self.data.len() > 80 {
let [max_x, max_y] =
self.data[1..outer_len]
.iter()
.fold(self.data[0], |[ax, ay], b| {
let (bx, by) = (b[0], b[1]);
[T::max(ax, bx), T::max(ay, by)]
});
[min_x, min_y] = self.data[1..outer_len]
.iter()
.fold(self.data[0], |[ax, ay], b| {
let (bx, by) = (b[0], b[1]);
(T::max(ax, bx), T::max(ay, by))
},
);
(min_x, min_y) = self.data[2..outer_len].windows(2).fold(
(self.data[0], self.data[1]),
|(ax, ay), b| {
let (bx, by) = (b[0], b[1]);
(T::min(ax, bx), T::min(ay, by))
},
);
[T::min(ax, bx), T::min(ay, by)]
});
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
inv_size = (max_x - min_x).max(max_y - min_y);
if inv_size != T::zero() {
@@ -223,17 +222,17 @@ impl<T: Float> Earcut<T> {
/// create a circular doubly linked list from polygon points in the specified winding order
fn linked_list(&mut self, start: usize, end: usize, clockwise: bool) -> Option<NodeIndex> {
let mut last_i: Option<NodeIndex> = None;
let iter = self.data[start..end].chunks_exact(2).enumerate();
let iter = self.data[start..end].iter().enumerate();

if clockwise == (signed_area(&self.data, start, end) > T::zero()) {
for (i, v) in iter {
let idx = start + i * 2;
last_i = Some(insert_node(&mut self.nodes, idx as u32, v[0], v[1], last_i));
for (i, &[x, y]) in iter {
let idx = start + i;
last_i = Some(insert_node(&mut self.nodes, idx as u32, x, y, last_i));
}
} else {
for (i, v) in iter.rev() {
let idx = start + i * 2;
last_i = Some(insert_node(&mut self.nodes, idx as u32, v[0], v[1], last_i));
for (i, &[x, y]) in iter.rev() {
let idx = start + i;
last_i = Some(insert_node(&mut self.nodes, idx as u32, x, y, last_i));
}
};

@@ -256,9 +255,9 @@ impl<T: Float> Earcut<T> {
) -> NodeIndex {
self.queue.clear();
for (i, hi) in hole_indices.iter().enumerate() {
let start = (*hi).into_usize() * 2;
let start = (*hi).into_usize();
let end = if i < hole_indices.len() - 1 {
hole_indices[i + 1].into_usize() * 2
hole_indices[i + 1].into_usize()
} else {
self.data.len()
};
@@ -334,9 +333,9 @@ fn earcut_linked<T: Float, N: Index>(
let next_next_i = next.next_i;

// cut off the triangle
triangles.push(N::from_usize(node!(nodes, pi).i as usize / 2));
triangles.push(N::from_usize(ear.i as usize / 2));
triangles.push(N::from_usize(next_i as usize / 2));
triangles.push(N::from_usize(node!(nodes, pi).i as usize));
triangles.push(N::from_usize(ear.i as usize));
triangles.push(N::from_usize(next_i as usize));

remove_node(nodes, ear_i);

@@ -516,9 +515,9 @@ fn cure_local_intersections<T: Float, N: Index>(
&& locally_inside(nodes, b, a)
{
triangles.extend([
N::from_usize(a.i as usize / 2),
N::from_usize(p.i as usize / 2),
N::from_usize(b.i as usize / 2),
N::from_usize(a.i as usize),
N::from_usize(p.i as usize),
N::from_usize(b.i as usize),
]);

remove_node(nodes, p_i);
@@ -881,7 +880,7 @@ fn find_hole_bridge<T: Float>(
if locally_inside(nodes, p, hole)
&& (tan < tan_min
|| (tan == tan_min
&& (p.x > m.x || p.x == m.x && sector_contains_sector(nodes, m, p))))
&& (p.x > m.x || (p.x == m.x && sector_contains_sector(nodes, m, p)))))
{
(m_i, m) = (p_i, p);
tan_min = tan;
@@ -974,8 +973,7 @@ fn insert_node<T: Float>(
Some(last_i) => {
let last = node_mut!(nodes, last_i);
let last_next_i = last.next_i;
last.next_i = p_i;
p.next_i = last_next_i;
(p.next_i, last.next_i) = (last_next_i, p_i);
p.prev_i = last_i;
node_mut!(nodes, last_next_i).prev_i = p_i;
}
@@ -1009,29 +1007,29 @@ fn remove_node<T: Float>(nodes: &mut [Node<T>], p_i: NodeIndex) -> (NodeIndex, N
/// Returns a percentage difference between the polygon area and its triangulation area;
/// used to verify correctness of triangulation
pub fn deviation<T: Float, N: Index>(
data: impl IntoIterator<Item = T>,
data: impl IntoIterator<Item = [T; 2]>,
hole_indices: &[N],
triangles: &[N],
) -> T {
let data = data.into_iter().collect::<Vec<T>>();
let data = data.into_iter().collect::<Vec<[T; 2]>>();
let has_holes = !hole_indices.is_empty();
let outer_len = match has_holes {
true => hole_indices[0].into_usize() * 2,
true => hole_indices[0].into_usize(),
false => data.len(),
};
let polygon_area = if data.len() < 6 {
let polygon_area = if data.len() < 3 {
T::zero()
} else {
let mut polygon_area = signed_area(&data, 0, outer_len).abs();
if has_holes {
for i in 0..hole_indices.len() {
let start = hole_indices[i].into_usize() * 2;
let start = hole_indices[i].into_usize();
let end = if i < hole_indices.len() - 1 {
hole_indices[i + 1].into_usize() * 2
hole_indices[i + 1].into_usize()
} else {
data.len()
};
if end - start >= 6 {
if end - start >= 3 {
polygon_area = polygon_area - signed_area(&data, start, end).abs();
}
}
@@ -1044,12 +1042,12 @@ pub fn deviation<T: Float, N: Index>(
.chunks_exact(3)
.map(|idxs| [idxs[0], idxs[1], idxs[2]])
{
let a = a.into_usize() * 2;
let b = b.into_usize() * 2;
let c = c.into_usize() * 2;
let a = a.into_usize();
let b = b.into_usize();
let c = c.into_usize();
triangles_area = triangles_area
+ ((data[a] - data[c]) * (data[b + 1] - data[a + 1])
- (data[a] - data[b]) * (data[c + 1] - data[a + 1]))
+ ((data[a][0] - data[c][0]) * (data[b][1] - data[a][1])
- (data[a][0] - data[b][0]) * (data[c][1] - data[a][1]))
.abs();
}
if polygon_area == T::zero() && triangles_area == T::zero() {
@@ -1060,12 +1058,10 @@ pub fn deviation<T: Float, N: Index>(
}

/// check if a point lies within a convex triangle
fn signed_area<T: Float>(data: &[T], start: usize, end: usize) -> T {
let mut bx = data[end - 2];
let mut by = data[end - 1];
fn signed_area<T: Float>(data: &[[T; 2]], start: usize, end: usize) -> T {
let [mut bx, mut by] = data[end - 1];
let mut sum = T::zero();
for a in data[start..end].chunks_exact(2) {
let (ax, ay) = (a[0], a[1]);
for &[ax, ay] in &data[start..end] {
sum = sum + (bx - ax) * (ay + by);
(bx, by) = (ax, ay);
}
2 changes: 1 addition & 1 deletion tests/fixture.rs
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ fn test_fixture(name: &str, num_triangles: usize, expected_deviation: f64) {

// prepare input
let num_holes = expected.len();
let data: Vec<f64> = expected.clone().into_iter().flatten().flatten().collect();
let data: Vec<[f64; 2]> = expected.clone().into_iter().flatten().collect();
let hole_indices: Vec<_> = expected
.into_iter()
.map(|x| x.len() as u32)
48 changes: 22 additions & 26 deletions tests/simple.rs
Original file line number Diff line number Diff line change
@@ -6,10 +6,10 @@ fn test_empty() {
let data: [[f64; 2]; 0] = [];
let hole_indices: &[u32] = &[];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles.len(), 0);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -20,10 +20,10 @@ fn test_invalid_point() {
let data = [[100.0, 200.0]];
let hole_indices: &[u32] = &[];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles.len(), 0);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -34,10 +34,10 @@ fn test_invalid_line() {
let data = [[0.0, 0.0], [100.0, 200.0]];
let hole_indices: &[u32] = &[];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles.len(), 0);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -48,10 +48,10 @@ fn test_invalid_empty_hole() {
let data = [[0.0, 0.0], [100.0, 0.0], [100.0, 100.0]];
let hole_indices: &[u32] = &[3];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles.len(), 3);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -62,10 +62,10 @@ fn test_steiner_point_hole() {
let data = [[0.0, 0.0], [100.0, 0.0], [100.0, 100.0], [50.0, 30.0]];
let hole_indices: &[u32] = &[3];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles.len(), 3 * 3);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -76,10 +76,10 @@ fn test_steiner_line_hole() {
let data = [[0., 0.], [100., 0.], [100., 100.], [50., 30.], [60., 30.]];
let hole_indices: &[u32] = &[3];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles.len(), 5 * 3);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -90,10 +90,10 @@ fn test_square() {
let data = [[0.0, 0.0], [100.0, 0.0], [100.0, 100.0], [0.0, 100.0]];
let hole_indices: &[u32] = &[];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles, vec![2, 3, 0, 0, 1, 2]);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -104,10 +104,10 @@ fn test_square_u16() {
let data = [[0.0, 0.0], [100.0, 0.0], [100.0, 100.0], [0.0, 100.0]];
let hole_indices: &[u16] = &[];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles, vec![2, 3, 0, 0, 1, 2]);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -118,10 +118,10 @@ fn test_square_usize() {
let data = [[0.0, 0.0], [100.0, 0.0], [100.0, 100.0], [0.0, 100.0]];
let hole_indices: &[usize] = &[];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(triangles, vec![2, 3, 0, 0, 1, 2]);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}
@@ -139,17 +139,13 @@ fn test_map_3d_to_2d() {
let hole_indices: &[usize] = &[];
let mut triangles = vec![];
earcut.earcut(
data.iter().flat_map(|v| [v[0], v[1]]),
data.iter().map(|v| [v[0], v[1]]),
hole_indices,
&mut triangles,
);
assert_eq!(triangles, vec![2, 3, 0, 0, 1, 2]);
assert_eq!(
deviation(
data.iter().flat_map(|v| [v[0], v[1]]),
hole_indices,
&triangles
),
deviation(data.iter().map(|v| [v[0], v[1]]), hole_indices, &triangles),
0.0
);
}
@@ -169,13 +165,13 @@ fn test_square_with_square_hole() {
];
let hole_indices: &[u32] = &[4];
let mut triangles = vec![];
earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles);
earcut.earcut(data.iter().copied(), hole_indices, &mut triangles);
assert_eq!(
triangles,
vec![0, 4, 7, 5, 4, 0, 3, 0, 7, 5, 0, 1, 2, 3, 7, 6, 5, 1, 2, 7, 6, 6, 1, 2]
);
assert_eq!(
deviation(data.iter().flatten().copied(), hole_indices, &triangles),
deviation(data.iter().copied(), hole_indices, &triangles),
0.0
);
}