#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Point2 {
pub x: f64,
pub y: f64,
}
pub const BOND_LEN: f64 = 1.0;
impl Point2 {
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
pub const ORIGIN: Self = Self { x: 0.0, y: 0.0 };
pub fn dist(self, other: Self) -> f64 {
(self - other).norm()
}
pub fn norm(self) -> f64 {
self.x.hypot(self.y)
}
pub fn normalized(self) -> Self {
let n = self.norm();
if n < f64::EPSILON {
self
} else {
Self::new(self.x / n, self.y / n)
}
}
pub fn rotated(self, radians: f64) -> Self {
let (s, c) = radians.sin_cos();
Self::new(self.x * c - self.y * s, self.x * s + self.y * c)
}
pub fn rotated_about(self, pivot: Self, radians: f64) -> Self {
(self - pivot).rotated(radians) + pivot
}
pub fn angle(self) -> f64 {
self.y.atan2(self.x)
}
pub fn cross(self, other: Self) -> f64 {
self.x * other.y - self.y * other.x
}
pub fn dot(self, other: Self) -> f64 {
self.x * other.x + self.y * other.y
}
pub fn mirrored(self, pivot: Self, through: Self) -> Self {
let d = through.normalized();
let v = self - pivot;
let proj = d * (2.0 * v.dot(d));
proj - v + pivot
}
}
impl std::ops::Add for Point2 {
type Output = Self;
fn add(self, o: Self) -> Self {
Self::new(self.x + o.x, self.y + o.y)
}
}
impl std::ops::Sub for Point2 {
type Output = Self;
fn sub(self, o: Self) -> Self {
Self::new(self.x - o.x, self.y - o.y)
}
}
impl std::ops::Mul<f64> for Point2 {
type Output = Self;
fn mul(self, k: f64) -> Self {
Self::new(self.x * k, self.y * k)
}
}
pub fn side_of(a: Point2, b: Point2, p: Point2) -> f64 {
(b - a).cross(p - a)
}
pub fn segments_cross(p1: Point2, p2: Point2, q1: Point2, q2: Point2) -> bool {
const EPS: f64 = 1e-9;
for a in [p1, p2] {
for b in [q1, q2] {
if a.dist(b) < EPS {
return false;
}
}
}
let d1 = side_of(q1, q2, p1);
let d2 = side_of(q1, q2, p2);
let d3 = side_of(p1, p2, q1);
let d4 = side_of(p1, p2, q2);
if ((d1 > EPS && d2 < -EPS) || (d1 < -EPS && d2 > EPS))
&& ((d3 > EPS && d4 < -EPS) || (d3 < -EPS && d4 > EPS))
{
return true;
}
let on = |a: Point2, b: Point2, p: Point2, d: f64| {
d.abs() < EPS
&& p.x >= a.x.min(b.x) - EPS
&& p.x <= a.x.max(b.x) + EPS
&& p.y >= a.y.min(b.y) - EPS
&& p.y <= a.y.max(b.y) + EPS
};
on(q1, q2, p1, d1) || on(q1, q2, p2, d2) || on(p1, p2, q1, d3) || on(p1, p2, q2, d4)
}
pub fn point_in_polygon(p: Point2, poly: &[Point2]) -> bool {
let n = poly.len();
if n < 3 {
return false;
}
let mut inside = false;
for i in 0..n {
let (a, b) = (poly[i], poly[(i + 1) % n]);
if (a.y > p.y) != (b.y > p.y) {
let t = (p.y - a.y) / (b.y - a.y);
if a.x + t * (b.x - a.x) > p.x {
inside = !inside;
}
}
}
inside
}
pub fn regular_polygon(n: usize, start_angle: f64) -> Vec<Point2> {
debug_assert!(n >= 3, "多边形至少三个顶点");
let r = BOND_LEN / (2.0 * (std::f64::consts::PI / n as f64).sin());
let step = std::f64::consts::TAU / n as f64;
(0..n)
.map(|i| Point2::new(r, 0.0).rotated(start_angle + step * i as f64))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 1e-9;
#[test]
fn a_regular_polygon_has_unit_sides() {
for n in 3..=12 {
let p = regular_polygon(n, 0.0);
assert_eq!(p.len(), n);
for i in 0..n {
let d = p[i].dist(p[(i + 1) % n]);
assert!((d - BOND_LEN).abs() < TOL, "{n} 边形第 {i} 条边长 {d}");
}
}
}
#[test]
fn mirroring_twice_is_identity() {
let pivot = Point2::new(1.0, 2.0);
let axis = Point2::new(0.6, -0.8);
let p = Point2::new(-3.0, 5.0);
let back = p.mirrored(pivot, axis).mirrored(pivot, axis);
assert!(p.dist(back) < TOL);
}
#[test]
fn mirroring_flips_which_side_a_point_is_on() {
let a = Point2::ORIGIN;
let b = Point2::new(1.0, 0.0);
let p = Point2::new(0.5, 1.0);
let m = p.mirrored(a, b - a);
assert!(
side_of(a, b, p) * side_of(a, b, m) < 0.0,
"镜像后没有换侧:{m:?}"
);
}
#[test]
fn crossing_segments_are_detected_and_touching_ones_are_not() {
let cross = segments_cross(
Point2::new(0.0, 0.0),
Point2::new(2.0, 2.0),
Point2::new(0.0, 2.0),
Point2::new(2.0, 0.0),
);
assert!(cross, "对角线必须判为交叉");
let shared = segments_cross(
Point2::new(0.0, 0.0),
Point2::new(1.0, 0.0),
Point2::new(1.0, 0.0),
Point2::new(1.5, 1.0),
);
assert!(!shared, "共端点不该判为交叉");
let apart = segments_cross(
Point2::new(0.0, 0.0),
Point2::new(1.0, 0.0),
Point2::new(0.0, 1.0),
Point2::new(1.0, 1.0),
);
assert!(!apart, "平行且分离的两段不该判为交叉");
}
#[test]
fn a_point_inside_a_convex_polygon_is_found_and_one_outside_is_not() {
let hex = regular_polygon(6, 0.0);
assert!(point_in_polygon(Point2::ORIGIN, &hex), "形心该在里面");
assert!(
!point_in_polygon(Point2::new(5.0, 0.0), &hex),
"远处的点该在外面"
);
assert!(
!point_in_polygon(Point2::new(-5.0, 0.0), &hex),
"左边也是外面"
);
}
#[test]
fn the_centroid_of_a_concave_polygon_can_be_outside_it() {
let u = [
Point2::new(0.0, 0.0),
Point2::new(3.0, 0.0),
Point2::new(3.0, 3.0),
Point2::new(2.0, 3.0),
Point2::new(2.0, 1.0),
Point2::new(1.0, 1.0),
Point2::new(1.0, 3.0),
Point2::new(0.0, 3.0),
];
let c = u.iter().fold(Point2::ORIGIN, |s, p| s + *p) * (1.0 / u.len() as f64);
assert!(
!point_in_polygon(c, &u),
"这个 U 形的形心 {c:?} 落在口子里,该判成外面"
);
assert!(point_in_polygon(Point2::new(0.5, 2.0), &u), "左臂里面");
assert!(point_in_polygon(Point2::new(1.5, 0.5), &u), "底下里面");
assert!(!point_in_polygon(Point2::new(1.5, 2.0), &u), "口子里是外面");
}
#[test]
fn a_degenerate_polygon_is_refused_instead_of_panicking() {
assert!(!point_in_polygon(Point2::ORIGIN, &[]));
assert!(!point_in_polygon(
Point2::ORIGIN,
&[Point2::new(1.0, 1.0), Point2::new(2.0, 2.0)]
));
}
#[test]
fn normalizing_a_zero_vector_does_not_produce_nan() {
let z = Point2::ORIGIN.normalized();
assert!(z.x.is_finite() && z.y.is_finite());
}
}