atagen

monolith

public
Star0
Watch0
ab9ba68728856efde9c209f02ebc800a6feac203
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/planner/src/validate.rsRust75 lines2.4 KB
1
use crate::geom::{Change, Rect};
2
use crate::plan::{Config, Plan, Style};
3
4
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5
pub enum Violation {
6
    /// Windows `a` and `b` overlap at some point during `phase`.
7
    Overlap { phase: usize, a: usize, b: usize },
8
    /// In orthogonal style, `window` changes both axes during `phase`.
9
    TwoAxes { phase: usize, window: usize },
10
}
11
12
/// Exact check of a plan under linear interpolation. Every window in a phase
13
/// shares one curve, so overlap depends only on the keyframes, not the easing.
14
pub fn validate(plan: &Plan, cfg: &Config) -> Vec<Violation> {
15
    let mut out = Vec::new();
16
    for (phase, pair) in plan.keyframes.windows(2).enumerate() {
17
        let (a, b) = (&pair[0], &pair[1]);
18
        for i in 0..a.len() {
19
            if cfg.style == Style::Orthogonal && Change::between(&a[i], &b[i]).both() {
20
                out.push(Violation::TwoAxes { phase, window: i });
21
            }
22
            for j in i + 1..a.len() {
23
                if overlaps_during(&a[i], &b[i], &a[j], &b[j], cfg.eps) {
24
                    out.push(Violation::Overlap { phase, a: i, b: j });
25
                }
26
            }
27
        }
28
    }
29
    out
30
}
31
32
/// Whether two linearly moving rectangles share more than `tol` of width and
33
/// height at any instant. Each condition is linear in time, so the set of
34
/// instants satisfying all of them is an interval.
35
fn overlaps_during(a0: &Rect, a1: &Rect, b0: &Rect, b1: &Rect, tol: f64) -> bool {
36
    let conditions = [
37
        (a0.w, a1.w),
38
        (b0.w, b1.w),
39
        (a0.right() - b0.x, a1.right() - b1.x),
40
        (b0.right() - a0.x, b1.right() - a1.x),
41
        (a0.h, a1.h),
42
        (b0.h, b1.h),
43
        (a0.bottom() - b0.y, a1.bottom() - b1.y),
44
        (b0.bottom() - a0.y, b1.bottom() - a1.y),
45
    ];
46
    let (mut lo, mut hi) = (0.0f64, 1.0f64);
47
    for (v0, v1) in conditions {
48
        let slope = v1 - v0;
49
        if slope.abs() < 1e-12 {
50
            if v0 <= tol {
51
                return false;
52
            }
53
            continue;
54
        }
55
        let root = (tol - v0) / slope;
56
        if slope > 0.0 {
57
            lo = lo.max(root);
58
        } else {
59
            hi = hi.min(root);
60
        }
61
    }
62
    lo < hi - 1e-9
63
}
64
65
#[cfg(test)]
66
mod tests {
67
    use super::*;
68
69
    #[test]
70
    fn zero_size_seed_inside_a_window_is_not_an_overlap() {
71
        let a = Rect::new(0.0, 0.0, 100.0, 100.0);
72
        let seed = Rect::new(50.0, 0.0, 0.0, 100.0);
73
        assert!(!overlaps_during(&a, &a, &seed, &seed, 0.5));
74
    }
75
}