Skip to main content

geop_core_math/primitives/
datum.rs

1//! [`Datum`]: reference geometry — a point, an axis, a plane or a whole
2//! coordinate frame — that geometry is built *with*, not *of*.
3
4use serde::{Deserialize, Serialize};
5
6use super::CoordinateSystem;
7use crate::{
8    geop_error::{GeopError, GeopResult},
9    scalars::Scalar,
10};
11
12/// What a datum stands for: which part of its frame.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum DatumKind {
16    /// The frame's origin.
17    Point,
18    /// The line through the frame's origin along its `w`.
19    Axis,
20    /// The plane through the frame's origin normal to its `w`; `u`/`v` are
21    /// a sketch's `x`/`y` on it.
22    Plane,
23    /// The frame itself: a coordinate system — its origin, its three axes
24    /// and the three planes between them, each usable on its own (see
25    /// [`Datum::component`]).
26    Frame,
27}
28
29/// One of a frame's own axes: `x` is its `u`, `y` its `v`, `z` its `w`.
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum FrameAxis {
33    X,
34    Y,
35    Z,
36}
37
38impl FrameAxis {
39    pub const ALL: [FrameAxis; 3] = [FrameAxis::X, FrameAxis::Y, FrameAxis::Z];
40
41    /// `x`, `y` or `z`.
42    pub fn letter(self) -> &'static str {
43        match self {
44            FrameAxis::X => "x",
45            FrameAxis::Y => "y",
46            FrameAxis::Z => "z",
47        }
48    }
49
50    /// The frame's axis this is.
51    pub fn of<S: Scalar>(self, frame: &CoordinateSystem<S>) -> crate::vector::Vector3<S> {
52        match self {
53            FrameAxis::X => *frame.u(),
54            FrameAxis::Y => *frame.v(),
55            FrameAxis::Z => *frame.w(),
56        }
57    }
58}
59
60/// A part of a frame datum that can be used on its own. Its origin is not
61/// one: the frame as a whole is used as its origin already.
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum DatumComponent {
65    /// One of its axes.
66    Axis(FrameAxis),
67    /// The plane normal to one of its axes: `Plane(Z)` is its `xy` plane.
68    Plane(FrameAxis),
69}
70
71impl std::fmt::Display for DatumComponent {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        match self {
74            DatumComponent::Axis(axis) => write!(f, "{} axis", axis.letter()),
75            DatumComponent::Plane(normal) => {
76                let [a, b] = match normal {
77                    FrameAxis::X => [FrameAxis::Y, FrameAxis::Z],
78                    FrameAxis::Y => [FrameAxis::Z, FrameAxis::X],
79                    FrameAxis::Z => [FrameAxis::X, FrameAxis::Y],
80                };
81                write!(f, "{}{} plane", a.letter(), b.letter())
82            }
83        }
84    }
85}
86
87/// One datum: a right-handed orthonormal frame, and which part of it the
88/// datum stands for. Every datum has a whole frame, whatever its kind, so
89/// anything built on it — a sketch on a plane, a datum offset from a point
90/// — has axes to be built along.
91#[derive(Clone, Debug)]
92pub struct Datum<S: Scalar> {
93    pub kind: DatumKind,
94    pub frame: CoordinateSystem<S>,
95}
96
97impl<S: Scalar> Datum<S> {
98    /// The datum `component` of this frame stands for, as a datum of its
99    /// own, with the frame turned so its `w` is that axis — or that plane's
100    /// normal — and its other two axes keep their order: a sketch's `x`/`y`
101    /// on the plane normal to `z` run along `x`/`y`, on the one normal to
102    /// `x` along `y`/`z`, on the one normal to `y` along `x`/`-z`. Fails if
103    /// this datum is not a frame.
104    pub fn component(&self, component: DatumComponent) -> GeopResult<Datum<S>> {
105        if self.kind != DatumKind::Frame {
106            return Err(GeopError::new(format!(
107                "Datum::component: a {:?} datum has no {component}, only a frame has",
108                self.kind
109            )));
110        }
111        let f = &self.frame;
112        let along = |axis: FrameAxis| {
113            let (u, v, w) = (*f.u(), *f.v(), *f.w());
114            let (u, v, w) = match axis {
115                FrameAxis::X => (v, w, u),
116                FrameAxis::Y => (u, w.neg(), v),
117                FrameAxis::Z => (u, v, w),
118            };
119            CoordinateSystem::try_new(*f.origin(), u, v, w)
120        };
121        Ok(match component {
122            DatumComponent::Axis(axis) => Datum {
123                kind: DatumKind::Axis,
124                frame: along(axis)?,
125            },
126            DatumComponent::Plane(normal) => Datum {
127                kind: DatumKind::Plane,
128                frame: along(normal)?,
129            },
130        })
131    }
132}