b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 1 | /// Rectangle edges as (left, right, top, bottom). |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 2 | pub type Edges = [f64; 4]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 3 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 4 | /// Initial progress slope for the first phase of a replanned motion group. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 5 | /// |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 6 | /// Each item is a window's current edge velocity (units per second) and its |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 7 | /// edge displacement over the new first phase. The fastest mover with a real |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 8 | /// displacement decides the slope, because windows sharing an edge must share |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 9 | /// a curve. Returns `None` when that mover would have to reverse, so the group |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 10 | /// starts from rest instead. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 11 | pub fn group_initial_slope( |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 12 | movers: impl IntoIterator<Item = (Edges, Edges)>, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 13 | phase_secs: f64, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 14 | ) -> Option<f64> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 15 | let mut best: Option<(f64, Edges, Edges)> = None; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 16 | for (velocity, displacement) in movers { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 17 | if dot(&displacement, &displacement) < 1.0 { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 18 | continue; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 19 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 20 | let speed = dot(&velocity, &velocity); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 21 | if best.is_none_or(|(s, _, _)| speed > s) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 22 | best = Some((speed, velocity, displacement)); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 23 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 24 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 25 | let (_, velocity, displacement) = best?; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 26 | let slope = dot(&velocity, &displacement) / dot(&displacement, &displacement) * phase_secs; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 27 | (slope > 0.05).then_some(slope) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 28 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 29 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 30 | fn dot(a: &Edges, b: &Edges) -> f64 { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 31 | a.iter().zip(b).map(|(x, y)| x * y).sum() |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 32 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 33 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 34 | #[cfg(test)] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 35 | mod tests { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 36 | use super::*; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 37 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 38 | #[test] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 39 | fn reversal_starts_from_rest() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 40 | let slope = |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 41 | group_initial_slope([([100.0, 100.0, 0.0, 0.0], [-50.0, -50.0, 0.0, 0.0])], 0.5); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 42 | assert_eq!(slope, None); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 43 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 44 | } |