1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum GeometryError {
10 NonFiniteComponent {
15 type_name: &'static str,
17 component: &'static str,
19 value: f64,
21 },
22 NegativeRadius(f64),
24 NonFiniteRadius(f64),
26 NonFiniteTolerance(f64),
28 NegativeTolerance(f64),
30 IdenticalPoints,
32 ZeroDirectionVector,
34 InvalidBounds {
36 min_x: f64,
38 min_y: f64,
40 max_x: f64,
42 max_y: f64,
44 },
45}
46
47impl GeometryError {
48 #[must_use]
50 pub const fn non_finite_component(
51 type_name: &'static str,
52 component: &'static str,
53 value: f64,
54 ) -> Self {
55 Self::NonFiniteComponent {
56 type_name,
57 component,
58 value,
59 }
60 }
61
62 pub const fn validate_tolerance(tolerance: f64) -> Result<f64, Self> {
71 if !tolerance.is_finite() {
72 return Err(Self::NonFiniteTolerance(tolerance));
73 }
74
75 if tolerance < 0.0 {
76 return Err(Self::NegativeTolerance(tolerance));
77 }
78
79 Ok(tolerance)
80 }
81}
82
83impl fmt::Display for GeometryError {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Self::NonFiniteComponent {
87 type_name,
88 component,
89 value,
90 } => write!(
91 formatter,
92 "{type_name} {component} component must be finite, got {value}"
93 ),
94 Self::NegativeRadius(value) => {
95 write!(formatter, "circle radius must be non-negative, got {value}")
96 }
97 Self::NonFiniteRadius(value) => {
98 write!(formatter, "circle radius must be finite, got {value}")
99 }
100 Self::NonFiniteTolerance(value) => {
101 write!(formatter, "tolerance must be finite, got {value}")
102 }
103 Self::NegativeTolerance(value) => {
104 write!(formatter, "tolerance must be non-negative, got {value}")
105 }
106 Self::IdenticalPoints => write!(formatter, "points must be distinct"),
107 Self::ZeroDirectionVector => write!(formatter, "direction vector must be non-zero"),
108 Self::InvalidBounds {
109 min_x,
110 min_y,
111 max_x,
112 max_y,
113 } => write!(
114 formatter,
115 "aabb min must not exceed max, got min=({min_x}, {min_y}) and max=({max_x}, {max_y})"
116 ),
117 }
118 }
119}
120
121impl Error for GeometryError {}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum Axis2 {
126 X,
128 Y,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum Axis3 {
135 X,
137 Y,
139 Z,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq)]
145pub struct Coordinate2 {
146 x: f64,
147 y: f64,
148}
149
150impl Coordinate2 {
151 #[must_use]
153 pub const fn new(x: f64, y: f64) -> Self {
154 Self { x, y }
155 }
156
157 #[must_use]
159 pub const fn origin() -> Self {
160 Self::new(0.0, 0.0)
161 }
162
163 #[must_use]
165 pub const fn x(self) -> f64 {
166 self.x
167 }
168
169 #[must_use]
171 pub const fn y(self) -> f64 {
172 self.y
173 }
174
175 #[must_use]
177 pub const fn component(self, axis: Axis2) -> f64 {
178 match axis {
179 Axis2::X => self.x,
180 Axis2::Y => self.y,
181 }
182 }
183
184 #[must_use]
186 pub const fn as_tuple(self) -> (f64, f64) {
187 (self.x, self.y)
188 }
189
190 #[must_use]
192 pub const fn is_finite(self) -> bool {
193 self.x.is_finite() && self.y.is_finite()
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq)]
199pub struct Coordinate3 {
200 x: f64,
201 y: f64,
202 z: f64,
203}
204
205impl Coordinate3 {
206 #[must_use]
208 pub const fn new(x: f64, y: f64, z: f64) -> Self {
209 Self { x, y, z }
210 }
211
212 #[must_use]
214 pub const fn origin() -> Self {
215 Self::new(0.0, 0.0, 0.0)
216 }
217
218 #[must_use]
220 pub const fn x(self) -> f64 {
221 self.x
222 }
223
224 #[must_use]
226 pub const fn y(self) -> f64 {
227 self.y
228 }
229
230 #[must_use]
232 pub const fn z(self) -> f64 {
233 self.z
234 }
235
236 #[must_use]
238 pub const fn component(self, axis: Axis3) -> f64 {
239 match axis {
240 Axis3::X => self.x,
241 Axis3::Y => self.y,
242 Axis3::Z => self.z,
243 }
244 }
245
246 #[must_use]
248 pub const fn as_tuple(self) -> (f64, f64, f64) {
249 (self.x, self.y, self.z)
250 }
251
252 #[must_use]
254 pub const fn is_finite(self) -> bool {
255 self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::{Axis2, Axis3, Coordinate2, Coordinate3, GeometryError};
262
263 #[test]
264 fn exposes_coordinate_components() {
265 let coordinate = Coordinate2::new(2.0, 3.0);
266
267 assert_eq!(coordinate.component(Axis2::X), 2.0);
268 assert_eq!(coordinate.component(Axis2::Y), 3.0);
269 assert_eq!(coordinate.as_tuple(), (2.0, 3.0));
270 assert!(coordinate.is_finite());
271 assert_eq!(Coordinate2::origin(), Coordinate2::new(0.0, 0.0));
272 }
273
274 #[test]
275 fn exposes_three_dimensional_components() {
276 let coordinate = Coordinate3::new(2.0, 3.0, 5.0);
277
278 assert_eq!(coordinate.component(Axis3::Z), 5.0);
279 assert_eq!(coordinate.as_tuple(), (2.0, 3.0, 5.0));
280 assert!(coordinate.is_finite());
281 assert_eq!(Coordinate3::origin(), Coordinate3::new(0.0, 0.0, 0.0));
282 }
283
284 #[test]
285 fn validates_tolerance_values() {
286 assert_eq!(GeometryError::validate_tolerance(0.25), Ok(0.25));
287 assert_eq!(
288 GeometryError::validate_tolerance(-0.25),
289 Err(GeometryError::NegativeTolerance(-0.25))
290 );
291 assert!(matches!(
292 GeometryError::validate_tolerance(f64::NAN),
293 Err(GeometryError::NonFiniteTolerance(value)) if value.is_nan()
294 ));
295 }
296}