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/poly",
|
||||
"layout/via",
|
||||
"math/circle",
|
||||
"math/cyclic_search",
|
||||
"math/line",
|
||||
"math/mod",
|
||||
"math/polygon_tangents",
|
||||
"math/bitangents",
|
||||
"math/tunnel",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use core::{fmt, ops::Sub};
|
||||
use core::fmt;
|
||||
use geo_types::geometry::Point;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -18,50 +18,6 @@ pub struct PointWithRotation {
|
|||
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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PointWithRotation")
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl<R: AccessRules> Layout<R> {
|
|||
let loose_hline = orig_hline.orthogonal_through(&match shape {
|
||||
PrimitiveShape::Seg(seg) => {
|
||||
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::Overlapping => shape.center(),
|
||||
LineIntersection::Point(pt) => pt,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use geo::{geometry::Point, Line};
|
||||
use specctra_core::math::Circle;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::math::Circle;
|
||||
|
||||
use super::{seq_perp_dot_product, LineInGeneralForm, RotationSense};
|
||||
|
||||
#[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.
|
||||
pub fn intersects(&self, b: &Self) -> LineIntersection {
|
||||
pub fn intersect(&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());
|
||||
|
|
@ -105,11 +105,11 @@ impl LineInGeneralForm {
|
|||
}
|
||||
|
||||
/// 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 nline2 = LineInGeneralForm::from(*line2);
|
||||
|
||||
match nline1.intersects(&nline2) {
|
||||
match nline1.intersect(&nline2) {
|
||||
LineIntersection::Empty | LineIntersection::Overlapping => None,
|
||||
LineIntersection::Point(pt) => {
|
||||
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 nray2 = LineInGeneralForm::from(*ray2);
|
||||
|
||||
match nline1.intersects(&nray2) {
|
||||
match nline1.intersect(&nray2) {
|
||||
LineIntersection::Empty | LineIntersection::Overlapping => None,
|
||||
LineIntersection::Point(pt) => {
|
||||
let parv1 = geo::point! {
|
||||
|
|
@ -194,7 +194,7 @@ mod tests {
|
|||
#[test]
|
||||
fn intersect_line_and_line00() {
|
||||
assert_eq!(
|
||||
intersect_lines(
|
||||
intersect_line_segments(
|
||||
&Line {
|
||||
start: 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. })
|
||||
);
|
||||
assert_eq!(
|
||||
intersect_lines(
|
||||
intersect_line_segments(
|
||||
&Line {
|
||||
start: geo::coord! { x: -1., y: -1. },
|
||||
end: geo::coord! { x: 1., y: 1. },
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use geo::algorithm::line_measures::{Distance, Euclidean};
|
||||
use geo::{point, Line, Point};
|
||||
pub use specctra_core::math::{Circle, PointWithRotation};
|
||||
use geo::Point;
|
||||
|
||||
mod cyclic_search;
|
||||
pub use cyclic_search::*;
|
||||
|
|
@ -21,6 +19,9 @@ pub use bitangents::*;
|
|||
mod tunnel;
|
||||
pub use tunnel::*;
|
||||
|
||||
mod circle;
|
||||
pub use circle::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum RotationSense {
|
||||
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
|
||||
/// `from` and `to`.
|
||||
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 geo::{Point, Rotate};
|
||||
use specctra_core::math::PointWithRotation;
|
||||
|
||||
use crate::{
|
||||
board::{edit::BoardEdit, AccessMesadata, Board},
|
||||
|
|
@ -21,7 +22,7 @@ use crate::{
|
|||
},
|
||||
geometry::{primitive::PrimitiveShape, GetLayer, GetWidth},
|
||||
layout::{poly::SolidPolyWeight, Layout},
|
||||
math::{Circle, PointWithRotation},
|
||||
math::Circle,
|
||||
specctra::{
|
||||
mesadata::SpecctraMesadata,
|
||||
read::ListTokenizer,
|
||||
|
|
|
|||
Loading…
Reference in New Issue