mirror of https://codeberg.org/topola/topola.git
fix(math/mod): Move code pertaining to circles to new file, circle.rs
This commit is contained in:
parent
a9cb7f8848
commit
4a057d3499
|
|
@ -60,8 +60,10 @@ allowed_scopes = [
|
||||||
"layout/layout",
|
"layout/layout",
|
||||||
"layout/poly",
|
"layout/poly",
|
||||||
"layout/via",
|
"layout/via",
|
||||||
|
"math/circle",
|
||||||
"math/cyclic_search",
|
"math/cyclic_search",
|
||||||
"math/line",
|
"math/line",
|
||||||
|
"math/mod",
|
||||||
"math/polygon_tangents",
|
"math/polygon_tangents",
|
||||||
"math/bitangents",
|
"math/bitangents",
|
||||||
"math/tunnel",
|
"math/tunnel",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use core::{fmt, ops::Sub};
|
use core::fmt;
|
||||||
use geo_types::geometry::Point;
|
use geo_types::geometry::Point;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -18,50 +18,6 @@ pub struct PointWithRotation {
|
||||||
pub rot: f64,
|
pub rot: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Circle {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("Circle")
|
|
||||||
.field("x", &self.pos.0.x)
|
|
||||||
.field("y", &self.pos.0.y)
|
|
||||||
.field("r", &self.r)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Circle {
|
|
||||||
/// Calculate the point that lies on the circle at angle `phi`,
|
|
||||||
/// relative to coordinate axes.
|
|
||||||
///
|
|
||||||
/// `phi` is the angle given in radians starting at `(r, 0)`.
|
|
||||||
pub fn position_at_angle(&self, phi: f64) -> Point {
|
|
||||||
geo_types::point! {
|
|
||||||
x: self.pos.0.x + self.r * phi.cos(),
|
|
||||||
y: self.pos.0.y + self.r * phi.sin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The (x,y) axis aligned bounding box for this circle.
|
|
||||||
#[cfg(feature = "rstar")]
|
|
||||||
pub fn bbox(&self, margin: f64) -> rstar::AABB<[f64; 2]> {
|
|
||||||
let r = self.r + margin;
|
|
||||||
rstar::AABB::from_corners(
|
|
||||||
[self.pos.0.x - r, self.pos.0.y - r],
|
|
||||||
[self.pos.0.x + r, self.pos.0.y + r],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sub for Circle {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn sub(self, other: Self) -> Self {
|
|
||||||
Self {
|
|
||||||
pos: self.pos - other.pos,
|
|
||||||
r: self.r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for PointWithRotation {
|
impl fmt::Debug for PointWithRotation {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("PointWithRotation")
|
f.debug_struct("PointWithRotation")
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ impl<R: AccessRules> Layout<R> {
|
||||||
let loose_hline = orig_hline.orthogonal_through(&match shape {
|
let loose_hline = orig_hline.orthogonal_through(&match shape {
|
||||||
PrimitiveShape::Seg(seg) => {
|
PrimitiveShape::Seg(seg) => {
|
||||||
let seg_hline = LineInGeneralForm::from(seg.middle_line());
|
let seg_hline = LineInGeneralForm::from(seg.middle_line());
|
||||||
match orig_hline.intersects(&seg_hline) {
|
match orig_hline.intersect(&seg_hline) {
|
||||||
LineIntersection::Empty => return None,
|
LineIntersection::Empty => return None,
|
||||||
LineIntersection::Overlapping => shape.center(),
|
LineIntersection::Overlapping => shape.center(),
|
||||||
LineIntersection::Point(pt) => pt,
|
LineIntersection::Point(pt) => pt,
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use geo::{geometry::Point, Line};
|
use geo::{geometry::Point, Line};
|
||||||
use specctra_core::math::Circle;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::math::Circle;
|
||||||
|
|
||||||
use super::{seq_perp_dot_product, LineInGeneralForm, RotationSense};
|
use super::{seq_perp_dot_product, LineInGeneralForm, RotationSense};
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy, PartialEq)]
|
#[derive(Error, Debug, Clone, Copy, PartialEq)]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Topola contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use std::ops::Sub;
|
||||||
|
|
||||||
|
use geo::{point, Distance, Euclidean, Line, Point};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||||
|
pub struct Circle {
|
||||||
|
pub pos: Point,
|
||||||
|
pub r: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Circle {
|
||||||
|
/// Calculate the point that lies on the circle at angle `phi`,
|
||||||
|
/// relative to coordinate axes.
|
||||||
|
///
|
||||||
|
/// `phi` is the angle given in radians starting at `(r, 0)`.
|
||||||
|
pub fn position_at_angle(&self, phi: f64) -> Point {
|
||||||
|
point! {
|
||||||
|
x: self.pos.0.x + self.r * phi.cos(),
|
||||||
|
y: self.pos.0.y + self.r * phi.sin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The (x,y) axis aligned bounding box for this circle.
|
||||||
|
pub fn bbox(&self, margin: f64) -> rstar::AABB<[f64; 2]> {
|
||||||
|
let r = self.r + margin;
|
||||||
|
rstar::AABB::from_corners(
|
||||||
|
[self.pos.0.x - r, self.pos.0.y - r],
|
||||||
|
[self.pos.0.x + r, self.pos.0.y + r],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for Circle {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(self, other: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
pos: self.pos - other.pos,
|
||||||
|
r: self.r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the intersection of two circles, `circle1` and `circle2`.
|
||||||
|
///
|
||||||
|
/// Returns a `Vec` holding zero, one, or two calculated intersection points,
|
||||||
|
/// depending on how many exist.
|
||||||
|
pub fn intersect_circles(circle1: &Circle, circle2: &Circle) -> Vec<Point> {
|
||||||
|
let delta = circle2.pos - circle1.pos;
|
||||||
|
let d = Euclidean::distance(&circle2.pos, &circle1.pos);
|
||||||
|
|
||||||
|
if d > circle1.r + circle2.r {
|
||||||
|
// No intersection.
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
if d < (circle2.r - circle1.r).abs() {
|
||||||
|
// One contains the other.
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance from `circle1.pos` to the intersection of the diagonals.
|
||||||
|
let a = (circle1.r * circle1.r - circle2.r * circle2.r + d * d) / (2.0 * d);
|
||||||
|
|
||||||
|
// Intersection of the diagonals.
|
||||||
|
let p = circle1.pos + delta * (a / d);
|
||||||
|
let h = (circle1.r * circle1.r - a * a).sqrt();
|
||||||
|
|
||||||
|
if h == 0.0 {
|
||||||
|
return [p].into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = point! {x: -delta.x(), y: delta.y()} * (h / d);
|
||||||
|
|
||||||
|
[p + r, p - r].into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the intersection between circle `circle` and line segment `segment`.
|
||||||
|
///
|
||||||
|
/// Returns a `Vec` holding zero, one, or two calculated intersection points,
|
||||||
|
/// depending on how many exist.
|
||||||
|
pub fn intersect_circle_segment(circle: &Circle, segment: &Line) -> Vec<Point> {
|
||||||
|
let delta: Point = segment.delta().into();
|
||||||
|
let from = segment.start_point();
|
||||||
|
let to = segment.end_point();
|
||||||
|
let epsilon = 1e-9;
|
||||||
|
let interval01 = 0.0..=1.0;
|
||||||
|
|
||||||
|
let a = delta.dot(delta);
|
||||||
|
let b =
|
||||||
|
2.0 * (delta.x() * (from.x() - circle.pos.x()) + delta.y() * (from.y() - circle.pos.y()));
|
||||||
|
let c = circle.pos.dot(circle.pos) + from.dot(from)
|
||||||
|
- 2.0 * circle.pos.dot(from)
|
||||||
|
- circle.r * circle.r;
|
||||||
|
let discriminant = b * b - 4.0 * a * c;
|
||||||
|
|
||||||
|
if a.abs() < epsilon || discriminant < 0.0 {
|
||||||
|
return [].into();
|
||||||
|
}
|
||||||
|
|
||||||
|
if discriminant == 0.0 {
|
||||||
|
let u = -b / (2.0 * a);
|
||||||
|
|
||||||
|
return if interval01.contains(&u) {
|
||||||
|
vec![from + (to - from) * -b / (2.0 * a)]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut v = vec![];
|
||||||
|
|
||||||
|
let u1 = (-b + discriminant.sqrt()) / (2.0 * a);
|
||||||
|
|
||||||
|
if interval01.contains(&u1) {
|
||||||
|
v.push(from + (to - from) * u1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let u2 = (-b - discriminant.sqrt()) / (2.0 * a);
|
||||||
|
|
||||||
|
if interval01.contains(&u2) {
|
||||||
|
v.push(from + (to - from) * u2);
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ impl LineInGeneralForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the intersection between two lines.
|
/// Calculate the intersection between two lines.
|
||||||
pub fn intersects(&self, b: &Self) -> LineIntersection {
|
pub fn intersect(&self, b: &Self) -> LineIntersection {
|
||||||
const ALMOST_ZERO: f64 = f64::EPSILON * 16.0;
|
const ALMOST_ZERO: f64 = f64::EPSILON * 16.0;
|
||||||
let (mut a, mut b) = (*self, *b);
|
let (mut a, mut b) = (*self, *b);
|
||||||
let _ = (a.make_normal_unit(), b.make_normal_unit());
|
let _ = (a.make_normal_unit(), b.make_normal_unit());
|
||||||
|
|
@ -105,11 +105,11 @@ impl LineInGeneralForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some(p)` when `p` lies in the intersection of the given lines.
|
/// Returns `Some(p)` when `p` lies in the intersection of the given lines.
|
||||||
pub fn intersect_lines(line1: &Line, line2: &Line) -> Option<Point> {
|
pub fn intersect_line_segments(line1: &Line, line2: &Line) -> Option<Point> {
|
||||||
let nline1 = LineInGeneralForm::from(*line1);
|
let nline1 = LineInGeneralForm::from(*line1);
|
||||||
let nline2 = LineInGeneralForm::from(*line2);
|
let nline2 = LineInGeneralForm::from(*line2);
|
||||||
|
|
||||||
match nline1.intersects(&nline2) {
|
match nline1.intersect(&nline2) {
|
||||||
LineIntersection::Empty | LineIntersection::Overlapping => None,
|
LineIntersection::Empty | LineIntersection::Overlapping => None,
|
||||||
LineIntersection::Point(pt) => {
|
LineIntersection::Point(pt) => {
|
||||||
let parv1 = geo::point! {
|
let parv1 = geo::point! {
|
||||||
|
|
@ -142,7 +142,7 @@ pub fn intersect_line_and_ray(line1: &Line, ray2: &Line) -> Option<Point> {
|
||||||
let nline1 = LineInGeneralForm::from(*line1);
|
let nline1 = LineInGeneralForm::from(*line1);
|
||||||
let nray2 = LineInGeneralForm::from(*ray2);
|
let nray2 = LineInGeneralForm::from(*ray2);
|
||||||
|
|
||||||
match nline1.intersects(&nray2) {
|
match nline1.intersect(&nray2) {
|
||||||
LineIntersection::Empty | LineIntersection::Overlapping => None,
|
LineIntersection::Empty | LineIntersection::Overlapping => None,
|
||||||
LineIntersection::Point(pt) => {
|
LineIntersection::Point(pt) => {
|
||||||
let parv1 = geo::point! {
|
let parv1 = geo::point! {
|
||||||
|
|
@ -194,7 +194,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn intersect_line_and_line00() {
|
fn intersect_line_and_line00() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
intersect_lines(
|
intersect_line_segments(
|
||||||
&Line {
|
&Line {
|
||||||
start: geo::coord! { x: -1., y: -1. },
|
start: geo::coord! { x: -1., y: -1. },
|
||||||
end: geo::coord! { x: 1., y: 1. },
|
end: geo::coord! { x: 1., y: 1. },
|
||||||
|
|
@ -207,7 +207,7 @@ mod tests {
|
||||||
Some(geo::point! { x: 0., y: 0. })
|
Some(geo::point! { x: 0., y: 0. })
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
intersect_lines(
|
intersect_line_segments(
|
||||||
&Line {
|
&Line {
|
||||||
start: geo::coord! { x: -1., y: -1. },
|
start: geo::coord! { x: -1., y: -1. },
|
||||||
end: geo::coord! { x: 1., y: 1. },
|
end: geo::coord! { x: 1., y: 1. },
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use geo::algorithm::line_measures::{Distance, Euclidean};
|
use geo::Point;
|
||||||
use geo::{point, Line, Point};
|
|
||||||
pub use specctra_core::math::{Circle, PointWithRotation};
|
|
||||||
|
|
||||||
mod cyclic_search;
|
mod cyclic_search;
|
||||||
pub use cyclic_search::*;
|
pub use cyclic_search::*;
|
||||||
|
|
@ -21,6 +19,9 @@ pub use bitangents::*;
|
||||||
mod tunnel;
|
mod tunnel;
|
||||||
pub use tunnel::*;
|
pub use tunnel::*;
|
||||||
|
|
||||||
|
mod circle;
|
||||||
|
pub use circle::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum RotationSense {
|
pub enum RotationSense {
|
||||||
Counterclockwise,
|
Counterclockwise,
|
||||||
|
|
@ -49,90 +50,6 @@ impl RotationSense {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the intersection of two circles, `circle1` and `circle2`.
|
|
||||||
///
|
|
||||||
/// Returns a `Vec` holding zero, one, or two calculated intersection points,
|
|
||||||
/// depending on how many exist.
|
|
||||||
pub fn intersect_circles(circle1: &Circle, circle2: &Circle) -> Vec<Point> {
|
|
||||||
let delta = circle2.pos - circle1.pos;
|
|
||||||
let d = Euclidean::distance(&circle2.pos, &circle1.pos);
|
|
||||||
|
|
||||||
if d > circle1.r + circle2.r {
|
|
||||||
// No intersection.
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
if d < (circle2.r - circle1.r).abs() {
|
|
||||||
// One contains the other.
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distance from `circle1.pos` to the intersection of the diagonals.
|
|
||||||
let a = (circle1.r * circle1.r - circle2.r * circle2.r + d * d) / (2.0 * d);
|
|
||||||
|
|
||||||
// Intersection of the diagonals.
|
|
||||||
let p = circle1.pos + delta * (a / d);
|
|
||||||
let h = (circle1.r * circle1.r - a * a).sqrt();
|
|
||||||
|
|
||||||
if h == 0.0 {
|
|
||||||
return [p].into();
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = point! {x: -delta.x(), y: delta.y()} * (h / d);
|
|
||||||
|
|
||||||
[p + r, p - r].into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the intersection between circle `circle` and line segment `segment`.
|
|
||||||
///
|
|
||||||
/// Returns a `Vec` holding zero, one, or two calculated intersection points,
|
|
||||||
/// depending on how many exist.
|
|
||||||
pub fn intersect_circle_segment(circle: &Circle, segment: &Line) -> Vec<Point> {
|
|
||||||
let delta: Point = segment.delta().into();
|
|
||||||
let from = segment.start_point();
|
|
||||||
let to = segment.end_point();
|
|
||||||
let epsilon = 1e-9;
|
|
||||||
let interval01 = 0.0..=1.0;
|
|
||||||
|
|
||||||
let a = delta.dot(delta);
|
|
||||||
let b =
|
|
||||||
2.0 * (delta.x() * (from.x() - circle.pos.x()) + delta.y() * (from.y() - circle.pos.y()));
|
|
||||||
let c = circle.pos.dot(circle.pos) + from.dot(from)
|
|
||||||
- 2.0 * circle.pos.dot(from)
|
|
||||||
- circle.r * circle.r;
|
|
||||||
let discriminant = b * b - 4.0 * a * c;
|
|
||||||
|
|
||||||
if a.abs() < epsilon || discriminant < 0.0 {
|
|
||||||
return [].into();
|
|
||||||
}
|
|
||||||
|
|
||||||
if discriminant == 0.0 {
|
|
||||||
let u = -b / (2.0 * a);
|
|
||||||
|
|
||||||
return if interval01.contains(&u) {
|
|
||||||
vec![from + (to - from) * -b / (2.0 * a)]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut v = vec![];
|
|
||||||
|
|
||||||
let u1 = (-b + discriminant.sqrt()) / (2.0 * a);
|
|
||||||
|
|
||||||
if interval01.contains(&u1) {
|
|
||||||
v.push(from + (to - from) * u1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let u2 = (-b - discriminant.sqrt()) / (2.0 * a);
|
|
||||||
|
|
||||||
if interval01.contains(&u2) {
|
|
||||||
v.push(from + (to - from) * u2);
|
|
||||||
}
|
|
||||||
|
|
||||||
v
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` the point `p` is between the supporting lines of vectors
|
/// Returns `true` the point `p` is between the supporting lines of vectors
|
||||||
/// `from` and `to`.
|
/// `from` and `to`.
|
||||||
pub fn between_vectors(p: Point, from: Point, to: Point) -> bool {
|
pub fn between_vectors(p: Point, from: Point, to: Point) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
use std::collections::{btree_map::Entry as BTreeMapEntry, BTreeMap};
|
use std::collections::{btree_map::Entry as BTreeMapEntry, BTreeMap};
|
||||||
|
|
||||||
use geo::{Point, Rotate};
|
use geo::{Point, Rotate};
|
||||||
|
use specctra_core::math::PointWithRotation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{edit::BoardEdit, AccessMesadata, Board},
|
board::{edit::BoardEdit, AccessMesadata, Board},
|
||||||
|
|
@ -21,7 +22,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
geometry::{primitive::PrimitiveShape, GetLayer, GetWidth},
|
geometry::{primitive::PrimitiveShape, GetLayer, GetWidth},
|
||||||
layout::{poly::SolidPolyWeight, Layout},
|
layout::{poly::SolidPolyWeight, Layout},
|
||||||
math::{Circle, PointWithRotation},
|
math::Circle,
|
||||||
specctra::{
|
specctra::{
|
||||||
mesadata::SpecctraMesadata,
|
mesadata::SpecctraMesadata,
|
||||||
read::ListTokenizer,
|
read::ListTokenizer,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue