geometry_strategy/
transform.rs1use core::marker::PhantomData;
16
17use geometry_coords::CoordinateScalar;
18use geometry_cs::{Cartesian, CartesianFamily, CoordinateSystem};
19use geometry_model::{Point2D, Point3D};
20use geometry_tag::SameAs;
21use geometry_trait::{Point, PointMut};
22
23pub trait TransformStrategy<P: Point> {
31 type Output: PointMut + Default;
33
34 fn transform(&self, src: &P) -> Self::Output;
39}
40
41#[derive(Debug, Clone, Copy)]
46pub struct Affine2<T: CoordinateScalar> {
47 pub m: [T; 9],
49 _t: PhantomData<T>,
50}
51
52impl<T: CoordinateScalar> Affine2<T> {
53 #[must_use]
55 pub fn identity() -> Self {
56 Self {
57 m: [
58 T::ONE,
59 T::ZERO,
60 T::ZERO,
61 T::ZERO,
62 T::ONE,
63 T::ZERO,
64 T::ZERO,
65 T::ZERO,
66 T::ONE,
67 ],
68 _t: PhantomData,
69 }
70 }
71
72 #[must_use]
74 pub fn translation(tx: T, ty: T) -> Self {
75 let mut m = Self::identity();
76 m.m[2] = tx;
77 m.m[5] = ty;
78 m
79 }
80
81 #[must_use]
83 pub fn scale(sx: T, sy: T) -> Self {
84 let mut m = Self::identity();
85 m.m[0] = sx;
86 m.m[4] = sy;
87 m
88 }
89
90 #[must_use]
93 pub fn then(self, other: Self) -> Self {
94 let a = &self.m;
97 let b = &other.m;
98 let mut out = [T::ZERO; 9];
99 for (r, row) in out.chunks_mut(3).enumerate() {
100 for (c, cell) in row.iter_mut().enumerate() {
101 *cell = a[r * 3] * b[c] + a[r * 3 + 1] * b[3 + c] + a[r * 3 + 2] * b[6 + c];
102 }
103 }
104 Self {
105 m: out,
106 _t: PhantomData,
107 }
108 }
109}
110
111impl Affine2<f64> {
112 #[cfg(feature = "std")]
120 #[must_use]
121 pub fn rotation(angle_rad: f64) -> Self {
122 let (s, c) = angle_rad.sin_cos();
123 let mut m = Self::identity();
124 m.m[0] = c;
125 m.m[1] = -s;
126 m.m[3] = s;
127 m.m[4] = c;
128 m
129 }
130}
131
132impl<T, P> TransformStrategy<P> for Affine2<T>
133where
134 T: CoordinateScalar,
135 P: Point<Scalar = T>,
136 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
137{
138 type Output = Point2D<T, Cartesian>;
139
140 #[inline]
141 fn transform(&self, src: &P) -> Self::Output {
142 let x = src.get::<0>();
143 let y = src.get::<1>();
144 let nx = self.m[0] * x + self.m[1] * y + self.m[2];
145 let ny = self.m[3] * x + self.m[4] * y + self.m[5];
146 Point2D::new(nx, ny)
147 }
148}
149
150#[derive(Debug, Clone, Copy)]
153pub struct Affine3<T: CoordinateScalar> {
154 pub m: [T; 16],
156 _t: PhantomData<T>,
157}
158
159impl<T: CoordinateScalar> Affine3<T> {
160 #[must_use]
162 pub fn identity() -> Self {
163 let mut m = [T::ZERO; 16];
164 m[0] = T::ONE;
165 m[5] = T::ONE;
166 m[10] = T::ONE;
167 m[15] = T::ONE;
168 Self { m, _t: PhantomData }
169 }
170
171 #[must_use]
173 pub fn translation(tx: T, ty: T, tz: T) -> Self {
174 let mut m = Self::identity();
175 m.m[3] = tx;
176 m.m[7] = ty;
177 m.m[11] = tz;
178 m
179 }
180
181 #[must_use]
183 pub fn scale(sx: T, sy: T, sz: T) -> Self {
184 let mut m = Self::identity();
185 m.m[0] = sx;
186 m.m[5] = sy;
187 m.m[10] = sz;
188 m
189 }
190}
191
192impl<T, P> TransformStrategy<P> for Affine3<T>
193where
194 T: CoordinateScalar,
195 P: Point<Scalar = T>,
196 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
197{
198 type Output = Point3D<T, Cartesian>;
199
200 #[inline]
201 fn transform(&self, src: &P) -> Self::Output {
202 let x = src.get::<0>();
203 let y = src.get::<1>();
204 let z = src.get::<2>();
205 let m = &self.m;
206 let nx = m[0] * x + m[1] * y + m[2] * z + m[3];
207 let ny = m[4] * x + m[5] * y + m[6] * z + m[7];
208 let nz = m[8] * x + m[9] * y + m[10] * z + m[11];
209 let mut out = Point3D::<T, Cartesian>::default();
210 out.set::<0>(nx);
211 out.set::<1>(ny);
212 out.set::<2>(nz);
213 out
214 }
215}
216
217#[cfg(test)]
218#[allow(
219 clippy::float_cmp,
220 reason = "Affine outputs of integer inputs are exact."
221)]
222mod tests {
223 use super::{Affine2, TransformStrategy};
228 use geometry_cs::Cartesian;
229 use geometry_model::Point2D;
230 use geometry_trait::Point as _;
231
232 type P = Point2D<f64, Cartesian>;
233
234 #[test]
235 fn translation_shifts_point() {
236 let t = Affine2::translation(3.0, 4.0);
237 let out = t.transform(&P::new(1.0, 1.0));
238 assert_eq!(out.get::<0>(), 4.0);
239 assert_eq!(out.get::<1>(), 5.0);
240 }
241
242 #[test]
243 fn scale_multiplies_point() {
244 let s = Affine2::scale(2.0, 3.0);
245 let out = s.transform(&P::new(1.0, 1.0));
246 assert_eq!(out.get::<0>(), 2.0);
247 assert_eq!(out.get::<1>(), 3.0);
248 }
249
250 #[test]
251 fn rotation_by_half_pi_maps_x_to_y() {
252 let r = Affine2::rotation(core::f64::consts::FRAC_PI_2);
253 let out = r.transform(&P::new(1.0, 0.0));
254 assert!((out.get::<0>() - 0.0).abs() < 1e-12);
255 assert!((out.get::<1>() - 1.0).abs() < 1e-12);
256 }
257
258 #[test]
259 fn then_composes_translate_after_scale() {
260 let combined = Affine2::translation(1.0, 1.0).then(Affine2::scale(2.0, 2.0));
263 let out = combined.transform(&P::new(1.0, 1.0));
264 assert_eq!(out.get::<0>(), 3.0);
265 assert_eq!(out.get::<1>(), 3.0);
266 }
267}