atagen

monolith

public
Star0
Watch0
b70469d195a1e94baeaf50d2eca27d2eafdb27a8
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/planner/src/validate.rs

b563d2da1c80atagen2026-10-16 08:25 UTC1
use crate::geom::{Change, Rect};
b563d2da1c80atagen2026-10-16 08:25 UTC2
use crate::plan::{Config, Plan, Style};
b563d2da1c80atagen2026-10-16 08:25 UTC3
b563d2da1c80atagen2026-10-16 08:25 UTC4
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
b563d2da1c80atagen2026-10-16 08:25 UTC5
pub enum Violation {
b563d2da1c80atagen2026-10-16 08:25 UTC6
    /// Windows `a` and `b` overlap at some point during `phase`.
b563d2da1c80atagen2026-10-16 08:25 UTC7
    Overlap { phase: usize, a: usize, b: usize },
b563d2da1c80atagen2026-10-16 08:25 UTC8
    /// In orthogonal style, `window` changes both axes during `phase`.
b563d2da1c80atagen2026-10-16 08:25 UTC9
    TwoAxes { phase: usize, window: usize },
b563d2da1c80atagen2026-10-16 08:25 UTC10
}
b563d2da1c80atagen2026-10-16 08:25 UTC11
b563d2da1c80atagen2026-10-16 08:25 UTC12
/// Exact check of a plan under linear interpolation. Every window in a phase
b563d2da1c80atagen2026-10-16 08:25 UTC13
/// shares one curve, so overlap depends only on the keyframes, not the easing.
b563d2da1c80atagen2026-10-16 08:25 UTC14
pub fn validate(plan: &Plan, cfg: &Config) -> Vec<Violation> {
b563d2da1c80atagen2026-10-16 08:25 UTC15
    let mut out = Vec::new();
b563d2da1c80atagen2026-10-16 08:25 UTC16
    for (phase, pair) in plan.keyframes.windows(2).enumerate() {
b563d2da1c80atagen2026-10-16 08:25 UTC17
        let (a, b) = (&pair[0], &pair[1]);
b563d2da1c80atagen2026-10-16 08:25 UTC18
        for i in 0..a.len() {
b563d2da1c80atagen2026-10-16 08:25 UTC19
            if cfg.style == Style::Orthogonal && Change::between(&a[i], &b[i]).both() {
b563d2da1c80atagen2026-10-16 08:25 UTC20
                out.push(Violation::TwoAxes { phase, window: i });
b563d2da1c80atagen2026-10-16 08:25 UTC21
            }
b563d2da1c80atagen2026-10-16 08:25 UTC22
            for j in i + 1..a.len() {
b563d2da1c80atagen2026-10-16 08:25 UTC23
                if overlaps_during(&a[i], &b[i], &a[j], &b[j], cfg.eps) {
b563d2da1c80atagen2026-10-16 08:25 UTC24
                    out.push(Violation::Overlap { phase, a: i, b: j });
b563d2da1c80atagen2026-10-16 08:25 UTC25
                }
b563d2da1c80atagen2026-10-16 08:25 UTC26
            }
b563d2da1c80atagen2026-10-16 08:25 UTC27
        }
b563d2da1c80atagen2026-10-16 08:25 UTC28
    }
b563d2da1c80atagen2026-10-16 08:25 UTC29
    out
b563d2da1c80atagen2026-10-16 08:25 UTC30
}
b563d2da1c80atagen2026-10-16 08:25 UTC31
b563d2da1c80atagen2026-10-16 08:25 UTC32
/// Whether two linearly moving rectangles share more than `tol` of width and
b563d2da1c80atagen2026-10-16 08:25 UTC33
/// height at any instant. Each condition is linear in time, so the set of
b563d2da1c80atagen2026-10-16 08:25 UTC34
/// instants satisfying all of them is an interval.
b563d2da1c80atagen2026-10-16 08:25 UTC35
fn overlaps_during(a0: &Rect, a1: &Rect, b0: &Rect, b1: &Rect, tol: f64) -> bool {
b563d2da1c80atagen2026-10-16 08:25 UTC36
    let conditions = [
b563d2da1c80atagen2026-10-16 08:25 UTC37
        (a0.w, a1.w),
b563d2da1c80atagen2026-10-16 08:25 UTC38
        (b0.w, b1.w),
b563d2da1c80atagen2026-10-16 08:25 UTC39
        (a0.right() - b0.x, a1.right() - b1.x),
b563d2da1c80atagen2026-10-16 08:25 UTC40
        (b0.right() - a0.x, b1.right() - a1.x),
b563d2da1c80atagen2026-10-16 08:25 UTC41
        (a0.h, a1.h),
b563d2da1c80atagen2026-10-16 08:25 UTC42
        (b0.h, b1.h),
b563d2da1c80atagen2026-10-16 08:25 UTC43
        (a0.bottom() - b0.y, a1.bottom() - b1.y),
b563d2da1c80atagen2026-10-16 08:25 UTC44
        (b0.bottom() - a0.y, b1.bottom() - a1.y),
b563d2da1c80atagen2026-10-16 08:25 UTC45
    ];
b563d2da1c80atagen2026-10-16 08:25 UTC46
    let (mut lo, mut hi) = (0.0f64, 1.0f64);
b563d2da1c80atagen2026-10-16 08:25 UTC47
    for (v0, v1) in conditions {
b563d2da1c80atagen2026-10-16 08:25 UTC48
        let slope = v1 - v0;
b563d2da1c80atagen2026-10-16 08:25 UTC49
        if slope.abs() < 1e-12 {
b563d2da1c80atagen2026-10-16 08:25 UTC50
            if v0 <= tol {
b563d2da1c80atagen2026-10-16 08:25 UTC51
                return false;
b563d2da1c80atagen2026-10-16 08:25 UTC52
            }
b563d2da1c80atagen2026-10-16 08:25 UTC53
            continue;
b563d2da1c80atagen2026-10-16 08:25 UTC54
        }
b563d2da1c80atagen2026-10-16 08:25 UTC55
        let root = (tol - v0) / slope;
b563d2da1c80atagen2026-10-16 08:25 UTC56
        if slope > 0.0 {
b563d2da1c80atagen2026-10-16 08:25 UTC57
            lo = lo.max(root);
b563d2da1c80atagen2026-10-16 08:25 UTC58
        } else {
b563d2da1c80atagen2026-10-16 08:25 UTC59
            hi = hi.min(root);
b563d2da1c80atagen2026-10-16 08:25 UTC60
        }
b563d2da1c80atagen2026-10-16 08:25 UTC61
    }
b563d2da1c80atagen2026-10-16 08:25 UTC62
    lo < hi - 1e-9
b563d2da1c80atagen2026-10-16 08:25 UTC63
}
b563d2da1c80atagen2026-10-16 08:25 UTC64
b563d2da1c80atagen2026-10-16 08:25 UTC65
#[cfg(test)]
b563d2da1c80atagen2026-10-16 08:25 UTC66
mod tests {
b563d2da1c80atagen2026-10-16 08:25 UTC67
    use super::*;
b563d2da1c80atagen2026-10-16 08:25 UTC68
b563d2da1c80atagen2026-10-16 08:25 UTC69
    #[test]
b563d2da1c80atagen2026-10-16 08:25 UTC70
    fn zero_size_seed_inside_a_window_is_not_an_overlap() {
b563d2da1c80atagen2026-10-16 08:25 UTC71
        let a = Rect::new(0.0, 0.0, 100.0, 100.0);
b563d2da1c80atagen2026-10-16 08:25 UTC72
        let seed = Rect::new(50.0, 0.0, 0.0, 100.0);
b563d2da1c80atagen2026-10-16 08:25 UTC73
        assert!(!overlaps_during(&a, &a, &seed, &seed, 0.5));
b563d2da1c80atagen2026-10-16 08:25 UTC74
    }
b563d2da1c80atagen2026-10-16 08:25 UTC75
}