Implement basic computation of repulsion forces

This commit is contained in:
Mikolaj Wielgus 2026-06-02 18:09:40 +02:00
parent 53a443615b
commit dde3720e04
7 changed files with 534 additions and 1 deletions

View File

@ -13,6 +13,55 @@ 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;
fn cast_vector(self, vector: Vector2<T>) -> Vector2<T>
where
T: Copy + Neg<Output = T> + 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)]

View File

@ -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<Item = PrimitiveId> + '_ {
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)),
)
}
}

View File

@ -11,6 +11,7 @@ mod locate;
mod modify;
mod overlap;
pub mod primitives;
mod repulsion;
mod transforms;
use derive_getters::Getters;

View File

@ -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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<i64> {
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<Rect2<i64>>,
infringer_pos: Vector2<i64>,
infringee_pos: Vector2<i64>,
orientation: Orientation,
) -> Vector2<i64> {
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())
}
}

View File

@ -9,6 +9,7 @@ mod drawer;
mod layout;
mod math;
mod navmesher;
mod orientation;
mod pathfinder;
mod ratsnest;
mod rect;

62
topola/src/orientation.rs Normal file
View File

@ -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<T>(self, vector: Vector2<T>) -> Option<PrincipalWind>
where
T: Copy + Add<Output = T> + 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))
}
}
}
}
}

View File

@ -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<T: Bounded + Copy> Rect2<T> {
}
}
impl<T: Copy + Sub<Output = T>> Rect2<T> {
pub fn size(&self) -> Vector2<T> {
Vector2::new(self.max.x - self.min.x, self.max.y - self.min.y)
}
}
impl<T: Copy> Rect2<T> {
pub fn corners(&self) -> [Vector2<T>; 4] {
[