#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
use std::error::Error;
#[derive(Debug, Clone, PartialEq)]
pub enum GeometryError {
NonFiniteComponent {
type_name: &'static str,
component: &'static str,
value: f64,
},
NegativeRadius(f64),
NonFiniteRadius(f64),
NonFiniteTolerance(f64),
NegativeTolerance(f64),
IdenticalPoints,
ZeroDirectionVector,
InvalidBounds {
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
},
}
impl GeometryError {
#[must_use]
pub const fn non_finite_component(
type_name: &'static str,
component: &'static str,
value: f64,
) -> Self {
Self::NonFiniteComponent {
type_name,
component,
value,
}
}
pub const fn validate_tolerance(tolerance: f64) -> Result<f64, Self> {
if !tolerance.is_finite() {
return Err(Self::NonFiniteTolerance(tolerance));
}
if tolerance < 0.0 {
return Err(Self::NegativeTolerance(tolerance));
}
Ok(tolerance)
}
}
impl fmt::Display for GeometryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonFiniteComponent {
type_name,
component,
value,
} => write!(
formatter,
"{type_name} {component} component must be finite, got {value}"
),
Self::NegativeRadius(value) => {
write!(formatter, "circle radius must be non-negative, got {value}")
}
Self::NonFiniteRadius(value) => {
write!(formatter, "circle radius must be finite, got {value}")
}
Self::NonFiniteTolerance(value) => {
write!(formatter, "tolerance must be finite, got {value}")
}
Self::NegativeTolerance(value) => {
write!(formatter, "tolerance must be non-negative, got {value}")
}
Self::IdenticalPoints => write!(formatter, "points must be distinct"),
Self::ZeroDirectionVector => write!(formatter, "direction vector must be non-zero"),
Self::InvalidBounds {
min_x,
min_y,
max_x,
max_y,
} => write!(
formatter,
"aabb min must not exceed max, got min=({min_x}, {min_y}) and max=({max_x}, {max_y})"
),
}
}
}
impl Error for GeometryError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Axis2 {
X,
Y,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Axis3 {
X,
Y,
Z,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Coordinate2 {
x: f64,
y: f64,
}
impl Coordinate2 {
#[must_use]
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
#[must_use]
pub const fn origin() -> Self {
Self::new(0.0, 0.0)
}
#[must_use]
pub const fn x(self) -> f64 {
self.x
}
#[must_use]
pub const fn y(self) -> f64 {
self.y
}
#[must_use]
pub const fn component(self, axis: Axis2) -> f64 {
match axis {
Axis2::X => self.x,
Axis2::Y => self.y,
}
}
#[must_use]
pub const fn as_tuple(self) -> (f64, f64) {
(self.x, self.y)
}
#[must_use]
pub const fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Coordinate3 {
x: f64,
y: f64,
z: f64,
}
impl Coordinate3 {
#[must_use]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
#[must_use]
pub const fn origin() -> Self {
Self::new(0.0, 0.0, 0.0)
}
#[must_use]
pub const fn x(self) -> f64 {
self.x
}
#[must_use]
pub const fn y(self) -> f64 {
self.y
}
#[must_use]
pub const fn z(self) -> f64 {
self.z
}
#[must_use]
pub const fn component(self, axis: Axis3) -> f64 {
match axis {
Axis3::X => self.x,
Axis3::Y => self.y,
Axis3::Z => self.z,
}
}
#[must_use]
pub const fn as_tuple(self) -> (f64, f64, f64) {
(self.x, self.y, self.z)
}
#[must_use]
pub const fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
}
}
#[cfg(test)]
mod tests {
use super::{Axis2, Axis3, Coordinate2, Coordinate3, GeometryError};
#[test]
fn exposes_coordinate_components() {
let coordinate = Coordinate2::new(2.0, 3.0);
assert_eq!(coordinate.component(Axis2::X), 2.0);
assert_eq!(coordinate.component(Axis2::Y), 3.0);
assert_eq!(coordinate.as_tuple(), (2.0, 3.0));
assert!(coordinate.is_finite());
assert_eq!(Coordinate2::origin(), Coordinate2::new(0.0, 0.0));
}
#[test]
fn exposes_three_dimensional_components() {
let coordinate = Coordinate3::new(2.0, 3.0, 5.0);
assert_eq!(coordinate.component(Axis3::Z), 5.0);
assert_eq!(coordinate.as_tuple(), (2.0, 3.0, 5.0));
assert!(coordinate.is_finite());
assert_eq!(Coordinate3::origin(), Coordinate3::new(0.0, 0.0, 0.0));
}
#[test]
fn validates_tolerance_values() {
assert_eq!(GeometryError::validate_tolerance(0.25), Ok(0.25));
assert_eq!(
GeometryError::validate_tolerance(-0.25),
Err(GeometryError::NegativeTolerance(-0.25))
);
assert!(matches!(
GeometryError::validate_tolerance(f64::NAN),
Err(GeometryError::NonFiniteTolerance(value)) if value.is_nan()
));
}
}