pub mod convex_hull;
pub mod polygon;
pub mod triangulation;
pub mod voronoi;
pub use convex_hull::{
convex_hull_area, convex_hull_merge, convex_hull_perimeter, gift_wrapping, graham_scan,
monotone_chain, point_in_convex_hull,
};
pub use polygon::{
convex_polygon_union, convex_polygons_intersect, line_segment_intersection,
minimum_bounding_rectangle, point_in_polygon, polygon_area, polygon_centroid,
polygon_clipping_sutherland_hodgman, polygon_is_convex, polygon_is_simple, polygon_perimeter,
polygon_signed_area, polygon_simplify_rdp, IntersectionResult,
};
pub use triangulation::{
constrained_delaunay, delaunay_triangulation, mesh_refine, point_location,
triangle_aspect_ratio, triangle_minimum_angle, DelaunayTriangulation,
};
pub use voronoi::{
lloyd_relaxation, nearest_neighbor_voronoi, voronoi_from_delaunay, VoronoiDiagram, VoronoiEdge,
};
use std::fmt;
use std::hash::{Hash, Hasher};
use thiserror::Error;
pub const GEOMETRY_EPSILON: f64 = 1e-10;
#[derive(Error, Debug, Clone)]
pub enum GeometryError {
#[error("Insufficient points: need at least {needed}, got {got}")]
InsufficientPoints {
needed: usize,
got: usize,
},
#[error("Degenerate geometry: {0}")]
DegenerateGeometry(String),
#[error("Invalid polygon: {0}")]
InvalidPolygon(String),
#[error("Point not found: {0}")]
PointNotFound(String),
#[error("Numerical error: {0}")]
NumericalError(String),
#[error("Index out of bounds: {0}")]
IndexOutOfBounds(String),
#[error("Computation error: {0}")]
ComputationError(String),
}
pub type GeometryResult<T> = std::result::Result<T, GeometryError>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point2D {
pub x: f64,
pub y: f64,
}
impl Point2D {
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
pub fn origin() -> Self {
Self { x: 0.0, y: 0.0 }
}
pub fn distance(&self, other: &Point2D) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
pub fn distance_squared(&self, other: &Point2D) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
dx * dx + dy * dy
}
pub fn dot(&self, other: &Point2D) -> f64 {
self.x * other.x + self.y * other.y
}
pub fn cross(&self, other: &Point2D) -> f64 {
self.x * other.y - self.y * other.x
}
pub fn sub(&self, other: &Point2D) -> Point2D {
Point2D::new(self.x - other.x, self.y - other.y)
}
pub fn add(&self, other: &Point2D) -> Point2D {
Point2D::new(self.x + other.x, self.y + other.y)
}
pub fn scale(&self, factor: f64) -> Point2D {
Point2D::new(self.x * factor, self.y * factor)
}
pub fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
pub fn angle(&self) -> f64 {
self.y.atan2(self.x)
}
}
impl fmt::Display for Point2D {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point3D {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Point3D {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
pub fn origin() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
}
}
pub fn distance(&self, other: &Point3D) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
let dz = self.z - other.z;
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn distance_squared(&self, other: &Point3D) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
let dz = self.z - other.z;
dx * dx + dy * dy + dz * dz
}
pub fn dot(&self, other: &Point3D) -> f64 {
self.x * other.x + self.y * other.y + self.z * other.z
}
pub fn cross(&self, other: &Point3D) -> Point3D {
Point3D::new(
self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x,
)
}
pub fn sub(&self, other: &Point3D) -> Point3D {
Point3D::new(self.x - other.x, self.y - other.y, self.z - other.z)
}
pub fn add(&self, other: &Point3D) -> Point3D {
Point3D::new(self.x + other.x, self.y + other.y, self.z + other.z)
}
pub fn scale(&self, factor: f64) -> Point3D {
Point3D::new(self.x * factor, self.y * factor, self.z * factor)
}
pub fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn to_2d(&self) -> Point2D {
Point2D::new(self.x, self.y)
}
}
impl fmt::Display for Point3D {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {}, {})", self.x, self.y, self.z)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Triangle {
pub a: usize,
pub b: usize,
pub c: usize,
}
impl Triangle {
pub fn new(a: usize, b: usize, c: usize) -> Self {
Self { a, b, c }
}
pub fn vertices(&self) -> (usize, usize, usize) {
(self.a, self.b, self.c)
}
pub fn edges(&self) -> [Edge; 3] {
[
Edge::new(self.a, self.b),
Edge::new(self.b, self.c),
Edge::new(self.c, self.a),
]
}
pub fn contains_vertex(&self, idx: usize) -> bool {
self.a == idx || self.b == idx || self.c == idx
}
pub fn shares_edge_with(&self, other: &Triangle) -> bool {
let my_edges = self.edges();
let other_edges = other.edges();
for me in &my_edges {
for oe in &other_edges {
if me == oe {
return true;
}
}
}
false
}
}
impl fmt::Display for Triangle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Triangle({}, {}, {})", self.a, self.b, self.c)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Edge {
pub start: usize,
pub end: usize,
}
impl Edge {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn canonical(&self) -> (usize, usize) {
if self.start <= self.end {
(self.start, self.end)
} else {
(self.end, self.start)
}
}
pub fn other(&self, vertex: usize) -> Option<usize> {
if vertex == self.start {
Some(self.end)
} else if vertex == self.end {
Some(self.start)
} else {
None
}
}
}
impl PartialEq for Edge {
fn eq(&self, other: &Self) -> bool {
self.canonical() == other.canonical()
}
}
impl Eq for Edge {}
impl Hash for Edge {
fn hash<H: Hasher>(&self, state: &mut H) {
let (a, b) = self.canonical();
a.hash(state);
b.hash(state);
}
}
impl fmt::Display for Edge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Edge({}, {})", self.start, self.end)
}
}
#[derive(Debug, Clone)]
pub struct Polygon {
pub vertices: Vec<Point2D>,
}
impl Polygon {
pub fn new(vertices: Vec<Point2D>) -> Self {
Self { vertices }
}
pub fn num_vertices(&self) -> usize {
self.vertices.len()
}
pub fn num_edges(&self) -> usize {
self.vertices.len()
}
pub fn is_valid(&self) -> bool {
self.vertices.len() >= 3
}
pub fn signed_area(&self) -> f64 {
polygon::polygon_signed_area(&self.vertices)
}
pub fn area(&self) -> f64 {
self.signed_area().abs()
}
pub fn centroid(&self) -> GeometryResult<Point2D> {
polygon::polygon_centroid(&self.vertices)
}
pub fn perimeter(&self) -> f64 {
polygon::polygon_perimeter(&self.vertices)
}
pub fn contains(&self, point: &Point2D) -> bool {
polygon::point_in_polygon(point, &self.vertices)
}
pub fn is_convex(&self) -> bool {
polygon::polygon_is_convex(&self.vertices)
}
}
impl fmt::Display for Polygon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Polygon({} vertices)", self.vertices.len())
}
}
pub fn orientation(p: &Point2D, q: &Point2D, r: &Point2D) -> f64 {
(q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x)
}
pub fn is_ccw(p: &Point2D, q: &Point2D, r: &Point2D) -> bool {
orientation(p, q, r) > GEOMETRY_EPSILON
}
pub fn is_collinear(p: &Point2D, q: &Point2D, r: &Point2D) -> bool {
orientation(p, q, r).abs() <= GEOMETRY_EPSILON
}
pub fn circumcircle(a: &Point2D, b: &Point2D, c: &Point2D) -> GeometryResult<(Point2D, f64)> {
let ax = a.x;
let ay = a.y;
let bx = b.x;
let by = b.y;
let cx = c.x;
let cy = c.y;
let d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
if d.abs() < GEOMETRY_EPSILON {
return Err(GeometryError::DegenerateGeometry(
"Points are collinear, circumcircle is undefined".to_string(),
));
}
let ux = ((ax * ax + ay * ay) * (by - cy)
+ (bx * bx + by * by) * (cy - ay)
+ (cx * cx + cy * cy) * (ay - by))
/ d;
let uy = ((ax * ax + ay * ay) * (cx - bx)
+ (bx * bx + by * by) * (ax - cx)
+ (cx * cx + cy * cy) * (bx - ax))
/ d;
let center = Point2D::new(ux, uy);
let r_sq = center.distance_squared(a);
Ok((center, r_sq))
}
pub fn in_circumcircle(p: &Point2D, a: &Point2D, b: &Point2D, c: &Point2D) -> bool {
let adx = a.x - p.x;
let ady = a.y - p.y;
let bdx = b.x - p.x;
let bdy = b.y - p.y;
let cdx = c.x - p.x;
let cdy = c.y - p.y;
let ab_lift = adx * adx + ady * ady;
let bb_lift = bdx * bdx + bdy * bdy;
let cb_lift = cdx * cdx + cdy * cdy;
let det = adx * (bdy * cb_lift - cdy * bb_lift) - ady * (bdx * cb_lift - cdx * bb_lift)
+ ab_lift * (bdx * cdy - cdx * bdy);
let orient = orientation(a, b, c);
if orient > 0.0 {
det > GEOMETRY_EPSILON
} else {
det < -GEOMETRY_EPSILON
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point2d_basic() {
let p = Point2D::new(3.0, 4.0);
assert!((p.magnitude() - 5.0).abs() < 1e-10);
let q = Point2D::new(0.0, 0.0);
assert!((p.distance(&q) - 5.0).abs() < 1e-10);
}
#[test]
fn test_point2d_operations() {
let a = Point2D::new(1.0, 2.0);
let b = Point2D::new(3.0, 4.0);
let sum = a.add(&b);
assert!((sum.x - 4.0).abs() < 1e-10);
assert!((sum.y - 6.0).abs() < 1e-10);
let diff = b.sub(&a);
assert!((diff.x - 2.0).abs() < 1e-10);
assert!((diff.y - 2.0).abs() < 1e-10);
let scaled = a.scale(2.0);
assert!((scaled.x - 2.0).abs() < 1e-10);
assert!((scaled.y - 4.0).abs() < 1e-10);
assert!((a.dot(&b) - 11.0).abs() < 1e-10);
assert!((a.cross(&b) - (-2.0)).abs() < 1e-10);
}
#[test]
fn test_point3d_basic() {
let p = Point3D::new(1.0, 2.0, 3.0);
let q = Point3D::origin();
let dist = p.distance(&q);
assert!((dist - (14.0_f64).sqrt()).abs() < 1e-10);
let p2d = p.to_2d();
assert!((p2d.x - 1.0).abs() < 1e-10);
assert!((p2d.y - 2.0).abs() < 1e-10);
}
#[test]
fn test_point3d_cross_product() {
let a = Point3D::new(1.0, 0.0, 0.0);
let b = Point3D::new(0.0, 1.0, 0.0);
let c = a.cross(&b);
assert!((c.x - 0.0).abs() < 1e-10);
assert!((c.y - 0.0).abs() < 1e-10);
assert!((c.z - 1.0).abs() < 1e-10);
}
#[test]
fn test_triangle_basic() {
let t = Triangle::new(0, 1, 2);
assert_eq!(t.vertices(), (0, 1, 2));
assert!(t.contains_vertex(1));
assert!(!t.contains_vertex(3));
let edges = t.edges();
assert_eq!(edges[0], Edge::new(0, 1));
assert_eq!(edges[1], Edge::new(1, 2));
assert_eq!(edges[2], Edge::new(2, 0));
}
#[test]
fn test_edge_equality() {
let e1 = Edge::new(0, 1);
let e2 = Edge::new(1, 0);
assert_eq!(e1, e2);
let e3 = Edge::new(0, 2);
assert_ne!(e1, e3);
}
#[test]
fn test_orientation() {
let p = Point2D::new(0.0, 0.0);
let q = Point2D::new(1.0, 0.0);
let r = Point2D::new(0.0, 1.0);
assert!(orientation(&p, &q, &r) > 0.0);
assert!(orientation(&p, &r, &q) < 0.0);
let s = Point2D::new(2.0, 0.0);
assert!(is_collinear(&p, &q, &s));
}
#[test]
fn test_circumcircle() {
let a = Point2D::new(0.0, 0.0);
let b = Point2D::new(1.0, 0.0);
let c = Point2D::new(0.5, 0.5_f64.sqrt());
let result = circumcircle(&a, &b, &c);
assert!(result.is_ok());
let (center, r_sq) = result.expect("circumcircle should succeed");
assert!((center.x - 0.5).abs() < 1e-6);
let d_a = center.distance_squared(&a);
let d_b = center.distance_squared(&b);
let d_c = center.distance_squared(&c);
assert!((d_a - d_b).abs() < 1e-8);
assert!((d_b - d_c).abs() < 1e-8);
assert!((d_a - r_sq).abs() < 1e-8);
}
#[test]
fn test_in_circumcircle() {
let a = Point2D::new(0.0, 0.0);
let b = Point2D::new(4.0, 0.0);
let c = Point2D::new(0.0, 4.0);
let inside = Point2D::new(1.0, 1.0);
assert!(in_circumcircle(&inside, &a, &b, &c));
let outside = Point2D::new(10.0, 10.0);
assert!(!in_circumcircle(&outside, &a, &b, &c));
}
#[test]
fn test_polygon_basic() {
let poly = Polygon::new(vec![
Point2D::new(0.0, 0.0),
Point2D::new(4.0, 0.0),
Point2D::new(4.0, 3.0),
Point2D::new(0.0, 3.0),
]);
assert!(poly.is_valid());
assert_eq!(poly.num_vertices(), 4);
assert_eq!(poly.num_edges(), 4);
assert!((poly.area() - 12.0).abs() < 1e-10);
assert!(poly.is_convex());
}
#[test]
fn test_triangle_shares_edge() {
let t1 = Triangle::new(0, 1, 2);
let t2 = Triangle::new(1, 2, 3);
let t3 = Triangle::new(3, 4, 5);
assert!(t1.shares_edge_with(&t2));
assert!(!t1.shares_edge_with(&t3));
}
#[test]
fn test_display_traits() {
let p2 = Point2D::new(1.5, 2.5);
assert_eq!(format!("{}", p2), "(1.5, 2.5)");
let p3 = Point3D::new(1.0, 2.0, 3.0);
assert_eq!(format!("{}", p3), "(1, 2, 3)");
let t = Triangle::new(0, 1, 2);
assert_eq!(format!("{}", t), "Triangle(0, 1, 2)");
let e = Edge::new(0, 1);
assert_eq!(format!("{}", e), "Edge(0, 1)");
}
}