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{x: 1.0, y: 1.0, z: 1.0};
103    /// assert_eq!(p0.map(|n| n*2.0), Point3d{x: 2.0, y: 2.0, z:2.0});
104    /// ```
105    pub fn map<U, F>(self, mut f: F) -> Point3d<U>
106    where
107        F: FnMut(T) -> U,
108    {
109        let Self { x, y, z } = self;
110        Point3d {
111            x: f(x),
112            y: f(y),
113            z: f(z),
114        }
115    }
116}
117
118/// A point in homogeneous (4D) space
119#[repr(C)]
120#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
121#[serde(rename = "Point4d")]
122#[serde(rename_all = "snake_case")]
123#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
124#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
125pub struct Point4d<T = f32> {
126    #[allow(missing_docs)]
127    pub x: T,
128    #[allow(missing_docs)]
129    pub y: T,
130    #[allow(missing_docs)]
131    pub z: T,
132    #[allow(missing_docs)]
133    pub w: T,
134}
135
136impl std::fmt::Display for Point4d<f64> {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        write!(f, "({}, {}, {}, {})", self.x, self.y, self.z, self.w)
139    }
140}
141impl<T> Point4d<T> {
142    /// Takes some closure, and calls it on each component of this point.
143    /// # Examples
144    /// ```
145    /// use kittycad_modeling_cmds::shared::Point4d;
146    /// let p0 = Point4d{x: 1.0, y: 1.0, z: 1.0, w: 1.0};
147    /// assert_eq!(p0.map(|n| n*2.0), Point4d{x: 2.0, y: 2.0, z: 2.0, w: 2.0});
148    /// ```
149    pub fn map<U, F>(self, mut f: F) -> Point4d<U>
150    where
151        F: FnMut(T) -> U,
152    {
153        let Self { x, y, z, w } = self;
154        Point4d {
155            x: f(x),
156            y: f(y),
157            z: f(z),
158            w: f(w),
159        }
160    }
161}
162impl<T> Point4d<T>
163where
164    T: Copy,
165{
166    /// Make a point where the X, Y and Z components have the same value,
167    /// but the W component has a different one.
168    pub const fn uniform_3d(xyz: T, w: T) -> Self {
169        Self {
170            x: xyz,
171            y: xyz,
172            z: xyz,
173            w,
174        }
175    }
176}
177
178///A quaternion
179pub type Quaternion = Point4d;
180
181impl Default for Quaternion {
182    /// (0, 0, 0, 1)
183    fn default() -> Self {
184        Self {
185            x: 0.0,
186            y: 0.0,
187            z: 0.0,
188            w: 1.0,
189        }
190    }
191}
192
193impl<T: PartialEq> PartialEq for Point4d<T> {
194    fn eq(&self, other: &Self) -> bool {
195        self.x == other.x && self.y == other.y && self.z == other.z && self.w == other.w
196    }
197}
198
199macro_rules! impl_arithmetic {
200    ($typ:ident, $op:ident, $op_assign:ident, $method:ident, $method_assign:ident, $($i:ident),*) => {
201        /// Arithmetic between two points happens component-wise, e.g. p + q == (p.x + q.x, p.y + q.y)
202        impl<T> std::ops::$op<$typ<T>> for $typ<T>
203        where
204            T: std::ops::$op<Output = T>,
205        {
206            type Output = $typ<T>;
207
208            fn $method(self, rhs: $typ<T>) -> Self::Output {
209                Self {
210                    $(
211                        $i: self.$i.$method(rhs.$i),
212                    )*
213                }
214            }
215        }
216        /// Arithmetic between two points happens component-wise, e.g. p + q == (p.x + q.x, p.y + q.y)
217        impl<T> std::ops::$op_assign for $typ<T>
218        where
219            T: std::ops::$op_assign<T>,
220        {
221
222            fn $method_assign(&mut self, other: Self) {
223                $(
224                    self.$i.$method_assign(other.$i);
225                )*
226            }
227        }
228    };
229}
230
231macro_rules! impl_scalar_arithmetic {
232    ($typ:ident, $op:ident, $op_assign:ident, $method:ident, $method_assign:ident, $($i:ident),*) => {
233        /// Applies an arithmetic operation to each component, e.g. p * 3 = (p.x * 3, p.y * 3)
234        impl<T> std::ops::$op<T> for $typ<T>
235        where
236            T: std::ops::$op<Output = T> + Copy,
237        {
238            type Output = $typ<T>;
239
240            fn $method(self, rhs: T) -> Self::Output {
241                Self {
242                    $(
243                        $i: self.$i.$method(rhs),
244                    )*
245                }
246            }
247        }
248        /// Applies an arithmetic operation to each component, e.g. p * 3 = (p.x * 3, p.y * 3)
249        impl<T> std::ops::$op_assign<T> for $typ<T>
250        where
251            T: std::ops::$op_assign<T> + Copy,
252        {
253
254            fn $method_assign(&mut self, other: T) {
255                $(
256                    self.$i.$method_assign(other);
257                )*
258            }
259        }
260    };
261}
262
263impl_arithmetic!(Point2d, Add, AddAssign, add, add_assign, x, y);
264impl_arithmetic!(Point3d, Add, AddAssign, add, add_assign, x, y, z);
265impl_arithmetic!(Point2d, Sub, SubAssign, sub, sub_assign, x, y);
266impl_arithmetic!(Point3d, Sub, SubAssign, sub, sub_assign, x, y, z);
267impl_arithmetic!(Point2d, Mul, MulAssign, mul, mul_assign, x, y);
268impl_arithmetic!(Point3d, Mul, MulAssign, mul, mul_assign, x, y, z);
269impl_arithmetic!(Point2d, Div, DivAssign, div, div_assign, x, y);
270impl_arithmetic!(Point3d, Div, DivAssign, div, div_assign, x, y, z);
271impl_scalar_arithmetic!(Point2d, Mul, MulAssign, mul, mul_assign, x, y);
272impl_scalar_arithmetic!(Point3d, Mul, MulAssign, mul, mul_assign, x, y, z);
273impl_scalar_arithmetic!(Point2d, Div, DivAssign, div, div_assign, x, y);
274impl_scalar_arithmetic!(Point3d, Div, DivAssign, div, div_assign, x, y, z);
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn test_math() {
282        let actual = Point2d { x: 1.0, y: 2.0 } + Point2d { x: 10.0, y: 20.0 };
283        let expected = Point2d { x: 11.0, y: 22.0 };
284        assert_eq!(actual, expected);
285    }
286
287    #[test]
288    fn test_math_assign() {
289        let mut p = Point2d { x: 1.0, y: 2.0 };
290        p += Point2d { x: 10.0, y: 20.0 };
291        let expected = Point2d { x: 11.0, y: 22.0 };
292        assert_eq!(p, expected);
293    }
294
295    #[test]
296    fn test_scaling() {
297        let actual = Point2d { x: 1.0, y: 2.0 } * 3.0;
298        let expected = Point2d { x: 3.0, y: 6.0 };
299        assert_eq!(actual, expected);
300    }
301    #[test]
302    fn test_scaling_assign() {
303        let mut actual = Point2d { x: 1.0, y: 2.0 };
304        actual *= 3.0;
305        let expected = Point2d { x: 3.0, y: 6.0 };
306        assert_eq!(actual, expected);
307    }
308}