Skip to main content

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