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::plan::{Config, GroupPlan, Strategy, Style}; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 3 | use crate::relation::{ABOVE, LEFT, X_BITS, Y_BITS, adjacency, separation}; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 4 | use crate::sat::{Lit, TwoSat}; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 5 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 6 | /// Upper bound on lane-assignment search steps before giving up on a lane count. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 7 | const LANE_SEARCH_BUDGET: usize = 20_000; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 8 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 9 | #[derive(Clone, Copy, PartialEq, Eq)] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 10 | enum Axis { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 11 | X, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 12 | Y, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 13 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 14 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 15 | pub(crate) fn plan_group(from: &[Rect], to: &[Rect], cfg: &Config) -> GroupPlan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 16 | let done = |keys: Vec<Vec<Rect>>, strategy| GroupPlan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 17 | keys, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 18 | strategy, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 19 | opens_hole: false, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 20 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 21 | let movers: Vec<usize> = (0..from.len()) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 22 | .filter(|&i| Change::between(&from[i], &to[i]).moves()) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 23 | .collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 24 | if movers.is_empty() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 25 | return done(vec![from.to_vec()], Strategy::Unchanged); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 26 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 27 | let pure = movers |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 28 | .iter() |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 29 | .all(|&i| !Change::between(&from[i], &to[i]).both()); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 30 | if phase_ok(from, to, cfg) && (cfg.style == Style::Direct || pure) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 31 | return done(vec![from.to_vec(), to.to_vec()], Strategy::Direct); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 32 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 33 | if let Some((k1, opens_hole)) = axis_order(from, to, &movers, cfg) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 34 | return GroupPlan { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 35 | keys: vec![from.to_vec(), k1, to.to_vec()], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 36 | strategy: Strategy::AxisOrder, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 37 | opens_hole, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 38 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 39 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 40 | for axis in [Axis::Y, Axis::X] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 41 | if let Some((k1, k2, lanes)) = lanes(from, to, &movers, axis, cfg) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 42 | return done( |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 43 | vec![from.to_vec(), k1, k2, to.to_vec()], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 44 | Strategy::Lanes { lanes }, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 45 | ); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 46 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 47 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 48 | if let Some((keys, cells)) = grid(from, to, &movers, cfg) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 49 | return done(keys, Strategy::Grid { cells }); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 50 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 51 | done(pinch(from, to, &movers), Strategy::Pinch) |
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 | /// Every pair shares a separating relation at both ends of the phase. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 55 | pub(crate) fn phase_ok(a: &[Rect], b: &[Rect], cfg: &Config) -> bool { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 56 | (0..a.len()).all(|i| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 57 | (i + 1..a.len()) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 58 | .all(|j| separation(&a[i], &a[j], cfg.eps) & separation(&b[i], &b[j], cfg.eps) != 0) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 59 | }) |
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 | fn pure_ok(keys: &[&[Rect]], cfg: &Config) -> bool { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 63 | cfg.style == Style::Direct |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 64 | || keys.windows(2).all(|w| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 65 | w[0].iter() |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 66 | .zip(w[1]) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 67 | .all(|(a, b)| !Change::between(a, b).both()) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 68 | }) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 69 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 70 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 71 | fn valid(keys: &[&[Rect]], cfg: &Config) -> bool { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 72 | keys.windows(2).all(|w| phase_ok(w[0], w[1], cfg)) && pure_ok(keys, cfg) |
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 | fn conflict_set(from: &[Rect], to: &[Rect], cfg: &Config) -> Vec<usize> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 76 | let n = from.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 77 | let mut hit = vec![false; n]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 78 | for i in 0..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 79 | for j in i + 1..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 80 | if separation(&from[i], &from[j], cfg.eps) & separation(&to[i], &to[j], cfg.eps) == 0 { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 81 | hit[i] = true; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 82 | hit[j] = true; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 83 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 84 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 85 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 86 | (0..n).filter(|&i| hit[i]).collect() |
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 | /// Two phases. Each moving window takes one axis of its target in phase 1 and |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 90 | /// the other in phase 2. The choice per window is a boolean, every pair forbids |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 91 | /// some combinations, and the result is a 2-SAT instance. Neighbours that touch |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 92 | /// at both ends must also touch in the middle keyframe when that is possible. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 93 | fn axis_order( |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 94 | from: &[Rect], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 95 | to: &[Rect], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 96 | movers: &[usize], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 97 | cfg: &Config, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 98 | ) -> Option<(Vec<Rect>, bool)> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 99 | let n = from.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 100 | let mut var = vec![None; n]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 101 | for (v, &i) in movers.iter().enumerate() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 102 | var[i] = Some(v); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 103 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 104 | let options = |i: usize| -> Vec<(Option<bool>, Rect)> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 105 | if var[i].is_some() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 106 | vec![ |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 107 | (Some(false), Rect::mixed(&from[i], &to[i])), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 108 | (Some(true), Rect::mixed(&to[i], &from[i])), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 109 | ] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 110 | } else { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 111 | vec![(None, from[i])] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 112 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 113 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 114 | let preferred: Vec<bool> = movers |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 115 | .iter() |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 116 | .map(|&i| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 117 | let c = Change::between(&from[i], &to[i]); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 118 | c.x && !c.y |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 119 | }) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 120 | .collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 121 | for keep_edges in [true, false] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 122 | let mut sat = TwoSat::new(movers.len()); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 123 | let mut impossible = false; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 124 | for i in 0..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 125 | for j in i + 1..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 126 | if var[i].is_none() && var[j].is_none() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 127 | continue; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 128 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 129 | let sf = separation(&from[i], &from[j], cfg.eps); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 130 | let st = separation(&to[i], &to[j], cfg.eps); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 131 | let coupled = adjacency(&from[i], &from[j], cfg.gap, cfg.eps) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 132 | & adjacency(&to[i], &to[j], cfg.gap, cfg.eps); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 133 | for (ci, ri) in options(i) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 134 | for (cj, rj) in options(j) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 135 | let s = separation(&ri, &rj, cfg.eps); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 136 | let held = !keep_edges |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 137 | || adjacency(&ri, &rj, cfg.gap, cfg.eps) & coupled == coupled; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 138 | if sf & s != 0 && s & st != 0 && held { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 139 | continue; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 140 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 141 | match (ci, cj) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 142 | (Some(a), Some(b)) => { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 143 | sat.either(Lit::new(var[i]?, !a), Lit::new(var[j]?, !b)) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 144 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 145 | (Some(a), None) => sat.require(Lit::new(var[i]?, !a)), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 146 | (None, Some(b)) => sat.require(Lit::new(var[j]?, !b)), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 147 | (None, None) => impossible = true, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 148 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 149 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 150 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 151 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 152 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 153 | if impossible { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 154 | continue; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 155 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 156 | if let Some(choice) = sat.solve_preferring(&preferred) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 157 | let mut k1 = from.to_vec(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 158 | for (v, &i) in movers.iter().enumerate() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 159 | k1[i] = if choice[v] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 160 | Rect::mixed(&to[i], &from[i]) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 161 | } else { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 162 | Rect::mixed(&from[i], &to[i]) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 163 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 164 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 165 | let hole = opens_hole(from, to, &k1, cfg); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 166 | return Some((k1, hole)); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 167 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 168 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 169 | None |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 170 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 171 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 172 | fn opens_hole(from: &[Rect], to: &[Rect], mid: &[Rect], cfg: &Config) -> bool { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 173 | let n = from.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 174 | (0..n).any(|i| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 175 | (i + 1..n).any(|j| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 176 | let coupled = adjacency(&from[i], &from[j], cfg.gap, cfg.eps) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 177 | & adjacency(&to[i], &to[j], cfg.gap, cfg.eps); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 178 | adjacency(&mid[i], &mid[j], cfg.gap, cfg.eps) & coupled != coupled |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 179 | }) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 180 | }) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 181 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 182 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 183 | /// Three phases. Windows that pass each other shrink across the axis of travel |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 184 | /// into separate lanes, slide, then expand. Lanes span only the band the |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 185 | /// involved windows occupy. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 186 | fn lanes( |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 187 | from: &[Rect], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 188 | to: &[Rect], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 189 | movers: &[usize], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 190 | axis: Axis, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 191 | cfg: &Config, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 192 | ) -> Option<(Vec<Rect>, Vec<Rect>, usize)> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 193 | let n = from.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 194 | let (travel, before) = match axis { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 195 | Axis::Y => (X_BITS, ABOVE), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 196 | Axis::X => (Y_BITS, LEFT), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 197 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 198 | let span = |r: &Rect| match axis { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 199 | Axis::Y => (r.y, r.bottom()), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 200 | Axis::X => (r.x, r.right()), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 201 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 202 | for set in [ |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 203 | conflict_set(from, to, cfg), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 204 | movers.to_vec(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 205 | (0..n).collect(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 206 | ] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 207 | let m = set.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 208 | if m < 2 { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 209 | continue; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 210 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 211 | let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 212 | for &i in &set { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 213 | for r in [&from[i], &to[i]] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 214 | let (a, b) = span(r); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 215 | lo = lo.min(a); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 216 | hi = hi.max(b); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 217 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 218 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 219 | let mut diff = vec![vec![false; m]; m]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 220 | let mut lt = vec![vec![false; m]; m]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 221 | for a in 0..m { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 222 | for b in 0..m { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 223 | if a == b { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 224 | continue; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 225 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 226 | let f = separation(&from[set[a]], &from[set[b]], cfg.eps); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 227 | let t = separation(&to[set[a]], &to[set[b]], cfg.eps); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 228 | diff[a][b] = f & t & travel == 0; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 229 | lt[a][b] = [f, t].iter().any(|r| r & travel == 0 && r & before != 0); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 230 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 231 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 232 | let in_set: Vec<Option<usize>> = (0..n).map(|i| set.iter().position(|&s| s == i)).collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 233 | for k in 2..=m { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 234 | let size = (hi - lo - (k - 1) as f64 * cfg.gap) / k as f64; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 235 | if size < cfg.min_lane { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 236 | break; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 237 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 238 | let mut lane = vec![usize::MAX; m]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 239 | let mut budget = LANE_SEARCH_BUDGET; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 240 | if !assign_lanes(0, k, &mut lane, &diff, <, &mut budget) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 241 | continue; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 242 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 243 | let strip = |l: usize| lo + l as f64 * (size + cfg.gap); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 244 | let in_lane = |r: &Rect, l: usize| match axis { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 245 | Axis::Y => Rect::new(r.x, strip(l), r.w, size), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 246 | Axis::X => Rect::new(strip(l), r.y, size, r.h), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 247 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 248 | for phase in [2, 1, 3] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 249 | let mut k1 = Vec::with_capacity(n); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 250 | let mut k2 = Vec::with_capacity(n); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 251 | for i in 0..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 252 | let (f, t) = (from[i], to[i]); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 253 | let (a, b) = match (in_set[i], phase) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 254 | (Some(s), _) => (in_lane(&f, lane[s]), in_lane(&t, lane[s])), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 255 | (None, 1) => (t, t), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 256 | (None, 2) => (f, t), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 257 | (None, _) => (f, f), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 258 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 259 | k1.push(a); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 260 | k2.push(b); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 261 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 262 | if valid(&[from, &k1, &k2, to], cfg) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 263 | return Some((k1, k2, k)); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 264 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 265 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 266 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 267 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 268 | None |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 269 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 270 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 271 | fn assign_lanes( |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 272 | i: usize, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 273 | k: usize, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 274 | lane: &mut [usize], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 275 | diff: &[Vec<bool>], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 276 | lt: &[Vec<bool>], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 277 | budget: &mut usize, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 278 | ) -> bool { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 279 | if i == lane.len() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 280 | return true; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 281 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 282 | for l in 0..k { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 283 | if *budget == 0 { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 284 | return false; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 285 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 286 | *budget -= 1; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 287 | let fits = (0..i).all(|j| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 288 | !(diff[i][j] && lane[j] == l) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 289 | && !(lt[i][j] && l >= lane[j]) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 290 | && !(lt[j][i] && lane[j] >= l) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 291 | }); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 292 | if fits { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 293 | lane[i] = l; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 294 | if assign_lanes(i + 1, k, lane, diff, lt, budget) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 295 | return true; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 296 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 297 | lane[i] = usize::MAX; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 298 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 299 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 300 | false |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 301 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 302 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 303 | /// Four phases that work for any input. Involved windows shrink into rows |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 304 | /// ordered by where they start, then into columns ordered by where they end, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 305 | /// then move vertically to their target, then horizontally. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 306 | fn grid( |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 307 | from: &[Rect], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 308 | to: &[Rect], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 309 | movers: &[usize], |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 310 | cfg: &Config, |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 311 | ) -> Option<(Vec<Vec<Rect>>, usize)> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 312 | let n = from.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 313 | for set in [ |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 314 | conflict_set(from, to, cfg), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 315 | movers.to_vec(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 316 | (0..n).collect(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 317 | ] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 318 | let m = set.len(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 319 | if m < 2 { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 320 | continue; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 321 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 322 | let area = Rect::bounding(set.iter().flat_map(|&i| [&from[i], &to[i]])); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 323 | let gap = |len: f64| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 324 | if len - (m - 1) as f64 * cfg.gap > m as f64 { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 325 | cfg.gap |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 326 | } else { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 327 | 0.0 |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 328 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 329 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 330 | let (gh, gw) = (gap(area.h), gap(area.w)); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 331 | let sh = (area.h - (m - 1) as f64 * gh) / m as f64; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 332 | let sw = (area.w - (m - 1) as f64 * gw) / m as f64; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 333 | let mut by_row = set.clone(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 334 | by_row.sort_by(|&a, &b| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 335 | from[a] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 336 | .center_y() |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 337 | .total_cmp(&from[b].center_y()) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 338 | .then(from[a].center_x().total_cmp(&from[b].center_x())) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 339 | }); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 340 | let mut by_col = set.clone(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 341 | by_col.sort_by(|&a, &b| { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 342 | to[a] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 343 | .center_x() |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 344 | .total_cmp(&to[b].center_x()) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 345 | .then(to[a].center_y().total_cmp(&to[b].center_y())) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 346 | }); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 347 | let mut row = vec![0.0; n]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 348 | let mut col = vec![0.0; n]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 349 | for (r, &i) in by_row.iter().enumerate() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 350 | row[i] = area.y + r as f64 * (sh + gh); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 351 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 352 | for (c, &i) in by_col.iter().enumerate() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 353 | col[i] = area.x + c as f64 * (sw + gw); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 354 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 355 | let in_set: Vec<bool> = (0..n).map(|i| set.contains(&i)).collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 356 | for phase in [3, 2, 4, 1] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 357 | let mut keys = vec![ |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 358 | from.to_vec(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 359 | Vec::new(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 360 | Vec::new(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 361 | Vec::new(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 362 | to.to_vec(), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 363 | ]; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 364 | for i in 0..n { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 365 | let (f, t) = (from[i], to[i]); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 366 | let mid = if in_set[i] { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 367 | [ |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 368 | Rect::new(f.x, row[i], f.w, sh), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 369 | Rect::new(col[i], row[i], sw, sh), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 370 | Rect::new(col[i], t.y, sw, t.h), |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 371 | ] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 372 | } else { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 373 | [1, 2, 3].map(|k| if k >= phase { t } else { f }) |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 374 | }; |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 375 | for (k, r) in mid.into_iter().enumerate() { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 376 | keys[k + 1].push(r); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 377 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 378 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 379 | let refs: Vec<&[Rect]> = keys.iter().map(|k| k.as_slice()).collect(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 380 | if valid(&refs, cfg) { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 381 | return Some((keys, m)); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 382 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 383 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 384 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 385 | None |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 386 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 387 | |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 388 | /// Safety net. Movers shrink to a point, travel invisibly, and regrow. |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 389 | fn pinch(from: &[Rect], to: &[Rect], movers: &[usize]) -> Vec<Vec<Rect>> { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 390 | let mut k1 = from.to_vec(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 391 | let mut k2 = to.to_vec(); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 392 | for &i in movers { |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 393 | k1[i] = Rect::new(from[i].center_x(), from[i].center_y(), 0.0, 0.0); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 394 | k2[i] = Rect::new(to[i].center_x(), to[i].center_y(), 0.0, 0.0); |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 395 | } |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 396 | vec![from.to_vec(), k1, k2, to.to_vec()] |
b563d2da1c80 | atagen | 2026-10-16 08:25 UTC | 397 | } |