use crate::draw_ctx::FillRule;
use agg_rust::basics::{is_end_poly, is_move_to, is_stop, VertexSource};
use tess2_rust::{ElementType, Tessellator, WindingRule};
pub fn agg_path_to_contours<VS: VertexSource>(path: &mut VS) -> Vec<Vec<[f32; 2]>> {
let mut out: Vec<Vec<[f32; 2]>> = Vec::new();
let mut cur: Vec<[f32; 2]> = Vec::new();
path.rewind(0);
loop {
let (mut x, mut y) = (0.0, 0.0);
let cmd = path.vertex(&mut x, &mut y);
if is_stop(cmd) {
break;
}
if is_move_to(cmd) {
if !cur.is_empty() {
push_contour(&mut out, std::mem::take(&mut cur));
}
cur.push([x as f32, y as f32]);
} else if is_end_poly(cmd) {
if !cur.is_empty() {
push_contour(&mut out, std::mem::take(&mut cur));
}
} else {
cur.push([x as f32, y as f32]);
}
}
if !cur.is_empty() {
push_contour(&mut out, cur);
}
out
}
fn push_contour(out: &mut Vec<Vec<[f32; 2]>>, mut contour: Vec<[f32; 2]>) {
contour = deduplicate_contour_v(&contour);
if contour.len() < 3 {
return;
}
if signed_area_2x(&contour).abs() < 1.0 {
return;
}
out.push(contour);
}
pub fn tessellate_path<VS: VertexSource>(path: &mut VS) -> Option<(Vec<f32>, Vec<u32>)> {
let contours = agg_path_to_contours(path);
if contours.is_empty() {
return None;
}
tessellate_fill(&contours)
}
#[derive(Clone, Debug)]
pub struct CachedTess {
pub vertices: Vec<f32>,
pub indices: Vec<u32>,
pub edge_flags: Vec<u8>,
}
pub fn tessellate_interior<VS: VertexSource>(path: &mut VS) -> Option<CachedTess> {
let contours = agg_path_to_contours(path);
if contours.is_empty() {
return None;
}
let mut tess = Tessellator::new();
for c in &contours {
let flat: Vec<f64> = c.iter().flat_map(|v| [v[0] as f64, v[1] as f64]).collect();
tess.add_contour(2, &flat);
}
let ok = tess.tessellate(WindingRule::Odd, ElementType::Polygons, 3, 2, None);
if !ok || tess.vertex_count() == 0 {
return None;
}
Some(CachedTess {
vertices: tess.vertices().iter().map(|&v| v as f32).collect(),
indices: tess.elements().to_vec(),
edge_flags: tess.edge_flags().to_vec(),
})
}
pub fn expand_aa_halo(
transformed_xy: &[f32],
cached: &CachedTess,
halo_px: f32,
) -> Option<(Vec<[f32; 3]>, Vec<u32>)> {
let n_interior = transformed_xy.len() / 2;
let n_indices = cached.indices.len();
if n_indices == 0 {
return None;
}
let mut out_verts: Vec<[f32; 3]> = Vec::with_capacity(n_interior + n_indices * 4);
let mut out_indices: Vec<u32> = Vec::with_capacity(n_indices + n_indices * 2);
for i in 0..n_interior {
out_verts.push([transformed_xy[i * 2], transformed_xy[i * 2 + 1], 1.0]);
}
out_indices.extend_from_slice(&cached.indices);
let n_tris = n_indices / 3;
for t in 0..n_tris {
let ia = cached.indices[t * 3] as usize;
let ib = cached.indices[t * 3 + 1] as usize;
let ic = cached.indices[t * 3 + 2] as usize;
if ia >= n_interior || ib >= n_interior || ic >= n_interior {
continue;
}
let p = [
[transformed_xy[ia * 2], transformed_xy[ia * 2 + 1]],
[transformed_xy[ib * 2], transformed_xy[ib * 2 + 1]],
[transformed_xy[ic * 2], transformed_xy[ic * 2 + 1]],
];
let flag = [
cached.edge_flags.get(t * 3).copied().unwrap_or(0),
cached.edge_flags.get(t * 3 + 1).copied().unwrap_or(0),
cached.edge_flags.get(t * 3 + 2).copied().unwrap_or(0),
];
for k in 0..3 {
if flag[k] == 0 {
continue;
}
let a = p[k];
let b = p[(k + 1) % 3];
let c = p[(k + 2) % 3];
let dx = b[0] - a[0];
let dy = b[1] - a[1];
let len = (dx * dx + dy * dy).sqrt();
if len < 1e-6 {
continue;
}
let mut nx = dy / len * halo_px;
let mut ny = -dx / len * halo_px;
let dot_c = nx * (c[0] - a[0]) + ny * (c[1] - a[1]);
if dot_c > 0.0 {
nx = -nx;
ny = -ny;
}
let base = out_verts.len() as u32;
out_verts.push([a[0], a[1], 1.0]);
out_verts.push([b[0], b[1], 1.0]);
out_verts.push([a[0] + nx, a[1] + ny, 0.0]);
out_verts.push([b[0] + nx, b[1] + ny, 0.0]);
out_indices.extend_from_slice(&[
base,
base + 1,
base + 2,
base + 1,
base + 3,
base + 2,
]);
}
}
Some((out_verts, out_indices))
}
pub fn tessellate_path_aa<VS: VertexSource>(
path: &mut VS,
halo_px: f32,
fill_rule: FillRule,
) -> Option<(Vec<[f32; 3]>, Vec<u32>)> {
let contours = agg_path_to_contours(path);
if contours.is_empty() {
return None;
}
struct TessOut {
verts: Vec<f32>,
indices: Vec<u32>,
flags: Vec<u8>,
vcount: usize,
}
let out = {
let mut tess = Tessellator::new();
for c in &contours {
let flat: Vec<f64> = c.iter().flat_map(|v| [v[0] as f64, v[1] as f64]).collect();
tess.add_contour(2, &flat);
}
let ok = tess.tessellate(
to_tess_winding_rule(fill_rule),
ElementType::Polygons,
3,
2,
None,
);
if !ok || tess.vertex_count() == 0 {
return None;
}
TessOut {
verts: tess.vertices().iter().map(|&v| v as f32).collect(),
indices: tess.elements().to_vec(),
flags: tess.edge_flags().to_vec(),
vcount: tess.vertex_count(),
}
};
let in_verts: &[f32] = &out.verts; let in_indices: &[u32] = &out.indices; let edge_flags: &[u8] = &out.flags;
let n_interior = out.vcount;
let n_indices = in_indices.len();
if n_indices == 0 {
return None;
}
let mut out_verts: Vec<[f32; 3]> = Vec::with_capacity(n_interior + n_indices * 4);
let mut out_indices: Vec<u32> = Vec::with_capacity(n_indices + n_indices * 2);
for i in 0..n_interior {
out_verts.push([in_verts[i * 2], in_verts[i * 2 + 1], 1.0]);
}
out_indices.extend_from_slice(in_indices);
let n_tris = n_indices / 3;
for t in 0..n_tris {
let ia = in_indices[t * 3] as usize;
let ib = in_indices[t * 3 + 1] as usize;
let ic = in_indices[t * 3 + 2] as usize;
if ia >= n_interior || ib >= n_interior || ic >= n_interior {
continue;
}
let p = [
[in_verts[ia * 2], in_verts[ia * 2 + 1]],
[in_verts[ib * 2], in_verts[ib * 2 + 1]],
[in_verts[ic * 2], in_verts[ic * 2 + 1]],
];
let flag = [
edge_flags.get(t * 3).copied().unwrap_or(0),
edge_flags.get(t * 3 + 1).copied().unwrap_or(0),
edge_flags.get(t * 3 + 2).copied().unwrap_or(0),
];
for k in 0..3 {
if flag[k] == 0 {
continue;
}
let a = p[k];
let b = p[(k + 1) % 3];
let c = p[(k + 2) % 3]; let dx = b[0] - a[0];
let dy = b[1] - a[1];
let len = (dx * dx + dy * dy).sqrt();
if len < 1e-6 {
continue;
}
let mut nx = dy / len * halo_px;
let mut ny = -dx / len * halo_px;
let dot_c = nx * (c[0] - a[0]) + ny * (c[1] - a[1]);
if dot_c > 0.0 {
nx = -nx;
ny = -ny;
}
let base = out_verts.len() as u32;
out_verts.push([a[0], a[1], 1.0]); out_verts.push([b[0], b[1], 1.0]); out_verts.push([a[0] + nx, a[1] + ny, 0.0]); out_verts.push([b[0] + nx, b[1] + ny, 0.0]); out_indices.extend_from_slice(&[
base,
base + 1,
base + 2,
base + 1,
base + 3,
base + 2,
]);
}
}
Some((out_verts, out_indices))
}
fn to_tess_winding_rule(fill_rule: FillRule) -> WindingRule {
match fill_rule {
FillRule::NonZero => WindingRule::NonZero,
FillRule::EvenOdd => WindingRule::Odd,
}
}
pub fn tessellate_fill(contours: &[Vec<[f32; 2]>]) -> Option<(Vec<f32>, Vec<u32>)> {
if contours.is_empty() {
return None;
}
let mut tess = Tessellator::new();
let mut n_added = 0;
for contour in contours {
if contour.len() < 3 {
continue;
}
let cleaned = deduplicate_contour(contour);
if cleaned.len() < 3 {
continue;
}
if signed_area_2x(&cleaned).abs() < 1.0 {
continue;
}
let flat: Vec<f64> = cleaned
.iter()
.flat_map(|v| [v[0] as f64, v[1] as f64])
.collect();
tess.add_contour(2, &flat);
n_added += 1;
}
if n_added == 0 {
return None;
}
let ok = tess.tessellate(
WindingRule::Odd, ElementType::Polygons,
3, 2, None,
);
if !ok || tess.vertex_count() == 0 {
return None;
}
let verts: Vec<f32> = tess.vertices().iter().map(|&v| v as f32).collect();
let indices: Vec<u32> = tess.elements().to_vec();
Some((verts, indices))
}
fn signed_area_2x(pts: &[[f32; 2]]) -> f32 {
let n = pts.len();
let mut a = 0.0f32;
for i in 0..n {
let j = (i + 1) % n;
a += pts[i][0] * pts[j][1] - pts[j][0] * pts[i][1];
}
a
}
fn deduplicate_contour(pts: &[[f32; 2]]) -> Vec<[f32; 2]> {
deduplicate_contour_v(pts)
}
fn deduplicate_contour_v(pts: &[[f32; 2]]) -> Vec<[f32; 2]> {
let mut out: Vec<[f32; 2]> = Vec::with_capacity(pts.len());
for &pt in pts {
match out.last() {
Some(&prev) if prev == pt => {}
_ => out.push(pt),
}
}
if out.len() >= 2 && out.first() == out.last() {
out.pop();
}
out
}
pub fn tessellate_rect(x: f32, y: f32, w: f32, h: f32) -> Option<(Vec<f32>, Vec<u32>)> {
let contour = vec![[x, y], [x + w, y], [x + w, y + h], [x, y + h]];
tessellate_fill(&[contour])
}
pub fn tessellate_rounded_rect(
x: f32,
y: f32,
w: f32,
h: f32,
r: f32,
segments: usize,
) -> Option<(Vec<f32>, Vec<u32>)> {
let r = r.min(w * 0.5).min(h * 0.5);
let seg = segments.max(3);
let mut contour: Vec<[f32; 2]> = Vec::with_capacity(seg * 4 + 4);
use std::f32::consts::PI;
let corners = [
(x + w - r, y + r, -PI * 0.5, 0.0), (x + w - r, y + h - r, 0.0, PI * 0.5), (x + r, y + h - r, PI * 0.5, PI), (x + r, y + r, PI, PI * 1.5), ];
for &(cx, cy, start, end) in &corners {
for i in 0..=seg {
let t = i as f32 / seg as f32;
let angle = start + t * (end - start);
contour.push([cx + angle.cos() * r, cy + angle.sin() * r]);
}
}
tessellate_fill(&[contour])
}
pub fn tessellate_circle(
cx: f32,
cy: f32,
r: f32,
segments: usize,
) -> Option<(Vec<f32>, Vec<u32>)> {
let seg = segments.max(8);
use std::f32::consts::TAU;
let contour: Vec<[f32; 2]> = (0..seg)
.map(|i| {
let angle = i as f32 / seg as f32 * TAU;
[cx + angle.cos() * r, cy + angle.sin() * r]
})
.collect();
tessellate_fill(&[contour])
}