mirror of https://codeberg.org/topola/topola.git
fix(layout): improve calculation of band positions for Seg's
This commit is contained in:
parent
e7b120e8ed
commit
5c91235761
|
|
@ -52,6 +52,7 @@ contracts-try = "0.7"
|
||||||
derive-getters.workspace = true
|
derive-getters.workspace = true
|
||||||
enum_dispatch = "0.3"
|
enum_dispatch = "0.3"
|
||||||
geo.workspace = true
|
geo.workspace = true
|
||||||
|
log.workspace = true
|
||||||
petgraph.workspace = true
|
petgraph.workspace = true
|
||||||
rstar.workspace = true
|
rstar.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use std::f64::consts::TAU;
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use geo::algorithm::line_measures::{Distance, Euclidean};
|
use geo::algorithm::line_measures::{Distance, Euclidean};
|
||||||
use geo::{point, polygon, Contains, Intersects, Point, Polygon, Rotate};
|
use geo::{point, polygon, Contains, Intersects, Line, Point, Polygon, Rotate};
|
||||||
use rstar::{RTreeObject, AABB};
|
use rstar::{RTreeObject, AABB};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -148,6 +148,13 @@ impl SegShape {
|
||||||
|
|
||||||
polygon![p1.0, p2.0, p3.0, p4.0]
|
polygon![p1.0, p2.0, p3.0, p4.0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn middle_line(&self) -> Line {
|
||||||
|
Line {
|
||||||
|
start: self.from.into(),
|
||||||
|
end: self.to.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MeasureLength for SegShape {
|
impl MeasureLength for SegShape {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
gear::GearIndex,
|
gear::GearIndex,
|
||||||
graph::{GetMaybeNet, IsInLayer, MakePrimitive, PrimitiveIndex, PrimitiveWeight},
|
graph::{GetMaybeNet, IsInLayer, MakePrimitive, PrimitiveIndex, PrimitiveWeight},
|
||||||
loose::LooseIndex,
|
loose::LooseIndex,
|
||||||
primitive::MakePrimitiveShape,
|
primitive::{GetWeight, MakePrimitiveShape, Primitive},
|
||||||
rules::AccessRules,
|
rules::AccessRules,
|
||||||
seg::{
|
seg::{
|
||||||
FixedSegIndex, FixedSegWeight, LoneLooseSegIndex, LoneLooseSegWeight, SegIndex,
|
FixedSegIndex, FixedSegWeight, LoneLooseSegIndex, LoneLooseSegWeight, SegIndex,
|
||||||
|
|
@ -28,16 +28,18 @@ use crate::{
|
||||||
Cane, Collect, Drawing, DrawingEdit, DrawingException, Infringement,
|
Cane, Collect, Drawing, DrawingEdit, DrawingException, Infringement,
|
||||||
},
|
},
|
||||||
geometry::{
|
geometry::{
|
||||||
|
compound::ManageCompounds,
|
||||||
edit::ApplyGeometryEdit,
|
edit::ApplyGeometryEdit,
|
||||||
primitive::{AccessPrimitiveShape, PrimitiveShape, SegShape},
|
primitive::{AccessPrimitiveShape, PrimitiveShape, SegShape},
|
||||||
shape::{AccessShape, Shape},
|
shape::{AccessShape, Shape},
|
||||||
GenericNode,
|
GenericNode, GetSetPos,
|
||||||
},
|
},
|
||||||
graph::{GenericIndex, GetPetgraphIndex},
|
graph::{GenericIndex, GetPetgraphIndex},
|
||||||
layout::{
|
layout::{
|
||||||
poly::{MakePolygon, Poly, PolyWeight},
|
poly::{MakePolygon, Poly, PolyWeight},
|
||||||
via::{Via, ViaWeight},
|
via::{Via, ViaWeight},
|
||||||
},
|
},
|
||||||
|
math::{HomogeneousLine, LineIntersection},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents a weight for various compounds
|
/// Represents a weight for various compounds
|
||||||
|
|
@ -346,11 +348,6 @@ impl<R: AccessRules> Layout<R> {
|
||||||
pub fn center_of_high_level_node(&self, node: NodeIndex) -> Option<Point> {
|
pub fn center_of_high_level_node(&self, node: NodeIndex) -> Option<Point> {
|
||||||
match node {
|
match node {
|
||||||
NodeIndex::Primitive(primitive) => {
|
NodeIndex::Primitive(primitive) => {
|
||||||
use crate::drawing::{
|
|
||||||
graph::MakePrimitive,
|
|
||||||
primitive::{GetWeight, Primitive},
|
|
||||||
};
|
|
||||||
use crate::geometry::{compound::ManageCompounds, GetSetPos};
|
|
||||||
if self
|
if self
|
||||||
.drawing()
|
.drawing()
|
||||||
.geometry()
|
.geometry()
|
||||||
|
|
@ -366,10 +363,7 @@ impl<R: AccessRules> Layout<R> {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeIndex::Compound(cwidx) => {
|
NodeIndex::Compound(cwidx) => Some(self.node_shape(node).center()),
|
||||||
use crate::geometry::shape::AccessShape;
|
|
||||||
Some(self.node_shape(node).center())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,15 +379,21 @@ impl<R: AccessRules> Layout<R> {
|
||||||
assert_ne!(left, right);
|
assert_ne!(left, right);
|
||||||
let left_pos = self.node_shape(left).center();
|
let left_pos = self.node_shape(left).center();
|
||||||
let right_pos = self.node_shape(right).center();
|
let right_pos = self.node_shape(right).center();
|
||||||
let delta = right_pos - left_pos;
|
let ltr_line = geo::Line {
|
||||||
let delta_len = delta.y().hypot(delta.x());
|
start: left_pos.into(),
|
||||||
let fake_seg = PrimitiveShape::Seg(SegShape {
|
end: right_pos.into(),
|
||||||
from: left_pos,
|
};
|
||||||
to: right_pos,
|
let fake_seg = SegShape {
|
||||||
// TODO: being able to use a zero-width segshape here would be optimal
|
from: left_pos.into(),
|
||||||
// check if that works
|
to: right_pos.into(),
|
||||||
width: f64::EPSILON,
|
width: f64::EPSILON * 16.0,
|
||||||
});
|
};
|
||||||
|
let mut orig_hline = HomogeneousLine::from(ltr_line);
|
||||||
|
orig_hline.make_normal_unit();
|
||||||
|
let orig_hline = orig_hline;
|
||||||
|
let location_denom = orig_hline.segment_interval(<r_line);
|
||||||
|
let location_start = location_denom.start();
|
||||||
|
let location_denom = location_denom.end() - location_denom.start();
|
||||||
|
|
||||||
let mut bands: Vec<_> = self
|
let mut bands: Vec<_> = self
|
||||||
.drawing
|
.drawing
|
||||||
|
|
@ -418,12 +418,36 @@ impl<R: AccessRules> Layout<R> {
|
||||||
let shape = prim.primitive(&self.drawing).shape();
|
let shape = prim.primitive(&self.drawing).shape();
|
||||||
(prim, loose, shape)
|
(prim, loose, shape)
|
||||||
})
|
})
|
||||||
.filter(|(_, _, shape)| shape.intersects(&fake_seg))
|
.filter_map(|(prim, loose, shape)| {
|
||||||
.map(|(_, loose, shape)| {
|
|
||||||
let band_uid = self.drawing.loose_band_uid(loose);
|
let band_uid = self.drawing.loose_band_uid(loose);
|
||||||
// TODO: check that the resulting order is correct
|
let loose_hline = orig_hline.orthogonal_through(&match shape {
|
||||||
let location = crate::math::dot_product(delta, shape.center()) / delta_len;
|
PrimitiveShape::Seg(seg) => {
|
||||||
(location, band_uid)
|
let seg_hline = HomogeneousLine::from(seg.middle_line());
|
||||||
|
match orig_hline.intersects(&seg_hline) {
|
||||||
|
LineIntersection::Empty => return None,
|
||||||
|
LineIntersection::Overlapping => shape.center(),
|
||||||
|
LineIntersection::Point(pt) => pt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !fake_seg.intersects(&shape) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
shape.center()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let location = (loose_hline.offset - location_start) / location_denom;
|
||||||
|
log::trace!(
|
||||||
|
"intersection ({:?}) with {:?} is at {:?}",
|
||||||
|
band_uid,
|
||||||
|
shape,
|
||||||
|
location
|
||||||
|
);
|
||||||
|
if 0.0 <= location && location <= 1.0 {
|
||||||
|
Some((location, band_uid))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
bands.sort_by(|a, b| f64::total_cmp(&a.0, &b.0));
|
bands.sort_by(|a, b| f64::total_cmp(&a.0, &b.0));
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,94 @@ pub use specctra_core::math::{Circle, PointWithRotation};
|
||||||
mod tangents;
|
mod tangents;
|
||||||
pub use tangents::*;
|
pub use tangents::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum LineIntersection {
|
||||||
|
Empty,
|
||||||
|
Overlapping,
|
||||||
|
Point(Point),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct HomogeneousLine<T> {
|
||||||
|
// x0*x + y0*y = offset
|
||||||
|
pub x: T,
|
||||||
|
pub y: T,
|
||||||
|
pub offset: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Line> for HomogeneousLine<f64> {
|
||||||
|
fn from(l: Line) -> Self {
|
||||||
|
// the normal vector is perpendicular to the line
|
||||||
|
let normal = geo::point! {
|
||||||
|
x: l.dy(),
|
||||||
|
y: -l.dx(),
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
x: normal.0.x,
|
||||||
|
y: normal.0.y,
|
||||||
|
offset: perp_dot_product(l.end.into(), l.start.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HomogeneousLine<f64> {
|
||||||
|
pub fn evaluate_at(&self, pt: Point) -> f64 {
|
||||||
|
self.x * pt.x() + self.y * pt.y() - self.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn angle(&self) -> f64 {
|
||||||
|
self.y.atan2(self.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_normal_unit(&mut self) {
|
||||||
|
let normal_len = self.y.hypot(self.x);
|
||||||
|
if normal_len > (f64::EPSILON * 16.0) {
|
||||||
|
self.x /= normal_len;
|
||||||
|
self.y /= normal_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersects(&self, b: &Self) -> LineIntersection {
|
||||||
|
const ALMOST_ZERO: f64 = f64::EPSILON * 16.0;
|
||||||
|
let (mut a, mut b) = (*self, *b);
|
||||||
|
let _ = (a.make_normal_unit(), b.make_normal_unit());
|
||||||
|
let apt = geo::point! { x: a.x, y: a.y };
|
||||||
|
let bpt = geo::point! { x: b.x, y: b.y };
|
||||||
|
let det = perp_dot_product(apt, bpt);
|
||||||
|
let rpx = b.y * a.offset - a.y * b.offset;
|
||||||
|
let rpy = -b.x * a.offset + a.x * b.offset;
|
||||||
|
|
||||||
|
if det.abs() > ALMOST_ZERO {
|
||||||
|
LineIntersection::Point(geo::point! { x: rpx, y: rpy } / det)
|
||||||
|
} else if rpx.abs() <= ALMOST_ZERO && rpy.abs() <= ALMOST_ZERO {
|
||||||
|
LineIntersection::Overlapping
|
||||||
|
} else {
|
||||||
|
LineIntersection::Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// project the point `pt` onto this line, and generate a new line which is orthogonal
|
||||||
|
/// to `self`, and goes through `pt`.
|
||||||
|
#[inline]
|
||||||
|
pub fn orthogonal_through(&self, pt: &Point) -> Self {
|
||||||
|
Self {
|
||||||
|
// recover the original parallel vector
|
||||||
|
x: -self.y,
|
||||||
|
y: self.x,
|
||||||
|
offset: self.x * pt.0.y - self.y * pt.0.x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn segment_interval(&self, line: &Line) -> core::ops::RangeInclusive<f64> {
|
||||||
|
// recover the original parallel vector
|
||||||
|
let parv = geo::point! {
|
||||||
|
x: -self.y,
|
||||||
|
y: self.x,
|
||||||
|
};
|
||||||
|
dot_product(parv, line.start.into())..=dot_product(parv, line.end.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn intersect_circles(circle1: &Circle, circle2: &Circle) -> Vec<Point> {
|
pub fn intersect_circles(circle1: &Circle, circle2: &Circle) -> Vec<Point> {
|
||||||
let delta = circle2.pos - circle1.pos;
|
let delta = circle2.pos - circle1.pos;
|
||||||
let d = Euclidean::distance(&circle2.pos, &circle1.pos);
|
let d = Euclidean::distance(&circle2.pos, &circle1.pos);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue