use super::capacity::{Occupancy, Ports};
use super::geometry;
use super::runs::{Chain, Slides};
use super::{Router, solve};
use crate::ast::Side;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Crossing {
pub pair: (usize, usize),
pub at: (f64, f64),
}
type BoxedPoly = (Vec<(f64, f64)>, (f64, f64, f64, f64));
fn boxed_polys(chains: &[Option<Chain>]) -> Vec<Option<BoxedPoly>> {
chains
.iter()
.map(|c| {
c.as_ref().map(|c| {
let p = geometry::polyline(c);
let bb = p.iter().fold(
(
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
),
|(x0, y0, x1, y1), &(x, y)| (x0.min(x), y0.min(y), x1.max(x), y1.max(y)),
);
(p, bb)
})
})
.collect()
}
fn boxes_apart(a: (f64, f64, f64, f64), b: (f64, f64, f64, f64), gap: f64) -> bool {
b.0 - a.2 >= gap || a.0 - b.2 >= gap || b.1 - a.3 >= gap || a.1 - b.3 >= gap
}
pub fn collect(chains: &[Option<Chain>]) -> Vec<Crossing> {
let polys = boxed_polys(chains);
let mut out = Vec::new();
for i in 0..polys.len() {
for j in i + 1..polys.len() {
let (Some((a, ba)), Some((b, bb))) = (&polys[i], &polys[j]) else {
continue;
};
if boxes_apart(*ba, *bb, 0.0) {
continue;
}
for sa in a.windows(2) {
for sb in b.windows(2) {
if let Some(at) = cross(sa, sb) {
out.push(Crossing { pair: (i, j), at });
}
}
}
}
}
out.sort_by(|x, y| {
(x.pair.1, x.pair.0)
.cmp(&(y.pair.1, y.pair.0))
.then(x.at.0.total_cmp(&y.at.0))
.then(x.at.1.total_cmp(&y.at.1))
});
out
}
pub(crate) fn cross(a: &[(f64, f64)], b: &[(f64, f64)]) -> Option<(f64, f64)> {
let (h, v) = if a[0].1 == a[1].1 && b[0].0 == b[1].0 {
(a, b)
} else if a[0].0 == a[1].0 && b[0].1 == b[1].1 {
(b, a)
} else {
return None;
};
let (x, y) = (v[0].0, h[0].1);
let (hx0, hx1) = (h[0].0.min(h[1].0), h[0].0.max(h[1].0));
let (vy0, vy1) = (v[0].1.min(v[1].1), v[0].1.max(v[1].1));
(hx0 < x && x < hx1 && vy0 < y && y < vy1).then_some((x, y))
}
pub fn run(
router: &Router,
raw: &mut Vec<Option<Chain>>,
drawn: &mut Vec<Option<Chain>>,
clearance: f64,
slides: &Slides,
) -> Vec<Crossing> {
loop {
let crossings = collect(drawn);
let total = crossings.len();
if total == 0 {
return Vec::new();
}
let base = law_score(router, drawn, clearance).0;
let clean = |c: &Candidate| law_score(router, &c.2, clearance).0 <= base;
let bundles = entangled_bundles(router, &crossings);
let mut best: Option<Candidate> = None;
let mut firsts: Vec<(usize, Vec<Candidate>)> = Vec::new();
for &bi in &bundles {
let cands = retry(router, raw, drawn, bi, clearance, slides);
for c in &cands {
if c.0 < total && best.as_ref().is_none_or(|b| c.0 < b.0) && clean(c) {
best = Some(c.clone());
}
}
firsts.push((bi, cands));
}
if best.is_none() {
'pairs: for (a, cas) in firsts.iter().take(2) {
for ca in cas.iter().take(2) {
for b in entangled_bundles(router, &collect(&ca.2))
.into_iter()
.take(3)
{
if b == *a {
continue;
}
for cb in retry(router, &ca.1, &ca.2, b, clearance, slides) {
if cb.0 < total && clean(&cb) {
best = Some(cb);
break 'pairs;
}
}
}
}
}
}
let Some((_, cand, cand_drawn)) = best else {
return crossings;
};
*raw = cand;
*drawn = cand_drawn;
}
}
type Candidate = (usize, Vec<Option<Chain>>, Vec<Option<Chain>>);
type Move = (Vec<Option<Chain>>, Vec<Option<Chain>>);
pub fn law_score(router: &Router, chains: &[Option<Chain>], clearance: f64) -> (usize, usize) {
(
breaches(chains, clearance).len() + body_breaches(router, chains, clearance).len(),
collect(chains).len(),
)
}
pub fn body_breaches(
router: &Router,
chains: &[Option<Chain>],
clearance: f64,
) -> Vec<(usize, super::rect::Rect)> {
let mut out = Vec::new();
for (ci, chain) in chains.iter().enumerate() {
let Some(chain) = chain else {
continue;
};
let poly = geometry::polyline(chain);
if poly.len() < 2 {
continue;
}
let req = &router.reqs[chain.req];
let segs = poly.len() - 1;
for r in router.index.solid_rects_for([&req.a_path, &req.b_path]) {
if poly
.windows(2)
.any(|s| seg_rect_dist(s, &r) < clearance - 1e-6)
{
out.push((ci, r));
}
}
for (e, end) in chain.ends.iter().enumerate() {
let partner = &chain.ends[1 - e].path;
if super::SceneIndex::contains(&end.path, partner) {
continue;
}
let hit = poly.windows(2).enumerate().any(|(k, s)| {
let own_stub = (k == 0 && chain.ends[0].path == end.path)
|| (k == segs - 1 && chain.ends[1].path == end.path);
!own_stub && seg_rect_dist(s, &end.rect) < clearance - 1e-6
});
if hit {
out.push((ci, end.rect));
}
}
}
out
}
fn seg_rect_dist(s: &[(f64, f64)], r: &super::rect::Rect) -> f64 {
let (sx0, sx1) = (s[0].0.min(s[1].0), s[0].0.max(s[1].0));
let (sy0, sy1) = (s[0].1.min(s[1].1), s[0].1.max(s[1].1));
let dx = (r.x0 - sx1).max(sx0 - r.x1).max(0.0);
let dy = (r.y0 - sy1).max(sy0 - r.y1).max(0.0);
(dx * dx + dy * dy).sqrt()
}
pub fn breaches(chains: &[Option<Chain>], clearance: f64) -> Vec<(usize, usize)> {
let polys = boxed_polys(chains);
let rows = compacted_bands(chains, clearance);
let mut out = Vec::new();
for i in 0..chains.len() {
for j in i + 1..chains.len() {
let (Some((a, ba)), Some((b, bb))) = (&polys[i], &polys[j]) else {
continue;
};
if boxes_apart(*ba, *bb, clearance - 1e-6) {
continue;
}
let (ca, cb) = (chains[i].as_ref().unwrap(), chains[j].as_ref().unwrap());
let fans = |c: &Chain| [c.ends[0].fan, c.ends[1].fan];
let fan_pair = fans(ca)
.iter()
.flatten()
.any(|g| fans(cb).contains(&Some(*g)));
let bands = shared_bands(&rows, ca, cb);
let hit = a.windows(2).any(|sa| {
b.windows(2).any(|sb| {
cross(sa, sb).is_none()
&& seg_dist(sa, sb) < clearance - 1e-6
&& !(fan_pair && trunk_contact(sa, sb, a, b))
&& !bands.iter().any(|&band| band_contact(band, sa, sb))
})
});
if hit {
out.push((i, j));
}
}
}
out.sort_by_key(|&(a, b)| (b, a));
out
}
pub fn separation(
router: &Router,
raw: &mut Vec<Option<Chain>>,
drawn: &mut Vec<Option<Chain>>,
clearance: f64,
slides: &mut Slides,
give_up: usize,
) -> Vec<usize> {
let mut undrawn = Vec::new();
loop {
if drawn.iter().filter(|c| c.is_none()).count() > give_up {
return undrawn;
}
let cur = breaches(drawn, clearance);
let bodies = body_breaches(router, drawn, clearance);
if cur.is_empty() && bodies.is_empty() {
return undrawn;
}
let total = law_score(router, drawn, clearance);
let score = |d: &[Option<Chain>]| law_score(router, d, clearance);
let mut best: Option<((usize, usize), Move, Option<Slides>)> = None;
let mut firsts: Vec<(usize, Vec<Move>)> = Vec::new();
for &pair in &cur {
for mover in movers(drawn, pair) {
let cands = nudge(router, raw, drawn, mover, pair, clearance, slides);
for cand in &cands {
let s = score(&cand.1);
if s < total && best.as_ref().is_none_or(|(bs, ..)| s < *bs) {
best = Some((s, cand.clone(), None));
}
}
firsts.push((mover, cands));
}
}
for &(ci, site) in &bodies {
let deny = vec![site.inflate(clearance - 1e-6)];
for cand in nudged(
router,
raw,
drawn,
ci,
deny,
BTreeSet::new(),
clearance,
slides,
false,
) {
let s = score(&cand.1);
if s < total && best.as_ref().is_none_or(|(bs, ..)| s < *bs) {
best = Some((s, cand, None));
}
}
}
for &pair in &cur {
for (ci, partner) in [(pair.1, pair.0), (pair.0, pair.1)] {
let Some(chain) = drawn[ci].clone() else {
continue;
};
let Some(partner_chain) = drawn[partner].as_ref() else {
continue;
};
let partner_poly = geometry::polyline(partner_chain);
for end in &chain.ends {
let port = [(end.port.0, end.port.1), (end.port.0, end.port.1)];
let at_mouth = partner_poly
.windows(2)
.any(|s| seg_dist(&port, s) < clearance - 1e-6);
let partner_shares_side = partner_chain
.ends
.iter()
.any(|e| e.path == end.path && e.side == end.side);
if !at_mouth || partner_shares_side {
continue;
}
let key = (end.path.clone(), end.side.index());
let base = slides.get(&key).copied().unwrap_or(0.0);
for step in [1.0, -1.0, 2.0, -2.0] {
let mut cand_slides = slides.clone();
cand_slides.insert(key.clone(), base + step * clearance);
let cand_drawn = solve(&router.worlds, raw, clearance, &cand_slides);
let s = score(&cand_drawn);
if s < total
&& contact_holds(&cand_drawn, clearance)
&& best.as_ref().is_none_or(|(bs, ..)| s < *bs)
{
best = Some((s, (raw.clone(), cand_drawn), Some(cand_slides)));
}
}
}
}
}
let mut accepted: Option<(Move, Option<Slides>)> = best.map(|(_, mv, sl)| (mv, sl));
if accepted.is_none() && give_up == usize::MAX {
'pairs: for (m1, cas) in &firsts {
for ca in cas {
for &pair2 in &breaches(&ca.1, clearance) {
for m2 in movers(&ca.1, pair2) {
if m2 == *m1 {
continue;
}
for cb in nudge(router, &ca.0, &ca.1, m2, pair2, clearance, slides) {
if score(&cb.1) < total {
accepted = Some((cb, None));
break 'pairs;
}
}
}
}
}
}
}
match accepted {
Some(((cand, cand_drawn), slid)) => {
if let Some(slid) = slid {
*slides = slid;
}
*raw = cand;
*drawn = cand_drawn;
}
None => {
let later = cur.first().map_or_else(|| bodies[0].0, |&(_, l)| l);
raw[later] = None;
*drawn = solve(&router.worlds, raw, clearance, slides);
undrawn.push(later);
}
}
}
}
type Insertion = (Vec<Option<Chain>>, Vec<Option<Chain>>, Slides, Vec<usize>);
pub fn complete(
router: &mut Router,
raw: &mut Vec<Option<Chain>>,
drawn: &mut Vec<Option<Chain>>,
clearance: f64,
slides: &mut Slides,
) -> Vec<usize> {
let mut undrawn = Vec::new();
let mut version: u64 = 0;
let mut memo: std::collections::BTreeMap<Vec<usize>, (u64, Vec<Insertion>)> =
Default::default();
let mut swaps_tried: BTreeSet<(u64, Vec<Vec<usize>>)> = BTreeSet::new();
let mut compacts_tried: BTreeSet<(u64, Vec<usize>)> = BTreeSet::new();
'pass: loop {
let total = completeness_score(router, drawn, clearance);
let better = |c: &Insertion| completeness_score(router, &c.1, clearance) < total;
let starved: Vec<usize> = router
.bundles
.iter()
.enumerate()
.filter(|(_, b)| {
let rep = &router.reqs[b.members[0]];
rep.a_path != rep.b_path && b.members.iter().any(|&m| raw[m].is_none())
})
.map(|(bi, _)| bi)
.collect();
let mut firsts: Vec<Vec<Insertion>> = Vec::new();
for &bi in &starved {
let key = router.bundles[bi].members.clone();
let mut cands = match memo.get(&key) {
Some((v, c)) if *v == version => c.clone(),
_ => {
let c = insertions(router, raw, drawn, bi, clearance, slides);
memo.insert(key, (version, c.clone()));
c
}
};
if let Some(i) = cands.iter().position(&better) {
let (cand, cand_drawn, cand_slides, lost) = cands.swap_remove(i);
adopt_fan_sides(router, &cand);
*raw = cand;
*drawn = cand_drawn;
*slides = cand_slides;
undrawn.extend(lost);
version += 1;
continue 'pass;
}
firsts.push(cands);
}
let starved_keys: Vec<Vec<usize>> = starved
.iter()
.map(|&bi| router.bundles[bi].members.clone())
.collect();
if swaps_tried.insert((version, starved_keys)) {
for (cand, cand_drawn, cand_slides, lost) in firsts.iter().flatten() {
for &l in lost {
let bj = bundle_of(router, l);
let mut seconds =
insertions(router, cand, cand_drawn, bj, clearance, cand_slides);
if let Some(i) = seconds.iter().position(&better) {
let (second, second_drawn, second_slides, lost2) = seconds.swap_remove(i);
adopt_fan_sides(router, &second);
*raw = second;
*drawn = second_drawn;
*slides = second_slides;
undrawn.extend(lost.iter().copied());
undrawn.extend(lost2);
version += 1;
continue 'pass;
}
}
}
}
let displaced = firsts
.iter()
.flatten()
.flat_map(|c| &c.3)
.map(|&l| bundle_of(router, l));
let blocking: Vec<usize> = starved.iter().copied().chain(displaced).collect();
for bi in blocking {
if router.splittable(bi) {
super::bundle::split(&mut router.bundles, bi);
continue 'pass;
}
}
for &bi in &starved {
let key = router.bundles[bi].members.clone();
if !compacts_tried.insert((version, key)) {
continue;
}
if compact_insertion(
router,
raw,
drawn,
bi,
clearance,
slides,
&mut undrawn,
total,
true,
) {
version += 1;
continue 'pass;
}
}
return undrawn;
}
}
#[allow(clippy::too_many_arguments)]
fn compact_insertion(
router: &mut Router,
raw: &mut Vec<Option<Chain>>,
drawn: &mut Vec<Option<Chain>>,
bi: usize,
clearance: f64,
slides: &mut Slides,
undrawn: &mut Vec<usize>,
total: (usize, usize, usize),
rehome: bool,
) -> bool {
let members = router.bundles[bi].members.clone();
for cleared in [false, true] {
let occ = if cleared {
Occupancy::new(clearance)
} else {
occupancy_without(raw, &members, clearance)
};
let ports = ports_without(raw, &members, clearance);
let polys: Vec<Vec<(f64, f64)>> = (0..drawn.len())
.filter(|w| !members.contains(w))
.filter_map(|w| drawn[w].as_ref().map(geometry::polyline))
.collect();
let count = counter(&polys);
let Some(picked) = [false, true].into_iter().find_map(|relaxed| {
router.route_bundle(
bi,
&occ,
&ports,
&count,
&[],
[None, None],
relaxed,
true,
None,
)
}) else {
continue;
};
let mut cand = raw.clone();
router.build_chains(bi, &picked, &mut cand);
let beat = drawn.iter().filter(|c| c.is_none()).count();
let mut cand_drawn = solve(&router.worlds, &cand, clearance, slides);
let mut cand_slides = slides.clone();
let lost = separation(
router,
&mut cand,
&mut cand_drawn,
clearance,
&mut cand_slides,
beat,
);
if completeness_score(router, &cand_drawn, clearance) < total {
*raw = cand;
*drawn = cand_drawn;
*slides = cand_slides;
undrawn.extend(lost);
return true;
}
for &l in &lost {
let bj = bundle_of(router, l);
let mut seconds = insertions(router, &cand, &cand_drawn, bj, clearance, &cand_slides);
let better = |c: &Insertion| completeness_score(router, &c.1, clearance) < total;
if let Some(i) = seconds.iter().position(better) {
let (second, second_drawn, second_slides, lost2) = seconds.swap_remove(i);
adopt_fan_sides(router, &second);
*raw = second;
*drawn = second_drawn;
*slides = second_slides;
undrawn.extend(lost.iter().copied());
undrawn.extend(lost2);
return true;
}
if !rehome {
continue;
}
let mut second = cand.clone();
let mut second_drawn = cand_drawn.clone();
let mut second_slides = cand_slides.clone();
let mut second_lost = Vec::new();
if compact_insertion(
router,
&mut second,
&mut second_drawn,
bj,
clearance,
&mut second_slides,
&mut second_lost,
total,
false,
) {
*raw = second;
*drawn = second_drawn;
*slides = second_slides;
undrawn.extend(lost.iter().copied());
undrawn.extend(second_lost);
return true;
}
}
}
false
}
fn compacted_bands(chains: &[Option<Chain>], clearance: f64) -> BTreeMap<(String, u8), (f64, f64)> {
let mut ords: BTreeMap<(String, u8), Vec<f64>> = BTreeMap::new();
for chain in chains.iter().flatten() {
for e in &chain.ends {
let o = match e.side {
Side::Left | Side::Right => e.port.1,
Side::Top | Side::Bottom => e.port.0,
};
ords.entry((e.path.clone(), e.side.index()))
.or_default()
.push(o);
}
}
let mut out = BTreeMap::new();
for (key, mut os) in ords {
os.sort_by(f64::total_cmp);
os.dedup_by(|a, b| (*a - *b).abs() <= 1e-6);
let tight = os.windows(2).any(|w| w[1] - w[0] < clearance - 1e-6);
if tight {
out.insert(key, (os[0], *os.last().unwrap()));
}
}
out
}
fn shared_bands(
bands: &BTreeMap<(String, u8), (f64, f64)>,
ca: &Chain,
cb: &Chain,
) -> Vec<(bool, f64, f64)> {
let mut out = Vec::new();
for ea in &ca.ends {
for eb in &cb.ends {
if ea.path != eb.path || ea.side != eb.side {
continue;
}
if let Some(&(lo, hi)) = bands.get(&(ea.path.clone(), ea.side.index())) {
let vertical = matches!(ea.side, Side::Left | Side::Right);
out.push((vertical, lo, hi));
}
}
}
out.sort_by(|a, b| a.partial_cmp(b).unwrap());
out.dedup();
out
}
pub(super) fn band_contact(
(vertical, lo, hi): (bool, f64, f64),
sa: &[(f64, f64)],
sb: &[(f64, f64)],
) -> bool {
let range = |s: &[(f64, f64)]| {
if vertical {
(s[0].1.min(s[1].1), s[0].1.max(s[1].1))
} else {
(s[0].0.min(s[1].0), s[0].0.max(s[1].0))
}
};
let (a, b) = (range(sa), range(sb));
let (w0, w1) = if a.1 < b.0 {
(a.1, b.0)
} else if b.1 < a.0 {
(b.1, a.0)
} else {
(a.0.max(b.0), a.1.min(b.1))
};
w0 >= lo - 1e-6 && w1 <= hi + 1e-6
}
pub(super) fn bundle_of(router: &Router, m: usize) -> usize {
router
.bundles
.iter()
.position(|b| b.members.contains(&m))
.expect("every chain belongs to a bundle")
}
fn insertions(
router: &Router,
raw: &[Option<Chain>],
drawn: &[Option<Chain>],
bi: usize,
clearance: f64,
slides: &Slides,
) -> Vec<Insertion> {
let m0 = router.bundles[bi].members[0];
let beat = drawn.iter().filter(|c| c.is_none()).count();
let mut out = Vec::new();
for cleared in [false, true] {
let moves = nudged(
router,
raw,
drawn,
m0,
Vec::new(),
BTreeSet::new(),
clearance,
slides,
cleared,
);
for (mut cand, mut cand_drawn) in moves {
let mut cand_slides = slides.clone();
let lost = separation(
router,
&mut cand,
&mut cand_drawn,
clearance,
&mut cand_slides,
beat,
);
out.push((cand, cand_drawn, cand_slides, lost));
}
}
out
}
fn completeness_score(
router: &Router,
drawn: &[Option<Chain>],
clearance: f64,
) -> (usize, usize, usize) {
let gone = drawn.iter().filter(|c| c.is_none()).count();
let (conflicts, crossings) = law_score(router, drawn, clearance);
(gone, conflicts, crossings)
}
fn adopt_fan_sides(router: &mut Router, raw: &[Option<Chain>]) {
for end in raw.iter().flatten().flat_map(|chain| &chain.ends) {
if let Some(g) = end.fan
&& router.fan_pick[g].is_none()
{
router.fan_pick[g] = Some(end.side);
}
}
}
fn contact_holds(chains: &[Option<Chain>], clearance: f64) -> bool {
chains.iter().flatten().all(|chain| {
chain.ends.iter().all(|e| {
let r = e.rect;
let (along, lo, hi) = match e.side {
Side::Left | Side::Right => (e.port.1, r.y0, r.y1),
Side::Top | Side::Bottom => (e.port.0, r.x0, r.x1),
};
let margin = clearance.min((hi - lo) / 2.0);
along >= lo + margin - 1e-6 && along <= hi - margin + 1e-6
})
})
}
fn movers(drawn: &[Option<Chain>], (a, b): (usize, usize)) -> Vec<usize> {
let mut out = vec![b, a];
let mut sides: Vec<(&str, Side)> = Vec::new();
for &ci in &[a, b] {
if let Some(ch) = &drawn[ci] {
sides.extend(ch.ends.iter().map(|e| (e.path.as_str(), e.side)));
}
}
for (ci, ch) in drawn.iter().enumerate() {
let Some(ch) = ch else {
continue;
};
if ci == a || ci == b {
continue;
}
if ch
.ends
.iter()
.any(|e| sides.contains(&(e.path.as_str(), e.side)))
{
out.push(ci);
}
}
out
}
fn nudge(
router: &Router,
raw: &[Option<Chain>],
drawn: &[Option<Chain>],
mover: usize,
pair: (usize, usize),
clearance: f64,
slides: &Slides,
) -> Vec<Move> {
let mut walled: BTreeSet<usize> = BTreeSet::new();
walled.insert(pair.0);
walled.insert(pair.1);
walled.remove(&mover);
let mut deny = conflict_sites(drawn, pair.0, pair.1, clearance);
deny.extend(conflict_sites(drawn, pair.1, pair.0, clearance));
nudged(
router, raw, drawn, mover, deny, walled, clearance, slides, false,
)
}
#[allow(clippy::too_many_arguments)]
fn nudged(
router: &Router,
raw: &[Option<Chain>],
drawn: &[Option<Chain>],
mover: usize,
mut deny: Vec<super::rect::Rect>,
mut walled: BTreeSet<usize>,
clearance: f64,
slides: &Slides,
cleared: bool,
) -> Vec<Move> {
let req = &router.reqs[mover];
let mut out = Vec::new();
if req.a_path == req.b_path {
return out; }
if deny.is_empty() && drawn[mover].is_some() {
return out; }
let bi = router
.bundles
.iter()
.position(|b| b.members.contains(&mover))
.expect("every chain belongs to a bundle");
let members = &router.bundles[bi].members;
let occ = if cleared {
Occupancy::new(clearance)
} else {
occupancy_without(raw, members, clearance)
};
let ports = ports_without(raw, members, clearance);
let stuck = members
.iter()
.filter_map(|&m| raw[m].as_ref())
.flat_map(|c| c.ends.iter())
.any(|e| e.fan.is_none() && ports.free(&e.path, e.side, e.rect) == 0);
let polys: Vec<Vec<(f64, f64)>> = (0..drawn.len())
.filter(|w| !members.contains(w))
.filter_map(|w| drawn[w].as_ref().map(geometry::polyline))
.collect();
let count = counter(&polys);
let avoids = match drawn[mover].as_ref() {
Some(cur) => {
let free = |end: usize| {
let forced = [req.side_a, req.side_b][end];
let fanned = cur.ends[end].fan.is_some();
(forced.is_none() && !fanned).then(|| cur.ends[end].side)
};
let mut avoids: Vec<[Option<Side>; 2]> = vec![[None, None]];
if let Some(s) = free(0) {
avoids.push([Some(s), None]);
}
if let Some(s) = free(1) {
avoids.push([None, Some(s)]);
}
avoids
}
None => vec![[None, None]],
};
let mut seen: BTreeSet<Vec<(u64, u64)>> = BTreeSet::new();
loop {
let mut grew = false;
for &avoid in &avoids {
let Some(picked) = [false, true].into_iter().find_map(|relaxed| {
router.route_bundle(bi, &occ, &ports, &count, &deny, avoid, relaxed, stuck, None)
}) else {
continue;
};
let mut cand = raw.to_vec();
router.build_chains(bi, &picked, &mut cand);
let key: Vec<(u64, u64)> = members
.iter()
.flat_map(|&m| geometry::polyline(cand[m].as_ref().unwrap()))
.map(|(x, y)| (x.to_bits(), y.to_bits()))
.collect();
if !seen.insert(key) {
continue;
}
let cand_drawn = solve(&router.worlds, &cand, clearance, slides);
for &(a, b) in &breaches(&cand_drawn, clearance) {
let partner = match (a == mover, b == mover) {
(true, _) => b,
(_, true) => a,
_ => continue,
};
if walled.insert(partner) {
deny.extend(conflict_sites(&cand_drawn, mover, partner, clearance));
grew = true;
}
}
out.push((cand, cand_drawn));
}
if !grew {
return out;
}
}
}
fn trunk_contact(
sa: &[(f64, f64)],
sb: &[(f64, f64)],
pa: &[(f64, f64)],
pb: &[(f64, f64)],
) -> bool {
let collinear_within = |t: &[(f64, f64)], s: &[(f64, f64)]| {
let (tx0, tx1) = (t[0].0.min(t[1].0), t[0].0.max(t[1].0));
let (ty0, ty1) = (t[0].1.min(t[1].1), t[0].1.max(t[1].1));
let (sx0, sx1) = (s[0].0.min(s[1].0), s[0].0.max(s[1].0));
let (sy0, sy1) = (s[0].1.min(s[1].1), s[0].1.max(s[1].1));
let along_x = ty0 == ty1 && sy0 == sy1 && (ty0 - sy0).abs() <= 1e-6;
let along_y = tx0 == tx1 && sx0 == sx1 && (tx0 - sx0).abs() <= 1e-6;
(along_x || along_y)
&& sx0 >= tx0 - 1e-6
&& sy0 >= ty0 - 1e-6
&& sx1 <= tx1 + 1e-6
&& sy1 <= ty1 + 1e-6
};
let on =
|s: &[(f64, f64)], path: &[(f64, f64)]| path.windows(2).any(|t| collinear_within(t, s));
let break_out = {
let horizontal = |s: &[(f64, f64)]| s[0].1 == s[1].1;
if horizontal(sa) != horizontal(sb) {
false
} else if horizontal(sa) {
sa[0].0.max(sa[1].0).min(sb[0].0.max(sb[1].0))
- sa[0].0.min(sa[1].0).max(sb[0].0.min(sb[1].0))
<= 1e-6
} else {
sa[0].1.max(sa[1].1).min(sb[0].1.max(sb[1].1))
- sa[0].1.min(sa[1].1).max(sb[0].1.min(sb[1].1))
<= 1e-6
}
};
seg_dist(sa, sb) <= 1e-6 || on(sb, pa) || on(sa, pb) || break_out
}
pub(super) fn seg_dist(sa: &[(f64, f64)], sb: &[(f64, f64)]) -> f64 {
let (ax0, ax1) = (sa[0].0.min(sa[1].0), sa[0].0.max(sa[1].0));
let (ay0, ay1) = (sa[0].1.min(sa[1].1), sa[0].1.max(sa[1].1));
let (bx0, bx1) = (sb[0].0.min(sb[1].0), sb[0].0.max(sb[1].0));
let (by0, by1) = (sb[0].1.min(sb[1].1), sb[0].1.max(sb[1].1));
let dx = (bx0 - ax1).max(ax0 - bx1).max(0.0);
let dy = (by0 - ay1).max(ay0 - by1).max(0.0);
(dx * dx + dy * dy).sqrt()
}
fn conflict_sites(
drawn: &[Option<Chain>],
mover: usize,
kept: usize,
clearance: f64,
) -> Vec<super::rect::Rect> {
let (Some(m), Some(k)) = (&drawn[mover], &drawn[kept]) else {
return Vec::new();
};
let (mp, kp) = (geometry::polyline(m), geometry::polyline(k));
let mut out = Vec::new();
for sk in kp.windows(2) {
let near = mp
.windows(2)
.any(|sm| cross(sm, sk).is_none() && seg_dist(sm, sk) < clearance - 1e-6);
if near {
let rk = super::rect::Rect::new(
sk[0].0.min(sk[1].0),
sk[0].1.min(sk[1].1),
sk[0].0.max(sk[1].0),
sk[0].1.max(sk[1].1),
);
out.push(rk.inflate(clearance - 1e-6));
}
}
out
}
fn retry(
router: &Router,
raw: &[Option<Chain>],
drawn: &[Option<Chain>],
bi: usize,
clearance: f64,
slides: &Slides,
) -> Vec<Candidate> {
let members = &router.bundles[bi].members;
let occ = occupancy_without(raw, members, clearance);
let ports = ports_without(raw, members, clearance);
let mut out: Vec<Candidate> = Vec::new();
let mut evaluate = |obstacle_wires: &[usize]| -> Option<Vec<usize>> {
let polys: Vec<Vec<(f64, f64)>> = obstacle_wires
.iter()
.filter_map(|&w| drawn[w].as_ref().map(geometry::polyline))
.collect();
let count = counter(&polys);
let picked = router.route_bundle(
bi,
&occ,
&ports,
&count,
&[],
[None, None],
false,
false,
None,
)?;
let mut cand = raw.to_vec();
router.build_chains(bi, &picked, &mut cand);
let cand_drawn = solve(&router.worlds, &cand, clearance, slides);
let crossings = collect(&cand_drawn);
let mut still: Vec<usize> = crossings
.iter()
.filter_map(
|c| match (members.contains(&c.pair.0), members.contains(&c.pair.1)) {
(true, false) => Some(c.pair.1),
(false, true) => Some(c.pair.0),
_ => None,
},
)
.collect();
still.sort_unstable();
still.dedup();
out.push((crossings.len(), cand, cand_drawn));
Some(still)
};
let all: Vec<usize> = (0..drawn.len())
.filter(|w| !members.contains(w) && drawn[*w].is_some())
.collect();
evaluate(&all);
let mut obstacle_wires: Vec<usize> = Vec::new();
loop {
let Some(still) = evaluate(&obstacle_wires) else {
return out;
};
let before = obstacle_wires.len();
obstacle_wires.extend(still);
obstacle_wires.sort_unstable();
obstacle_wires.dedup();
if obstacle_wires.len() == before {
return out;
}
}
}
fn entangled_bundles(router: &Router, crossings: &[Crossing]) -> Vec<usize> {
let mut counts: Vec<(usize, usize)> = Vec::new();
for x in crossings {
for wire in [x.pair.1, x.pair.0] {
let req = &router.reqs[wire];
if req.a_path == req.b_path {
continue;
}
let bi = router
.bundles
.iter()
.position(|b| b.members.contains(&wire))
.expect("every chain belongs to a bundle");
match counts.iter_mut().find(|(b, _)| *b == bi) {
Some((_, n)) => *n += 1,
None => counts.push((bi, 1)),
}
}
}
counts.sort_by_key(|&(bi, n)| (usize::MAX - n, bi));
counts.into_iter().map(|(bi, _)| bi).collect()
}
fn counter(
obstacles: &[Vec<(f64, f64)>],
) -> impl Fn(super::rect::Rect, super::graph::Axis) -> u32 + '_ {
|band, axis| {
obstacles
.iter()
.flat_map(|p| p.windows(2))
.filter(|s| intrudes(s, band, axis))
.count() as u32
}
}
fn intrudes(s: &[(f64, f64)], band: super::rect::Rect, axis: super::graph::Axis) -> bool {
use super::graph::Axis;
let vertical = s[0].0 == s[1].0 && s[0].1 != s[1].1;
let horizontal = s[0].1 == s[1].1 && s[0].0 != s[1].0;
match axis {
Axis::H if vertical => {
let (y0, y1) = (s[0].1.min(s[1].1), s[0].1.max(s[1].1));
s[0].0 >= band.x0 && s[0].0 <= band.x1 && y0 <= band.y1 && y1 >= band.y0
}
Axis::V if horizontal => {
let (x0, x1) = (s[0].0.min(s[1].0), s[0].0.max(s[1].0));
s[0].1 >= band.y0 && s[0].1 <= band.y1 && x0 <= band.x1 && x1 >= band.x0
}
_ => false,
}
}
pub(super) fn occupancy_without(
chains: &[Option<Chain>],
skip: &[usize],
clearance: f64,
) -> Occupancy {
let mut occ = Occupancy::new(clearance);
for (ci, chain) in chains.iter().enumerate() {
if skip.contains(&ci) {
continue;
}
if let Some(chain) = chain {
occ.commit_chain(chain);
}
}
occ
}
pub(super) fn ports_without(chains: &[Option<Chain>], skip: &[usize], clearance: f64) -> Ports {
let mut ports = Ports::new(clearance);
let mut fans_seen: BTreeSet<usize> = BTreeSet::new();
for (ci, chain) in chains.iter().enumerate() {
if skip.contains(&ci) {
continue;
}
let Some(chain) = chain else {
continue;
};
for e in &chain.ends {
match e.fan {
Some(g) => {
if fans_seen.insert(g) {
ports.commit(&e.path, e.side, 1);
}
}
None => ports.commit(&e.path, e.side, 1),
}
}
}
ports
}
#[cfg(test)]
mod tests {
use super::*;
fn seg(a: (f64, f64), b: (f64, f64)) -> [(f64, f64); 2] {
[a, b]
}
#[test]
fn transversal_crossings_are_strict_interior_only() {
let h = seg((0.0, 5.0), (10.0, 5.0));
let v = seg((4.0, 0.0), (4.0, 10.0));
assert_eq!(cross(&h, &v), Some((4.0, 5.0)));
assert_eq!(cross(&v, &h), Some((4.0, 5.0)));
assert_eq!(cross(&h, &seg((4.0, 5.0), (4.0, 10.0))), None);
assert_eq!(cross(&h, &seg((10.0, 0.0), (10.0, 10.0))), None);
assert_eq!(cross(&h, &seg((0.0, 7.0), (10.0, 7.0))), None);
assert_eq!(cross(&h, &seg((2.0, 5.0), (8.0, 5.0))), None);
}
}