topola/src/router/ng/poly.rs

207 lines
5.9 KiB
Rust

// SPDX-FileCopyrightText: 2025 Topola contributors
//
// SPDX-License-Identifier: MIT
use geo::Point;
use specctra_core::rules::AccessRules;
use crate::{
board::edit::BoardEdit,
drawing::{
band::BandUid,
dot::FixedDotIndex,
graph::{MakePrimitiveRef as _, PrimitiveIndex},
head::{CaneHead, Head},
primitive::MakePrimitiveShape as _,
},
geometry::{compound::ManageCompounds, shape::AccessShape as _, GetSetPos as _},
graph::{GenericIndex, GetIndex as _},
layout::{poly::PolyWeight, CompoundEntryLabel, Layout},
math::{is_poly_convex_hull_cw, CachedPolyExt, RotationSense},
router::ng::{
pie::{mayrev, utils::rotate_iter},
EvalException,
},
};
#[derive(Clone, Debug)]
// the entry point is where `active_head` ends
// TODO: two-phase initialization via separate types
pub struct PolygonRouting {
pub idx: GenericIndex<PolyWeight>,
pub apex: FixedDotIndex,
pub convex_hull: CachedPolyExt<FixedDotIndex>,
pub entry_point: Option<FixedDotIndex>,
pub inner: Option<BandUid>,
pub cw: RotationSense,
}
impl PolygonRouting {
/// calculates the convex hull of a poly exterior
pub fn new<R>(
layout: &Layout<R>,
cw: RotationSense,
polyidx: GenericIndex<PolyWeight>,
apex: FixedDotIndex,
) -> Self {
let convex_hull = layout
.drawing()
.geometry()
.compound_members(GenericIndex::new(polyidx.index()))
.filter_map(|(entry_label, primitive_node)| {
let PrimitiveIndex::FixedDot(poly_dot) = primitive_node else {
return None;
};
if apex == poly_dot {
None
} else {
Some((
layout
.drawing()
.geometry()
.dot_weight(poly_dot.into())
.pos(),
entry_label,
poly_dot,
))
}
})
.filter(|(_, entry_label, _)| *entry_label == CompoundEntryLabel::Normal)
.map(|(pt, _, idx)| (pt, idx))
.collect::<Vec<_>>();
// `poly_ext` is convex, so any pivot point is okay
let ext_is_cw = is_poly_convex_hull_cw(&convex_hull[..], 1);
Self {
idx: polyidx,
apex,
convex_hull: CachedPolyExt::new(&convex_hull[..], ext_is_cw),
entry_point: None,
inner: None,
cw,
}
}
pub fn center<R: AccessRules>(&self, layout: &Layout<R>) -> Point {
self.apex.primitive_ref(layout.drawing()).shape().center()
}
/// calculate the entry or exit point for the polygon (set `invert_cw` to `true` for exit point)
pub fn entry_point(
&self,
destination: Point,
invert_cw: bool,
) -> Result<FixedDotIndex, EvalException> {
// note that the left-most point has the greatest angle (measured counter-clockwise)
let Some((lhs, rhs)) = self.convex_hull.tangent_points(destination) else {
return Err(EvalException::InvalidPolyTangentData {
poly_ext: self.convex_hull.clone(),
origin: destination,
});
};
let cw = matches!(self.cw, RotationSense::Clockwise);
Ok(if invert_cw ^ cw { lhs } else { rhs })
}
fn route_next<R: AccessRules>(
&self,
layout: &mut Layout<R>,
recorder: &mut BoardEdit,
route_length: &mut f64,
old_head: Head,
ext_core: FixedDotIndex,
width: f64,
) -> Result<CaneHead, EvalException> {
super::cane_around(
layout,
recorder,
route_length,
old_head,
ext_core,
self.inner,
self.cw,
width,
)
}
pub fn route_to_entry<R: AccessRules>(
&self,
layout: &mut Layout<R>,
recorder: &mut BoardEdit,
old_head: Head,
entry_point: FixedDotIndex,
width: f64,
) -> Result<(Head, f64), EvalException> {
let mut route_length = 0.0;
let active_head = Head::Cane(self.route_next(
layout,
recorder,
&mut route_length,
old_head,
entry_point,
width,
)?);
Ok((active_head, route_length))
}
pub fn route_to_exit<R: AccessRules>(
&self,
layout: &mut Layout<R>,
recorder: &mut BoardEdit,
mut active_head: Head,
exit: FixedDotIndex,
width: f64,
) -> Result<(Head, f64), EvalException> {
let old_entry = self.entry_point.unwrap();
if old_entry == exit {
// nothing to do
return Ok((active_head, 0.0));
}
log::debug!(
"route_to_exit on {:?} from {:?} to {:?} sense {:?}",
self.apex,
old_entry,
exit,
self.cw,
);
let mut mr = mayrev::MaybeReversed::new(&self.convex_hull.0[..]);
// the convex hull is oriented counter-clockwise
mr.reversed = matches!(self.cw, RotationSense::Clockwise);
let mut route_length = 0.0;
for pdot in rotate_iter(Iterator::map(mr.iter(), |(_, pdot, _)| *pdot), |&pdot| {
pdot == old_entry
})
.1
.skip(1)
{
// route along the origin polygon
active_head = Head::Cane(self.route_next(
layout,
recorder,
&mut route_length,
active_head,
pdot,
width,
)?);
if pdot == exit {
break;
}
}
log::debug!(
"route_to_exit on {:?} finished, head = {:?}",
self.apex,
active_head,
);
Ok((active_head, route_length))
}
}