diff --git a/topola/src/compass.rs b/topola/src/compass.rs index e5d5b96..75b396d 100644 --- a/topola/src/compass.rs +++ b/topola/src/compass.rs @@ -13,6 +13,55 @@ pub trait CompassDirection: Copy + PartialEq + Into> { fn nearest_from_vector(vector: Vector2) -> Self; fn turn_clockwise(self) -> Self; fn turn_counterclockwise(self) -> Self; + + fn cast_vector(self, vector: Vector2) -> Vector2 + where + T: Copy + Neg + PartialOrd + Signed + Zero, + { + let axis = self.into(); + let zero = T::zero(); + + if axis.y.is_zero() { + let x = if axis.x < zero { + -vector.x + } else { + vector.x + }; + return Vector2::new(x, zero); + } + + if axis.x.is_zero() { + let y = if axis.y < zero { + -vector.y + } else { + vector.y + }; + return Vector2::new(zero, y); + } + + let magnitude = if vector.x.abs() < vector.y.abs() { + vector.x.abs() + } else { + vector.y.abs() + }; + + Vector2::new( + if axis.x > zero { + magnitude + } else if axis.x < zero { + -magnitude + } else { + zero + }, + if axis.y > zero { + magnitude + } else if axis.y < zero { + -magnitude + } else { + zero + }, + ) + } } #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] diff --git a/topola/src/layout/compounds/component.rs b/topola/src/layout/compounds/component.rs index 39e92ed..186b810 100644 --- a/topola/src/layout/compounds/component.rs +++ b/topola/src/layout/compounds/component.rs @@ -5,7 +5,10 @@ use derive_more::{Constructor, From}; use serde::{Deserialize, Serialize}; -use crate::layout::primitives::{JointId, PolygonId, SegmentId, ViaId}; +use crate::{ + layout::primitives::{JointId, PolygonId, SegmentId, ViaId}, + primitives::PrimitiveId, +}; #[derive( Clone, @@ -42,4 +45,21 @@ impl Component { pub fn new() -> Self { Default::default() } + + pub fn primitives(&self) -> impl Iterator + '_ { + self.joints + .iter() + .map(|&joint_id| PrimitiveId::Joint(joint_id)) + .chain( + self.segments + .iter() + .map(|&segment_id| PrimitiveId::Segment(segment_id)), + ) + .chain(self.vias.iter().map(|&via_id| PrimitiveId::Via(via_id))) + .chain( + self.polygons + .iter() + .map(|&polygon_id| PrimitiveId::Polygon(polygon_id)), + ) + } } diff --git a/topola/src/layout/mod.rs b/topola/src/layout/mod.rs index 539305b..c9a6235 100644 --- a/topola/src/layout/mod.rs +++ b/topola/src/layout/mod.rs @@ -11,6 +11,7 @@ mod locate; mod modify; mod overlap; pub mod primitives; +mod repulsion; mod transforms; use derive_getters::Getters; diff --git a/topola/src/layout/repulsion.rs b/topola/src/layout/repulsion.rs new file mode 100644 index 0000000..d6aecfe --- /dev/null +++ b/topola/src/layout/repulsion.rs @@ -0,0 +1,392 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{ + Board, Rect2, Vector2, + compass::CompassDirection, + layout::compounds::ComponentId, + orientation::Orientation, + primitives::{JointId, PolygonId, PrimitiveId, SegmentId, ViaId}, +}; + +impl Board { + pub fn component_component_repulsion( + &self, + infringer: ComponentId, + infringee: ComponentId, + orientation: Orientation, + ) -> Vector2 { + let mut max_repulsion = Vector2::new(0, 0); + let mut max_repulsion_magnitude = 0; + + for infringer_primitive in self.layout().component(infringer).primitives() { + for infringee_primitive in self.layout().component(infringee).primitives() { + let repulsion = self.primitive_primitive_repulsion( + infringer_primitive, + infringee_primitive, + orientation, + ); + let repulsion_magnitude = repulsion.x.abs() + repulsion.y.abs(); + + if repulsion_magnitude > max_repulsion_magnitude { + max_repulsion = repulsion; + max_repulsion_magnitude = repulsion_magnitude; + } + } + } + + max_repulsion + } + + pub fn primitive_primitive_repulsion( + &self, + infringer: PrimitiveId, + infringee: PrimitiveId, + orientation: Orientation, + ) -> Vector2 { + match infringer { + PrimitiveId::Joint(infringer) => { + self.joint_primitive_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Segment(infringer) => { + self.segment_primitive_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Via(infringer) => { + self.via_primitive_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Polygon(infringer) => { + self.polygon_primitive_repulsion(infringer, infringee, orientation) + } + } + } + + pub fn joint_joint_repulsion( + &self, + infringer: JointId, + infringee: JointId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.joint_joint_rect_overlap(infringer, infringee), + self.layout().joint(infringer).center(), + self.layout().joint(infringee).center(), + orientation, + ) + } + + pub fn joint_segment_repulsion( + &self, + infringer: JointId, + infringee: SegmentId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.joint_segment_rect_overlap(infringer, infringee), + self.layout().joint(infringer).center(), + self.layout().segment(infringee).center(), + orientation, + ) + } + + pub fn joint_via_repulsion( + &self, + infringer: JointId, + infringee: ViaId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.joint_via_rect_overlap(infringer, infringee), + self.layout().joint(infringer).center(), + self.layout().via(infringee).position, + orientation, + ) + } + + pub fn joint_polygon_repulsion( + &self, + infringer: JointId, + infringee: PolygonId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.joint_polygon_rect_overlap(infringer, infringee), + self.layout().joint(infringer).center(), + self.layout().polygon(infringee).center(), + orientation, + ) + } + + pub fn joint_primitive_repulsion( + &self, + infringer: JointId, + infringee: PrimitiveId, + orientation: Orientation, + ) -> Vector2 { + match infringee { + PrimitiveId::Joint(infringee) => { + self.joint_joint_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Segment(infringee) => { + self.joint_segment_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Via(infringee) => { + self.joint_via_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Polygon(infringee) => { + self.joint_polygon_repulsion(infringer, infringee, orientation) + } + } + } + + pub fn segment_joint_repulsion( + &self, + infringer: SegmentId, + infringee: JointId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.segment_joint_rect_overlap(infringer, infringee), + self.layout().segment(infringer).center(), + self.layout().joint(infringee).center(), + orientation, + ) + } + + pub fn segment_segment_repulsion( + &self, + infringer: SegmentId, + infringee: SegmentId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.segment_segment_rect_overlap(infringer, infringee), + self.layout().segment(infringer).center(), + self.layout().segment(infringee).center(), + orientation, + ) + } + + pub fn segment_via_repulsion( + &self, + infringer: SegmentId, + infringee: ViaId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.segment_via_rect_overlap(infringer, infringee), + self.layout().segment(infringer).center(), + self.layout().via(infringee).position, + orientation, + ) + } + + pub fn segment_polygon_repulsion( + &self, + infringer: SegmentId, + infringee: PolygonId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.segment_polygon_rect_overlap(infringer, infringee), + self.layout().segment(infringer).center(), + self.layout().polygon(infringee).center(), + orientation, + ) + } + + pub fn segment_primitive_repulsion( + &self, + infringer: SegmentId, + infringee: PrimitiveId, + orientation: Orientation, + ) -> Vector2 { + match infringee { + PrimitiveId::Joint(infringee) => { + self.segment_joint_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Segment(infringee) => { + self.segment_segment_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Via(infringee) => { + self.segment_via_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Polygon(infringee) => { + self.segment_polygon_repulsion(infringer, infringee, orientation) + } + } + } + + pub fn via_joint_repulsion( + &self, + infringer: ViaId, + infringee: JointId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.via_joint_rect_overlap(infringer, infringee), + self.layout().via(infringer).position, + self.layout().joint(infringee).center(), + orientation, + ) + } + + pub fn via_segment_repulsion( + &self, + infringer: ViaId, + infringee: SegmentId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.via_segment_rect_overlap(infringer, infringee), + self.layout().via(infringer).position, + self.layout().segment(infringee).center(), + orientation, + ) + } + + pub fn via_via_repulsion( + &self, + infringer: ViaId, + infringee: ViaId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.via_via_rect_overlap(infringer, infringee), + self.layout().via(infringer).position, + self.layout().via(infringee).position, + orientation, + ) + } + + pub fn via_polygon_repulsion( + &self, + infringer: ViaId, + infringee: PolygonId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.via_polygon_rect_overlap(infringer, infringee), + self.layout().via(infringer).position, + self.layout().polygon(infringee).center(), + orientation, + ) + } + + pub fn via_primitive_repulsion( + &self, + infringer: ViaId, + infringee: PrimitiveId, + orientation: Orientation, + ) -> Vector2 { + match infringee { + PrimitiveId::Joint(infringee) => { + self.via_joint_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Segment(infringee) => { + self.via_segment_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Via(infringee) => { + self.via_via_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Polygon(infringee) => { + self.via_polygon_repulsion(infringer, infringee, orientation) + } + } + } + + pub fn polygon_joint_repulsion( + &self, + infringer: PolygonId, + infringee: JointId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.polygon_joint_rect_overlap(infringer, infringee), + self.layout().polygon(infringer).center(), + self.layout().joint(infringee).center(), + orientation, + ) + } + + pub fn polygon_segment_repulsion( + &self, + infringer: PolygonId, + infringee: SegmentId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.polygon_segment_rect_overlap(infringer, infringee), + self.layout().polygon(infringer).center(), + self.layout().segment(infringee).center(), + orientation, + ) + } + + pub fn polygon_via_repulsion( + &self, + infringer: PolygonId, + infringee: ViaId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.polygon_via_rect_overlap(infringer, infringee), + self.layout().polygon(infringer).center(), + self.layout().via(infringee).position, + orientation, + ) + } + + pub fn polygon_polygon_repulsion( + &self, + infringer: PolygonId, + infringee: PolygonId, + orientation: Orientation, + ) -> Vector2 { + Self::repulsion_from_rect_overlap( + self.polygon_polygon_rect_overlap(infringer, infringee), + self.layout().polygon(infringer).center(), + self.layout().polygon(infringee).center(), + orientation, + ) + } + + pub fn polygon_primitive_repulsion( + &self, + infringer: PolygonId, + infringee: PrimitiveId, + orientation: Orientation, + ) -> Vector2 { + match infringee { + PrimitiveId::Joint(infringee) => { + self.polygon_joint_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Segment(infringee) => { + self.polygon_segment_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Via(infringee) => { + self.polygon_via_repulsion(infringer, infringee, orientation) + } + PrimitiveId::Polygon(infringee) => { + self.polygon_polygon_repulsion(infringer, infringee, orientation) + } + } + } + + fn repulsion_from_rect_overlap( + overlap: Option>, + infringer_pos: Vector2, + infringee_pos: Vector2, + orientation: Orientation, + ) -> Vector2 { + let Some(overlap) = overlap else { + return Vector2::new(0, 0); + }; + + let Some(wind) = orientation.principal_wind(infringer_pos - infringee_pos) else { + return Vector2::new(0, 0); + }; + + wind.cast_vector(overlap.size()) + } +} diff --git a/topola/src/lib.rs b/topola/src/lib.rs index 4897ab2..d6557a7 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -9,6 +9,7 @@ mod drawer; mod layout; mod math; mod navmesher; +mod orientation; mod pathfinder; mod ratsnest; mod rect; diff --git a/topola/src/orientation.rs b/topola/src/orientation.rs new file mode 100644 index 0000000..0f58c3c --- /dev/null +++ b/topola/src/orientation.rs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::ops::Add; + +use derive_more::From; +use num_traits::{Signed, Zero}; +use serde::{Deserialize, Serialize}; + +use crate::{ + Vector2, + compass::{CompassDirection, PrincipalWind}, +}; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize)] +pub enum OrthogonalOrientation { + Horizontal, + Vertical, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize)] +pub enum Orientation { + Horizontal, + Vertical, + Oblique, +} + +impl Orientation { + pub fn principal_wind(self, vector: Vector2) -> Option + where + T: Copy + Add + PartialOrd + Signed + Zero, + { + match self { + Self::Horizontal => { + if vector.x.is_zero() { + None + } else if vector.x > T::zero() { + Some(PrincipalWind::East) + } else { + Some(PrincipalWind::West) + } + } + Self::Vertical => { + if vector.y.is_zero() { + None + } else if vector.y < T::zero() { + Some(PrincipalWind::North) + } else { + Some(PrincipalWind::South) + } + } + Self::Oblique => { + if vector.x.is_zero() && vector.y.is_zero() { + None + } else { + Some(PrincipalWind::nearest_from_vector(vector)) + } + } + } + } +} diff --git a/topola/src/rect.rs b/topola/src/rect.rs index 18845fd..1ca9ea0 100644 --- a/topola/src/rect.rs +++ b/topola/src/rect.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::ops::Sub; + use derive_getters::Getters; use derive_more::Constructor; use num_traits::Bounded; @@ -66,6 +68,12 @@ impl Rect2 { } } +impl> Rect2 { + pub fn size(&self) -> Vector2 { + Vector2::new(self.max.x - self.min.x, self.max.y - self.min.y) + } +} + impl Rect2 { pub fn corners(&self) -> [Vector2; 4] { [