use crate::types::{Fixed, Fixed64, Point};
const HALF: Fixed = Fixed::from_raw(128);
#[derive(Clone, Copy)]
pub(super) struct PreparedEdge {
pub base: Point,
pub nx: Fixed,
pub ny: Fixed,
pub inv_len: Fixed64,
}
pub(super) fn prepare_quad_edges(q: &[Point; 4], cw: bool) -> [PreparedEdge; 4] {
core::array::from_fn(|i| {
let (a, b) = if cw {
(q[i], q[(i + 1) & 3])
} else {
(q[(i + 1) & 3], q[i])
};
let edge_dx = b.x - a.x;
let edge_dy = b.y - a.y;
let len_sq = Fixed64::from_fixed(edge_dx) * Fixed64::from_fixed(edge_dx)
+ Fixed64::from_fixed(edge_dy) * Fixed64::from_fixed(edge_dy);
let len = len_sq.sqrt();
let inv_len = if len > Fixed64::ZERO {
Fixed64::ONE / len
} else {
Fixed64::ZERO
};
PreparedEdge {
base: a,
nx: -edge_dy,
ny: edge_dx,
inv_len,
}
})
}
#[derive(Clone, Copy)]
pub(super) struct PreparedCorner {
pub center: Point,
pub ua: Point,
pub ub: Point,
pub radius: Fixed,
}
#[inline]
pub(super) fn quad_pixel_coverage_sdf_inner(
edges: &[PreparedEdge; 4],
corners: Option<&[PreparedCorner; 4]>,
cx: Fixed,
cy: Fixed,
) -> Fixed {
let mut min_sdf = Fixed::MAX;
for e in edges {
let dx = cx - e.base.x;
let dy = cy - e.base.y;
let raw = Fixed64::from_fixed(dx) * Fixed64::from_fixed(e.nx)
+ Fixed64::from_fixed(dy) * Fixed64::from_fixed(e.ny);
let d = (raw * e.inv_len).to_fixed();
if d < min_sdf {
min_sdf = d;
}
}
if let Some(corner_arr) = corners {
for c in corner_arr {
let dx = cx - c.center.x;
let dy = cy - c.center.y;
let proj_a = dx * c.ua.x + dy * c.ua.y;
let proj_b = dx * c.ub.x + dy * c.ub.y;
if proj_a < Fixed::ZERO && proj_b < Fixed::ZERO {
let dist_sq = Fixed64::from_fixed(dx) * Fixed64::from_fixed(dx)
+ Fixed64::from_fixed(dy) * Fixed64::from_fixed(dy);
let dist = dist_sq.sqrt().to_fixed();
let corner_sdf = c.radius - dist;
if corner_sdf < min_sdf {
min_sdf = corner_sdf;
}
break; }
}
}
if min_sdf >= HALF {
Fixed::ONE
} else if min_sdf <= -HALF {
Fixed::ZERO
} else {
min_sdf + HALF
}
}
#[inline]
pub(super) fn shoelace_is_cw(q: &[Point; 4]) -> bool {
let mut sum = Fixed::ZERO;
for i in 0..4 {
let a = q[i];
let b = q[(i + 1) & 3];
sum += a.x * b.y - b.x * a.y;
}
sum > Fixed::ZERO
}
#[cfg(test)]
mod tests {
use super::*;
fn pt(x: f32, y: f32) -> Point {
Point {
x: Fixed::from_f32(x),
y: Fixed::from_f32(y),
}
}
fn square_cw() -> [Point; 4] {
[pt(0.0, 0.0), pt(10.0, 0.0), pt(10.0, 10.0), pt(0.0, 10.0)]
}
fn cov_at(edges: &[PreparedEdge; 4], px: i32, py: i32) -> Fixed {
let cx = Fixed::from_int(px) + HALF;
let cy = Fixed::from_int(py) + HALF;
quad_pixel_coverage_sdf_inner(edges, None, cx, cy)
}
#[test]
fn straight_quad_center_cov_is_full() {
let q = square_cw();
let edges = prepare_quad_edges(&q, true);
assert_eq!(cov_at(&edges, 5, 5), Fixed::ONE);
}
#[test]
fn straight_quad_outside_cov_is_zero() {
let q = square_cw();
let edges = prepare_quad_edges(&q, true);
assert_eq!(cov_at(&edges, 20, 20), Fixed::ZERO);
}
#[test]
fn straight_quad_edge_half_cov() {
let q = [pt(0.0, 0.0), pt(9.5, 0.0), pt(9.5, 10.0), pt(0.0, 10.0)];
let edges = prepare_quad_edges(&q, true);
let cov = cov_at(&edges, 9, 5);
assert!((cov.to_f32() - 0.5).abs() < 0.05, "cov = {}", cov.to_f32());
}
#[test]
fn straight_quad_ccw_still_inside() {
let q = [pt(0.0, 0.0), pt(0.0, 10.0), pt(10.0, 10.0), pt(10.0, 0.0)];
let cw = shoelace_is_cw(&q);
assert!(!cw);
let edges = prepare_quad_edges(&q, cw);
assert_eq!(cov_at(&edges, 5, 5), Fixed::ONE);
}
#[test]
fn shoelace_cw_positive() {
assert!(shoelace_is_cw(&square_cw()));
}
}