use super::{
BALL, SOCKET,
gaps::Gaps,
measure::Model,
model::{End, Shape},
place::Plan,
ports::Ports,
};
pub(super) struct Route {
pub(super) points: Vec<(f64, f64)>,
pub(super) label_at: (f64, f64),
pub(super) against: bool,
pub(super) after: bool,
pub(super) clear: f64,
}
fn edge_depth(plan: &Plan, model: &Model, (node, edge): (usize, usize), offset: f64) -> f64 {
if model.diagram.nodes[node].shape == Shape::Interface {
return if model.diagram.edges[edge].ends.head == End::Socket {
SOCKET
} else {
BALL
};
}
let (width, height) = plan.sizes[node];
let (main, cross) = if plan.landscape {
(width, height)
} else {
(height, width)
};
if model.diagram.nodes[node].shape == Shape::Decision {
main / 2.0 * (1.0 - 2.0 * offset.abs() / cross).max(0.0)
} else {
main / 2.0
}
}
fn port_points(
plan: &Plan,
model: &Model,
(node, edge, middle): (usize, usize, f64),
(offset, along, into): (f64, Option<f64>, Option<f64>),
leaving: bool,
) -> Vec<(f64, f64)> {
let cross = plan.cross[node];
let Some(along) = along else {
let depth = edge_depth(plan, model, (node, edge), offset);
let main = if leaving {
middle + depth
} else {
middle - depth
};
return match (into, leaving) {
(None, _) => vec![(main, cross + offset)],
(Some(into), true) => vec![(middle + into, cross + offset), (main, cross + offset)],
(Some(into), false) => vec![(main, cross + offset), (middle + into, cross + offset)],
};
};
let side = side_depth(plan, model, node, along).copysign(offset);
let main = middle + along;
let mut points = vec![(main, cross + side), (main, cross + offset)];
if !leaving {
points.reverse();
}
points
}
fn side_depth(plan: &Plan, model: &Model, node: usize, along: f64) -> f64 {
let (width, height) = plan.sizes[node];
let (main, cross) = if plan.landscape {
(width, height)
} else {
(height, width)
};
if model.diagram.nodes[node].shape == Shape::Decision {
cross / 2.0 * (1.0 - 2.0 * along.abs() / main).max(0.0)
} else {
cross / 2.0
}
}
pub(super) fn route(
model: &Model,
plan: &Plan,
ports: &Ports,
gaps: &Gaps,
stretch: f64,
) -> Vec<Option<Route>> {
let graph = &model.graph;
let middle = |item: usize| {
let layer = plan.layers[graph.layer[item]];
f64::midpoint(layer.0, layer.1)
};
graph
.chains
.iter()
.enumerate()
.map(|(edge, chain)| {
let chain = chain.as_ref()?;
let items = &chain.items;
let first = items[0];
let last = items[items.len() - 1];
let (leave, reach) = (ports.leave[edge], ports.reach[edge]);
let leaving = (leave, ports.leave_along[edge], ports.leave_into[edge]);
let mut points = port_points(plan, model, (first, edge, middle(first)), leaving, true);
let start = points[points.len() - 1];
for piece in gaps.pieces.iter().filter(|piece| piece.edge == edge) {
if let Some(track) = piece.track {
let main = gaps.track(piece.layer, track, plan.layers[piece.layer].1, stretch);
points.push((main, piece.from));
points.push((main, piece.to));
}
}
let reaching = (reach, ports.reach_along[edge], ports.reach_into[edge]);
let arrival = port_points(plan, model, (last, edge, middle(last)), reaching, false);
let end = arrival[0];
points.extend(arrival);
let (label_at, offset) = if chain.reversed {
(end, reach)
} else {
(start, leave)
};
let siblings: Vec<f64> = graph
.chains
.iter()
.enumerate()
.filter_map(|(other, sibling)| {
let items = &sibling.as_ref()?.items;
let meets = if chain.reversed {
items[items.len() - 1] == last
} else {
items[0] == first
};
let offset = if chain.reversed {
ports.reach[other]
} else {
ports.leave[other]
};
(other != edge && meets).then_some(offset)
})
.collect();
let taken_after = siblings.iter().any(|offset| *offset > 0.5);
let taken_before = siblings.iter().any(|offset| *offset < -0.5);
let after = if offset > 0.5 {
true
} else if offset < -0.5 {
false
} else if plan.landscape {
taken_before && !taken_after
} else {
!taken_after || taken_before
};
let clear = siblings
.iter()
.map(|sibling| {
if after {
sibling - offset
} else {
offset - sibling
}
})
.filter(|distance| *distance > 0.5)
.fold(0.0, f64::max);
if chain.reversed {
points.reverse();
}
Some(Route {
points,
label_at,
against: chain.reversed,
after,
clear,
})
})
.collect()
}