use super::geometry::{axis_aligned, pill_feature, rect_feature, Feature};
use super::math::{add, rotate};
use crate::gerber::apertures::Aperture;
const ARC_STEPS: usize = 8;
const MITER_LIMIT: f64 = 4.0;
pub(super) fn round_rect_feature(
w: f64,
h: f64,
r: f64,
rot: f64,
at: [f64; 2],
) -> Result<Feature, String> {
if r <= 0.0 {
return rect_feature(w, h, rot, at);
}
if 2.0 * r >= w.min(h) {
return pill_feature(w, h, rot, at);
}
if let Some((w, h)) = axis_aligned(w, h, rot) {
return Ok(Feature::Flash(Aperture::round_rect(w, h, r)?, at));
}
Aperture::round_rect(w, h, r)?;
Ok(Feature::Region(rounded_outline(w, h, r, rot, at)))
}
fn rounded_outline(w: f64, h: f64, r: f64, rot: f64, at: [f64; 2]) -> Vec<[f64; 2]> {
let (cx, cy) = (w / 2.0 - r, h / 2.0 - r);
let corners = [
(cx, cy, 0.0),
(-cx, cy, 90.0),
(-cx, -cy, 180.0),
(cx, -cy, 270.0),
];
let mut points = Vec::new();
for (x, y, start) in corners {
for step in 0..=ARC_STEPS {
let angle = start + 90.0 * step as f64 / ARC_STEPS as f64;
let arc = rotate([r, 0.0], angle);
points.push(add(at, rotate([x + arc[0], y + arc[1]], rot)));
}
}
points
}
pub(super) fn offset_polygon(points: &[[f64; 2]], margin: f64) -> Result<Vec<[f64; 2]>, String> {
if margin == 0.0 {
return Ok(points.to_vec());
}
let sign = if signed_area(points) >= 0.0 {
1.0
} else {
-1.0
};
let n = points.len();
let grown: Vec<[f64; 2]> = (0..n)
.map(|i| {
miter(
points[(i + n - 1) % n],
points[i],
points[(i + 1) % n],
margin * sign,
)
})
.collect();
if grown.iter().flatten().all(|v| v.is_finite()) {
Ok(grown)
} else {
Err("polygon pad has coincident vertices".to_string())
}
}
fn signed_area(points: &[[f64; 2]]) -> f64 {
let n = points.len();
(0..n)
.map(|i| points[i][0] * points[(i + 1) % n][1] - points[(i + 1) % n][0] * points[i][1])
.sum::<f64>()
/ 2.0
}
fn miter(prev: [f64; 2], p: [f64; 2], next: [f64; 2], d: f64) -> [f64; 2] {
let n1 = edge_normal(prev, p);
let n2 = edge_normal(p, next);
let sum = [n1[0] + n2[0], n1[1] + n2[1]];
let len_sq = sum[0] * sum[0] + sum[1] * sum[1];
let scale = (2.0 / len_sq).min(MITER_LIMIT * MITER_LIMIT);
[p[0] + d * sum[0] * scale, p[1] + d * sum[1] * scale]
}
fn edge_normal(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
let len = dx.hypot(dy);
[dy / len, -dx / len]
}