atagen

monolith

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

Blame: crates/motion/src/velocity.rs

b563d2da1c80atagen2026-10-16 08:25 UTC1
/// Rectangle edges as (left, right, top, bottom).
b563d2da1c80atagen2026-10-16 08:25 UTC2
pub type Edges = [f64; 4];
b563d2da1c80atagen2026-10-16 08:25 UTC3
b563d2da1c80atagen2026-10-16 08:25 UTC4
/// Initial progress slope for the first phase of a replanned motion group.
b563d2da1c80atagen2026-10-16 08:25 UTC5
///
b563d2da1c80atagen2026-10-16 08:25 UTC6
/// Each item is a window's current edge velocity (units per second) and its
b563d2da1c80atagen2026-10-16 08:25 UTC7
/// edge displacement over the new first phase. The fastest mover with a real
b563d2da1c80atagen2026-10-16 08:25 UTC8
/// displacement decides the slope, because windows sharing an edge must share
b563d2da1c80atagen2026-10-16 08:25 UTC9
/// a curve. Returns `None` when that mover would have to reverse, so the group
b563d2da1c80atagen2026-10-16 08:25 UTC10
/// starts from rest instead.
b563d2da1c80atagen2026-10-16 08:25 UTC11
pub fn group_initial_slope(
b563d2da1c80atagen2026-10-16 08:25 UTC12
    movers: impl IntoIterator<Item = (Edges, Edges)>,
b563d2da1c80atagen2026-10-16 08:25 UTC13
    phase_secs: f64,
b563d2da1c80atagen2026-10-16 08:25 UTC14
) -> Option<f64> {
b563d2da1c80atagen2026-10-16 08:25 UTC15
    let mut best: Option<(f64, Edges, Edges)> = None;
b563d2da1c80atagen2026-10-16 08:25 UTC16
    for (velocity, displacement) in movers {
b563d2da1c80atagen2026-10-16 08:25 UTC17
        if dot(&displacement, &displacement) < 1.0 {
b563d2da1c80atagen2026-10-16 08:25 UTC18
            continue;
b563d2da1c80atagen2026-10-16 08:25 UTC19
        }
b563d2da1c80atagen2026-10-16 08:25 UTC20
        let speed = dot(&velocity, &velocity);
b563d2da1c80atagen2026-10-16 08:25 UTC21
        if best.is_none_or(|(s, _, _)| speed > s) {
b563d2da1c80atagen2026-10-16 08:25 UTC22
            best = Some((speed, velocity, displacement));
b563d2da1c80atagen2026-10-16 08:25 UTC23
        }
b563d2da1c80atagen2026-10-16 08:25 UTC24
    }
b563d2da1c80atagen2026-10-16 08:25 UTC25
    let (_, velocity, displacement) = best?;
b563d2da1c80atagen2026-10-16 08:25 UTC26
    let slope = dot(&velocity, &displacement) / dot(&displacement, &displacement) * phase_secs;
b563d2da1c80atagen2026-10-16 08:25 UTC27
    (slope > 0.05).then_some(slope)
b563d2da1c80atagen2026-10-16 08:25 UTC28
}
b563d2da1c80atagen2026-10-16 08:25 UTC29
b563d2da1c80atagen2026-10-16 08:25 UTC30
fn dot(a: &Edges, b: &Edges) -> f64 {
b563d2da1c80atagen2026-10-16 08:25 UTC31
    a.iter().zip(b).map(|(x, y)| x * y).sum()
b563d2da1c80atagen2026-10-16 08:25 UTC32
}
b563d2da1c80atagen2026-10-16 08:25 UTC33
b563d2da1c80atagen2026-10-16 08:25 UTC34
#[cfg(test)]
b563d2da1c80atagen2026-10-16 08:25 UTC35
mod tests {
b563d2da1c80atagen2026-10-16 08:25 UTC36
    use super::*;
b563d2da1c80atagen2026-10-16 08:25 UTC37
b563d2da1c80atagen2026-10-16 08:25 UTC38
    #[test]
b563d2da1c80atagen2026-10-16 08:25 UTC39
    fn reversal_starts_from_rest() {
b563d2da1c80atagen2026-10-16 08:25 UTC40
        let slope =
b563d2da1c80atagen2026-10-16 08:25 UTC41
            group_initial_slope([([100.0, 100.0, 0.0, 0.0], [-50.0, -50.0, 0.0, 0.0])], 0.5);
b563d2da1c80atagen2026-10-16 08:25 UTC42
        assert_eq!(slope, None);
b563d2da1c80atagen2026-10-16 08:25 UTC43
    }
b563d2da1c80atagen2026-10-16 08:25 UTC44
}