Skip to main content

kittycad_modeling_cmds/
coord.rs

1use parse_display::{Display, FromStr};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5/// Co-ordinate axis specifier.
6///
7/// See [cglearn.eu] for background reading.
8///
9/// [cglearn.eu]: https://cglearn.eu/pub/computer-graphics/introduction-to-geometry#material-coordinate-systems-1
10#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)]
11#[serde(rename_all = "snake_case")]
12#[display(style = "snake_case")]
13#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
14#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
15#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
16#[cfg_attr(
17    feature = "python",
18    pyo3::pyclass(from_py_object),
19    pyo3_stub_gen::derive::gen_stub_pyclass_enum
20)]
21pub enum Axis {
22    /// 'Y' axis.
23    Y = 1,
24    /// 'Z' axis.
25    Z = 2,
26}
27
28/// Specifies the sign of a co-ordinate axis.
29#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)]
30#[serde(rename_all = "snake_case")]
31#[display(style = "snake_case")]
32#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
33#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
34#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
35#[cfg_attr(
36    feature = "python",
37    pyo3::pyclass(from_py_object),
38    pyo3_stub_gen::derive::gen_stub_pyclass_enum
39)]
40pub enum Direction {
41    /// Increasing numbers.
42    Positive = 1,
43    /// Decreasing numbers.
44    Negative = -1,
45}
46
47impl std::ops::Mul for Direction {
48    type Output = Self;
49    fn mul(self, rhs: Self) -> Self::Output {
50        match self as i32 * rhs as i32 {
51            1 => Direction::Positive,
52            -1 => Direction::Negative,
53            _ => unreachable!(),
54        }
55    }
56}
57
58/// An [`Axis`] paired with a [`Direction`].
59#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)]
60#[display("({axis}, {direction})")]
61#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
62#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
63#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
64#[cfg_attr(
65    feature = "python",
66    pyo3::pyclass(from_py_object),
67    pyo3_stub_gen::derive::gen_stub_pyclass
68)]
69pub struct AxisDirectionPair {
70    /// Axis specifier.
71    pub axis: Axis,
72
73    /// Specifies which direction the axis is pointing.
74    pub direction: Direction,
75}
76
77/// Co-ordinate system definition.
78///
79/// The `up` axis must be orthogonal to the `forward` axis.
80///
81/// See [cglearn.eu] for background reading.
82///
83/// [cglearn.eu](https://cglearn.eu/pub/computer-graphics/introduction-to-geometry#material-coordinate-systems-1)
84#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)]
85#[display("forward: {forward}, up: {up}")]
86#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
87#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
88#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
89#[cfg_attr(
90    feature = "python",
91    pyo3::pyclass(from_py_object),
92    pyo3_stub_gen::derive::gen_stub_pyclass
93)]
94pub struct System {
95    /// Axis the front face of a model looks along.
96    pub forward: AxisDirectionPair,
97    /// Axis pointing up and away from a model.
98    pub up: AxisDirectionPair,
99}
100
101/// KittyCAD co-ordinate system.
102///
103/// * Forward: -Y
104/// * Up: +Z
105/// * Handedness: Right
106pub const KITTYCAD: &System = &System {
107    // -Y
108    forward: AxisDirectionPair {
109        axis: Axis::Y,
110        direction: Direction::Negative,
111    },
112    // +Z
113    up: AxisDirectionPair {
114        axis: Axis::Z,
115        direction: Direction::Positive,
116    },
117};
118
119/// OpenGL co-ordinate system.
120///
121/// * Forward: +Z
122/// * Up: +Y
123/// * Handedness: Right
124pub const OPENGL: &System = &System {
125    // +Z
126    forward: AxisDirectionPair {
127        axis: Axis::Z,
128        direction: Direction::Positive,
129    },
130    // +Y
131    up: AxisDirectionPair {
132        axis: Axis::Y,
133        direction: Direction::Positive,
134    },
135};
136
137/// Vulkan co-ordinate system.
138///
139/// * Forward: +Z
140/// * Up: -Y
141/// * Handedness: Left
142pub const VULKAN: &System = &System {
143    // +Z
144    forward: AxisDirectionPair {
145        axis: Axis::Z,
146        direction: Direction::Positive,
147    },
148    // -Y
149    up: AxisDirectionPair {
150        axis: Axis::Y,
151        direction: Direction::Negative,
152    },
153};
154
155/// Perform co-ordinate system transform.
156///
157/// # Examples
158///
159/// KittyCAD (+Z up, -Y forward) to OpenGL (+Y up, +Z forward):
160///
161/// ```
162/// # use kittycad_modeling_cmds::coord::*;
163/// let a = [1.0, 2.0, 3.0];
164/// let b = transform(a, KITTYCAD, OPENGL);
165/// assert_eq!(b, [1.0, 3.0, -2.0]);
166/// ```
167///
168/// OpenGL (+Y up, +Z forward) to KittyCAD (+Z up, -Y forward):
169///
170/// ```
171/// # use kittycad_modeling_cmds::coord::*;
172/// let a = [1.0, 2.0, 3.0];
173/// let b = transform(a, OPENGL, KITTYCAD);
174/// assert_eq!(b, [1.0, -3.0, 2.0]);
175/// ```
176///
177/// KittyCAD (+Z up, -Y forward) to Vulkan (-Y up, +Z forward):
178///
179/// ```
180/// # use kittycad_modeling_cmds::coord::*;
181/// let a = [1.0, 2.0, 3.0];
182/// let b = transform(a, KITTYCAD, VULKAN);
183/// assert_eq!(b, [1.0, -3.0, -2.0]);
184/// ```
185///
186/// OpenGL (+Y up, +Z forward) to Vulkan (-Y up, +Z forward):
187///
188/// ```
189/// # use kittycad_modeling_cmds::coord::*;
190/// let a = [1.0, 2.0, 3.0];
191/// let b = transform(a, OPENGL, VULKAN);
192/// assert_eq!(b, [1.0, -2.0, 3.0]);
193/// ```
194#[inline]
195pub fn transform(a: [f32; 3], from: &System, to: &System) -> [f32; 3] {
196    let mut b = a;
197    b[to.forward.axis as usize] =
198        (from.forward.direction * to.forward.direction) as i32 as f32 * a[from.forward.axis as usize];
199    b[to.up.axis as usize] = (from.up.direction * to.up.direction) as i32 as f32 * a[from.up.axis as usize];
200    b
201}