topola 0.1.0

Work-in-progress free and open-source topological (rubberband) router and autorouter for printed circuit boards (PCBs)
Documentation
// SPDX-FileCopyrightText: 2026 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::ops::Sub;

use derive_getters::Getters;
use derive_more::Constructor;
use num_traits::Bounded;
use rstar::{AABB, RTreeNum, primitives::Rectangle};
use serde::{Deserialize, Serialize};

use crate::{Vector2, Vector3};

#[derive(
    Clone, Constructor, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize,
)]
pub struct Rect2<T> {
    pub min: Vector2<T>,
    pub max: Vector2<T>,
}

impl<T: Copy + Ord> Rect2<T> {
    pub fn union(self, other: Self) -> Self {
        Self {
            min: Vector2::new(
                std::cmp::min(self.min.x, other.min.x),
                std::cmp::min(self.min.y, other.min.y),
            ),
            max: Vector2::new(
                std::cmp::max(self.max.x, other.max.x),
                std::cmp::max(self.max.y, other.max.y),
            ),
        }
    }

    pub fn intersection(self, other: Self) -> Option<Self> {
        let min = Vector2::new(
            std::cmp::max(self.min.x, other.min.x),
            std::cmp::max(self.min.y, other.min.y),
        );
        let max = Vector2::new(
            std::cmp::min(self.max.x, other.max.x),
            std::cmp::min(self.max.y, other.max.y),
        );

        if min.x > max.x || min.y > max.y {
            return None;
        }

        Some(Self { min, max })
    }

    pub fn z_extruded(self, from: T, to: T) -> Rect3<T> {
        Rect3 {
            min: Vector3::new(self.min.x, self.min.y, std::cmp::min(from, to)),
            max: Vector3::new(self.max.x, self.max.y, std::cmp::max(from, to)),
        }
    }
}

impl<T: Bounded + Copy> Rect2<T> {
    pub fn z_extruded_infinitely(self) -> Rect3<T> {
        Rect3 {
            min: Vector3::new(self.min.x, self.min.y, Bounded::min_value()),
            max: Vector3::new(self.max.x, self.max.y, Bounded::max_value()),
        }
    }
}

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] {
        [
            self.min,
            Vector2::new(self.max.x, self.min.y),
            self.max,
            Vector2::new(self.min.x, self.max.y),
        ]
    }
}

impl<T: RTreeNum> Rect2<T> {
    pub fn aabb(self) -> AABB<[T; 2]> {
        AABB::from_corners([self.min.x, self.min.y], [self.max.x, self.max.y])
    }
}

macro_rules! impl_rect2_contains_point {
    ($type:ty) => {
        impl Rect2<$type> {
            pub fn contains_point(&self, point: Vector2<$type>) -> bool {
                point.x >= self.min.x
                    && point.x <= self.max.x
                    && point.y >= self.min.y
                    && point.y <= self.max.y
            }
        }
    };
}

impl_rect2_contains_point!(i8);
impl_rect2_contains_point!(i16);
impl_rect2_contains_point!(i32);
impl_rect2_contains_point!(i64);
impl_rect2_contains_point!(i128);
impl_rect2_contains_point!(u8);
impl_rect2_contains_point!(u16);
impl_rect2_contains_point!(u32);
impl_rect2_contains_point!(u64);
impl_rect2_contains_point!(u128);
impl_rect2_contains_point!(f32);
impl_rect2_contains_point!(f64);

macro_rules! impl_rect2_contains_circle {
    ($type:ty) => {
        impl Rect2<$type> {
            pub fn contains_circle(&self, center: Vector2<$type>, radius: $type) -> bool {
                let min_x = self.min.x as f64;
                let min_y = self.min.y as f64;
                let max_x = self.max.x as f64;
                let max_y = self.max.y as f64;
                let cx = center.x as f64;
                let cy = center.y as f64;
                let r = radius as f64;

                cx - r >= min_x && cx + r <= max_x && cy - r >= min_y && cy + r <= max_y
            }
        }
    };
}

impl_rect2_contains_circle!(i8);
impl_rect2_contains_circle!(i16);
impl_rect2_contains_circle!(i32);
impl_rect2_contains_circle!(i64);
impl_rect2_contains_circle!(i128);
impl_rect2_contains_circle!(u8);
impl_rect2_contains_circle!(u16);
impl_rect2_contains_circle!(u32);
impl_rect2_contains_circle!(u64);
impl_rect2_contains_circle!(u128);
impl_rect2_contains_circle!(f32);
impl_rect2_contains_circle!(f64);

macro_rules! impl_rect2_intersects_circle {
    ($type:ty) => {
        impl Rect2<$type> {
            pub fn intersects_circle(&self, center: Vector2<$type>, radius: $type) -> bool {
                let min_x = self.min.x as f64;
                let min_y = self.min.y as f64;
                let max_x = self.max.x as f64;
                let max_y = self.max.y as f64;
                let cx = center.x as f64;
                let cy = center.y as f64;
                let r = radius as f64;

                let closest_x = cx.clamp(min_x, max_x);
                let closest_y = cy.clamp(min_y, max_y);
                let dx = cx - closest_x;
                let dy = cy - closest_y;

                dx * dx + dy * dy <= r * r
            }
        }
    };
}

impl_rect2_intersects_circle!(i8);
impl_rect2_intersects_circle!(i16);
impl_rect2_intersects_circle!(i32);
impl_rect2_intersects_circle!(i64);
impl_rect2_intersects_circle!(i128);
impl_rect2_intersects_circle!(u8);
impl_rect2_intersects_circle!(u16);
impl_rect2_intersects_circle!(u32);
impl_rect2_intersects_circle!(u64);
impl_rect2_intersects_circle!(u128);
impl_rect2_intersects_circle!(f32);
impl_rect2_intersects_circle!(f64);

macro_rules! impl_rect2_contains_poly {
    ($type:ty) => {
        impl Rect2<$type> {
            pub fn contains_poly(&self, poly: &[Vector2<$type>]) -> bool {
                let corners = self.corners();
                poly.iter().all(|point| point.inside_polygon(&corners))
            }
        }
    };
}

impl_rect2_contains_poly!(i8);
impl_rect2_contains_poly!(i16);
impl_rect2_contains_poly!(i32);
impl_rect2_contains_poly!(i64);
impl_rect2_contains_poly!(i128);
impl_rect2_contains_poly!(u8);
impl_rect2_contains_poly!(u16);
impl_rect2_contains_poly!(u32);
impl_rect2_contains_poly!(u64);
impl_rect2_contains_poly!(u128);
impl_rect2_contains_poly!(f32);
impl_rect2_contains_poly!(f64);

macro_rules! impl_rect2_intersects_poly {
    ($type:ty) => {
        impl Rect2<$type> {
            pub fn intersects_poly(&self, poly: &[Vector2<$type>]) -> bool {
                if poly.is_empty() {
                    return false;
                }

                if poly.iter().any(|&vertex| self.contains_point(vertex)) {
                    return true;
                }

                if self
                    .corners()
                    .iter()
                    .any(|corner| corner.inside_polygon(poly))
                {
                    return true;
                }

                false

                // TODO: Now handle cases where no vertex is inside but only
                // edges intersect.
            }
        }
    };
}

impl_rect2_intersects_poly!(i8);
impl_rect2_intersects_poly!(i16);
impl_rect2_intersects_poly!(i32);
impl_rect2_intersects_poly!(i64);
impl_rect2_intersects_poly!(i128);
impl_rect2_intersects_poly!(u8);
impl_rect2_intersects_poly!(u16);
impl_rect2_intersects_poly!(u32);
impl_rect2_intersects_poly!(u64);
impl_rect2_intersects_poly!(u128);
impl_rect2_intersects_poly!(f32);
impl_rect2_intersects_poly!(f64);

#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Rect3<T> {
    pub min: Vector3<T>,
    pub max: Vector3<T>,
}

impl<T: Ord + Copy> Rect3<T> {
    pub fn new(from: Vector3<T>, to: Vector3<T>) -> Self {
        Self {
            min: Vector3::new(
                std::cmp::min(from.x, to.x),
                std::cmp::min(from.y, to.y),
                std::cmp::min(from.z, to.z),
            ),
            max: Vector3::new(
                std::cmp::max(from.x, to.x),
                std::cmp::max(from.y, to.y),
                std::cmp::max(from.z, to.z),
            ),
        }
    }

    pub fn union(self, other: Self) -> Self {
        Self {
            min: Vector3::new(
                std::cmp::min(self.min.x, other.min.x),
                std::cmp::min(self.min.y, other.min.y),
                std::cmp::min(self.min.z, other.min.z),
            ),
            max: Vector3::new(
                std::cmp::max(self.max.x, other.max.x),
                std::cmp::max(self.max.y, other.max.y),
                std::cmp::max(self.max.z, other.max.z),
            ),
        }
    }

    pub fn intersection(self, other: Self) -> Option<Self> {
        let min = Vector3::new(
            std::cmp::max(self.min.x, other.min.x),
            std::cmp::max(self.min.y, other.min.y),
            std::cmp::max(self.min.z, other.min.z),
        );
        let max = Vector3::new(
            std::cmp::min(self.max.x, other.max.x),
            std::cmp::min(self.max.y, other.max.y),
            std::cmp::min(self.max.z, other.max.z),
        );

        if min.x > max.x || min.y > max.y || min.z > max.z {
            return None;
        }

        Some(Self { min, max })
    }
}

impl<T: Copy + Ord> Rect3<T> {
    pub fn xy(self) -> Rect2<T> {
        Rect2::new(self.min.xy(), self.max.xy())
    }
}

impl<T: RTreeNum> Rect3<T> {
    pub fn aabb(self) -> AABB<[T; 3]> {
        AABB::from_corners(
            [self.min.x, self.min.y, self.min.z],
            [self.max.x, self.max.y, self.max.z],
        )
    }

    pub fn rtree_rectangle(self) -> Rectangle<[T; 3]> {
        Rectangle::from_aabb(self.aabb())
    }
}