From 3a889bb6d79e6060f5bef48704ebeac45fc6b385 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Mon, 1 Apr 2024 16:45:59 +0900 Subject: [PATCH] Improve API (copied iterator, flat indices) (#7) * v0.2 * update utils3d * Improve API * Use f64 instead of [f64; 2] (similar to JS api) --- Cargo.toml | 8 +-- LICENSE.txt | 1 + benches/benchmark.rs | 27 ++++++---- examples/example.rs | 13 +++-- src/lib.rs | 35 +++++++------ tests/fixture.rs | 8 +-- tests/simple.rs | 121 ++++++++++++++++++++++++++++++------------- 7 files changed, 138 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a69b69f..35f4792 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,16 @@ [package] name = "earcut-rs" -version = "0.2.0" +version = "0.3.0" edition = "2021" authors = ["Taku Fukada ", "MIERUNE Inc. "] license-file = "LICENSE.txt" [dependencies] -num-traits = "0.2.16" +num-traits = "0.2.18" [dev-dependencies] -serde_json = { version = "1.0.107", features = ["float_roundtrip"] } -serde = { version = "1.0.188", features = ["derive"] } +serde_json = { version = "1.0.115", features = ["float_roundtrip"] } +serde = { version = "1.0.197", features = ["derive"] } criterion = "0.5.1" [[bench]] diff --git a/LICENSE.txt b/LICENSE.txt index 8cb9041..23147d2 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,7 @@ ISC License Copyright (c) 2016, Mapbox +Copyright (c) 2023, Taku Fukada Copyright (c) 2023, MIERUNE Inc. Permission to use, copy, modify, and/or distribute this software for any purpose diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 8e539ba..bf0941b 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -4,7 +4,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use earcut_rs::Earcut; -fn load_fixture(name: &str) -> (Vec<[f64; 2]>, Vec) { +fn load_fixture(name: &str) -> (Vec, Vec) { // load JSON type Coords = Vec>; let s = fs::read_to_string("./tests/fixtures/".to_string() + name + ".json").unwrap(); @@ -12,7 +12,12 @@ fn load_fixture(name: &str) -> (Vec<[f64; 2]>, Vec) { // prepare input let num_holes = expected.len(); - let data: Vec<_> = expected.clone().into_iter().flatten().collect(); + let data = expected + .clone() + .into_iter() + .flatten() + .flatten() + .collect::>(); let hole_indices: Vec<_> = expected .iter() .map(|x| x.len()) @@ -33,42 +38,42 @@ fn bench(c: &mut Criterion) { c.bench_function("water", |b| { let (data, hole_indices) = load_fixture("water"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); }) }); c.bench_function("building", |b| { let (data, hole_indices) = load_fixture("building"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); }) }); c.bench_function("water2", |b| { let (data, hole_indices) = load_fixture("water2"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); }) }); c.bench_function("water3", |b| { let (data, hole_indices) = load_fixture("water3"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); }) }); c.bench_function("water3b", |b| { let (data, hole_indices) = load_fixture("water3b"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); }) }); c.bench_function("water-huge", |b| { let (data, hole_indices) = load_fixture("water-huge"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); assert_eq!(triangles.len(), 5177 * 3) }) }); @@ -76,21 +81,21 @@ fn bench(c: &mut Criterion) { c.bench_function("water-huge2", |b| { let (data, hole_indices) = load_fixture("water-huge2"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); }) }); c.bench_function("rain", |b| { let (data, hole_indices) = load_fixture("rain"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); }) }); c.bench_function("hilbert", |b| { let (data, hole_indices) = load_fixture("hilbert"); b.iter(|| { - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); }) }); } diff --git a/examples/example.rs b/examples/example.rs index 1c39448..072380a 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -10,7 +10,12 @@ fn load_fixture(name: &str, num_triangles: usize, expected_deviation: f64) { // prepare input let num_holes = expected.len(); - let vertices: Vec<_> = expected.clone().into_iter().flatten().collect(); + let vertices = expected + .clone() + .into_iter() + .flatten() + .flatten() + .collect::>(); let hole_indices: Vec<_> = expected .into_iter() .map(|x| x.len() as u32) @@ -25,13 +30,15 @@ fn load_fixture(name: &str, num_triangles: usize, expected_deviation: f64) { let mut triangles = vec![]; let mut earcut = Earcut::new(); for _ in 0..500 { - earcut.earcut(&vertices, &hole_indices, &mut triangles); + earcut.earcut(vertices.iter().copied(), &hole_indices, &mut triangles); } // check assert!(triangles.len() == num_triangles); if !triangles.is_empty() { - assert!(deviation(&vertices, &hole_indices, &triangles) <= expected_deviation); + assert!( + deviation(vertices.iter().copied(), &hole_indices, &triangles) <= expected_deviation + ); } } diff --git a/src/lib.rs b/src/lib.rs index 1710434..8acb6e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,16 +118,14 @@ impl Earcut { self.nodes.reserve(capacity); } - pub fn earcut<'a, N: Index>( + pub fn earcut( &mut self, - data: impl IntoIterator, + data: impl IntoIterator, hole_indices: &[N], - triangles_out: &mut Vec<[N; 3]>, - ) where - T: 'a, - { + triangles_out: &mut Vec, + ) { self.data.clear(); - self.data.extend(data.into_iter().flatten()); + self.data.extend(data); triangles_out.clear(); self.reset(self.data.len() / 2 * 3 / 2); @@ -253,7 +251,7 @@ impl Earcut { fn earcut_linked( &mut self, ear_i: usize, - triangles: &mut Vec<[N; 3]>, + triangles: &mut Vec, min_x: T, min_y: T, inv_size: T, @@ -281,7 +279,7 @@ impl Earcut { }; if is_ear { // cut off the triangle - triangles.push([ + triangles.extend([ N::from_usize(node!(self.nodes, prev_i).i / 2), N::from_usize(ear.i / 2), N::from_usize(node!(self.nodes, next_i).i / 2), @@ -445,7 +443,7 @@ impl Earcut { fn cure_local_intersections( &mut self, mut start_i: usize, - triangles: &mut Vec<[N; 3]>, + triangles: &mut Vec, ) -> usize { let mut p_i = start_i; loop { @@ -463,7 +461,7 @@ impl Earcut { && self.locally_inside(a, b) && self.locally_inside(b, a) { - triangles.push([ + triangles.extend([ N::from_usize(a.i / 2), N::from_usize(p.i / 2), N::from_usize(b.i / 2), @@ -486,7 +484,7 @@ impl Earcut { fn split_earcut( &mut self, start_i: usize, - triangles: &mut Vec<[N; 3]>, + triangles: &mut Vec, min_x: T, min_y: T, inv_size: T, @@ -938,12 +936,12 @@ fn remove_node(nodes: &mut [Node], p_i: usize) -> (usize, usize) { /// return a percentage difference between the polygon area and its triangulation area; /// used to verify correctness of triangulation -pub fn deviation<'a, T: Float + 'a, N: Index>( - data: impl IntoIterator, +pub fn deviation( + data: impl IntoIterator, hole_indices: &[N], - triangles: &[[N; 3]], + triangles: &[N], ) -> T { - let data = data.into_iter().copied().flatten().collect::>(); + let data = data.into_iter().collect::>(); let has_holes = !hole_indices.is_empty(); let outer_len = match has_holes { @@ -964,7 +962,10 @@ pub fn deviation<'a, T: Float + 'a, N: Index>( } let mut triangles_area = T::zero(); - for [a, b, c] in triangles { + for [a, b, c] in triangles + .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; diff --git a/tests/fixture.rs b/tests/fixture.rs index 2241601..e6fbda4 100644 --- a/tests/fixture.rs +++ b/tests/fixture.rs @@ -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; 2]> = expected.clone().into_iter().flatten().collect(); + let data: Vec = expected.clone().into_iter().flatten().flatten().collect(); let hole_indices: Vec<_> = expected .into_iter() .map(|x| x.len() as u32) @@ -24,12 +24,12 @@ fn test_fixture(name: &str, num_triangles: usize, expected_deviation: f64) { // earcut let mut triangles = vec![]; let mut earcut = Earcut::new(); - earcut.earcut(&data, &hole_indices, &mut triangles); + earcut.earcut(data.iter().copied(), &hole_indices, &mut triangles); // check - assert!(triangles.len() == num_triangles); + assert!(triangles.len() == num_triangles * 3); if !triangles.is_empty() { - assert!(deviation(&data, &hole_indices, &triangles) <= expected_deviation); + assert!(deviation(data.iter().copied(), &hole_indices, &triangles) <= expected_deviation); } } diff --git a/tests/simple.rs b/tests/simple.rs index 500c345..0247b99 100644 --- a/tests/simple.rs +++ b/tests/simple.rs @@ -6,9 +6,12 @@ fn test_empty() { let data: [[f64; 2]; 0] = []; let hole_indices: &[u32] = &[]; let mut triangles = vec![]; - earcut.earcut(&data, hole_indices, &mut triangles); + earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles); assert_eq!(triangles.len(), 0); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + assert_eq!( + deviation(data.iter().flatten().copied(), hole_indices, &triangles), + 0.0 + ); } #[test] @@ -17,9 +20,12 @@ fn test_invalid_point() { let data = [[100.0, 200.0]]; let hole_indices: &[u32] = &[]; let mut triangles = vec![]; - earcut.earcut(&data, hole_indices, &mut triangles); + earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles); assert_eq!(triangles.len(), 0); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + assert_eq!( + deviation(data.iter().flatten().copied(), hole_indices, &triangles), + 0.0 + ); } #[test] @@ -28,9 +34,12 @@ 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, hole_indices, &mut triangles); + earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles); assert_eq!(triangles.len(), 0); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + assert_eq!( + deviation(data.iter().flatten().copied(), hole_indices, &triangles), + 0.0 + ); } #[test] @@ -39,9 +48,12 @@ 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, hole_indices, &mut triangles); - assert_eq!(triangles.len(), 1); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles); + assert_eq!(triangles.len(), 3); + assert_eq!( + deviation(data.iter().flatten().copied(), hole_indices, &triangles), + 0.0 + ); } #[test] @@ -50,9 +62,12 @@ 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, hole_indices, &mut triangles); - assert_eq!(triangles.len(), 3); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles); + assert_eq!(triangles.len(), 3 * 3); + assert_eq!( + deviation(data.iter().flatten().copied(), hole_indices, &triangles), + 0.0 + ); } #[test] @@ -61,9 +76,12 @@ 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, hole_indices, &mut triangles); - assert_eq!(triangles.len(), 5); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + earcut.earcut(data.iter().flatten().copied(), hole_indices, &mut triangles); + assert_eq!(triangles.len(), 5 * 3); + assert_eq!( + deviation(data.iter().flatten().copied(), hole_indices, &triangles), + 0.0 + ); } #[test] @@ -72,9 +90,12 @@ 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, hole_indices, &mut triangles); - assert_eq!(triangles, vec![[2, 3, 0], [0, 1, 2]]); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + earcut.earcut(data.iter().flatten().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), + 0.0 + ); } #[test] @@ -83,9 +104,12 @@ 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, hole_indices, &mut triangles); - assert_eq!(triangles, vec![[2, 3, 0], [0, 1, 2]]); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + earcut.earcut(data.iter().flatten().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), + 0.0 + ); } #[test] @@ -94,9 +118,40 @@ 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, hole_indices, &mut triangles); - assert_eq!(triangles, vec![[2, 3, 0], [0, 1, 2]]); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); + earcut.earcut(data.iter().flatten().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), + 0.0 + ); +} + +#[test] +fn test_map_3d_to_2d() { + let mut earcut = Earcut::new(); + #[allow(clippy::useless_vec)] + let data = vec![ + [0.0, 0.0, 1.0], + [100.0, 0.0, 1.0], + [100.0, 100.0, 1.0], + [0.0, 100.0, 1.0], + ]; + let hole_indices: &[usize] = &[]; + let mut triangles = vec![]; + earcut.earcut( + data.iter().flat_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 + ), + 0.0 + ); } #[test] @@ -114,19 +169,13 @@ fn test_square_with_square_hole() { ]; let hole_indices: &[u32] = &[4]; let mut triangles = vec![]; - earcut.earcut(&data, hole_indices, &mut triangles); + earcut.earcut(data.iter().flatten().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] - ] + 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), + 0.0 ); - assert_eq!(deviation(&data, hole_indices, &triangles), 0.0); }