mirror of https://codeberg.org/topola/topola.git
fix(math/bitangents): Calculate bitangents even for intersecting circles
This fixes the bug where the router was failing to draw around SMD pads.
This commit is contained in:
parent
5fd4926fb6
commit
3738bacf6f
|
|
@ -63,7 +63,7 @@ allowed_scopes = [
|
|||
"math/cyclic_search",
|
||||
"math/line",
|
||||
"math/polygon_tangents",
|
||||
"math/tangents",
|
||||
"math/bitangents",
|
||||
"math/tunnel",
|
||||
"router/draw",
|
||||
"router/navcord",
|
||||
|
|
|
|||
|
|
@ -235,13 +235,13 @@ impl<'a> Displayer<'a> {
|
|||
Self::node_guide_circle(board, navmesh, navcord, edge.source().0),
|
||||
Self::node_guide_circle(board, navmesh, navcord, edge.target().0),
|
||||
) {
|
||||
if let Ok(tangents) =
|
||||
math::tangent_segments(from_circle, None, to_circle, None)
|
||||
if let Ok(bitangents) =
|
||||
math::bitangents(from_circle, None, to_circle, None)
|
||||
{
|
||||
for tangent in tangents {
|
||||
for bitangent in bitangents {
|
||||
self.painter.paint_line_segment(
|
||||
tangent.start_point(),
|
||||
tangent.end_point(),
|
||||
bitangent.start_point(),
|
||||
bitangent.end_point(),
|
||||
egui::Stroke::new(1.0, egui::Color32::WHITE),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ use crate::{
|
|||
GetLayer, GetOffset, GetSetPos, GetWidth,
|
||||
},
|
||||
graph::{GenericIndex, GetPetgraphIndex, MakeRef},
|
||||
math::{NoTangents, RotationSense},
|
||||
math::{NoBitangents, RotationSense},
|
||||
};
|
||||
|
||||
use super::gear::{GetOuterGears, WalkOutwards};
|
||||
|
|
@ -47,7 +47,7 @@ use super::gear::{GetOuterGears, WalkOutwards};
|
|||
#[derive(Clone, Copy, Error)]
|
||||
pub enum DrawingException {
|
||||
#[error(transparent)]
|
||||
NoTangents(#[from] NoTangents),
|
||||
NoTangents(#[from] NoBitangents),
|
||||
#[error(transparent)]
|
||||
Infringement(#[from] Infringement),
|
||||
#[error(transparent)]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use geo::Line;
|
|||
|
||||
use crate::{
|
||||
geometry::{primitive::PrimitiveShape, shape::AccessShape, GetWidth},
|
||||
math::{self, Circle, NoTangents, RotationSense},
|
||||
math::{self, Circle, NoBitangents, RotationSense},
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
|
@ -25,7 +25,7 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
|
|||
head: &Head,
|
||||
into: FixedDotIndex,
|
||||
width: f64,
|
||||
) -> Result<Line, NoTangents> {
|
||||
) -> Result<Line, NoBitangents> {
|
||||
let from_circle = self.head_circle(head, width);
|
||||
let to_circle = Circle {
|
||||
pos: self.primitive(into).weight().0.circle.pos,
|
||||
|
|
@ -33,7 +33,7 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
|
|||
};
|
||||
|
||||
let from_sense = self.head_sense(head);
|
||||
math::tangent_segment(from_circle, from_sense, to_circle, None)
|
||||
math::bitangent(from_circle, from_sense, to_circle, None)
|
||||
}
|
||||
|
||||
pub fn guides_for_head_around_dot(
|
||||
|
|
@ -41,14 +41,14 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
|
|||
head: &Head,
|
||||
around: DotIndex,
|
||||
width: f64,
|
||||
) -> Result<(Line, Line), NoTangents> {
|
||||
) -> Result<(Line, Line), NoBitangents> {
|
||||
let from_circle = self.head_circle(head, width);
|
||||
let to_circle =
|
||||
self.dot_circle(around, width, self.conditions(head.face().into()).as_ref());
|
||||
|
||||
let from_sense = self.head_sense(head);
|
||||
let tangents: Vec<Line> =
|
||||
math::tangent_segments(from_circle, from_sense, to_circle, None)?.collect();
|
||||
math::bitangents(from_circle, from_sense, to_circle, None)?.collect();
|
||||
Ok((tangents[0], tangents[1]))
|
||||
}
|
||||
|
||||
|
|
@ -58,13 +58,13 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
|
|||
around: DotIndex,
|
||||
sense: RotationSense,
|
||||
width: f64,
|
||||
) -> Result<Line, NoTangents> {
|
||||
) -> Result<Line, NoBitangents> {
|
||||
let from_circle = self.head_circle(head, width);
|
||||
let to_circle =
|
||||
self.dot_circle(around, width, self.conditions(head.face().into()).as_ref());
|
||||
|
||||
let from_sense = self.head_sense(head);
|
||||
math::tangent_segment(from_circle, from_sense, to_circle, Some(sense))
|
||||
math::bitangent(from_circle, from_sense, to_circle, Some(sense))
|
||||
}
|
||||
|
||||
pub fn offset_for_guide_for_head_around_dot(
|
||||
|
|
@ -84,14 +84,14 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
|
|||
head: &Head,
|
||||
around: BendIndex,
|
||||
width: f64,
|
||||
) -> Result<(Line, Line), NoTangents> {
|
||||
) -> Result<(Line, Line), NoBitangents> {
|
||||
let from_circle = self.head_circle(head, width);
|
||||
let to_circle =
|
||||
self.bend_circle(around, width, self.conditions(head.face().into()).as_ref());
|
||||
|
||||
let from_sense = self.head_sense(head);
|
||||
let tangents: Vec<Line> =
|
||||
math::tangent_segments(from_circle, from_sense, to_circle, None)?.collect();
|
||||
math::bitangents(from_circle, from_sense, to_circle, None)?.collect();
|
||||
Ok((tangents[0], tangents[1]))
|
||||
}
|
||||
|
||||
|
|
@ -101,13 +101,13 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
|
|||
around: BendIndex,
|
||||
sense: RotationSense,
|
||||
width: f64,
|
||||
) -> Result<Line, NoTangents> {
|
||||
) -> Result<Line, NoBitangents> {
|
||||
let from_circle = self.head_circle(head, width);
|
||||
let to_circle =
|
||||
self.bend_circle(around, width, self.conditions(head.face().into()).as_ref());
|
||||
|
||||
let from_sense = self.head_sense(head);
|
||||
math::tangent_segment(from_circle, from_sense, to_circle, Some(sense))
|
||||
math::bitangent(from_circle, from_sense, to_circle, Some(sense))
|
||||
}
|
||||
|
||||
pub fn offset_for_guide_for_head_around_bend(
|
||||
|
|
|
|||
|
|
@ -10,9 +10,16 @@ use super::{seq_perp_dot_product, LineInGeneralForm, RotationSense};
|
|||
|
||||
#[derive(Error, Debug, Clone, Copy, PartialEq)]
|
||||
#[error("no tangents for {0:?} and {1:?}")] // TODO add real error message
|
||||
pub struct NoTangents(pub Circle, pub Circle);
|
||||
pub struct NoBitangents(pub Circle, pub Circle);
|
||||
|
||||
fn _bitangent(center: Point, r1: f64, r2: f64) -> Result<LineInGeneralForm, ()> {
|
||||
// Taken from https://cp-algorithms.com/geometry/tangents-to-two-circles.html
|
||||
// with small changes.
|
||||
|
||||
if approx::relative_eq!(center.x(), 0.0) && approx::relative_eq!(center.y(), 0.0) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
fn _tangent(center: Point, r1: f64, r2: f64) -> Result<LineInGeneralForm, ()> {
|
||||
let epsilon = 1e-9;
|
||||
let dr = r2 - r1;
|
||||
let norm = center.x() * center.x() + center.y() * center.y();
|
||||
|
|
@ -31,22 +38,25 @@ fn _tangent(center: Point, r1: f64, r2: f64) -> Result<LineInGeneralForm, ()> {
|
|||
})
|
||||
}
|
||||
|
||||
fn _tangents(circle1: Circle, circle2: Circle) -> Result<[LineInGeneralForm; 4], ()> {
|
||||
let mut tgs: [LineInGeneralForm; 4] = [
|
||||
_tangent((circle2 - circle1).pos, -circle1.r, -circle2.r)?,
|
||||
_tangent((circle2 - circle1).pos, -circle1.r, circle2.r)?,
|
||||
_tangent((circle2 - circle1).pos, circle1.r, -circle2.r)?,
|
||||
_tangent((circle2 - circle1).pos, circle1.r, circle2.r)?,
|
||||
];
|
||||
fn _bitangents(circle1: Circle, circle2: Circle) -> Vec<LineInGeneralForm> {
|
||||
let mut tgs: Vec<LineInGeneralForm> = [
|
||||
_bitangent((circle2 - circle1).pos, -circle1.r, -circle2.r),
|
||||
_bitangent((circle2 - circle1).pos, -circle1.r, circle2.r),
|
||||
_bitangent((circle2 - circle1).pos, circle1.r, -circle2.r),
|
||||
_bitangent((circle2 - circle1).pos, circle1.r, circle2.r),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
for tg in tgs.iter_mut() {
|
||||
tg.c -= tg.a * circle1.pos.x() + tg.b * circle1.pos.y();
|
||||
}
|
||||
|
||||
Ok(tgs)
|
||||
tgs
|
||||
}
|
||||
|
||||
fn cast_point_to_canonical_line(pt: Point, line: LineInGeneralForm) -> Point {
|
||||
fn cast_point_to_line(pt: Point, line: LineInGeneralForm) -> Point {
|
||||
(
|
||||
(line.b * (line.b * pt.x() - line.a * pt.y()) - line.a * line.c)
|
||||
/ (line.a * line.a + line.b * line.b),
|
||||
|
|
@ -56,39 +66,34 @@ fn cast_point_to_canonical_line(pt: Point, line: LineInGeneralForm) -> Point {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn tangent_point_pairs(
|
||||
fn bitangent_point_pairs(
|
||||
circle1: Circle,
|
||||
circle2: Circle,
|
||||
) -> Result<[(Point, Point); 4], NoTangents> {
|
||||
let tgs = _tangents(circle1, circle2).map_err(|_| NoTangents(circle1, circle2))?;
|
||||
) -> Result<Vec<(Point, Point)>, NoBitangents> {
|
||||
let bitangents: Vec<(Point, Point)> = _bitangents(circle1, circle2)
|
||||
.into_iter()
|
||||
.map(|tg| {
|
||||
(
|
||||
cast_point_to_line(circle1.pos, tg),
|
||||
cast_point_to_line(circle2.pos, tg),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok([
|
||||
(
|
||||
cast_point_to_canonical_line(circle1.pos, tgs[0]),
|
||||
cast_point_to_canonical_line(circle2.pos, tgs[0]),
|
||||
),
|
||||
(
|
||||
cast_point_to_canonical_line(circle1.pos, tgs[1]),
|
||||
cast_point_to_canonical_line(circle2.pos, tgs[1]),
|
||||
),
|
||||
(
|
||||
cast_point_to_canonical_line(circle1.pos, tgs[2]),
|
||||
cast_point_to_canonical_line(circle2.pos, tgs[2]),
|
||||
),
|
||||
(
|
||||
cast_point_to_canonical_line(circle1.pos, tgs[3]),
|
||||
cast_point_to_canonical_line(circle2.pos, tgs[3]),
|
||||
),
|
||||
])
|
||||
if bitangents.is_empty() {
|
||||
return Err(NoBitangents(circle1, circle2));
|
||||
}
|
||||
|
||||
Ok(bitangents)
|
||||
}
|
||||
|
||||
pub fn tangent_segments(
|
||||
pub fn bitangents(
|
||||
circle1: Circle,
|
||||
maybe_sense1: Option<RotationSense>,
|
||||
circle2: Circle,
|
||||
maybe_sense2: Option<RotationSense>,
|
||||
) -> Result<impl Iterator<Item = Line>, NoTangents> {
|
||||
Ok(tangent_point_pairs(circle1, circle2)?
|
||||
) -> Result<impl Iterator<Item = Line>, NoBitangents> {
|
||||
Ok(bitangent_point_pairs(circle1, circle2)?
|
||||
.into_iter()
|
||||
.filter_map(move |tangent_point_pair| {
|
||||
if let Some(sense1) = maybe_sense1 {
|
||||
|
|
@ -117,15 +122,13 @@ pub fn tangent_segments(
|
|||
}))
|
||||
}
|
||||
|
||||
pub fn tangent_segment(
|
||||
pub fn bitangent(
|
||||
circle1: Circle,
|
||||
maybe_sense1: Option<RotationSense>,
|
||||
circle2: Circle,
|
||||
maybe_sense2: Option<RotationSense>,
|
||||
) -> Result<Line, NoTangents> {
|
||||
Ok(
|
||||
tangent_segments(circle1, maybe_sense1, circle2, maybe_sense2)?
|
||||
) -> Result<Line, NoBitangents> {
|
||||
Ok(bitangents(circle1, maybe_sense1, circle2, maybe_sense2)?
|
||||
.next()
|
||||
.unwrap(),
|
||||
)
|
||||
.ok_or(NoBitangents(circle1, circle2))?)
|
||||
}
|
||||
|
|
@ -15,8 +15,8 @@ pub use line::*;
|
|||
mod polygon_tangents;
|
||||
pub use polygon_tangents::*;
|
||||
|
||||
mod tangents;
|
||||
pub use tangents::*;
|
||||
mod bitangents;
|
||||
pub use bitangents::*;
|
||||
|
||||
mod tunnel;
|
||||
pub use tunnel::*;
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ use crate::{
|
|||
},
|
||||
geometry::{GetLayer, GetSetPos},
|
||||
layout::{Layout, LayoutEdit},
|
||||
math::{Circle, NoTangents, RotationSense},
|
||||
math::{Circle, NoBitangents, RotationSense},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug, Clone, Copy)]
|
||||
pub enum DrawException {
|
||||
#[error(transparent)]
|
||||
NoTangents(#[from] NoTangents),
|
||||
NoTangents(#[from] NoBitangents),
|
||||
// TODO add real error messages + these should eventually use Display
|
||||
#[error("cannot finish in {0:?}")]
|
||||
CannotFinishIn(FixedDotIndex, #[source] DrawingException),
|
||||
|
|
|
|||
Loading…
Reference in New Issue