diff --git a/topola/src/board/mod.rs b/topola/src/board/mod.rs index 671e2f9..20b721d 100644 --- a/topola/src/board/mod.rs +++ b/topola/src/board/mod.rs @@ -22,7 +22,7 @@ use crate::{ LayerId, Layout, LayoutHalfDelta, compounds::{ComponentId, NetId, PinId}, }, - math::Vector2, + vector::Vector2, }; #[derive(Clone, Debug, Getters, Delta)] diff --git a/topola/src/layout/locate.rs b/topola/src/layout/locate.rs index 8a876ed..8e53736 100644 --- a/topola/src/layout/locate.rs +++ b/topola/src/layout/locate.rs @@ -24,73 +24,94 @@ impl Layout { &self, rect: Rect3, ) -> impl Iterator { - let rect_aabb = rect.aabb3(); self.joints_rtree .as_ref() - .locate_in_envelope_intersecting(&rect_aabb) + .locate_in_envelope_intersecting(&rect.aabb3()) .map(|geom_with_data| geom_with_data.data) + .filter(move |&joint_id| { + let joint = self.joint(joint_id); + rect.rect2() + .intersects_circle(joint.spec.position, joint.spec.radius as i64) + }) } pub fn locate_joints_inside_rect(&self, rect: Rect3) -> impl Iterator { - let rect_aabb = rect.aabb3(); self.joints_rtree .as_ref() - .locate_in_envelope(&rect_aabb) + .locate_in_envelope(&rect.aabb3()) .map(|geom_with_data| geom_with_data.data) + .filter(move |&joint_id| { + let joint = self.joint(joint_id); + rect.rect2() + .contains_circle(joint.spec.position, joint.spec.radius as i64) + }) } pub fn locate_segments_at_point(&self, point: Vector3) -> impl Iterator { - let point2 = point.xy(); self.segments_rtree .as_ref() .locate_all_at_point(&[point.x, point.y, point.z]) .map(|geom_with_data| geom_with_data.data) - .filter(move |&segment_id| self.segment(segment_id).contains_point(point2)) + .filter(move |&segment_id| self.segment(segment_id).contains_point(point.xy())) } pub fn locate_segments_intersecting_rect( &self, rect: Rect3, ) -> impl Iterator { - let rect_aabb = rect.aabb3(); self.segments_rtree .as_ref() - .locate_in_envelope_intersecting(&rect_aabb) + .locate_in_envelope_intersecting(&rect.aabb3()) .map(|geom_with_data| geom_with_data.data) + .filter(move |&segment_id| { + let segment = self.segment(segment_id); + rect.rect2() + .intersects_polygon(&segment.bounding_rectangle()) + }) } pub fn locate_segments_inside_rect(&self, rect: Rect3) -> impl Iterator { - let rect_aabb = rect.aabb3(); self.segments_rtree .as_ref() - .locate_in_envelope(&rect_aabb) + .locate_in_envelope(&rect.aabb3()) .map(|geom_with_data| geom_with_data.data) + .filter(move |&segment_id| { + let segment = self.segment(segment_id); + rect.rect2().contains_polygon(&segment.bounding_rectangle()) + }) } pub fn locate_vias_at_point(&self, point: Vector3) -> impl Iterator { let layer = LayerId::new(point.z as usize); - let point2 = point.xy(); self.vias_rtree .as_ref() .locate_all_at_point(&[point.x, point.y, point.z]) .map(|geom_with_data| geom_with_data.data) - .filter(move |&via_id| self.vias[via_id.index()].contains_point(layer, point2)) + .filter(move |&via_id| self.vias[via_id.index()].contains_point(layer, point.xy())) } pub fn locate_vias_intersecting_rect(&self, rect: Rect3) -> impl Iterator { - let rect_aabb = rect.aabb3(); self.vias_rtree .as_ref() - .locate_in_envelope_intersecting(&rect_aabb) + .locate_in_envelope_intersecting(&rect.aabb3()) .map(|geom_with_data| geom_with_data.data) + .filter(move |&via_id| { + let via = self.via(via_id); + rect.rect2() + .intersects_circle(via.position, via.spec.radius as i64) + }) } pub fn locate_vias_inside_rect(&self, rect: Rect3) -> impl Iterator { - let rect_aabb = rect.aabb3(); self.vias_rtree .as_ref() - .locate_in_envelope(&rect_aabb) + .locate_in_envelope(&rect.aabb3()) .map(|geom_with_data| geom_with_data.data) + .filter(move |&via_id| { + let via = self.via(via_id); + rect.rect2() + .contains_circle(via.position, via.spec.radius as i64) + }) } pub fn locate_polygons_at_point(&self, point: Vector3) -> impl Iterator { @@ -106,19 +127,25 @@ impl Layout { &self, rect: Rect3, ) -> impl Iterator { - let rect_aabb = rect.aabb3(); self.polygons_rtree .as_ref() - .locate_in_envelope_intersecting(&rect_aabb) + .locate_in_envelope_intersecting(&rect.aabb3()) .map(|geom_with_data| geom_with_data.data) + .filter(move |&polygon_id| { + let polygon = self.polygon(polygon_id); + rect.rect2().intersects_polygon(&polygon.vertices) + }) } pub fn locate_polygons_inside_rect(&self, rect: Rect3) -> impl Iterator { - let rect_aabb = rect.aabb3(); self.polygons_rtree .as_ref() - .locate_in_envelope(&rect_aabb) + .locate_in_envelope(&rect.aabb3()) .map(|geom_with_data| geom_with_data.data) + .filter(move |&polygon_id| { + let polygon = self.polygon(polygon_id); + rect.rect2().contains_polygon(&polygon.vertices) + }) } pub fn locate_nets_intersecting_rect(&self, rect: Rect3) -> impl Iterator { diff --git a/topola/src/layout/primitives/joint.rs b/topola/src/layout/primitives/joint.rs index 7431245..41b4890 100644 --- a/topola/src/layout/primitives/joint.rs +++ b/topola/src/layout/primitives/joint.rs @@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize}; use crate::layout::LayerId; use crate::layout::compounds::{ComponentId, NetId, PinId}; -use crate::math::Vector2; use crate::primitives::{SegmentId, ViaId}; +use crate::vector::Vector2; #[derive( Clone, diff --git a/topola/src/layout/primitives/polygon.rs b/topola/src/layout/primitives/polygon.rs index 24f2512..3c498e6 100644 --- a/topola/src/layout/primitives/polygon.rs +++ b/topola/src/layout/primitives/polygon.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::layout::LayerId; use crate::layout::compounds::{ComponentId, NetId, PinId}; -use crate::math::Vector2; +use crate::vector::Vector2; #[derive( Clone, diff --git a/topola/src/layout/primitives/segment.rs b/topola/src/layout/primitives/segment.rs index 04f7eee..beac50a 100644 --- a/topola/src/layout/primitives/segment.rs +++ b/topola/src/layout/primitives/segment.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::layout::LayerId; use crate::layout::compounds::{ComponentId, NetId, PinId}; -use crate::math::Vector2; +use crate::vector::Vector2; use super::JointId; diff --git a/topola/src/layout/primitives/via.rs b/topola/src/layout/primitives/via.rs index 6790c5a..1782316 100644 --- a/topola/src/layout/primitives/via.rs +++ b/topola/src/layout/primitives/via.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::layout::LayerId; use crate::layout::compounds::{ComponentId, NetId, PinId}; -use crate::math::Vector2; +use crate::vector::Vector2; use super::JointId; diff --git a/topola/src/layout/transforms/move_by.rs b/topola/src/layout/transforms/move_by.rs index 844e6b2..072abfd 100644 --- a/topola/src/layout/transforms/move_by.rs +++ b/topola/src/layout/transforms/move_by.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::{Layout, layout::compounds::ComponentId, math::Vector2}; +use crate::{Layout, layout::compounds::ComponentId, vector::Vector2}; impl Layout { pub fn move_component_by(&mut self, id: ComponentId, translation: Vector2) { diff --git a/topola/src/lib.rs b/topola/src/lib.rs index e7195cc..78731df 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -10,8 +10,10 @@ mod math; mod navmesher; mod pathfinder; mod ratsnest; +mod rect; mod router; mod specctra; +mod vector; mod workspace; pub use crate::autorouter::Autorouter; @@ -28,6 +30,7 @@ pub use crate::layout::LayerId; pub use crate::layout::Layout; pub use crate::layout::compounds::{Pin, PinId}; pub use crate::layout::primitives; -pub use crate::math::{Rect2, Rect3, Vector2, Vector3}; pub use crate::ratsnest::{Ratline, Ratsnest}; +pub use crate::rect::{Rect2, Rect3}; +pub use crate::vector::{Vector2, Vector3}; pub use crate::workspace::{AutorouterWorkspace, BoardWorkspace, Workspace}; diff --git a/topola/src/math.rs b/topola/src/math.rs index c55aeb9..d009387 100644 --- a/topola/src/math.rs +++ b/topola/src/math.rs @@ -2,208 +2,9 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use derive_getters::Getters; -use derive_more::{ - Add, AddAssign, Constructor, Div, DivAssign, From, Into, Mul, MulAssign, Sub, SubAssign, -}; use polygon_unionfind::UnionFind; -use rstar::AABB; -use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)] -pub struct Rect2 { - min: Vector2, - max: Vector2, -} - -impl Rect2 { - pub fn new(from: Vector2, to: Vector2) -> Self { - Self { - min: Vector2::new(std::cmp::min(from.x, to.x), std::cmp::min(from.y, to.y)), - max: Vector2::new(std::cmp::max(from.x, to.x), std::cmp::max(from.y, to.y)), - } - } -} - -impl Rect2 { - pub fn aabb3(self, z: i64) -> AABB<[i64; 3]> { - AABB::from_corners([self.min.x, self.min.y, z], [self.max.x, self.max.y, z]) - } -} - -#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)] -pub struct Rect3 { - min: Vector3, - max: Vector3, -} - -impl Rect3 { - pub fn new(from: Vector3, to: Vector3) -> 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), - ), - } - } -} - -impl Rect3 { - pub fn aabb3(self) -> AABB<[i64; 3]> { - AABB::from_corners( - [self.min.x, self.min.y, self.min.z], - [self.max.x, self.max.y, self.max.z], - ) - } -} - -#[derive( - Add, - AddAssign, - Clone, - Constructor, - Copy, - Debug, - Deserialize, - Div, - DivAssign, - Eq, - From, - Into, - Mul, - MulAssign, - Ord, - PartialEq, - PartialOrd, - Serialize, - Sub, - SubAssign, -)] -pub struct Vector2 { - pub x: T, - pub y: T, -} - -#[derive( - Add, - AddAssign, - Clone, - Constructor, - Copy, - Debug, - Deserialize, - Div, - DivAssign, - Eq, - From, - Into, - Mul, - MulAssign, - Ord, - PartialEq, - PartialOrd, - Serialize, - Sub, - SubAssign, -)] -pub struct Vector3 { - pub x: T, - pub y: T, - pub z: T, -} - -impl Vector3 { - pub fn xy(self) -> Vector2 { - Vector2::new(self.x, self.y) - } -} - -impl From<[T; 2]> for Vector2 { - fn from(from: [T; 2]) -> Self { - Self { - x: from[0], - y: from[1], - } - } -} - -impl From> for [T; 2] { - fn from(from: Vector2) -> Self { - [from.x, from.y] - } -} - -macro_rules! impl_inside_polygon { - ($type:ty) => { - impl Vector2<$type> { - // Checks if the point is inside a polygon by casting a ray to the - // right. Division is not used to avoid integer truncation errors. - pub fn inside_polygon(&self, polygon: &[Vector2<$type>]) -> bool { - let mut inside = false; - let n = polygon.len(); - - // `self` is `v0`. - - // `v1` is the previous vertex. - let mut v1 = &polygon[n - 1]; - - // `v2` is the current vertex. - for v2 in polygon.iter() { - let dx12 = v2.x - v1.x; - let dy12 = v2.y - v1.y; - - // First, check if the line of the horizontal rightward ray - // cast to actually crosses the vertical span of the current - // `(v1, v2)` edge. - if dy12 != (0 as $type) && (self.y > v1.y) != (self.y > v2.y) { - let dx01 = self.x - v1.x; - let dy01 = self.y - v1.y; - - // Now check if the (v1, v2) edge is actually on the - // right side of the ray and not on the left. - // - // This just compares the X coordinate of `self` (`v0`) - // to the X coordinate of the intersection between the - // horizontal rightward ray and the current `(v1, v2)` edge: - // - // `self.x < v1.x + (self.y - v1.y) * (dx12 / dy12)` - // - // but is algebraically simplified and rewritten to not - // use division. - let crosses = if dy12 > (0 as $type) { - dx01 * dy12 < dx12 * dy01 - } else { - dx01 * dy12 > dx12 * dy01 - }; - - // Even-odd rule: flip whether the point is inside or - // outside upon each detected crossing. - if crosses { - inside = !inside; - } - } - - // Make the current vertex previous for the next loop - // iteration. - v1 = v2; - } - - inside - } - } - }; -} - -impl_inside_polygon!(f32); -impl_inside_polygon!(f64); -impl_inside_polygon!(i32); -impl_inside_polygon!(i64); +use crate::Vector2; /// Returns the four vertices of a segment inflated by `half_width`, forming a /// convex quadrilateral. The segment goes from (x1, y1) to (x2, y2). @@ -225,60 +26,6 @@ pub fn inflated_segment(x1: i64, y1: i64, x2: i64, y2: i64, half_width: u64) -> ] } -macro_rules! impl_rotate_around_point { - ($type:ty) => { - impl Vector2<$type> { - pub fn rotate_around_point(&mut self, angle: $type, origin: Vector2<$type>) -> Self { - let sin = angle.sin(); - let cos = angle.cos(); - - let tx = self.x - origin.x; - let ty = self.y - origin.y; - - let rx = tx * cos - ty * sin; - let ry = tx * sin + ty * cos; - - self.x = rx + origin.x; - self.y = ry + origin.y; - - *self - } - - pub fn rotate_around_point_degrees( - &mut self, - angle: $type, - origin: Vector2<$type>, - ) -> Self { - self.rotate_around_point(angle.to_radians(), origin) - } - } - }; -} - -impl_rotate_around_point!(f32); -impl_rotate_around_point!(f64); - -macro_rules! impl_polygon_centroid { - ($type:ty) => { - impl Vector2<$type> { - pub fn polygon_centroid(polygon: &[Vector2<$type>]) -> Self { - let mut sum = Vector2::new(0 as $type, 0 as $type); - - for vertex in polygon.iter() { - sum += *vertex; - } - - sum / polygon.len() as $type - } - } - }; -} - -impl_polygon_centroid!(f32); -impl_polygon_centroid!(f64); -impl_polygon_centroid!(i32); -impl_polygon_centroid!(i64); - /// Kruskal's minimum spanning tree algorithm. pub fn kruskal_mst( vertex_count: usize, diff --git a/topola/src/navmesher.rs b/topola/src/navmesher.rs index 727b900..7aa82f0 100644 --- a/topola/src/navmesher.rs +++ b/topola/src/navmesher.rs @@ -12,8 +12,8 @@ use undoredo::Recorder; use crate::{ Board, layout::LayerId, - math::Vector2, primitives::{Joint, JointId, JointSpec, Polygon, PolygonId, Segment, SegmentId}, + vector::Vector2, }; #[derive( diff --git a/topola/src/ratsnest.rs b/topola/src/ratsnest.rs index bafa509..746b394 100644 --- a/topola/src/ratsnest.rs +++ b/topola/src/ratsnest.rs @@ -12,8 +12,8 @@ use crate::{ Board, layout::LayerId, layout::compounds::NetId, - math::Vector2, primitives::{JointId, PolygonId, PrimitiveId, SegmentId}, + vector::Vector2, }; #[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)] diff --git a/topola/src/rect.rs b/topola/src/rect.rs new file mode 100644 index 0000000..326baeb --- /dev/null +++ b/topola/src/rect.rs @@ -0,0 +1,209 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use derive_getters::Getters; +use rstar::{AABB, RTreeNum}; +use serde::{Deserialize, Serialize}; + +use crate::{Vector2, Vector3}; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Rect2 { + min: Vector2, + max: Vector2, +} + +impl Rect2 { + pub fn corners(&self) -> [Vector2; 4] { + [ + self.min, + Vector2::new(self.max.x, self.min.y), + self.max, + Vector2::new(self.min.x, self.max.y), + ] + } +} + +impl Rect2 { + pub fn new(from: Vector2, to: Vector2) -> Self { + Self { + min: Vector2::new(std::cmp::min(from.x, to.x), std::cmp::min(from.y, to.y)), + max: Vector2::new(std::cmp::max(from.x, to.x), std::cmp::max(from.y, to.y)), + } + } +} + +impl Rect2 { + pub fn aabb3(self, z: T) -> AABB<[T; 3]> { + AABB::from_corners([self.min.x, self.min.y, z], [self.max.x, self.max.y, z]) + } +} + +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_polygon { + ($type:ty) => { + impl Rect2<$type> { + pub fn contains_polygon(&self, polygon: &[Vector2<$type>]) -> bool { + let corners = self.corners(); + polygon.iter().all(|point| point.inside_polygon(&corners)) + } + } + }; +} + +impl_rect2_contains_polygon!(i8); +impl_rect2_contains_polygon!(i16); +impl_rect2_contains_polygon!(i32); +impl_rect2_contains_polygon!(i64); +impl_rect2_contains_polygon!(i128); +impl_rect2_contains_polygon!(u8); +impl_rect2_contains_polygon!(u16); +impl_rect2_contains_polygon!(u32); +impl_rect2_contains_polygon!(u64); +impl_rect2_contains_polygon!(u128); +impl_rect2_contains_polygon!(f32); +impl_rect2_contains_polygon!(f64); + +macro_rules! impl_rect2_intersects_polygon { + ($type:ty) => { + impl Rect2<$type> { + pub fn intersects_polygon(&self, polygon: &[Vector2<$type>]) -> bool { + if polygon.is_empty() { + return false; + } + + if self.contains_polygon(polygon) { + return true; + } + + let corners = self.corners(); + if corners.iter().any(|corner| corner.inside_polygon(polygon)) { + return true; + } + + false + + // TODO: Now handle cases where no vertex is inside but only + // edges intersect. + } + } + }; +} + +impl_rect2_intersects_polygon!(i8); +impl_rect2_intersects_polygon!(i16); +impl_rect2_intersects_polygon!(i32); +impl_rect2_intersects_polygon!(i64); +impl_rect2_intersects_polygon!(i128); +impl_rect2_intersects_polygon!(u8); +impl_rect2_intersects_polygon!(u16); +impl_rect2_intersects_polygon!(u32); +impl_rect2_intersects_polygon!(u64); +impl_rect2_intersects_polygon!(u128); +impl_rect2_intersects_polygon!(f32); +impl_rect2_intersects_polygon!(f64); + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Rect3 { + min: Vector3, + max: Vector3, +} + +impl Rect3 { + pub fn new(from: Vector3, to: Vector3) -> 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), + ), + } + } +} + +impl Rect3 { + pub fn rect2(&self) -> Rect2 { + Rect2::new(self.min.xy(), self.max.xy()) + } +} + +impl Rect3 { + pub fn aabb3(&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], + ) + } +} diff --git a/topola/src/specctra.rs b/topola/src/specctra.rs index ab1309d..10f760e 100644 --- a/topola/src/specctra.rs +++ b/topola/src/specctra.rs @@ -14,8 +14,8 @@ use crate::{ board::{Board, LayerDesc, LayerSide, LayerType}, layout::LayerId, layout::compounds::{ComponentId, NetId, PinId}, - math::Vector2, primitives::{JointSpec, Polygon, Segment, SegmentSpec}, + vector::Vector2, }; impl Board { diff --git a/topola/src/vector.rs b/topola/src/vector.rs new file mode 100644 index 0000000..aa5aa1c --- /dev/null +++ b/topola/src/vector.rs @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use derive_more::{ + Add, AddAssign, Constructor, Div, DivAssign, From, Into, Mul, MulAssign, Sub, SubAssign, +}; +use serde::{Deserialize, Serialize}; + +#[derive( + Add, + AddAssign, + Clone, + Constructor, + Copy, + Debug, + Deserialize, + Div, + DivAssign, + Eq, + From, + Into, + Mul, + MulAssign, + Ord, + PartialEq, + PartialOrd, + Serialize, + Sub, + SubAssign, +)] +pub struct Vector2 { + pub x: T, + pub y: T, +} + +#[derive( + Add, + AddAssign, + Clone, + Constructor, + Copy, + Debug, + Deserialize, + Div, + DivAssign, + Eq, + From, + Into, + Mul, + MulAssign, + Ord, + PartialEq, + PartialOrd, + Serialize, + Sub, + SubAssign, +)] +pub struct Vector3 { + pub x: T, + pub y: T, + pub z: T, +} + +impl Vector3 { + pub fn xy(self) -> Vector2 { + Vector2::new(self.x, self.y) + } +} + +impl From<[T; 2]> for Vector2 { + fn from(from: [T; 2]) -> Self { + Self { + x: from[0], + y: from[1], + } + } +} + +impl From> for [T; 2] { + fn from(from: Vector2) -> Self { + [from.x, from.y] + } +} + +macro_rules! impl_vector2_inside_polygon { + ($type:ty) => { + impl Vector2<$type> { + // Checks if the point is inside a polygon by casting a ray to the + // right. Division is not used to avoid integer truncation errors. + pub fn inside_polygon(&self, polygon: &[Vector2<$type>]) -> bool { + let mut inside = false; + let n = polygon.len(); + + // `self` is `v0`. + + // `v1` is the previous vertex. + let mut v1 = &polygon[n - 1]; + + // `v2` is the current vertex. + for v2 in polygon.iter() { + let dx12 = v2.x - v1.x; + let dy12 = v2.y - v1.y; + + // First, check if the line of the horizontal rightward ray + // cast to actually crosses the vertical span of the current + // `(v1, v2)` edge. + if dy12 != (0 as $type) && (self.y > v1.y) != (self.y > v2.y) { + let dx01 = self.x - v1.x; + let dy01 = self.y - v1.y; + + // Now check if the (v1, v2) edge is actually on the + // right side of the ray and not on the left. + // + // This just compares the X coordinate of `self` (`v0`) + // to the X coordinate of the intersection between the + // horizontal rightward ray and the current `(v1, v2)` edge: + // + // `self.x < v1.x + (self.y - v1.y) * (dx12 / dy12)` + // + // but is algebraically simplified and rewritten to not + // use division. + let crosses = if dy12 > (0 as $type) { + dx01 * dy12 < dx12 * dy01 + } else { + dx01 * dy12 > dx12 * dy01 + }; + + // Even-odd rule: flip whether the point is inside or + // outside upon each detected crossing. + if crosses { + inside = !inside; + } + } + + // Make the current vertex previous for the next loop + // iteration. + v1 = v2; + } + + inside + } + } + }; +} + +impl_vector2_inside_polygon!(i8); +impl_vector2_inside_polygon!(i16); +impl_vector2_inside_polygon!(i32); +impl_vector2_inside_polygon!(i64); +impl_vector2_inside_polygon!(i128); +impl_vector2_inside_polygon!(u8); +impl_vector2_inside_polygon!(u16); +impl_vector2_inside_polygon!(u32); +impl_vector2_inside_polygon!(u64); +impl_vector2_inside_polygon!(u128); +impl_vector2_inside_polygon!(f32); +impl_vector2_inside_polygon!(f64); + +macro_rules! impl_vector2_rotate_around_point { + ($type:ty) => { + impl Vector2<$type> { + pub fn rotate_around_point(&mut self, angle: $type, origin: Vector2<$type>) -> Self { + let sin = angle.sin(); + let cos = angle.cos(); + + let tx = self.x - origin.x; + let ty = self.y - origin.y; + + let rx = tx * cos - ty * sin; + let ry = tx * sin + ty * cos; + + self.x = rx + origin.x; + self.y = ry + origin.y; + + *self + } + + pub fn rotate_around_point_degrees( + &mut self, + angle: $type, + origin: Vector2<$type>, + ) -> Self { + self.rotate_around_point(angle.to_radians(), origin) + } + } + }; +} + +impl_vector2_rotate_around_point!(f32); +impl_vector2_rotate_around_point!(f64); + +macro_rules! impl_polygon_centroid { + ($type:ty) => { + impl Vector2<$type> { + pub fn polygon_centroid(polygon: &[Vector2<$type>]) -> Self { + let mut sum = Vector2::new(0 as $type, 0 as $type); + + for vertex in polygon.iter() { + sum += *vertex; + } + + sum / polygon.len() as $type + } + } + }; +} + +impl_polygon_centroid!(i8); +impl_polygon_centroid!(i16); +impl_polygon_centroid!(i32); +impl_polygon_centroid!(i64); +impl_polygon_centroid!(u8); +impl_polygon_centroid!(u16); +impl_polygon_centroid!(u32); +impl_polygon_centroid!(u64); +impl_polygon_centroid!(f32); +impl_polygon_centroid!(f64);