b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 1 | use crate::geom::{Change, Rect}; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 2 | use crate::strategy::plan_group; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 3 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 4 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 5 | pub enum Style { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 6 | /// Every window moves or resizes along one axis per phase. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 7 | Orthogonal, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 8 | /// Windows may change both axes in one phase when no pair loses its separation. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 9 | Direct, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 10 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 11 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 12 | #[derive(Clone, Copy, Debug, PartialEq)] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 13 | pub struct Config { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 14 | pub style: Style, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 15 | /// Gap between tiled windows. Neighbours closer than this are adjacent. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 16 | pub gap: f64, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 17 | /// Tolerance for separation and adjacency tests. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 18 | pub eps: f64, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 19 | /// Lanes thinner than this are not used. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 20 | pub min_lane: f64, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 21 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 22 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 23 | impl Default for Config { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 24 | fn default() -> Self { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 25 | Self { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 26 | style: Style::Orthogonal, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 27 | gap: 10.0, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 28 | eps: 0.5, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 29 | min_lane: 6.0, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 30 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 31 | } |
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 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 35 | pub enum Strategy { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 36 | Unchanged, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 37 | Direct, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 38 | AxisOrder, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 39 | Lanes { lanes: usize }, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 40 | Grid { cells: usize }, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 41 | Pinch, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 42 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 43 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 44 | /// Windows that plan together. Groups never touch each other while moving. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 45 | #[derive(Clone, Debug, PartialEq)] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 46 | pub struct Group { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 47 | pub members: Vec<usize>, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 48 | pub strategy: Strategy, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 49 | pub phases: usize, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 50 | /// Whether the plan briefly separates two neighbours that touch at both ends. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 51 | pub opens_hole: bool, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 52 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 53 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 54 | /// Keyframe layouts indexed like the input. Every phase lasts the same time |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 55 | /// and every window in a group moves on that group's curve. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 56 | #[derive(Clone, Debug, PartialEq)] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 57 | pub struct Plan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 58 | pub keyframes: Vec<Vec<Rect>>, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 59 | pub groups: Vec<Group>, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 60 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 61 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 62 | impl Plan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 63 | pub fn phases(&self) -> usize { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 64 | self.keyframes.len().saturating_sub(1) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 65 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 66 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 67 | /// Window positions at `phase` with eased progress `s`. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 68 | pub fn sample(&self, phase: usize, s: f64) -> Vec<Rect> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 69 | match (self.keyframes.get(phase), self.keyframes.get(phase + 1)) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 70 | (Some(a), Some(b)) => a.iter().zip(b).map(|(a, b)| a.lerp(b, s)).collect(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 71 | _ => self.keyframes.last().cloned().unwrap_or_default(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 72 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 73 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 74 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 75 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 76 | pub(crate) struct GroupPlan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 77 | pub keys: Vec<Vec<Rect>>, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 78 | pub strategy: Strategy, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 79 | pub opens_hole: bool, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 80 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 81 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 82 | impl GroupPlan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 83 | pub fn reversed(mut self) -> Self { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 84 | self.keys.reverse(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 85 | self |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 86 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 87 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 88 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 89 | /// Plans the transition from `from` to `to`. Both slices index the same windows. |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 90 | /// When some windows appear while others vanish in the same place, no single |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 91 | /// set of phases keeps them apart, so the vanishing ones finish first. |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 92 | pub fn plan(from: &[Rect], to: &[Rect], cfg: &Config) -> Plan { |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 93 | let direct = plan_once(from, to, cfg); |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 94 | if !direct.groups.iter().any(|g| g.strategy == Strategy::Pinch) { |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 95 | return direct; |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 96 | } |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 97 | staged(from, to, cfg).unwrap_or(direct) |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 98 | } |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 99 | |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 100 | /// Plans everything but the windows that appear, then lets those grow. |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 101 | /// A window that appears from a seed sits inside the space a vanishing one |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 102 | /// still fills, and the exact overlap check rejects growing into it. |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 103 | fn staged(from: &[Rect], to: &[Rect], cfg: &Config) -> Option<Plan> { |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 104 | let appears = |i: usize| is_empty(&from[i], cfg) && !is_empty(&to[i], cfg); |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 105 | if !(0..from.len()).any(appears) { |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 106 | return None; |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 107 | } |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 108 | let mid: Vec<Rect> = (0..from.len()) |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 109 | .map(|i| if appears(i) { from[i] } else { to[i] }) |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 110 | .collect(); |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 111 | let (first, second) = (plan_once(from, &mid, cfg), plan_once(&mid, to, cfg)); |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 112 | let groups: Vec<Group> = first.groups.into_iter().chain(second.groups).collect(); |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 113 | if groups.iter().any(|g| g.strategy == Strategy::Pinch) { |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 114 | return None; |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 115 | } |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 116 | let mut keyframes = first.keyframes; |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 117 | keyframes.extend(second.keyframes.into_iter().skip(1)); |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 118 | Some(Plan { keyframes, groups }) |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 119 | } |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 120 | |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 121 | fn is_empty(r: &Rect, cfg: &Config) -> bool { |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 122 | r.w <= cfg.eps || r.h <= cfg.eps |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 123 | } |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 124 | |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 125 | /// Windows with no area at either end, like hidden ones held at their seed, |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 126 | /// jump straight to `to`. Planning them would let an invisible point inside |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 127 | /// a visible window join its group, or block every strategy and pinch. |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 128 | fn plan_once(from: &[Rect], to: &[Rect], cfg: &Config) -> Plan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 129 | assert_eq!( |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 130 | from.len(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 131 | to.len(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 132 | "layouts must describe the same windows" |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 133 | ); |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 134 | let live: Vec<usize> = (0..from.len()) |
ab9ba6872885 | atagen | 2026-10-19 10:38 UTC | 135 | .filter(|&i| !(is_empty(&from[i], cfg) && is_empty(&to[i], cfg))) |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 136 | .collect(); |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 137 | if live.len() == from.len() { |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 138 | return plan_live(from, to, cfg); |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 139 | } |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 140 | let pick = |rects: &[Rect]| live.iter().map(|&i| rects[i]).collect::<Vec<_>>(); |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 141 | let inner = plan_live(&pick(from), &pick(to), cfg); |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 142 | let keyframes = inner |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 143 | .keyframes |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 144 | .iter() |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 145 | .map(|key| { |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 146 | let mut frame = to.to_vec(); |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 147 | for (k, &i) in live.iter().enumerate() { |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 148 | frame[i] = key[k]; |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 149 | } |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 150 | frame |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 151 | }) |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 152 | .collect(); |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 153 | let groups = inner |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 154 | .groups |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 155 | .into_iter() |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 156 | .map(|g| Group { |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 157 | members: g.members.iter().map(|&k| live[k]).collect(), |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 158 | ..g |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 159 | }) |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 160 | .collect(); |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 161 | Plan { keyframes, groups } |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 162 | } |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 163 | |
1a10e81ae5c1 | atagen | 2026-10-17 13:31 UTC | 164 | fn plan_live(from: &[Rect], to: &[Rect], cfg: &Config) -> Plan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 165 | let mut groups = Vec::new(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 166 | let mut plans = Vec::new(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 167 | for members in motion_groups(from, to, cfg) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 168 | let f: Vec<Rect> = members.iter().map(|&i| from[i]).collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 169 | let t: Vec<Rect> = members.iter().map(|&i| to[i]).collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 170 | let gp = plan_canonical(&f, &t, cfg); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 171 | groups.push(Group { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 172 | members: members.clone(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 173 | strategy: gp.strategy, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 174 | phases: gp.keys.len() - 1, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 175 | opens_hole: gp.opens_hole, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 176 | }); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 177 | plans.push((members, gp)); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 178 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 179 | let phases = groups.iter().map(|g| g.phases).max().unwrap_or(0); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 180 | let mut keyframes = vec![to.to_vec(); phases + 1]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 181 | for (members, gp) in &plans { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 182 | for (k, frame) in keyframes.iter_mut().enumerate() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 183 | let key = &gp.keys[k.min(gp.keys.len() - 1)]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 184 | for (local, &i) in members.iter().enumerate() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 185 | frame[i] = key[local]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 186 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 187 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 188 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 189 | Plan { keyframes, groups } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 190 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 191 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 192 | /// Plans in one fixed direction per pair of layouts, so planning back plays |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 193 | /// the same plan in reverse. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 194 | pub(crate) fn plan_canonical(from: &[Rect], to: &[Rect], cfg: &Config) -> GroupPlan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 195 | if canonical_order(from, to).is_le() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 196 | plan_group(from, to, cfg) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 197 | } else { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 198 | plan_group(to, from, cfg).reversed() |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 199 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 200 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 201 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 202 | fn canonical_order(a: &[Rect], b: &[Rect]) -> std::cmp::Ordering { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 203 | let q = |v: f64| (v * 10.0).round() as i64; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 204 | let key = |r: &Rect| [q(r.x), q(r.y), q(r.w), q(r.h)]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 205 | a.iter().map(key).cmp(b.iter().map(key)) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 206 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 207 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 208 | /// Partitions windows so that windows in different groups can never meet. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 209 | /// A moving window joins every window its swept area comes within a gap of. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 210 | fn motion_groups(from: &[Rect], to: &[Rect], cfg: &Config) -> Vec<Vec<usize>> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 211 | let n = from.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 212 | let swept: Vec<Rect> = (0..n) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 213 | .map(|i| Rect::bounding([&from[i], &to[i]]).expanded(cfg.gap / 2.0 + cfg.eps)) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 214 | .collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 215 | let moves: Vec<bool> = (0..n) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 216 | .map(|i| Change::between(&from[i], &to[i]).moves()) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 217 | .collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 218 | let mut parent: Vec<usize> = (0..n).collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 219 | fn find(parent: &mut [usize], mut i: usize) -> usize { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 220 | while parent[i] != i { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 221 | parent[i] = parent[parent[i]]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 222 | i = parent[i]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 223 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 224 | i |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 225 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 226 | for i in 0..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 227 | for j in i + 1..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 228 | if (moves[i] || moves[j]) && swept[i].intersects(&swept[j]) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 229 | let (a, b) = (find(&mut parent, i), find(&mut parent, j)); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 230 | parent[a] = b; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 231 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 232 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 233 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 234 | let mut groups: Vec<Vec<usize>> = Vec::new(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 235 | let mut slot = vec![usize::MAX; n]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 236 | for i in 0..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 237 | let root = find(&mut parent, i); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 238 | if slot[root] == usize::MAX { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 239 | slot[root] = groups.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 240 | groups.push(Vec::new()); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 241 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 242 | groups[slot[root]].push(i); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 243 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 244 | groups |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 245 | } |