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