main
John Lauer source: the crates, flows, docs and skills of adom-aiflow 0.1.5, so PRs can be opened against them ad411ea
//! The raster every geometric stage works on: 0.1 mm cells over the outline's box, with
//! conservative rasterisers (a cell counts as copper when any part of the shape touches its
//! square), disk dilation for clearance maps, and the outline's inside and edge distance.
use aiflow_board::Pt;

pub const RES: f64 = 0.1;

#[derive(Clone)]
pub struct Grid {
    pub res: f64,
    pub x0: f64,
    pub y0: f64,
    pub w: usize,
    pub h: usize,
}

/// A boolean raster, row-major (index = j * w + i).
#[derive(Clone)]
pub struct Mask {
    pub w: usize,
    pub h: usize,
    pub bits: Vec<bool>,
}

impl Mask {
    pub fn new(w: usize, h: usize) -> Mask {
        Mask { w, h, bits: vec![false; w * h] }
    }
    #[inline]
    pub fn get(&self, i: usize, j: usize) -> bool {
        self.bits[j * self.w + i]
    }
    #[inline]
    pub fn set(&mut self, i: usize, j: usize, v: bool) {
        self.bits[j * self.w + i] = v;
    }
    pub fn or_with(&mut self, other: &Mask) {
        for (a, b) in self.bits.iter_mut().zip(&other.bits) {
            *a |= *b;
        }
    }
    pub fn and_not(&mut self, other: &Mask) {
        for (a, b) in self.bits.iter_mut().zip(&other.bits) {
            *a &= !*b;
        }
    }
    pub fn any(&self) -> bool {
        self.bits.iter().any(|b| *b)
    }
    pub fn count(&self) -> usize {
        self.bits.iter().filter(|b| **b).count()
    }
}

impl Grid {
    pub fn new(bbox: (f64, f64, f64, f64)) -> Grid {
        Grid::with_res(bbox, RES)
    }

    pub fn with_res(bbox: (f64, f64, f64, f64), res: f64) -> Grid {
        Grid { res, x0: bbox.0, y0: bbox.1, w: ((bbox.2 - bbox.0) / res).round() as usize + 1, h: ((bbox.3 - bbox.1) / res).round() as usize + 1 }
    }

    /// Round half up (pads on .x5 coordinates at 0.5 mm pitch snap a consistent 5 cells apart),
    /// with 0.1 um of tolerance for KiCad's nanometre noise.
    #[inline]
    pub fn to_cell(&self, x: f64, y: f64) -> (i64, i64) {
        (((x - self.x0) / self.res + 0.5 + 1e-3).floor() as i64, ((y - self.y0) / self.res + 0.5 + 1e-3).floor() as i64)
    }

    #[inline]
    pub fn to_xy(&self, i: i64, j: i64) -> Pt {
        let r = |v: f64| (v * 10000.0).round() / 10000.0;
        (r(self.x0 + i as f64 * self.res), r(self.y0 + j as f64 * self.res))
    }

    #[inline]
    pub fn in_bounds(&self, i: i64, j: i64) -> bool {
        i >= 0 && j >= 0 && (i as usize) < self.w && (j as usize) < self.h
    }

    pub fn mask(&self) -> Mask {
        Mask::new(self.w, self.h)
    }

    /// Mark every cell whose centre is inside the polygon, plus the cells its edges pass through.
    pub fn poly_mask(&self, poly: &[Pt], mask: &mut Mask) {
        let n = poly.len();
        if n < 3 {
            return;
        }
        let (bx0, by0, bx1, by1) = aiflow_board::geom::bbox(poly);
        let i0 = (((bx0 - self.x0) / self.res).floor() as i64 - 1).max(0) as usize;
        let i1 = ((((bx1 - self.x0) / self.res).ceil() as i64 + 1).min(self.w as i64 - 1)).max(0) as usize;
        let j0 = (((by0 - self.y0) / self.res).floor() as i64 - 1).max(0) as usize;
        let j1 = ((((by1 - self.y0) / self.res).ceil() as i64 + 1).min(self.h as i64 - 1)).max(0) as usize;
        if i1 < i0 || j1 < j0 {
            return;
        }
        for j in j0..=j1 {
            let cy = self.y0 + j as f64 * self.res;
            for i in i0..=i1 {
                let cx = self.x0 + i as f64 * self.res;
                if aiflow_board::geom::point_in_poly(cx, cy, poly) {
                    mask.set(i, j, true);
                }
            }
        }
        // boundary supercover
        for k in 0..n {
            let (xa, ya) = poly[k];
            let (xb, yb) = poly[(k + 1) % n];
            let l = (xb - xa).hypot(yb - ya);
            let steps = ((l / 0.025).ceil() as usize).max(1);
            for s in 0..=steps {
                let t = s as f64 / steps as f64;
                let ex = xa + (xb - xa) * t;
                let ey = ya + (yb - ya) * t;
                let ii = ((ex - self.x0) / self.res).round() as i64;
                let jj = ((ey - self.y0) / self.res).round() as i64;
                if ii >= i0 as i64 && ii <= i1 as i64 && jj >= j0 as i64 && jj <= j1 as i64 {
                    mask.set(ii as usize, jj as usize, true);
                }
            }
        }
    }

    /// Flat indices of the cells whose centre is within half_width + slack of segment a-b.
    pub fn segment_cells(&self, a: Pt, b: Pt, half_width: f64, slack: Option<f64>) -> Vec<usize> {
        let slack = slack.unwrap_or(self.res / 2.0);
        let r = half_width + slack;
        let (xa, ya) = a;
        let (xb, yb) = b;
        let i0 = (((xa.min(xb) - r - self.x0) / self.res).floor() as i64).max(0);
        let i1 = (((xa.max(xb) + r - self.x0) / self.res).ceil() as i64).min(self.w as i64 - 1);
        let j0 = (((ya.min(yb) - r - self.y0) / self.res).floor() as i64).max(0);
        let j1 = (((ya.max(yb) + r - self.y0) / self.res).ceil() as i64).min(self.h as i64 - 1);
        let mut out = Vec::new();
        if i1 < i0 || j1 < j0 {
            return out;
        }
        let (dx, dy) = (xb - xa, yb - ya);
        let l2 = dx * dx + dy * dy;
        let r2 = (r + 1e-9) * (r + 1e-9);
        for j in j0..=j1 {
            let cy = self.y0 + j as f64 * self.res;
            for i in i0..=i1 {
                let cx = self.x0 + i as f64 * self.res;
                let t = if l2 < 1e-12 { 0.0 } else { (((cx - xa) * dx + (cy - ya) * dy) / l2).clamp(0.0, 1.0) };
                let px = xa + t * dx;
                let py = ya + t * dy;
                let d2 = (cx - px) * (cx - px) + (cy - py) * (cy - py);
                if d2 <= r2 {
                    out.push(j as usize * self.w + i as usize);
                }
            }
        }
        out
    }

    pub fn disk_cells(&self, c: Pt, radius: f64, slack: Option<f64>) -> Vec<usize> {
        self.segment_cells(c, c, radius, slack)
    }

    /// Binary dilation by a disk of `r_cells` cells. Separable enough to be fast: per row offsets.
    pub fn dilate(&self, mask: &Mask, r_cells: f64) -> Mask {
        if r_cells <= 0.0 {
            return mask.clone();
        }
        let rr = r_cells.floor() as i64;
        let r2 = r_cells * r_cells + 1e-9;
        // horizontal half-chords per dy
        let mut chords = Vec::with_capacity((2 * rr + 1) as usize);
        for dy in -rr..=rr {
            let rem = r2 - (dy * dy) as f64;
            chords.push((dy, if rem >= 0.0 { rem.sqrt().floor() as i64 } else { -1 }));
        }
        let w = mask.w as i64;
        let h = mask.h as i64;
        let mut out = Mask::new(mask.w, mask.h);
        // pass 1: horizontal runs per dy would require a 2D scan; do it directly but skip empty rows
        for j in 0..h {
            let row = &mask.bits[(j * w) as usize..((j + 1) * w) as usize];
            if !row.iter().any(|b| *b) {
                continue;
            }
            for i in 0..w {
                if !row[i as usize] {
                    continue;
                }
                for &(dy, hc) in &chords {
                    if hc < 0 {
                        continue;
                    }
                    let jj = j + dy;
                    if jj < 0 || jj >= h {
                        continue;
                    }
                    let a = (i - hc).max(0) as usize;
                    let b = (i + hc).min(w - 1) as usize;
                    let base = (jj * w) as usize;
                    for k in a..=b {
                        out.bits[base + k] = true;
                    }
                }
            }
        }
        out
    }

    /// (inside mask, distance of every cell centre to the nearest outline edge).
    pub fn outline_distance(&self, outline: &[Pt]) -> (Mask, Vec<f32>) {
        let mut inside = self.mask();
        let mut dmin = vec![f32::INFINITY; self.w * self.h];
        let n = outline.len();
        for j in 0..self.h {
            let cy = self.y0 + j as f64 * self.res;
            for i in 0..self.w {
                let cx = self.x0 + i as f64 * self.res;
                let mut ins = false;
                let mut best = f64::INFINITY;
                for k in 0..n {
                    let (xa, ya) = outline[k];
                    let (xb, yb) = outline[(k + 1) % n];
                    if (ya > cy) != (yb > cy) {
                        let xint = xa + (cy - ya) * (xb - xa) / (yb - ya);
                        if cx < xint {
                            ins = !ins;
                        }
                    }
                    let d = aiflow_board::geom::seg_distance(cx, cy, xa, ya, xb, yb);
                    if d < best {
                        best = d;
                    }
                }
                inside.set(i, j, ins);
                dmin[j * self.w + i] = best as f32;
            }
        }
        (inside, dmin)
    }

    pub fn outline_inside(&self, outline: &[Pt], margin: f64) -> Mask {
        let (mut inside, dmin) = self.outline_distance(outline);
        for (b, d) in inside.bits.iter_mut().zip(&dmin) {
            *b = *b && (*d as f64) >= margin;
        }
        inside
    }
}