atagen

monolith

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

Blame: crates/planner/src/strategy.rs

b563d2da1c80atagen2026-10-16 08:25 UTC1
use crate::geom::{Change, Rect};
b563d2da1c80atagen2026-10-16 08:25 UTC2
use crate::plan::{Config, GroupPlan, Strategy, Style};
b563d2da1c80atagen2026-10-16 08:25 UTC3
use crate::relation::{ABOVE, LEFT, X_BITS, Y_BITS, adjacency, separation};
b563d2da1c80atagen2026-10-16 08:25 UTC4
use crate::sat::{Lit, TwoSat};
b563d2da1c80atagen2026-10-16 08:25 UTC5
b563d2da1c80atagen2026-10-16 08:25 UTC6
/// Upper bound on lane-assignment search steps before giving up on a lane count.
b563d2da1c80atagen2026-10-16 08:25 UTC7
const LANE_SEARCH_BUDGET: usize = 20_000;
b563d2da1c80atagen2026-10-16 08:25 UTC8
b563d2da1c80atagen2026-10-16 08:25 UTC9
#[derive(Clone, Copy, PartialEq, Eq)]
b563d2da1c80atagen2026-10-16 08:25 UTC10
enum Axis {
b563d2da1c80atagen2026-10-16 08:25 UTC11
    X,
b563d2da1c80atagen2026-10-16 08:25 UTC12
    Y,
b563d2da1c80atagen2026-10-16 08:25 UTC13
}
b563d2da1c80atagen2026-10-16 08:25 UTC14
b563d2da1c80atagen2026-10-16 08:25 UTC15
pub(crate) fn plan_group(from: &[Rect], to: &[Rect], cfg: &Config) -> GroupPlan {
b563d2da1c80atagen2026-10-16 08:25 UTC16
    let done = |keys: Vec<Vec<Rect>>, strategy| GroupPlan {
b563d2da1c80atagen2026-10-16 08:25 UTC17
        keys,
b563d2da1c80atagen2026-10-16 08:25 UTC18
        strategy,
b563d2da1c80atagen2026-10-16 08:25 UTC19
        opens_hole: false,
b563d2da1c80atagen2026-10-16 08:25 UTC20
    };
b563d2da1c80atagen2026-10-16 08:25 UTC21
    let movers: Vec<usize> = (0..from.len())
b563d2da1c80atagen2026-10-16 08:25 UTC22
        .filter(|&i| Change::between(&from[i], &to[i]).moves())
b563d2da1c80atagen2026-10-16 08:25 UTC23
        .collect();
b563d2da1c80atagen2026-10-16 08:25 UTC24
    if movers.is_empty() {
b563d2da1c80atagen2026-10-16 08:25 UTC25
        return done(vec![from.to_vec()], Strategy::Unchanged);
b563d2da1c80atagen2026-10-16 08:25 UTC26
    }
b563d2da1c80atagen2026-10-16 08:25 UTC27
    let pure = movers
b563d2da1c80atagen2026-10-16 08:25 UTC28
        .iter()
b563d2da1c80atagen2026-10-16 08:25 UTC29
        .all(|&i| !Change::between(&from[i], &to[i]).both());
b563d2da1c80atagen2026-10-16 08:25 UTC30
    if phase_ok(from, to, cfg) && (cfg.style == Style::Direct || pure) {
b563d2da1c80atagen2026-10-16 08:25 UTC31
        return done(vec![from.to_vec(), to.to_vec()], Strategy::Direct);
b563d2da1c80atagen2026-10-16 08:25 UTC32
    }
b563d2da1c80atagen2026-10-16 08:25 UTC33
    if let Some((k1, opens_hole)) = axis_order(from, to, &movers, cfg) {
b563d2da1c80atagen2026-10-16 08:25 UTC34
        return GroupPlan {
b563d2da1c80atagen2026-10-16 08:25 UTC35
            keys: vec![from.to_vec(), k1, to.to_vec()],
b563d2da1c80atagen2026-10-16 08:25 UTC36
            strategy: Strategy::AxisOrder,
b563d2da1c80atagen2026-10-16 08:25 UTC37
            opens_hole,
b563d2da1c80atagen2026-10-16 08:25 UTC38
        };
b563d2da1c80atagen2026-10-16 08:25 UTC39
    }
b563d2da1c80atagen2026-10-16 08:25 UTC40
    for axis in [Axis::Y, Axis::X] {
b563d2da1c80atagen2026-10-16 08:25 UTC41
        if let Some((k1, k2, lanes)) = lanes(from, to, &movers, axis, cfg) {
b563d2da1c80atagen2026-10-16 08:25 UTC42
            return done(
b563d2da1c80atagen2026-10-16 08:25 UTC43
                vec![from.to_vec(), k1, k2, to.to_vec()],
b563d2da1c80atagen2026-10-16 08:25 UTC44
                Strategy::Lanes { lanes },
b563d2da1c80atagen2026-10-16 08:25 UTC45
            );
b563d2da1c80atagen2026-10-16 08:25 UTC46
        }
b563d2da1c80atagen2026-10-16 08:25 UTC47
    }
b563d2da1c80atagen2026-10-16 08:25 UTC48
    if let Some((keys, cells)) = grid(from, to, &movers, cfg) {
b563d2da1c80atagen2026-10-16 08:25 UTC49
        return done(keys, Strategy::Grid { cells });
b563d2da1c80atagen2026-10-16 08:25 UTC50
    }
b563d2da1c80atagen2026-10-16 08:25 UTC51
    done(pinch(from, to, &movers), Strategy::Pinch)
b563d2da1c80atagen2026-10-16 08:25 UTC52
}
b563d2da1c80atagen2026-10-16 08:25 UTC53
b563d2da1c80atagen2026-10-16 08:25 UTC54
/// Every pair shares a separating relation at both ends of the phase.
b563d2da1c80atagen2026-10-16 08:25 UTC55
pub(crate) fn phase_ok(a: &[Rect], b: &[Rect], cfg: &Config) -> bool {
b563d2da1c80atagen2026-10-16 08:25 UTC56
    (0..a.len()).all(|i| {
b563d2da1c80atagen2026-10-16 08:25 UTC57
        (i + 1..a.len())
b563d2da1c80atagen2026-10-16 08:25 UTC58
            .all(|j| separation(&a[i], &a[j], cfg.eps) & separation(&b[i], &b[j], cfg.eps) != 0)
b563d2da1c80atagen2026-10-16 08:25 UTC59
    })
b563d2da1c80atagen2026-10-16 08:25 UTC60
}
b563d2da1c80atagen2026-10-16 08:25 UTC61
b563d2da1c80atagen2026-10-16 08:25 UTC62
fn pure_ok(keys: &[&[Rect]], cfg: &Config) -> bool {
b563d2da1c80atagen2026-10-16 08:25 UTC63
    cfg.style == Style::Direct
b563d2da1c80atagen2026-10-16 08:25 UTC64
        || keys.windows(2).all(|w| {
b563d2da1c80atagen2026-10-16 08:25 UTC65
            w[0].iter()
b563d2da1c80atagen2026-10-16 08:25 UTC66
                .zip(w[1])
b563d2da1c80atagen2026-10-16 08:25 UTC67
                .all(|(a, b)| !Change::between(a, b).both())
b563d2da1c80atagen2026-10-16 08:25 UTC68
        })
b563d2da1c80atagen2026-10-16 08:25 UTC69
}
b563d2da1c80atagen2026-10-16 08:25 UTC70
b563d2da1c80atagen2026-10-16 08:25 UTC71
fn valid(keys: &[&[Rect]], cfg: &Config) -> bool {
b563d2da1c80atagen2026-10-16 08:25 UTC72
    keys.windows(2).all(|w| phase_ok(w[0], w[1], cfg)) && pure_ok(keys, cfg)
b563d2da1c80atagen2026-10-16 08:25 UTC73
}
b563d2da1c80atagen2026-10-16 08:25 UTC74
b563d2da1c80atagen2026-10-16 08:25 UTC75
fn conflict_set(from: &[Rect], to: &[Rect], cfg: &Config) -> Vec<usize> {
b563d2da1c80atagen2026-10-16 08:25 UTC76
    let n = from.len();
b563d2da1c80atagen2026-10-16 08:25 UTC77
    let mut hit = vec![false; n];
b563d2da1c80atagen2026-10-16 08:25 UTC78
    for i in 0..n {
b563d2da1c80atagen2026-10-16 08:25 UTC79
        for j in i + 1..n {
b563d2da1c80atagen2026-10-16 08:25 UTC80
            if separation(&from[i], &from[j], cfg.eps) & separation(&to[i], &to[j], cfg.eps) == 0 {
b563d2da1c80atagen2026-10-16 08:25 UTC81
                hit[i] = true;
b563d2da1c80atagen2026-10-16 08:25 UTC82
                hit[j] = true;
b563d2da1c80atagen2026-10-16 08:25 UTC83
            }
b563d2da1c80atagen2026-10-16 08:25 UTC84
        }
b563d2da1c80atagen2026-10-16 08:25 UTC85
    }
b563d2da1c80atagen2026-10-16 08:25 UTC86
    (0..n).filter(|&i| hit[i]).collect()
b563d2da1c80atagen2026-10-16 08:25 UTC87
}
b563d2da1c80atagen2026-10-16 08:25 UTC88
b563d2da1c80atagen2026-10-16 08:25 UTC89
/// Two phases. Each moving window takes one axis of its target in phase 1 and
b563d2da1c80atagen2026-10-16 08:25 UTC90
/// the other in phase 2. The choice per window is a boolean, every pair forbids
b563d2da1c80atagen2026-10-16 08:25 UTC91
/// some combinations, and the result is a 2-SAT instance. Neighbours that touch
b563d2da1c80atagen2026-10-16 08:25 UTC92
/// at both ends must also touch in the middle keyframe when that is possible.
b563d2da1c80atagen2026-10-16 08:25 UTC93
fn axis_order(
b563d2da1c80atagen2026-10-16 08:25 UTC94
    from: &[Rect],
b563d2da1c80atagen2026-10-16 08:25 UTC95
    to: &[Rect],
b563d2da1c80atagen2026-10-16 08:25 UTC96
    movers: &[usize],
b563d2da1c80atagen2026-10-16 08:25 UTC97
    cfg: &Config,
b563d2da1c80atagen2026-10-16 08:25 UTC98
) -> Option<(Vec<Rect>, bool)> {
b563d2da1c80atagen2026-10-16 08:25 UTC99
    let n = from.len();
b563d2da1c80atagen2026-10-16 08:25 UTC100
    let mut var = vec![None; n];
b563d2da1c80atagen2026-10-16 08:25 UTC101
    for (v, &i) in movers.iter().enumerate() {
b563d2da1c80atagen2026-10-16 08:25 UTC102
        var[i] = Some(v);
b563d2da1c80atagen2026-10-16 08:25 UTC103
    }
b563d2da1c80atagen2026-10-16 08:25 UTC104
    let options = |i: usize| -> Vec<(Option<bool>, Rect)> {
b563d2da1c80atagen2026-10-16 08:25 UTC105
        if var[i].is_some() {
b563d2da1c80atagen2026-10-16 08:25 UTC106
            vec![
b563d2da1c80atagen2026-10-16 08:25 UTC107
                (Some(false), Rect::mixed(&from[i], &to[i])),
b563d2da1c80atagen2026-10-16 08:25 UTC108
                (Some(true), Rect::mixed(&to[i], &from[i])),
b563d2da1c80atagen2026-10-16 08:25 UTC109
            ]
b563d2da1c80atagen2026-10-16 08:25 UTC110
        } else {
b563d2da1c80atagen2026-10-16 08:25 UTC111
            vec![(None, from[i])]
b563d2da1c80atagen2026-10-16 08:25 UTC112
        }
b563d2da1c80atagen2026-10-16 08:25 UTC113
    };
b563d2da1c80atagen2026-10-16 08:25 UTC114
    let preferred: Vec<bool> = movers
b563d2da1c80atagen2026-10-16 08:25 UTC115
        .iter()
b563d2da1c80atagen2026-10-16 08:25 UTC116
        .map(|&i| {
b563d2da1c80atagen2026-10-16 08:25 UTC117
            let c = Change::between(&from[i], &to[i]);
b563d2da1c80atagen2026-10-16 08:25 UTC118
            c.x && !c.y
b563d2da1c80atagen2026-10-16 08:25 UTC119
        })
b563d2da1c80atagen2026-10-16 08:25 UTC120
        .collect();
b563d2da1c80atagen2026-10-16 08:25 UTC121
    for keep_edges in [true, false] {
b563d2da1c80atagen2026-10-16 08:25 UTC122
        let mut sat = TwoSat::new(movers.len());
b563d2da1c80atagen2026-10-16 08:25 UTC123
        let mut impossible = false;
b563d2da1c80atagen2026-10-16 08:25 UTC124
        for i in 0..n {
b563d2da1c80atagen2026-10-16 08:25 UTC125
            for j in i + 1..n {
b563d2da1c80atagen2026-10-16 08:25 UTC126
                if var[i].is_none() && var[j].is_none() {
b563d2da1c80atagen2026-10-16 08:25 UTC127
                    continue;
b563d2da1c80atagen2026-10-16 08:25 UTC128
                }
b563d2da1c80atagen2026-10-16 08:25 UTC129
                let sf = separation(&from[i], &from[j], cfg.eps);
b563d2da1c80atagen2026-10-16 08:25 UTC130
                let st = separation(&to[i], &to[j], cfg.eps);
b563d2da1c80atagen2026-10-16 08:25 UTC131
                let coupled = adjacency(&from[i], &from[j], cfg.gap, cfg.eps)
b563d2da1c80atagen2026-10-16 08:25 UTC132
                    & adjacency(&to[i], &to[j], cfg.gap, cfg.eps);
b563d2da1c80atagen2026-10-16 08:25 UTC133
                for (ci, ri) in options(i) {
b563d2da1c80atagen2026-10-16 08:25 UTC134
                    for (cj, rj) in options(j) {
b563d2da1c80atagen2026-10-16 08:25 UTC135
                        let s = separation(&ri, &rj, cfg.eps);
b563d2da1c80atagen2026-10-16 08:25 UTC136
                        let held = !keep_edges
b563d2da1c80atagen2026-10-16 08:25 UTC137
                            || adjacency(&ri, &rj, cfg.gap, cfg.eps) & coupled == coupled;
b563d2da1c80atagen2026-10-16 08:25 UTC138
                        if sf & s != 0 && s & st != 0 && held {
b563d2da1c80atagen2026-10-16 08:25 UTC139
                            continue;
b563d2da1c80atagen2026-10-16 08:25 UTC140
                        }
b563d2da1c80atagen2026-10-16 08:25 UTC141
                        match (ci, cj) {
b563d2da1c80atagen2026-10-16 08:25 UTC142
                            (Some(a), Some(b)) => {
b563d2da1c80atagen2026-10-16 08:25 UTC143
                                sat.either(Lit::new(var[i]?, !a), Lit::new(var[j]?, !b))
b563d2da1c80atagen2026-10-16 08:25 UTC144
                            }
b563d2da1c80atagen2026-10-16 08:25 UTC145
                            (Some(a), None) => sat.require(Lit::new(var[i]?, !a)),
b563d2da1c80atagen2026-10-16 08:25 UTC146
                            (None, Some(b)) => sat.require(Lit::new(var[j]?, !b)),
b563d2da1c80atagen2026-10-16 08:25 UTC147
                            (None, None) => impossible = true,
b563d2da1c80atagen2026-10-16 08:25 UTC148
                        }
b563d2da1c80atagen2026-10-16 08:25 UTC149
                    }
b563d2da1c80atagen2026-10-16 08:25 UTC150
                }
b563d2da1c80atagen2026-10-16 08:25 UTC151
            }
b563d2da1c80atagen2026-10-16 08:25 UTC152
        }
b563d2da1c80atagen2026-10-16 08:25 UTC153
        if impossible {
b563d2da1c80atagen2026-10-16 08:25 UTC154
            continue;
b563d2da1c80atagen2026-10-16 08:25 UTC155
        }
b563d2da1c80atagen2026-10-16 08:25 UTC156
        if let Some(choice) = sat.solve_preferring(&preferred) {
b563d2da1c80atagen2026-10-16 08:25 UTC157
            let mut k1 = from.to_vec();
b563d2da1c80atagen2026-10-16 08:25 UTC158
            for (v, &i) in movers.iter().enumerate() {
b563d2da1c80atagen2026-10-16 08:25 UTC159
                k1[i] = if choice[v] {
b563d2da1c80atagen2026-10-16 08:25 UTC160
                    Rect::mixed(&to[i], &from[i])
b563d2da1c80atagen2026-10-16 08:25 UTC161
                } else {
b563d2da1c80atagen2026-10-16 08:25 UTC162
                    Rect::mixed(&from[i], &to[i])
b563d2da1c80atagen2026-10-16 08:25 UTC163
                };
b563d2da1c80atagen2026-10-16 08:25 UTC164
            }
b563d2da1c80atagen2026-10-16 08:25 UTC165
            let hole = opens_hole(from, to, &k1, cfg);
b563d2da1c80atagen2026-10-16 08:25 UTC166
            return Some((k1, hole));
b563d2da1c80atagen2026-10-16 08:25 UTC167
        }
b563d2da1c80atagen2026-10-16 08:25 UTC168
    }
b563d2da1c80atagen2026-10-16 08:25 UTC169
    None
b563d2da1c80atagen2026-10-16 08:25 UTC170
}
b563d2da1c80atagen2026-10-16 08:25 UTC171
b563d2da1c80atagen2026-10-16 08:25 UTC172
fn opens_hole(from: &[Rect], to: &[Rect], mid: &[Rect], cfg: &Config) -> bool {
b563d2da1c80atagen2026-10-16 08:25 UTC173
    let n = from.len();
b563d2da1c80atagen2026-10-16 08:25 UTC174
    (0..n).any(|i| {
b563d2da1c80atagen2026-10-16 08:25 UTC175
        (i + 1..n).any(|j| {
b563d2da1c80atagen2026-10-16 08:25 UTC176
            let coupled = adjacency(&from[i], &from[j], cfg.gap, cfg.eps)
b563d2da1c80atagen2026-10-16 08:25 UTC177
                & adjacency(&to[i], &to[j], cfg.gap, cfg.eps);
b563d2da1c80atagen2026-10-16 08:25 UTC178
            adjacency(&mid[i], &mid[j], cfg.gap, cfg.eps) & coupled != coupled
b563d2da1c80atagen2026-10-16 08:25 UTC179
        })
b563d2da1c80atagen2026-10-16 08:25 UTC180
    })
b563d2da1c80atagen2026-10-16 08:25 UTC181
}
b563d2da1c80atagen2026-10-16 08:25 UTC182
b563d2da1c80atagen2026-10-16 08:25 UTC183
/// Three phases. Windows that pass each other shrink across the axis of travel
b563d2da1c80atagen2026-10-16 08:25 UTC184
/// into separate lanes, slide, then expand. Lanes span only the band the
b563d2da1c80atagen2026-10-16 08:25 UTC185
/// involved windows occupy.
b563d2da1c80atagen2026-10-16 08:25 UTC186
fn lanes(
b563d2da1c80atagen2026-10-16 08:25 UTC187
    from: &[Rect],
b563d2da1c80atagen2026-10-16 08:25 UTC188
    to: &[Rect],
b563d2da1c80atagen2026-10-16 08:25 UTC189
    movers: &[usize],
b563d2da1c80atagen2026-10-16 08:25 UTC190
    axis: Axis,
b563d2da1c80atagen2026-10-16 08:25 UTC191
    cfg: &Config,
b563d2da1c80atagen2026-10-16 08:25 UTC192
) -> Option<(Vec<Rect>, Vec<Rect>, usize)> {
b563d2da1c80atagen2026-10-16 08:25 UTC193
    let n = from.len();
b563d2da1c80atagen2026-10-16 08:25 UTC194
    let (travel, before) = match axis {
b563d2da1c80atagen2026-10-16 08:25 UTC195
        Axis::Y => (X_BITS, ABOVE),
b563d2da1c80atagen2026-10-16 08:25 UTC196
        Axis::X => (Y_BITS, LEFT),
b563d2da1c80atagen2026-10-16 08:25 UTC197
    };
b563d2da1c80atagen2026-10-16 08:25 UTC198
    let span = |r: &Rect| match axis {
b563d2da1c80atagen2026-10-16 08:25 UTC199
        Axis::Y => (r.y, r.bottom()),
b563d2da1c80atagen2026-10-16 08:25 UTC200
        Axis::X => (r.x, r.right()),
b563d2da1c80atagen2026-10-16 08:25 UTC201
    };
b563d2da1c80atagen2026-10-16 08:25 UTC202
    for set in [
b563d2da1c80atagen2026-10-16 08:25 UTC203
        conflict_set(from, to, cfg),
b563d2da1c80atagen2026-10-16 08:25 UTC204
        movers.to_vec(),
b563d2da1c80atagen2026-10-16 08:25 UTC205
        (0..n).collect(),
b563d2da1c80atagen2026-10-16 08:25 UTC206
    ] {
b563d2da1c80atagen2026-10-16 08:25 UTC207
        let m = set.len();
b563d2da1c80atagen2026-10-16 08:25 UTC208
        if m < 2 {
b563d2da1c80atagen2026-10-16 08:25 UTC209
            continue;
b563d2da1c80atagen2026-10-16 08:25 UTC210
        }
b563d2da1c80atagen2026-10-16 08:25 UTC211
        let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
b563d2da1c80atagen2026-10-16 08:25 UTC212
        for &i in &set {
b563d2da1c80atagen2026-10-16 08:25 UTC213
            for r in [&from[i], &to[i]] {
b563d2da1c80atagen2026-10-16 08:25 UTC214
                let (a, b) = span(r);
b563d2da1c80atagen2026-10-16 08:25 UTC215
                lo = lo.min(a);
b563d2da1c80atagen2026-10-16 08:25 UTC216
                hi = hi.max(b);
b563d2da1c80atagen2026-10-16 08:25 UTC217
            }
b563d2da1c80atagen2026-10-16 08:25 UTC218
        }
b563d2da1c80atagen2026-10-16 08:25 UTC219
        let mut diff = vec![vec![false; m]; m];
b563d2da1c80atagen2026-10-16 08:25 UTC220
        let mut lt = vec![vec![false; m]; m];
b563d2da1c80atagen2026-10-16 08:25 UTC221
        for a in 0..m {
b563d2da1c80atagen2026-10-16 08:25 UTC222
            for b in 0..m {
b563d2da1c80atagen2026-10-16 08:25 UTC223
                if a == b {
b563d2da1c80atagen2026-10-16 08:25 UTC224
                    continue;
b563d2da1c80atagen2026-10-16 08:25 UTC225
                }
b563d2da1c80atagen2026-10-16 08:25 UTC226
                let f = separation(&from[set[a]], &from[set[b]], cfg.eps);
b563d2da1c80atagen2026-10-16 08:25 UTC227
                let t = separation(&to[set[a]], &to[set[b]], cfg.eps);
b563d2da1c80atagen2026-10-16 08:25 UTC228
                diff[a][b] = f & t & travel == 0;
b563d2da1c80atagen2026-10-16 08:25 UTC229
                lt[a][b] = [f, t].iter().any(|r| r & travel == 0 && r & before != 0);
b563d2da1c80atagen2026-10-16 08:25 UTC230
            }
b563d2da1c80atagen2026-10-16 08:25 UTC231
        }
b563d2da1c80atagen2026-10-16 08:25 UTC232
        let in_set: Vec<Option<usize>> = (0..n).map(|i| set.iter().position(|&s| s == i)).collect();
b563d2da1c80atagen2026-10-16 08:25 UTC233
        for k in 2..=m {
b563d2da1c80atagen2026-10-16 08:25 UTC234
            let size = (hi - lo - (k - 1) as f64 * cfg.gap) / k as f64;
b563d2da1c80atagen2026-10-16 08:25 UTC235
            if size < cfg.min_lane {
b563d2da1c80atagen2026-10-16 08:25 UTC236
                break;
b563d2da1c80atagen2026-10-16 08:25 UTC237
            }
b563d2da1c80atagen2026-10-16 08:25 UTC238
            let mut lane = vec![usize::MAX; m];
b563d2da1c80atagen2026-10-16 08:25 UTC239
            let mut budget = LANE_SEARCH_BUDGET;
b563d2da1c80atagen2026-10-16 08:25 UTC240
            if !assign_lanes(0, k, &mut lane, &diff, &lt, &mut budget) {
b563d2da1c80atagen2026-10-16 08:25 UTC241
                continue;
b563d2da1c80atagen2026-10-16 08:25 UTC242
            }
b563d2da1c80atagen2026-10-16 08:25 UTC243
            let strip = |l: usize| lo + l as f64 * (size + cfg.gap);
b563d2da1c80atagen2026-10-16 08:25 UTC244
            let in_lane = |r: &Rect, l: usize| match axis {
b563d2da1c80atagen2026-10-16 08:25 UTC245
                Axis::Y => Rect::new(r.x, strip(l), r.w, size),
b563d2da1c80atagen2026-10-16 08:25 UTC246
                Axis::X => Rect::new(strip(l), r.y, size, r.h),
b563d2da1c80atagen2026-10-16 08:25 UTC247
            };
b563d2da1c80atagen2026-10-16 08:25 UTC248
            for phase in [2, 1, 3] {
b563d2da1c80atagen2026-10-16 08:25 UTC249
                let mut k1 = Vec::with_capacity(n);
b563d2da1c80atagen2026-10-16 08:25 UTC250
                let mut k2 = Vec::with_capacity(n);
b563d2da1c80atagen2026-10-16 08:25 UTC251
                for i in 0..n {
b563d2da1c80atagen2026-10-16 08:25 UTC252
                    let (f, t) = (from[i], to[i]);
b563d2da1c80atagen2026-10-16 08:25 UTC253
                    let (a, b) = match (in_set[i], phase) {
b563d2da1c80atagen2026-10-16 08:25 UTC254
                        (Some(s), _) => (in_lane(&f, lane[s]), in_lane(&t, lane[s])),
b563d2da1c80atagen2026-10-16 08:25 UTC255
                        (None, 1) => (t, t),
b563d2da1c80atagen2026-10-16 08:25 UTC256
                        (None, 2) => (f, t),
b563d2da1c80atagen2026-10-16 08:25 UTC257
                        (None, _) => (f, f),
b563d2da1c80atagen2026-10-16 08:25 UTC258
                    };
b563d2da1c80atagen2026-10-16 08:25 UTC259
                    k1.push(a);
b563d2da1c80atagen2026-10-16 08:25 UTC260
                    k2.push(b);
b563d2da1c80atagen2026-10-16 08:25 UTC261
                }
b563d2da1c80atagen2026-10-16 08:25 UTC262
                if valid(&[from, &k1, &k2, to], cfg) {
b563d2da1c80atagen2026-10-16 08:25 UTC263
                    return Some((k1, k2, k));
b563d2da1c80atagen2026-10-16 08:25 UTC264
                }
b563d2da1c80atagen2026-10-16 08:25 UTC265
            }
b563d2da1c80atagen2026-10-16 08:25 UTC266
        }
b563d2da1c80atagen2026-10-16 08:25 UTC267
    }
b563d2da1c80atagen2026-10-16 08:25 UTC268
    None
b563d2da1c80atagen2026-10-16 08:25 UTC269
}
b563d2da1c80atagen2026-10-16 08:25 UTC270
b563d2da1c80atagen2026-10-16 08:25 UTC271
fn assign_lanes(
b563d2da1c80atagen2026-10-16 08:25 UTC272
    i: usize,
b563d2da1c80atagen2026-10-16 08:25 UTC273
    k: usize,
b563d2da1c80atagen2026-10-16 08:25 UTC274
    lane: &mut [usize],
b563d2da1c80atagen2026-10-16 08:25 UTC275
    diff: &[Vec<bool>],
b563d2da1c80atagen2026-10-16 08:25 UTC276
    lt: &[Vec<bool>],
b563d2da1c80atagen2026-10-16 08:25 UTC277
    budget: &mut usize,
b563d2da1c80atagen2026-10-16 08:25 UTC278
) -> bool {
b563d2da1c80atagen2026-10-16 08:25 UTC279
    if i == lane.len() {
b563d2da1c80atagen2026-10-16 08:25 UTC280
        return true;
b563d2da1c80atagen2026-10-16 08:25 UTC281
    }
b563d2da1c80atagen2026-10-16 08:25 UTC282
    for l in 0..k {
b563d2da1c80atagen2026-10-16 08:25 UTC283
        if *budget == 0 {
b563d2da1c80atagen2026-10-16 08:25 UTC284
            return false;
b563d2da1c80atagen2026-10-16 08:25 UTC285
        }
b563d2da1c80atagen2026-10-16 08:25 UTC286
        *budget -= 1;
b563d2da1c80atagen2026-10-16 08:25 UTC287
        let fits = (0..i).all(|j| {
b563d2da1c80atagen2026-10-16 08:25 UTC288
            !(diff[i][j] && lane[j] == l)
b563d2da1c80atagen2026-10-16 08:25 UTC289
                && !(lt[i][j] && l >= lane[j])
b563d2da1c80atagen2026-10-16 08:25 UTC290
                && !(lt[j][i] && lane[j] >= l)
b563d2da1c80atagen2026-10-16 08:25 UTC291
        });
b563d2da1c80atagen2026-10-16 08:25 UTC292
        if fits {
b563d2da1c80atagen2026-10-16 08:25 UTC293
            lane[i] = l;
b563d2da1c80atagen2026-10-16 08:25 UTC294
            if assign_lanes(i + 1, k, lane, diff, lt, budget) {
b563d2da1c80atagen2026-10-16 08:25 UTC295
                return true;
b563d2da1c80atagen2026-10-16 08:25 UTC296
            }
b563d2da1c80atagen2026-10-16 08:25 UTC297
            lane[i] = usize::MAX;
b563d2da1c80atagen2026-10-16 08:25 UTC298
        }
b563d2da1c80atagen2026-10-16 08:25 UTC299
    }
b563d2da1c80atagen2026-10-16 08:25 UTC300
    false
b563d2da1c80atagen2026-10-16 08:25 UTC301
}
b563d2da1c80atagen2026-10-16 08:25 UTC302
b563d2da1c80atagen2026-10-16 08:25 UTC303
/// Four phases that work for any input. Involved windows shrink into rows
b563d2da1c80atagen2026-10-16 08:25 UTC304
/// ordered by where they start, then into columns ordered by where they end,
b563d2da1c80atagen2026-10-16 08:25 UTC305
/// then move vertically to their target, then horizontally.
b563d2da1c80atagen2026-10-16 08:25 UTC306
fn grid(
b563d2da1c80atagen2026-10-16 08:25 UTC307
    from: &[Rect],
b563d2da1c80atagen2026-10-16 08:25 UTC308
    to: &[Rect],
b563d2da1c80atagen2026-10-16 08:25 UTC309
    movers: &[usize],
b563d2da1c80atagen2026-10-16 08:25 UTC310
    cfg: &Config,
b563d2da1c80atagen2026-10-16 08:25 UTC311
) -> Option<(Vec<Vec<Rect>>, usize)> {
b563d2da1c80atagen2026-10-16 08:25 UTC312
    let n = from.len();
b563d2da1c80atagen2026-10-16 08:25 UTC313
    for set in [
b563d2da1c80atagen2026-10-16 08:25 UTC314
        conflict_set(from, to, cfg),
b563d2da1c80atagen2026-10-16 08:25 UTC315
        movers.to_vec(),
b563d2da1c80atagen2026-10-16 08:25 UTC316
        (0..n).collect(),
b563d2da1c80atagen2026-10-16 08:25 UTC317
    ] {
b563d2da1c80atagen2026-10-16 08:25 UTC318
        let m = set.len();
b563d2da1c80atagen2026-10-16 08:25 UTC319
        if m < 2 {
b563d2da1c80atagen2026-10-16 08:25 UTC320
            continue;
b563d2da1c80atagen2026-10-16 08:25 UTC321
        }
b563d2da1c80atagen2026-10-16 08:25 UTC322
        let area = Rect::bounding(set.iter().flat_map(|&i| [&from[i], &to[i]]));
b563d2da1c80atagen2026-10-16 08:25 UTC323
        let gap = |len: f64| {
b563d2da1c80atagen2026-10-16 08:25 UTC324
            if len - (m - 1) as f64 * cfg.gap > m as f64 {
b563d2da1c80atagen2026-10-16 08:25 UTC325
                cfg.gap
b563d2da1c80atagen2026-10-16 08:25 UTC326
            } else {
b563d2da1c80atagen2026-10-16 08:25 UTC327
                0.0
b563d2da1c80atagen2026-10-16 08:25 UTC328
            }
b563d2da1c80atagen2026-10-16 08:25 UTC329
        };
b563d2da1c80atagen2026-10-16 08:25 UTC330
        let (gh, gw) = (gap(area.h), gap(area.w));
b563d2da1c80atagen2026-10-16 08:25 UTC331
        let sh = (area.h - (m - 1) as f64 * gh) / m as f64;
b563d2da1c80atagen2026-10-16 08:25 UTC332
        let sw = (area.w - (m - 1) as f64 * gw) / m as f64;
b563d2da1c80atagen2026-10-16 08:25 UTC333
        let mut by_row = set.clone();
b563d2da1c80atagen2026-10-16 08:25 UTC334
        by_row.sort_by(|&a, &b| {
b563d2da1c80atagen2026-10-16 08:25 UTC335
            from[a]
b563d2da1c80atagen2026-10-16 08:25 UTC336
                .center_y()
b563d2da1c80atagen2026-10-16 08:25 UTC337
                .total_cmp(&from[b].center_y())
b563d2da1c80atagen2026-10-16 08:25 UTC338
                .then(from[a].center_x().total_cmp(&from[b].center_x()))
b563d2da1c80atagen2026-10-16 08:25 UTC339
        });
b563d2da1c80atagen2026-10-16 08:25 UTC340
        let mut by_col = set.clone();
b563d2da1c80atagen2026-10-16 08:25 UTC341
        by_col.sort_by(|&a, &b| {
b563d2da1c80atagen2026-10-16 08:25 UTC342
            to[a]
b563d2da1c80atagen2026-10-16 08:25 UTC343
                .center_x()
b563d2da1c80atagen2026-10-16 08:25 UTC344
                .total_cmp(&to[b].center_x())
b563d2da1c80atagen2026-10-16 08:25 UTC345
                .then(to[a].center_y().total_cmp(&to[b].center_y()))
b563d2da1c80atagen2026-10-16 08:25 UTC346
        });
b563d2da1c80atagen2026-10-16 08:25 UTC347
        let mut row = vec![0.0; n];
b563d2da1c80atagen2026-10-16 08:25 UTC348
        let mut col = vec![0.0; n];
b563d2da1c80atagen2026-10-16 08:25 UTC349
        for (r, &i) in by_row.iter().enumerate() {
b563d2da1c80atagen2026-10-16 08:25 UTC350
            row[i] = area.y + r as f64 * (sh + gh);
b563d2da1c80atagen2026-10-16 08:25 UTC351
        }
b563d2da1c80atagen2026-10-16 08:25 UTC352
        for (c, &i) in by_col.iter().enumerate() {
b563d2da1c80atagen2026-10-16 08:25 UTC353
            col[i] = area.x + c as f64 * (sw + gw);
b563d2da1c80atagen2026-10-16 08:25 UTC354
        }
b563d2da1c80atagen2026-10-16 08:25 UTC355
        let in_set: Vec<bool> = (0..n).map(|i| set.contains(&i)).collect();
b563d2da1c80atagen2026-10-16 08:25 UTC356
        for phase in [3, 2, 4, 1] {
b563d2da1c80atagen2026-10-16 08:25 UTC357
            let mut keys = vec![
b563d2da1c80atagen2026-10-16 08:25 UTC358
                from.to_vec(),
b563d2da1c80atagen2026-10-16 08:25 UTC359
                Vec::new(),
b563d2da1c80atagen2026-10-16 08:25 UTC360
                Vec::new(),
b563d2da1c80atagen2026-10-16 08:25 UTC361
                Vec::new(),
b563d2da1c80atagen2026-10-16 08:25 UTC362
                to.to_vec(),
b563d2da1c80atagen2026-10-16 08:25 UTC363
            ];
b563d2da1c80atagen2026-10-16 08:25 UTC364
            for i in 0..n {
b563d2da1c80atagen2026-10-16 08:25 UTC365
                let (f, t) = (from[i], to[i]);
b563d2da1c80atagen2026-10-16 08:25 UTC366
                let mid = if in_set[i] {
b563d2da1c80atagen2026-10-16 08:25 UTC367
                    [
b563d2da1c80atagen2026-10-16 08:25 UTC368
                        Rect::new(f.x, row[i], f.w, sh),
b563d2da1c80atagen2026-10-16 08:25 UTC369
                        Rect::new(col[i], row[i], sw, sh),
b563d2da1c80atagen2026-10-16 08:25 UTC370
                        Rect::new(col[i], t.y, sw, t.h),
b563d2da1c80atagen2026-10-16 08:25 UTC371
                    ]
b563d2da1c80atagen2026-10-16 08:25 UTC372
                } else {
b563d2da1c80atagen2026-10-16 08:25 UTC373
                    [1, 2, 3].map(|k| if k >= phase { t } else { f })
b563d2da1c80atagen2026-10-16 08:25 UTC374
                };
b563d2da1c80atagen2026-10-16 08:25 UTC375
                for (k, r) in mid.into_iter().enumerate() {
b563d2da1c80atagen2026-10-16 08:25 UTC376
                    keys[k + 1].push(r);
b563d2da1c80atagen2026-10-16 08:25 UTC377
                }
b563d2da1c80atagen2026-10-16 08:25 UTC378
            }
b563d2da1c80atagen2026-10-16 08:25 UTC379
            let refs: Vec<&[Rect]> = keys.iter().map(|k| k.as_slice()).collect();
b563d2da1c80atagen2026-10-16 08:25 UTC380
            if valid(&refs, cfg) {
b563d2da1c80atagen2026-10-16 08:25 UTC381
                return Some((keys, m));
b563d2da1c80atagen2026-10-16 08:25 UTC382
            }
b563d2da1c80atagen2026-10-16 08:25 UTC383
        }
b563d2da1c80atagen2026-10-16 08:25 UTC384
    }
b563d2da1c80atagen2026-10-16 08:25 UTC385
    None
b563d2da1c80atagen2026-10-16 08:25 UTC386
}
b563d2da1c80atagen2026-10-16 08:25 UTC387
b563d2da1c80atagen2026-10-16 08:25 UTC388
/// Safety net. Movers shrink to a point, travel invisibly, and regrow.
b563d2da1c80atagen2026-10-16 08:25 UTC389
fn pinch(from: &[Rect], to: &[Rect], movers: &[usize]) -> Vec<Vec<Rect>> {
b563d2da1c80atagen2026-10-16 08:25 UTC390
    let mut k1 = from.to_vec();
b563d2da1c80atagen2026-10-16 08:25 UTC391
    let mut k2 = to.to_vec();
b563d2da1c80atagen2026-10-16 08:25 UTC392
    for &i in movers {
b563d2da1c80atagen2026-10-16 08:25 UTC393
        k1[i] = Rect::new(from[i].center_x(), from[i].center_y(), 0.0, 0.0);
b563d2da1c80atagen2026-10-16 08:25 UTC394
        k2[i] = Rect::new(to[i].center_x(), to[i].center_y(), 0.0, 0.0);
b563d2da1c80atagen2026-10-16 08:25 UTC395
    }
b563d2da1c80atagen2026-10-16 08:25 UTC396
    vec![from.to_vec(), k1, k2, to.to_vec()]
b563d2da1c80atagen2026-10-16 08:25 UTC397
}