#[cfg(all(feature = "quad-aa", feature = "std"))]
use crate::types::Fixed64;
use crate::types::{Fixed, Point};
#[derive(Clone, Copy)]
pub(super) struct PreparedEdge {
pub base: Point,
pub nx: Fixed,
pub ny: Fixed,
#[cfg(all(feature = "quad-aa", feature = "std"))]
pub inv_len: Fixed64,
#[cfg(all(feature = "quad-aa", feature = "std"))]
pub half_len_sq: Fixed64,
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
pub qx: Fixed,
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
pub qy: Fixed,
}
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 nx = -edge_dy;
let ny = edge_dx;
#[cfg(all(feature = "quad-aa", feature = "std"))]
let (inv_len, half_len_sq) = {
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
};
(inv_len, Fixed64::from_raw(len_sq.raw() / 4))
};
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
let (qx, qy) = {
(Fixed::from_raw(nx.raw() / 4), Fixed::from_raw(ny.raw() / 4))
};
PreparedEdge {
base: a,
nx,
ny,
#[cfg(all(feature = "quad-aa", feature = "std"))]
inv_len,
#[cfg(all(feature = "quad-aa", feature = "std"))]
half_len_sq,
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
qx,
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
qy,
}
})
}
#[derive(Clone, Copy)]
pub(super) struct PreparedCorner {
pub center: Point,
pub ua: Point,
pub ub: Point,
pub radius: Fixed,
}
pub(super) struct EdgeRowState {
pub raw: [Fixed; 4],
}
impl EdgeRowState {
pub(super) fn new(edges: &[PreparedEdge; 4], cx_start: Fixed, cy: Fixed) -> Self {
let mut raw = [Fixed::ZERO; 4];
for i in 0..4 {
let e = &edges[i];
let dx = cx_start - e.base.x;
let dy = cy - e.base.y;
raw[i] = dx * e.nx + dy * e.ny;
}
Self { raw }
}
#[inline]
pub(super) fn step(&mut self, edges: &[PreparedEdge; 4]) {
for (raw, e) in self.raw.iter_mut().zip(edges.iter()) {
*raw += e.nx;
}
}
}
#[cfg(not(feature = "quad-aa"))]
pub(super) use quad_pixel_coverage_row_binary as quad_pixel_coverage_row;
#[cfg(all(feature = "quad-aa", feature = "std"))]
pub(super) use quad_pixel_coverage_row_sdf as quad_pixel_coverage_row;
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
pub(super) use quad_pixel_coverage_row_supersample as quad_pixel_coverage_row;
#[cfg(not(feature = "quad-aa"))]
#[inline]
pub(super) fn quad_pixel_coverage_row_binary(
_edges: &[PreparedEdge; 4],
corners: Option<&[PreparedCorner; 4]>,
cx: Fixed,
cy: Fixed,
row: &EdgeRowState,
) -> Fixed {
for raw in row.raw.iter() {
if raw.raw() < 0 {
return Fixed::ZERO;
}
}
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 dx_raw = dx.raw() as i64;
let dy_raw = dy.raw() as i64;
let dist_sq = dx_raw * dx_raw + dy_raw * dy_raw;
let r_raw = c.radius.raw() as i64;
if dist_sq > r_raw * r_raw {
return Fixed::ZERO;
}
break;
}
}
}
Fixed::ONE
}
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
#[inline]
pub(super) fn quad_pixel_coverage_row_supersample(
edges: &[PreparedEdge; 4],
corners: Option<&[PreparedCorner; 4]>,
cx: Fixed,
cy: Fixed,
row: &EdgeRowState,
) -> Fixed {
let s00 = sample_inside(edges, row, -1, -1);
let s10 = sample_inside(edges, row, 1, -1);
let s01 = sample_inside(edges, row, -1, 1);
let s11 = sample_inside(edges, row, 1, 1);
let mut hit = s00 as u32 + s10 as u32 + s01 as u32 + s11 as u32;
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 {
hit = corner_sample_hit(c, cx, cy);
break;
}
}
}
match hit {
0 => Fixed::ZERO,
1 => Fixed::from_raw(64), 2 => Fixed::HALF, 3 => Fixed::from_raw(192), _ => Fixed::ONE,
}
}
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
#[inline(always)]
fn sample_inside(edges: &[PreparedEdge; 4], row: &EdgeRowState, sx: i32, sy: i32) -> bool {
for (e, raw_center) in edges.iter().zip(row.raw.iter()) {
let qx = Fixed::from_raw(sx * e.qx.raw());
let qy = Fixed::from_raw(sy * e.qy.raw());
let raw = *raw_center + qx + qy;
if raw.raw() < 0 {
return false;
}
}
true
}
#[cfg(all(feature = "quad-aa", not(feature = "std")))]
#[inline]
fn corner_sample_hit(c: &PreparedCorner, cx: Fixed, cy: Fixed) -> u32 {
let r = c.radius;
let r_sq_raw = r.raw() as i64 * r.raw() as i64;
let quarter = Fixed::from_raw(64); let offsets = [
(-quarter, -quarter),
(quarter, -quarter),
(-quarter, quarter),
(quarter, quarter),
];
let mut hit = 0u32;
for (dx, dy) in offsets {
let sx = (cx + dx) - c.center.x;
let sy = (cy + dy) - c.center.y;
let sx_raw = sx.raw() as i64;
let sy_raw = sy.raw() as i64;
if sx_raw * sx_raw + sy_raw * sy_raw < r_sq_raw {
hit += 1;
}
}
hit
}
#[cfg(all(feature = "quad-aa", feature = "std"))]
#[inline]
pub(super) fn quad_pixel_coverage_row_sdf(
edges: &[PreparedEdge; 4],
corners: Option<&[PreparedCorner; 4]>,
cx: Fixed,
cy: Fixed,
row: &EdgeRowState,
) -> Fixed {
let mut min_sdf = Fixed::MAX;
for (e, raw_fixed) in edges.iter().zip(row.raw.iter()) {
let raw = Fixed64::from_fixed(*raw_fixed);
let raw_sq = raw * raw;
if raw_sq >= e.half_len_sq {
if raw.raw() < 0 {
return Fixed::ZERO;
}
continue;
}
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 >= Fixed::HALF {
Fixed::ONE
} else if min_sdf <= -Fixed::HALF {
Fixed::ZERO
} else {
min_sdf + Fixed::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) + Fixed::HALF;
let cy = Fixed::from_int(py) + Fixed::HALF;
let row = EdgeRowState::new(edges, cx, cy);
quad_pixel_coverage_row(edges, None, cx, cy, &row)
}
#[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);
}
#[cfg(feature = "quad-aa")]
#[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()));
}
}