use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap, HashSet};
use crate::records::sch::SchRecord;
use crate::types::{Coord, CoordPoint, CoordRect};
use super::layout::LayoutEngine;
use super::types::{Direction, Grid, RoutingPath, WireSegment};
const STRAIGHT_COST: i64 = 1;
const TURN_COST: i64 = 2;
const CROSSING_COST: i64 = 5;
#[derive(Clone, Eq, PartialEq)]
struct PathNode {
position: CoordPoint,
g_cost: i64, f_cost: i64, direction: Option<Direction>,
}
impl Ord for PathNode {
fn cmp(&self, other: &Self) -> Ordering {
other.f_cost.cmp(&self.f_cost)
}
}
impl PartialOrd for PathNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub struct RoutingEngine {
grid: Grid,
obstacles: Vec<CoordRect>,
existing_wires: Vec<WireSegment>,
max_iterations: usize,
}
impl Default for RoutingEngine {
fn default() -> Self {
Self::new()
}
}
impl RoutingEngine {
pub fn new() -> Self {
Self {
grid: Grid::default(),
obstacles: Vec::new(),
existing_wires: Vec::new(),
max_iterations: 10000,
}
}
pub fn set_grid(&mut self, grid: Grid) {
self.grid = grid;
}
pub fn set_max_iterations(&mut self, max: usize) {
self.max_iterations = max;
}
pub fn update_obstacles(&mut self, primitives: &[SchRecord], layout: &LayoutEngine) {
self.obstacles.clear();
self.existing_wires.clear();
for component in layout.get_placed_components(primitives) {
let margin = Coord::from_mils(5.0);
self.obstacles.push(CoordRect::from_points(
component.bounds.location1.x + margin,
component.bounds.location1.y + margin,
component.bounds.location2.x - margin,
component.bounds.location2.y - margin,
));
}
for record in primitives {
if let SchRecord::Wire(wire) = record {
for i in 0..wire.vertices.len().saturating_sub(1) {
let start = CoordPoint::from_raw(wire.vertices[i].0, wire.vertices[i].1);
let end = CoordPoint::from_raw(wire.vertices[i + 1].0, wire.vertices[i + 1].1);
self.existing_wires.push(WireSegment::new(start, end));
}
}
}
}
pub fn route(&self, start: CoordPoint, end: CoordPoint) -> Option<RoutingPath> {
let start = self.grid.snap(start);
let end = self.grid.snap(end);
if let Some(path) = self.try_direct_path(start, end) {
return Some(path);
}
if let Some(path) = self.try_l_path(start, end) {
return Some(path);
}
self.route_astar(start, end)
}
fn try_direct_path(&self, start: CoordPoint, end: CoordPoint) -> Option<RoutingPath> {
let segment = WireSegment::new(start, end);
if !segment.is_horizontal() && !segment.is_vertical() {
return None;
}
if self.segment_blocked(&segment) {
return None;
}
let mut path = RoutingPath::new();
path.add_segment(segment);
Some(path)
}
fn try_l_path(&self, start: CoordPoint, end: CoordPoint) -> Option<RoutingPath> {
let corner1 = CoordPoint::new(end.x, start.y);
let seg1_h = WireSegment::new(start, corner1);
let seg2_v = WireSegment::new(corner1, end);
if !self.segment_blocked(&seg1_h) && !self.segment_blocked(&seg2_v) {
let mut path = RoutingPath::new();
path.add_segment(seg1_h);
path.add_segment(seg2_v);
return Some(path);
}
let corner2 = CoordPoint::new(start.x, end.y);
let seg1_v = WireSegment::new(start, corner2);
let seg2_h = WireSegment::new(corner2, end);
if !self.segment_blocked(&seg1_v) && !self.segment_blocked(&seg2_h) {
let mut path = RoutingPath::new();
path.add_segment(seg1_v);
path.add_segment(seg2_h);
return Some(path);
}
None
}
fn segment_blocked(&self, segment: &WireSegment) -> bool {
let bounds = segment.bounds();
for obstacle in &self.obstacles {
if bounds.intersects(*obstacle) {
if self.line_intersects_rect(segment, obstacle) {
return true;
}
}
}
false
}
fn line_intersects_rect(&self, segment: &WireSegment, rect: &CoordRect) -> bool {
let (x1, y1) = (segment.start.x.to_raw(), segment.start.y.to_raw());
let (x2, y2) = (segment.end.x.to_raw(), segment.end.y.to_raw());
let (rx1, ry1) = (rect.location1.x.to_raw(), rect.location1.y.to_raw());
let (rx2, ry2) = (rect.location2.x.to_raw(), rect.location2.y.to_raw());
if segment.is_horizontal() {
let y = y1;
if y < ry1 || y > ry2 {
return false;
}
let (min_x, max_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
return max_x > rx1 && min_x < rx2;
}
if segment.is_vertical() {
let x = x1;
if x < rx1 || x > rx2 {
return false;
}
let (min_y, max_y) = if y1 < y2 { (y1, y2) } else { (y2, y1) };
return max_y > ry1 && min_y < ry2;
}
false
}
fn route_astar(&self, start: CoordPoint, end: CoordPoint) -> Option<RoutingPath> {
let mut open_set = BinaryHeap::new();
let mut came_from: HashMap<(i32, i32), ((i32, i32), Direction)> = HashMap::new();
let mut g_score: HashMap<(i32, i32), i64> = HashMap::new();
let mut closed_set: HashSet<(i32, i32)> = HashSet::new();
let start_key = (start.x.to_raw(), start.y.to_raw());
let end_key = (end.x.to_raw(), end.y.to_raw());
g_score.insert(start_key, 0);
open_set.push(PathNode {
position: start,
g_cost: 0,
f_cost: self.heuristic(start, end),
direction: None,
});
let grid_step = self.grid.spacing.to_raw();
let directions = [
(Direction::Right, (grid_step, 0)),
(Direction::Left, (-grid_step, 0)),
(Direction::Up, (0, grid_step)),
(Direction::Down, (0, -grid_step)),
];
let mut iterations = 0;
while let Some(current) = open_set.pop() {
iterations += 1;
if iterations > self.max_iterations {
return None; }
let current_key = (current.position.x.to_raw(), current.position.y.to_raw());
if current_key == end_key {
return Some(self.reconstruct_path(came_from, start_key, end_key));
}
if closed_set.contains(¤t_key) {
continue;
}
closed_set.insert(current_key);
for (dir, (dx, dy)) in &directions {
let next_pos = CoordPoint::from_raw(
current.position.x.to_raw() + dx,
current.position.y.to_raw() + dy,
);
let next_key = (next_pos.x.to_raw(), next_pos.y.to_raw());
if closed_set.contains(&next_key) {
continue;
}
if self.point_blocked(next_pos) && next_key != end_key {
continue;
}
let mut move_cost = STRAIGHT_COST;
if let Some(prev_dir) = current.direction {
if prev_dir != *dir {
move_cost += TURN_COST;
}
}
let test_segment = WireSegment::new(current.position, next_pos);
if self.crosses_wire(&test_segment) {
move_cost += CROSSING_COST;
}
let tentative_g = current.g_cost + move_cost;
if tentative_g < *g_score.get(&next_key).unwrap_or(&i64::MAX) {
came_from.insert(next_key, (current_key, *dir));
g_score.insert(next_key, tentative_g);
let f_cost = tentative_g + self.heuristic(next_pos, end);
open_set.push(PathNode {
position: next_pos,
g_cost: tentative_g,
f_cost,
direction: Some(*dir),
});
}
}
}
None }
fn heuristic(&self, from: CoordPoint, to: CoordPoint) -> i64 {
let dx = (to.x.to_raw() - from.x.to_raw()).abs() as i64;
let dy = (to.y.to_raw() - from.y.to_raw()).abs() as i64;
dx + dy
}
fn point_blocked(&self, point: CoordPoint) -> bool {
for obstacle in &self.obstacles {
if obstacle.contains(point) {
return true;
}
}
false
}
fn crosses_wire(&self, segment: &WireSegment) -> bool {
for wire in &self.existing_wires {
if self.segments_intersect(segment, wire) {
return true;
}
}
false
}
fn segments_intersect(&self, s1: &WireSegment, s2: &WireSegment) -> bool {
if (s1.is_horizontal() && s2.is_horizontal()) || (s1.is_vertical() && s2.is_vertical()) {
return false;
}
let (h_seg, v_seg) = if s1.is_horizontal() {
(s1, s2)
} else if s1.is_vertical() && s2.is_horizontal() {
(s2, s1)
} else {
return false;
};
let h_y = h_seg.start.y.to_raw();
let h_x1 = h_seg.start.x.to_raw().min(h_seg.end.x.to_raw());
let h_x2 = h_seg.start.x.to_raw().max(h_seg.end.x.to_raw());
let v_x = v_seg.start.x.to_raw();
let v_y1 = v_seg.start.y.to_raw().min(v_seg.end.y.to_raw());
let v_y2 = v_seg.start.y.to_raw().max(v_seg.end.y.to_raw());
v_x > h_x1 && v_x < h_x2 && h_y > v_y1 && h_y < v_y2
}
fn reconstruct_path(
&self,
came_from: HashMap<(i32, i32), ((i32, i32), Direction)>,
start: (i32, i32),
end: (i32, i32),
) -> RoutingPath {
let mut path = RoutingPath::new();
let mut vertices = vec![CoordPoint::from_raw(end.0, end.1)];
let mut current = end;
while current != start {
if let Some((prev, _dir)) = came_from.get(¤t) {
vertices.push(CoordPoint::from_raw(prev.0, prev.1));
current = *prev;
} else {
break;
}
}
vertices.reverse();
let simplified = self.simplify_vertices(&vertices);
for i in 0..simplified.len().saturating_sub(1) {
path.add_segment(WireSegment::new(simplified[i], simplified[i + 1]));
}
path
}
fn simplify_vertices(&self, vertices: &[CoordPoint]) -> Vec<CoordPoint> {
if vertices.len() <= 2 {
return vertices.to_vec();
}
let mut simplified = vec![vertices[0]];
for i in 1..vertices.len() - 1 {
let prev = simplified.last().unwrap();
let curr = &vertices[i];
let next = &vertices[i + 1];
let dx1 = curr.x.to_raw() - prev.x.to_raw();
let dy1 = curr.y.to_raw() - prev.y.to_raw();
let dx2 = next.x.to_raw() - curr.x.to_raw();
let dy2 = next.y.to_raw() - curr.y.to_raw();
let collinear = (dx1 == 0 && dx2 == 0) || (dy1 == 0 && dy2 == 0);
if !collinear {
simplified.push(*curr);
}
}
simplified.push(*vertices.last().unwrap());
simplified
}
pub fn find_junctions(&self, path: &RoutingPath) -> Vec<CoordPoint> {
let mut junctions = Vec::new();
for segment in &path.segments {
for wire in &self.existing_wires {
if let Some(intersection) = self.find_intersection(segment, wire) {
if intersection != segment.start && intersection != segment.end {
junctions.push(intersection);
}
}
}
}
junctions.sort_by_key(|p| (p.x.to_raw(), p.y.to_raw()));
junctions.dedup_by(|a, b| a.x == b.x && a.y == b.y);
junctions
}
fn find_intersection(&self, s1: &WireSegment, s2: &WireSegment) -> Option<CoordPoint> {
if (s1.is_horizontal() && s2.is_horizontal()) || (s1.is_vertical() && s2.is_vertical()) {
return None;
}
let (h_seg, v_seg) = if s1.is_horizontal() {
(s1, s2)
} else if s1.is_vertical() && s2.is_horizontal() {
(s2, s1)
} else {
return None;
};
let h_y = h_seg.start.y;
let h_x1 = h_seg.start.x.min(h_seg.end.x);
let h_x2 = h_seg.start.x.max(h_seg.end.x);
let v_x = v_seg.start.x;
let v_y1 = v_seg.start.y.min(v_seg.end.y);
let v_y2 = v_seg.start.y.max(v_seg.end.y);
if v_x >= h_x1 && v_x <= h_x2 && h_y >= v_y1 && h_y <= v_y2 {
Some(CoordPoint::new(v_x, h_y))
} else {
None
}
}
pub fn auto_route_all(
&mut self,
connections: &[(CoordPoint, CoordPoint)],
) -> Vec<Option<RoutingPath>> {
let mut results = Vec::new();
let mut sorted_connections: Vec<_> = connections.iter().enumerate().collect();
sorted_connections.sort_by_key(|(_, (start, end))| {
let dx = (end.x - start.x).abs().to_raw();
let dy = (end.y - start.y).abs().to_raw();
dx + dy
});
for (_, (start, end)) in sorted_connections {
let path = self.route(*start, *end);
if let Some(ref p) = path {
for segment in &p.segments {
self.existing_wires.push(*segment);
}
}
results.push(path);
}
results
}
}