kittycad_modeling_cmds/shared/
point.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4mod convert;
5mod only;
6mod uniform;
7mod zero;
8
9/// A point in 2D space
10#[repr(C)]
11#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)]
12#[serde(rename = "Point2d")]
13#[serde(rename_all = "snake_case")]
14#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
15#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
16pub struct Point2d<T = f32> {
17    #[allow(missing_docs)]
18    pub x: T,
19    #[allow(missing_docs)]
20    pub y: T,
21}
22
23impl std::fmt::Display for Point2d<f64> {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "({}, {})", self.x, self.y)
26    }
27}
28
29impl<T: PartialEq> PartialEq for Point2d<T> {
30    fn eq(&self, other: &Self) -> bool {
31        self.x == other.x && self.y == other.y
32    }
33}
34
35impl<T> Point2d<T> {
36    /// Add the given `z` component to a 2D point to produce a 3D point.
37    pub fn with_z(self, z: T) -> Point3d<T> {
38        let Self { x, y } = self;
39        Point3d { x, y, z }
40    }
41
42    /// Takes some closure, and calls it on each component of this point.
43    /// # Examples
44    /// ```
45    /// use kittycad_modeling_cmds::shared::Point2d;
46    /// let p0 = Point2d { x: 1.0, y: 1.0 };
47    /// assert_eq!(p0.map(|n| n * 2.0), Point2d { x: 2.0, y: 2.0 });
48    /// ```
49    pub fn map<U, F>(self, mut f: F) -> Point2d<U>
50    where
51        F: FnMut(T) -> U,
52    {
53        let Self { x, y } = self;
54        Point2d { x: f(x), y: f(y) }
55    }
56}
57
58/// A point in 3D space
59#[repr(C)]
60#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
61#[serde(rename = "Point3d")]
62#[serde(rename_all = "snake_case")]
63#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
64#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
65pub struct Point3d<T = f32> {
66    #[allow(missing_docs)]
67    pub x: T,
68    #[allow(missing_docs)]
69    pub y: T,
70    #[allow(missing_docs)]
71    pub z: T,
72}
73
74impl std::fmt::Display for Point3d<f64> {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        write!(f, "({}, {}, {})", self.x, self.y, self.z)
77    }
78}
79
80impl From<euler::Vec3> for Point3d<f32> {
81    fn from(v: euler::Vec3) -> Self {
82        Self { x: v.x, y: v.y, z: v.z }
83    }
84}
85
86impl<T> Point3d<T> {
87    /// Add the given `z` component to a 2D point to produce a 3D point.
88    pub fn from_2d(Point2d { x, y }: Point2d<T>, z: T) -> Self {
89        Self { x, y, z }
90    }
91
92    /// Add the given `w` component to a 3D point to produce a 4D point.
93    pub fn with_w(self, w: T) -> Point4d<T> {
94        let Self { x, y, z } = self;
95        Point4d { x, y, z, w }
96    }
97
98    /// Takes some closure, and calls it on each component of this point.
99    /// # Examples
100    /// ```
101    /// use kittycad_modeling_cmds::shared::Point3d;
102    /// let p0 = Point3d {
103    ///     x: 1.0,
104    ///     y: 1.0,
105    ///     z: 1.0,
106    /// };
107    /// assert_eq!(
108    ///     p0.map(|n| n * 2.0),
109    ///     Point3d {
110    ///         x: 2.0,
111    ///         y: 2.0,
112    ///         z: 2.0
113    ///     }
114    /// );
115    /// ```
116    pub fn map<U, F>(self, mut f: F) -> Point3d<U>
117    where
118        F: FnMut(T) -> U,
119    {
120        let Self { x, y, z } = self;
121        Point3d {
122            x: f(x),
123            y: f(y),
124            z: f(z),
125        }
126    }
127}
128
129/// A point in homogeneous (4D) space
130#[repr(C)]
131#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
132#[serde(rename = "Point4d")]
133#[serde(rename_all = "snake_case")]
134#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
135#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
136pub struct Point4d<T = f32> {
137    #[allow(missing_docs)]
138    pub x: T,
139    #[allow(missing_docs)]
140    pub y: T,
141    #[allow(missing_docs)]
142    pub z: T,
143    #[allow(missing_docs)]
144    pub w: T,
145}
146
147impl std::fmt::Display for Point4d<f64> {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        write!(f, "({}, {}, {}, {})", self.x, self.y, self.z, self.w)
150    }
151}
152impl<T> Point4d<T> {
153    /// Takes some closure, and calls it on each component of this point.
154    /// # Examples
155    /// ```
156    /// use kittycad_modeling_cmds::shared::Point4d;
157    /// let p0 = Point4d {
158    ///     x: 1.0,
159    ///     y: 1.0,
160    ///     z: 1.0,
161    ///     w: 1.0,
162    /// };
163    /// assert_eq!(
164    ///     p0.map(|n| n * 2.0),
165    ///     Point4d {
166    ///         x: 2.0,
167    ///         y: 2.0,
168    ///         z: 2.0,
169    ///         w: 2.0
170    ///     }
171    /// );
172    /// ```
173    pub fn map<U, F>(self, mut f: F) -> Point4d<U>
174    where
175        F: FnMut(T) -> U,
176    {
177        let Self { x, y, z, w } = self;
178        Point4d {
179            x: f(x),
180            y: f(y),
181            z: f(z),
182            w: f(w),
183        }
184    }
185}
186impl<T> Point4d<T>
187where
188    T: Copy,
189{
190    /// Make a point where the X, Y and Z components have the same value,
191    /// but the W component has a different one.
192    pub const fn uniform_3d(xyz: T, w: T) -> Self {
193        Self {
194            x: xyz,
195            y: xyz,
196            z: xyz,
197            w,
198        }
199    }
200}
201
202///A quaternion
203pub type Quaternion = Point4d;
204
205impl Default for Quaternion {
206    /// (0, 0, 0, 1)
207    fn default() -> Self {
208        Self {
209            x: 0.0,
210            y: 0.0,
211            z: 0.0,
212            w: 1.0,
213        }
214    }
215}
216
217impl<T: PartialEq> PartialEq for Point4d<T> {
218    fn eq(&self, other: &Self) -> bool {
219        self.x == other.x && self.y == other.y && self.z == other.z && self.w == other.w
220    }
221}
222
223macro_rules! impl_arithmetic {
224    ($typ:ident, $op:ident, $op_assign:ident, $method:ident, $method_assign:ident, $($i:ident),*) => {
225        /// Arithmetic between two points happens component-wise, e.g. p + q == (p.x + q.x, p.y + q.y)
226        impl<T> std::ops::$op<$typ<T>> for $typ<T>
227        where
228            T: std::ops::$op<Output = T>,
229        {
230            type Output = $typ<T>;
231
232            fn $method(self, rhs: $typ<T>) -> Self::Output {
233                Self {
234                    $(
235                        $i: self.$i.$method(rhs.$i),
236                    )*
237                }
238            }
239        }
240        /// Arithmetic between two points happens component-wise, e.g. p + q == (p.x + q.x, p.y + q.y)
241        impl<T> std::ops::$op_assign for $typ<T>
242        where
243            T: std::ops::$op_assign<T>,
244        {
245
246            fn $method_assign(&mut self, other: Self) {
247                $(
248                    self.$i.$method_assign(other.$i);
249                )*
250            }
251        }
252    };
253}
254
255macro_rules! impl_scalar_arithmetic {
256    ($typ:ident, $op:ident, $op_assign:ident, $method:ident, $method_assign:ident, $($i:ident),*) => {
257        /// Applies an arithmetic operation to each component, e.g. p * 3 = (p.x * 3, p.y * 3)
258        impl<T> std::ops::$op<T> for $typ<T>
259        where
260            T: std::ops::$op<Output = T> + Copy,
261        {
262            type Output = $typ<T>;
263
264            fn $method(self, rhs: T) -> Self::Output {
265                Self {
266                    $(
267                        $i: self.$i.$method(rhs),
268                    )*
269                }
270            }
271        }
272        /// Applies an arithmetic operation to each component, e.g. p * 3 = (p.x * 3, p.y * 3)
273        impl<T> std::ops::$op_assign<T> for $typ<T>
274        where
275            T: std::ops::$op_assign<T> + Copy,
276        {
277
278            fn $method_assign(&mut self, other: T) {
279                $(
280                    self.$i.$method_assign(other);
281                )*
282            }
283        }
284    };
285}
286
287impl_arithmetic!(Point2d, Add, AddAssign, add, add_assign, x, y);
288impl_arithmetic!(Point3d, Add, AddAssign, add, add_assign, x, y, z);
289impl_arithmetic!(Point2d, Sub, SubAssign, sub, sub_assign, x, y);
290impl_arithmetic!(Point3d, Sub, SubAssign, sub, sub_assign, x, y, z);
291impl_arithmetic!(Point2d, Mul, MulAssign, mul, mul_assign, x, y);
292impl_arithmetic!(Point3d, Mul, MulAssign, mul, mul_assign, x, y, z);
293impl_arithmetic!(Point2d, Div, DivAssign, div, div_assign, x, y);
294impl_arithmetic!(Point3d, Div, DivAssign, div, div_assign, x, y, z);
295impl_scalar_arithmetic!(Point2d, Mul, MulAssign, mul, mul_assign, x, y);
296impl_scalar_arithmetic!(Point3d, Mul, MulAssign, mul, mul_assign, x, y, z);
297impl_scalar_arithmetic!(Point2d, Div, DivAssign, div, div_assign, x, y);
298impl_scalar_arithmetic!(Point3d, Div, DivAssign, div, div_assign, x, y, z);
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn test_math() {
306        let actual = Point2d { x: 1.0, y: 2.0 } + Point2d { x: 10.0, y: 20.0 };
307        let expected = Point2d { x: 11.0, y: 22.0 };
308        assert_eq!(actual, expected);
309    }
310
311    #[test]
312    fn test_math_assign() {
313        let mut p = Point2d { x: 1.0, y: 2.0 };
314        p += Point2d { x: 10.0, y: 20.0 };
315        let expected = Point2d { x: 11.0, y: 22.0 };
316        assert_eq!(p, expected);
317    }
318
319    #[test]
320    fn test_scaling() {
321        let actual = Point2d { x: 1.0, y: 2.0 } * 3.0;
322        let expected = Point2d { x: 3.0, y: 6.0 };
323        assert_eq!(actual, expected);
324    }
325    #[test]
326    fn test_scaling_assign() {
327        let mut actual = Point2d { x: 1.0, y: 2.0 };
328        actual *= 3.0;
329        let expected = Point2d { x: 3.0, y: 6.0 };
330        assert_eq!(actual, expected);
331    }
332}