Add compass directions

This commit is contained in:
Mikolaj Wielgus 2026-06-01 13:45:03 +02:00
parent 1264c3183a
commit ae211b302e
7 changed files with 286 additions and 12 deletions

265
topola/src/compass.rs Normal file
View File

@ -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<T>: Copy + PartialEq + Into<Vector2<T>> {
fn nearest_from_vector(vector: Vector2<T>) -> 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<T: Copy + Neg<Output = T> + One + Zero> From<CardinalDirection> for Vector2<T> {
fn from(cardinal_direction: CardinalDirection) -> Vector2<T> {
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<T: Copy + PartialOrd + Signed + Zero> CompassDirection<T> for CardinalDirection {
fn nearest_from_vector(vector: Vector2<T>) -> 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<T: Copy + Neg<Output = T> + One + Zero> From<OrdinalDirection> for Vector2<T> {
fn from(ordinal_direction: OrdinalDirection) -> Vector2<T> {
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<T: Copy + PartialOrd + Signed + Zero> CompassDirection<T> for OrdinalDirection {
fn nearest_from_vector(vector: Vector2<T>) -> 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<T: Copy + Neg<Output = T> + One + Zero> From<PrincipalWind> for Vector2<T> {
fn from(principal_wind: PrincipalWind) -> Vector2<T> {
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<T: Copy + Add<Output = T> + PartialOrd + Signed + Zero> CompassDirection<T> for PrincipalWind {
fn nearest_from_vector(vector: Vector2<T>) -> 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,
}
}
}

View File

@ -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<F>(&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<F>(&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),
(),
);
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -4,6 +4,7 @@
mod autorouter;
mod board;
mod compass;
mod drawer;
mod layout;
mod math;