Exactly filter upon location after bbox hit

This commit is contained in:
Mikolaj Wielgus 2026-05-28 01:56:41 +02:00
parent e1795413bd
commit 85f33f1a6c
14 changed files with 488 additions and 284 deletions

View File

@ -22,7 +22,7 @@ use crate::{
LayerId, Layout, LayoutHalfDelta,
compounds::{ComponentId, NetId, PinId},
},
math::Vector2,
vector::Vector2,
};
#[derive(Clone, Debug, Getters, Delta)]

View File

@ -24,73 +24,94 @@ impl Layout {
&self,
rect: Rect3<i64>,
) -> impl Iterator<Item = JointId> {
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<i64>) -> impl Iterator<Item = JointId> {
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<i64>) -> impl Iterator<Item = SegmentId> {
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<i64>,
) -> impl Iterator<Item = SegmentId> {
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<i64>) -> impl Iterator<Item = SegmentId> {
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<i64>) -> impl Iterator<Item = ViaId> {
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<i64>) -> impl Iterator<Item = ViaId> {
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<i64>) -> impl Iterator<Item = ViaId> {
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<i64>) -> impl Iterator<Item = PolygonId> {
@ -106,19 +127,25 @@ impl Layout {
&self,
rect: Rect3<i64>,
) -> impl Iterator<Item = PolygonId> {
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<i64>) -> impl Iterator<Item = PolygonId> {
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<i64>) -> impl Iterator<Item = NetId> {

View File

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

View File

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

View File

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

View File

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

View File

@ -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<i64>) {

View File

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

View File

@ -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<T> {
min: Vector2<T>,
max: Vector2<T>,
}
impl<T: Ord + Copy> Rect2<T> {
pub fn new(from: Vector2<T>, to: Vector2<T>) -> 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<i64> {
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<T> {
min: Vector3<T>,
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),
),
}
}
}
impl Rect3<i64> {
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<T> {
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<T> {
pub x: T,
pub y: T,
pub z: T,
}
impl<T: Copy> Vector3<T> {
pub fn xy(self) -> Vector2<T> {
Vector2::new(self.x, self.y)
}
}
impl<T: Copy> From<[T; 2]> for Vector2<T> {
fn from(from: [T; 2]) -> Self {
Self {
x: from[0],
y: from[1],
}
}
}
impl<T: Copy> From<Vector2<T>> for [T; 2] {
fn from(from: Vector2<T>) -> 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<W: Copy + Ord>(
vertex_count: usize,

View File

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

View File

@ -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)]

209
topola/src/rect.rs Normal file
View File

@ -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<T> {
min: Vector2<T>,
max: Vector2<T>,
}
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: Copy + Ord> Rect2<T> {
pub fn new(from: Vector2<T>, to: Vector2<T>) -> 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<T: RTreeNum> Rect2<T> {
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<T> {
min: Vector3<T>,
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),
),
}
}
}
impl<T: Copy + Ord> Rect3<T> {
pub fn rect2(&self) -> Rect2<T> {
Rect2::new(self.min.xy(), self.max.xy())
}
}
impl<T: RTreeNum> Rect3<T> {
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],
)
}
}

View File

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

218
topola/src/vector.rs Normal file
View File

@ -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<T> {
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<T> {
pub x: T,
pub y: T,
pub z: T,
}
impl<T: Copy> Vector3<T> {
pub fn xy(self) -> Vector2<T> {
Vector2::new(self.x, self.y)
}
}
impl<T: Copy> From<[T; 2]> for Vector2<T> {
fn from(from: [T; 2]) -> Self {
Self {
x: from[0],
y: from[1],
}
}
}
impl<T: Copy> From<Vector2<T>> for [T; 2] {
fn from(from: Vector2<T>) -> 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);