From ae211b302e10aab794a776508f862df0ffe716f7 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Mon, 1 Jun 2026 13:45:03 +0200 Subject: [PATCH] Add compass directions --- topola/src/compass.rs | 265 ++++++++++++++++++++++++ topola/src/layout/modify.rs | 24 ++- topola/src/layout/primitives/joint.rs | 2 +- topola/src/layout/primitives/polygon.rs | 2 +- topola/src/layout/primitives/segment.rs | 2 +- topola/src/layout/primitives/via.rs | 2 +- topola/src/lib.rs | 1 + 7 files changed, 286 insertions(+), 12 deletions(-) create mode 100644 topola/src/compass.rs diff --git a/topola/src/compass.rs b/topola/src/compass.rs new file mode 100644 index 0000000..3025ef2 --- /dev/null +++ b/topola/src/compass.rs @@ -0,0 +1,265 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::ops::{Add, Neg}; + +use num_traits::{One, Signed, Zero}; +use serde::{Deserialize, Serialize}; + +use crate::Vector2; + +pub trait CompassDirection: Copy + PartialEq + Into> { + fn nearest_from_vector(vector: Vector2) -> Self; + fn turn_clockwise(self) -> Self; + fn turn_counterclockwise(self) -> Self; +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum CardinalDirection { + East, + North, + West, + South, +} + +impl + One + Zero> From for Vector2 { + fn from(cardinal_direction: CardinalDirection) -> Vector2 { + match cardinal_direction { + CardinalDirection::East => [T::one(), T::zero()].into(), + CardinalDirection::North => [T::zero(), -T::one()].into(), + CardinalDirection::West => [-T::one(), T::zero()].into(), + CardinalDirection::South => [T::zero(), T::one()].into(), + } + } +} + +impl CompassDirection for CardinalDirection { + fn nearest_from_vector(vector: Vector2) -> Self { + if vector.x.abs() > vector.y.abs() { + if vector.x > T::zero() { + Self::East + } else { + Self::West + } + } else if vector.y > T::zero() { + Self::South + } else { + Self::North + } + } + + fn turn_clockwise(self) -> Self { + match self { + Self::East => Self::South, + Self::North => Self::East, + Self::West => Self::North, + Self::South => Self::West, + } + } + + fn turn_counterclockwise(self) -> Self { + match self { + Self::East => Self::North, + Self::North => Self::West, + Self::West => Self::South, + Self::South => Self::East, + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum OrdinalDirection { + NorthWest, + SouthWest, + SouthEast, + NorthEast, +} + +impl + One + Zero> From for Vector2 { + fn from(ordinal_direction: OrdinalDirection) -> Vector2 { + match ordinal_direction { + OrdinalDirection::NorthEast => [T::one(), -T::one()].into(), + OrdinalDirection::NorthWest => [-T::one(), -T::one()].into(), + OrdinalDirection::SouthWest => [-T::one(), T::one()].into(), + OrdinalDirection::SouthEast => [T::one(), T::one()].into(), + } + } +} + +impl CompassDirection for OrdinalDirection { + fn nearest_from_vector(vector: Vector2) -> Self { + let zero = T::zero(); + + if vector.x > zero && vector.y > zero { + Self::SouthEast + } else if vector.x > zero && vector.y < zero { + Self::NorthEast + } else if vector.x < zero && vector.y < zero { + Self::NorthWest + } else { + Self::SouthWest + } + } + + fn turn_clockwise(self) -> Self { + match self { + Self::NorthWest => Self::NorthEast, + Self::NorthEast => Self::SouthEast, + Self::SouthEast => Self::SouthWest, + Self::SouthWest => Self::NorthWest, + } + } + + fn turn_counterclockwise(self) -> Self { + match self { + Self::NorthWest => Self::SouthWest, + Self::SouthWest => Self::SouthEast, + Self::SouthEast => Self::NorthEast, + Self::NorthEast => Self::NorthWest, + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum PrincipalWind { + North, + NorthWest, + West, + SouthWest, + South, + SouthEast, + East, + NorthEast, +} + +impl + One + Zero> From for Vector2 { + fn from(principal_wind: PrincipalWind) -> Vector2 { + match principal_wind { + PrincipalWind::North => [T::zero(), -T::one()].into(), + PrincipalWind::NorthWest => [-T::one(), -T::one()].into(), + PrincipalWind::West => [-T::one(), T::zero()].into(), + PrincipalWind::SouthWest => [-T::one(), T::one()].into(), + PrincipalWind::South => [T::zero(), T::one()].into(), + PrincipalWind::SouthEast => [T::one(), T::one()].into(), + PrincipalWind::East => [T::one(), T::zero()].into(), + PrincipalWind::NorthEast => [T::one(), -T::one()].into(), + } + } +} + +impl + PartialOrd + Signed + Zero> CompassDirection for PrincipalWind { + fn nearest_from_vector(vector: Vector2) -> Self { + if vector.x.is_zero() && vector.y.is_zero() { + panic!("zero vector has no direction"); + } + + if vector.x.is_zero() { + return if vector.y < T::zero() { + Self::North + } else { + Self::South + }; + } + + if vector.y.is_zero() { + return if vector.x > T::zero() { + Self::East + } else { + Self::West + }; + } + + if vector.x > T::zero() && vector.y < T::zero() { + if vector.x.abs() > vector.y.abs() { + if vector.y.abs() + vector.y.abs() <= vector.x.abs() { + Self::East + } else { + Self::NorthEast + } + } else if vector.y.abs() > vector.x.abs() { + if vector.x.abs() + vector.x.abs() <= vector.y.abs() { + Self::North + } else { + Self::NorthEast + } + } else { + Self::NorthEast + } + } else if vector.x > T::zero() && vector.y > T::zero() { + if vector.x.abs() > vector.y.abs() { + if vector.y.abs() + vector.y.abs() <= vector.x.abs() { + Self::East + } else { + Self::SouthEast + } + } else if vector.y.abs() > vector.x.abs() { + if vector.x.abs() + vector.x.abs() <= vector.y.abs() { + Self::South + } else { + Self::SouthEast + } + } else { + Self::SouthEast + } + } else if vector.x < T::zero() && vector.y > T::zero() { + if vector.x.abs() > vector.y.abs() { + if vector.y.abs() + vector.y.abs() <= vector.x.abs() { + Self::West + } else { + Self::SouthWest + } + } else if vector.y.abs() > vector.x.abs() { + if vector.x.abs() + vector.x.abs() <= vector.y.abs() { + Self::South + } else { + Self::SouthWest + } + } else { + Self::SouthWest + } + } else { + if vector.x.abs() > vector.y.abs() { + if vector.y.abs() + vector.y.abs() <= vector.x.abs() { + Self::West + } else { + Self::NorthWest + } + } else if vector.y.abs() > vector.x.abs() { + if vector.x.abs() + vector.x.abs() <= vector.y.abs() { + Self::North + } else { + Self::NorthWest + } + } else { + Self::NorthWest + } + } + } + + fn turn_clockwise(self) -> Self { + match self { + Self::North => Self::NorthEast, + Self::NorthEast => Self::East, + Self::East => Self::SouthEast, + Self::SouthEast => Self::South, + Self::South => Self::SouthWest, + Self::SouthWest => Self::West, + Self::West => Self::NorthWest, + Self::NorthWest => Self::North, + } + } + + fn turn_counterclockwise(self) -> Self { + match self { + Self::North => Self::NorthWest, + Self::NorthWest => Self::West, + Self::West => Self::SouthWest, + Self::SouthWest => Self::South, + Self::South => Self::SouthEast, + Self::SouthEast => Self::East, + Self::East => Self::NorthEast, + Self::NorthEast => Self::North, + } + } +} diff --git a/topola/src/layout/modify.rs b/topola/src/layout/modify.rs index 638cc29..3c17ce6 100644 --- a/topola/src/layout/modify.rs +++ b/topola/src/layout/modify.rs @@ -39,8 +39,10 @@ impl Layout { self.joints.modify(id.index(), |joint| f(joint)); let new_joint = self.joints[id.index()].clone(); - self.joints_rtree - .insert(GeomWithData::new(new_joint.bbox().rtree_rectangle(), id), ()); + self.joints_rtree.insert( + GeomWithData::new(new_joint.bbox().rtree_rectangle(), id), + (), + ); } pub fn modify_segment(&mut self, id: SegmentId, f: F) @@ -55,8 +57,10 @@ impl Layout { .modify(id.index(), |segment| f(&mut segment.spec)); let new_segment = &self.segments[id.index()]; - self.segments_rtree - .insert(GeomWithData::new(new_segment.bbox().rtree_rectangle(), id), ()); + self.segments_rtree.insert( + GeomWithData::new(new_segment.bbox().rtree_rectangle(), id), + (), + ); } pub(super) fn update_segment(&mut self, id: SegmentId) { @@ -76,8 +80,10 @@ impl Layout { }); let new_segment = &self.segments[id.index()]; - self.segments_rtree - .insert(GeomWithData::new(new_segment.bbox().rtree_rectangle(), id), ()); + self.segments_rtree.insert( + GeomWithData::new(new_segment.bbox().rtree_rectangle(), id), + (), + ); } pub fn modify_via(&mut self, id: ViaId, f: F) @@ -128,7 +134,9 @@ impl Layout { self.polygons.modify(id.index(), |polygon| f(polygon)); let new_polygon = &self.polygons[id.index()]; - self.polygons_rtree - .insert(GeomWithData::new(new_polygon.bbox().rtree_rectangle(), id), ()); + self.polygons_rtree.insert( + GeomWithData::new(new_polygon.bbox().rtree_rectangle(), id), + (), + ); } } diff --git a/topola/src/layout/primitives/joint.rs b/topola/src/layout/primitives/joint.rs index 74b6c30..d3fa793 100644 --- a/topola/src/layout/primitives/joint.rs +++ b/topola/src/layout/primitives/joint.rs @@ -5,10 +5,10 @@ use derive_more::{Constructor, From}; use serde::{Deserialize, Serialize}; -use crate::{Rect3, Vector3, layout::LayerId}; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::primitives::{SegmentId, ViaId}; use crate::vector::Vector2; +use crate::{Rect3, Vector3, layout::LayerId}; #[derive( Clone, diff --git a/topola/src/layout/primitives/polygon.rs b/topola/src/layout/primitives/polygon.rs index 94167d1..e8f63bd 100644 --- a/topola/src/layout/primitives/polygon.rs +++ b/topola/src/layout/primitives/polygon.rs @@ -5,9 +5,9 @@ use derive_more::{Constructor, From}; use serde::{Deserialize, Serialize}; -use crate::{Rect3, Vector3, layout::LayerId}; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::vector::Vector2; +use crate::{Rect3, Vector3, layout::LayerId}; #[derive( Clone, diff --git a/topola/src/layout/primitives/segment.rs b/topola/src/layout/primitives/segment.rs index 6bf4260..daa06c6 100644 --- a/topola/src/layout/primitives/segment.rs +++ b/topola/src/layout/primitives/segment.rs @@ -5,9 +5,9 @@ use derive_more::{Constructor, From}; use serde::{Deserialize, Serialize}; -use crate::{Rect3, Vector3, layout::LayerId}; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::vector::Vector2; +use crate::{Rect3, Vector3, layout::LayerId}; use super::JointId; diff --git a/topola/src/layout/primitives/via.rs b/topola/src/layout/primitives/via.rs index 81b3487..aa5d7ed 100644 --- a/topola/src/layout/primitives/via.rs +++ b/topola/src/layout/primitives/via.rs @@ -5,9 +5,9 @@ use derive_more::{Constructor, From}; use serde::{Deserialize, Serialize}; -use crate::{Rect3, Vector3, layout::LayerId}; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::vector::Vector2; +use crate::{Rect3, Vector3, layout::LayerId}; use super::JointId; diff --git a/topola/src/lib.rs b/topola/src/lib.rs index b600261..4897ab2 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -4,6 +4,7 @@ mod autorouter; mod board; +mod compass; mod drawer; mod layout; mod math;