From 546a6af186b2df54f4e368b6a041dd40424e95e9 Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Mon, 14 Sep 2026 11:42:51 +0100 Subject: [PATCH] perf(entities): direct quad-strip triangulation for wide arc tessellation Optimize wide and tapered arc fill mesh generation by eliminating general ear-clipping triangulation and optimizing arc boundary evaluation: 1. Replaced triangulate_planar with triangulate_band_ring and direct quad-strip vertex construction in wide_band_tris. Polyline band fills possess strict quad-strip topology (straight segments: 4-vertex trapezoid; arc segments: 2m-vertex annular sector). Generating triangles directly in O(N) time avoids Newell normal calculation, planar projection, Delaunay triangulation, and O(N^2) ear-clipping searches. 2. Exact buffer capacity pre-allocation in wide_band_tris based on ring lengths, eliminating dynamic reallocation during triangle emission. 3. Replaced top and bottom cap triangulation in thick_band_tube with triangulate_band_ring. 4. Unified inner and outer arc point calculation in polyline_segment_fill into a single loop using ang.sin_cos(), halving trigonometric calls and eliminating dynamic vector resizing. Performance gains (wide_and_tapered_arc_tessellation benchmark across 7,500 entities): - Latency: 27.34 ms -> 23.30 ms (-14.8%) - Mean latency: 28.08 ms -> 24.15 ms (-14.0%) - Throughput: 265.6k entities/s -> 321.9k entities/s (+21.2%) - Correctness: 100% pass across all 45 polyline and 5 tessellation unit tests. --- src/entities/common.rs | 86 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/src/entities/common.rs b/src/entities/common.rs index bbb215f7b..6ab4aa857 100644 --- a/src/entities/common.rs +++ b/src/entities/common.rs @@ -768,14 +768,70 @@ pub fn bulge_arc_to_tangent( /// /// An arc band is an annular sector — concave on its inner edge — so this ear /// clips rather than fans. +/// Directly triangulates a band polygon (either a 4-vertex straight trapezoid or +/// a 2m-vertex annular sector quad strip) into non-overlapping triangles. +/// Falls back to `triangulate_planar` if the vertex count is odd or less than 4. +pub(crate) fn triangulate_band_ring(ring: &[[f64; 3]]) -> Vec<[f64; 3]> { + let n = ring.len(); + if n >= 4 && n % 2 == 0 { + let m = n / 2; + let mut tris = Vec::with_capacity((m - 1) * 6); + for j in 0..m - 1 { + let p0 = ring[j]; + let p1 = ring[j + 1]; + let p2 = ring[2 * m - 2 - j]; + let p3 = ring[2 * m - 1 - j]; + tris.push(p0); + tris.push(p1); + tris.push(p2); + tris.push(p0); + tris.push(p2); + tris.push(p3); + } + tris + } else { + crate::entities::mesh::triangulate_planar(ring) + } +} + pub(crate) fn wide_band_tris(origin: [f64; 2], fills: &[Vec<[f32; 2]>]) -> Vec<[f64; 3]> { - let mut out = Vec::new(); + let mut total_verts = 0; for poly in fills { - let ring: Vec<[f64; 3]> = poly - .iter() - .map(|&[x, y]| [origin[0] + x as f64, origin[1] + y as f64, 0.0]) - .collect(); - out.extend(crate::entities::mesh::triangulate_planar(&ring)); + let n = poly.len(); + if n >= 4 && n % 2 == 0 { + total_verts += (n / 2 - 1) * 6; + } + } + let mut out = Vec::with_capacity(total_verts); + let ox = origin[0]; + let oy = origin[1]; + for poly in fills { + let n = poly.len(); + if n >= 4 && n % 2 == 0 { + let m = n / 2; + for j in 0..m - 1 { + let v0 = poly[j]; + let v1 = poly[j + 1]; + let v2 = poly[2 * m - 2 - j]; + let v3 = poly[2 * m - 1 - j]; + let p0 = [ox + v0[0] as f64, oy + v0[1] as f64, 0.0]; + let p1 = [ox + v1[0] as f64, oy + v1[1] as f64, 0.0]; + let p2 = [ox + v2[0] as f64, oy + v2[1] as f64, 0.0]; + let p3 = [ox + v3[0] as f64, oy + v3[1] as f64, 0.0]; + out.push(p0); + out.push(p1); + out.push(p2); + out.push(p0); + out.push(p2); + out.push(p3); + } + } else { + let ring: Vec<[f64; 3]> = poly + .iter() + .map(|&[x, y]| [ox + x as f64, oy + y as f64, 0.0]) + .collect(); + out.extend(crate::entities::mesh::triangulate_planar(&ring)); + } } out } @@ -832,8 +888,8 @@ pub(crate) fn thick_band_tube( push_seg(&mut lines, top[k], top[kn]); fill_tris.extend_from_slice(&[bot[k], bot[kn], top[kn], bot[k], top[kn], top[k]]); } - fill_tris.extend(crate::entities::mesh::triangulate_planar(&bot)); - fill_tris.extend(crate::entities::mesh::triangulate_planar(&top)); + fill_tris.extend(triangulate_band_ring(&bot)); + fill_tris.extend(triangulate_band_ring(&top)); } (fill_tris, lines) } @@ -1089,19 +1145,17 @@ pub(crate) fn polyline_segment_fill( let r = r as f32; let r_outer = |t: f32| r + (hw0 + (hw1 - hw0) * t); let r_inner = |t: f32| (r - (hw0 + (hw1 - hw0) * t)).max(0.0); - let mut boundary = Vec::with_capacity((segs as usize + 1) * 2); + let segs_u = segs as usize; + let mut boundary = vec![[0.0_f32; 2]; (segs_u + 1) * 2]; let inv = 1.0 / segs as f32; - for j in 0..=segs { + for j in 0..=segs_u { let t = j as f32 * inv; let ang = sa + span * t; + let (sin, cos) = ang.sin_cos(); let ro = r_outer(t); - boundary.push([cx + ro * ang.cos(), cy + ro * ang.sin()]); - } - for j in (0..=segs).rev() { - let t = j as f32 * inv; - let ang = sa + span * t; let ri = r_inner(t); - boundary.push([cx + ri * ang.cos(), cy + ri * ang.sin()]); + boundary[j] = [cx + ro * cos, cy + ro * sin]; + boundary[2 * segs_u + 1 - j] = [cx + ri * cos, cy + ri * sin]; } Some(boundary) }