geometry/
transform.rs

1//! Transformation types and traits.
2
3use std::f64::consts::PI;
4
5pub use geometry_macros::{TransformMut, TransformRef, TranslateMut, TranslateRef};
6use impl_trait_for_tuples::impl_for_tuples;
7use serde::{Deserialize, Serialize};
8
9use super::orientation::Orientation;
10use crate::point::Point;
11
12/// A transformation representing a Manhattan translation, rotation, and/or reflection of geometry.
13///
14/// This object does not support scaling of geometry, and as such all transformation matrices
15/// should be unitary.
16#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
17pub struct Transformation {
18    /// The transformation matrix.
19    pub(crate) mat: TransformationMatrix,
20    /// The x-y translation applied after the transformation.
21    pub(crate) b: Point,
22}
23
24impl Default for Transformation {
25    fn default() -> Self {
26        Self::identity()
27    }
28}
29
30/// A Manhattan rotation: 0, 90, 180, or 270 degrees counterclockwise.
31#[derive(Debug, Clone, Copy, Default, Eq, Ord, PartialOrd, PartialEq, Serialize, Deserialize)]
32pub enum Rotation {
33    /// 0 degrees; no rotation.
34    #[default]
35    R0,
36    /// 90 degrees counterclockwise.
37    R90,
38    /// 180 degrees counterclockwise.
39    R180,
40    /// 270 degrees counterclockwise.
41    R270,
42}
43
44impl std::ops::Add<Rotation> for Rotation {
45    type Output = Rotation;
46    fn add(self, rhs: Rotation) -> Self::Output {
47        use Rotation::*;
48        match (self, rhs) {
49            (R0, R0) => R0,
50            (R0, R90) => R90,
51            (R0, R180) => R180,
52            (R0, R270) => R270,
53            (R90, R0) => R90,
54            (R90, R90) => R180,
55            (R90, R180) => R270,
56            (R90, R270) => R0,
57            (R180, R0) => R180,
58            (R180, R90) => R270,
59            (R180, R180) => R0,
60            (R180, R270) => R90,
61            (R270, R0) => R270,
62            (R270, R90) => R0,
63            (R270, R180) => R90,
64            (R270, R270) => R180,
65        }
66    }
67}
68
69impl std::ops::AddAssign for Rotation {
70    fn add_assign(&mut self, rhs: Self) {
71        *self = *self + rhs;
72    }
73}
74
75impl std::ops::Sub<Rotation> for Rotation {
76    type Output = Rotation;
77    fn sub(self, rhs: Rotation) -> Self::Output {
78        use Rotation::*;
79        match (self, rhs) {
80            (R0, R0) => R0,
81            (R0, R90) => R270,
82            (R0, R180) => R180,
83            (R0, R270) => R90,
84            (R90, R0) => R90,
85            (R90, R90) => R0,
86            (R90, R180) => R270,
87            (R90, R270) => R180,
88            (R180, R0) => R180,
89            (R180, R90) => R90,
90            (R180, R180) => R0,
91            (R180, R270) => R270,
92            (R270, R0) => R270,
93            (R270, R90) => R180,
94            (R270, R180) => R90,
95            (R270, R270) => R0,
96        }
97    }
98}
99
100impl std::ops::SubAssign for Rotation {
101    fn sub_assign(&mut self, rhs: Self) {
102        *self = *self - rhs;
103    }
104}
105
106impl Rotation {
107    /// The transformation matrix representing this rotation.
108    #[inline]
109    pub fn transformation_matrix(&self) -> TransformationMatrix {
110        TransformationMatrix::from(*self)
111    }
112
113    /// The angle of this rotation, in degrees.
114    pub fn degrees(&self) -> f64 {
115        match self {
116            Rotation::R0 => 0.,
117            Rotation::R90 => 90.,
118            Rotation::R180 => 180.,
119            Rotation::R270 => 270.,
120        }
121    }
122
123    /// The angle of this rotation, in radians.
124    pub fn radians(&self) -> f64 {
125        match self {
126            Rotation::R0 => 0.,
127            Rotation::R90 => PI / 2.,
128            Rotation::R180 => PI,
129            Rotation::R270 => PI * 1.5,
130        }
131    }
132}
133
134/// Indicates that an angle was not a valid Manhattan angle.
135///
136/// Manhattan angles (in degrees) are 0, 90, 180, 270,
137/// or any equivalent angle modulo 360 degrees.
138pub struct NonManhattanAngleError;
139
140impl TryFrom<f64> for Rotation {
141    type Error = NonManhattanAngleError;
142    fn try_from(value: f64) -> Result<Self, Self::Error> {
143        let value = (((value % 360.) + 360.) % 360.).round() as i64;
144        match value {
145            0 => Ok(Rotation::R0),
146            90 => Ok(Rotation::R90),
147            180 => Ok(Rotation::R180),
148            270 => Ok(Rotation::R270),
149            _ => Err(NonManhattanAngleError),
150        }
151    }
152}
153
154/// A matrix representing a unitary transformation.
155///
156/// Can represent rotations, reflections, or combinations of rotations/reflections.
157#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
158pub struct TransformationMatrix([[i8; 2]; 2]);
159
160impl TransformationMatrix {
161    /// The identity transformation.
162    ///
163    /// Maps any point to itself.
164    #[inline]
165    pub fn identity() -> Self {
166        Self([[1, 0], [0, 1]])
167    }
168
169    /// A rotation matrix rotating by the given [`Rotation`].
170    pub fn rotate(&self, angle: Rotation) -> Self {
171        angle.transformation_matrix() * *self
172    }
173
174    /// A matrix representing a reflection across the x-axis.
175    pub fn reflect_vert(&self) -> Self {
176        Self([[1, 0], [0, -1]]) * *self
177    }
178
179    /// The inverse of the transformation matrix.
180    pub fn inverse(&self) -> Self {
181        Self(unitary_matinv(&self.0))
182    }
183}
184
185impl From<Rotation> for TransformationMatrix {
186    fn from(value: Rotation) -> Self {
187        Self(match value {
188            Rotation::R0 => [[1, 0], [0, 1]],
189            Rotation::R90 => [[0, -1], [1, 0]],
190            Rotation::R180 => [[-1, 0], [0, -1]],
191            Rotation::R270 => [[0, 1], [-1, 0]],
192        })
193    }
194}
195
196/// Multiples two 2x2 matrices, returning a new 2x2 matrix
197fn matmul_i8(a: &[[i8; 2]; 2], b: &[[i8; 2]; 2]) -> [[i8; 2]; 2] {
198    [
199        [
200            a[0][0] * b[0][0] + a[0][1] * b[1][0],
201            a[0][0] * b[0][1] + a[0][1] * b[1][1],
202        ],
203        [
204            a[1][0] * b[0][0] + a[1][1] * b[1][0],
205            a[1][0] * b[0][1] + a[1][1] * b[1][1],
206        ],
207    ]
208}
209
210/// Multiplies a 2x2 matrix by a 2-entry vector, returning a new 2-entry vector.
211fn matvec_i8_i64(a: &[[i8; 2]; 2], b: &[i64; 2]) -> [i64; 2] {
212    [
213        a[0][0] as i64 * b[0] + a[0][1] as i64 * b[1],
214        a[1][0] as i64 * b[0] + a[1][1] as i64 * b[1],
215    ]
216}
217
218impl std::ops::Mul<TransformationMatrix> for TransformationMatrix {
219    type Output = Self;
220    fn mul(self, rhs: TransformationMatrix) -> Self::Output {
221        Self(matmul_i8(&self.0, &rhs.0))
222    }
223}
224
225impl std::ops::Mul<Point> for TransformationMatrix {
226    type Output = Point;
227    fn mul(self, rhs: Point) -> Self::Output {
228        let out = matvec_i8_i64(&self.0, &[rhs.x, rhs.y]);
229        Point::new(out[0], out[1])
230    }
231}
232
233impl std::ops::Deref for TransformationMatrix {
234    type Target = [[i8; 2]; 2];
235    fn deref(&self) -> &Self::Target {
236        &self.0
237    }
238}
239
240impl std::ops::DerefMut for TransformationMatrix {
241    fn deref_mut(&mut self) -> &mut Self::Target {
242        &mut self.0
243    }
244}
245
246impl Default for TransformationMatrix {
247    #[inline]
248    fn default() -> Self {
249        Self::identity()
250    }
251}
252
253impl Transformation {
254    /// Returns the identity transform, leaving any transformed object unmodified.
255    pub fn identity() -> Self {
256        Self {
257            mat: TransformationMatrix::identity(),
258            b: Point::zero(),
259        }
260    }
261    /// Returns a translation by `(x,y)`.
262    pub fn translate(x: i64, y: i64) -> Self {
263        Self {
264            mat: TransformationMatrix::identity(),
265            b: Point::new(x, y),
266        }
267    }
268    /// Translates the current transformation by `(x, y)`, returning a new [`Transformation`].
269    pub fn translate_ref(&self, x: i64, y: i64) -> Self {
270        Self {
271            mat: self.mat,
272            b: Point::new(x, y) + self.b,
273        }
274    }
275
276    /// Returns a rotatation by `angle` degrees.
277    pub fn rotate(angle: Rotation) -> Self {
278        let mat = TransformationMatrix::from(angle);
279        Self {
280            mat,
281            b: Point::zero(),
282        }
283    }
284
285    /// Returns a reflection about the x-axis.
286    pub fn reflect_vert() -> Self {
287        Self {
288            mat: TransformationMatrix([[1, 0], [0, -1]]),
289            b: Point::zero(),
290        }
291    }
292
293    /// Returns a new [`TransformationBuilder`].
294    #[inline]
295    pub fn builder() -> TransformationBuilder {
296        TransformationBuilder::default()
297    }
298
299    /// Creates a transform from only an offset.
300    ///
301    /// The resulting transformation will apply only a translation
302    /// (i.e. no rotations/reflections).
303    pub fn from_offset(offset: Point) -> Self {
304        Self::builder()
305            .point(offset)
306            .orientation(Orientation::default())
307            .build()
308    }
309
310    /// Creates a transform from an offset and [`Orientation`].
311    pub fn from_offset_and_orientation(offset: Point, orientation: impl Into<Orientation>) -> Self {
312        Self::builder()
313            .point(offset)
314            .orientation(orientation.into())
315            .build()
316    }
317
318    /// Creates a transform from an offset, angle, and a bool indicating
319    /// whether or not to reflect vertically.
320    pub fn from_opts(offset: Point, reflect_vert: bool, angle: Rotation) -> Self {
321        Self::builder()
322            .point(offset)
323            .reflect_vert(reflect_vert)
324            .angle(angle)
325            .build()
326    }
327
328    /// Create a new [`Transformation`] that is the cascade of `parent` and `child`.
329    ///
330    /// "Parents" and "children" refer to typical layout-instance hierarchies,
331    /// in which each layer of instance has a nested set of transformations relative to its top-level parent.
332    ///
333    /// Note this operation *is not* commutative.
334    /// For example the set of transformations:
335    /// * (a) Reflect vertically, then
336    /// * (b) Translate by (1,1)
337    /// * (c) Place a point at (local coordinate) (1,1)
338    ///
339    /// Lands said point at (2,-2) in top-level space,
340    /// whereas reversing the order of (a) and (b) lands it at (2,0).
341    pub fn cascade(parent: Transformation, child: Transformation) -> Transformation {
342        // The result-transform's origin is the parent's origin,
343        // plus the parent-transformed child's origin
344        let mut b = parent.mat * child.b;
345        b += parent.b;
346        // And the cascade-matrix is the product of the parent's and child's
347        let mat = parent.mat * child.mat;
348        Self { mat, b }
349    }
350
351    /// The point representing the translation of this transformation.
352    pub fn offset_point(&self) -> Point {
353        self.b
354    }
355
356    /// Returns an [`Orientation`] corresponding to this transformation.
357    pub fn orientation(&self) -> Orientation {
358        let reflect_vert = if self.mat[0][0] == 0 {
359            self.mat[0][1].signum() == self.mat[1][0].signum()
360        } else {
361            self.mat[0][0].signum() != self.mat[1][1].signum()
362        };
363        let cos = self.mat[0][0];
364        let sin = self.mat[1][0];
365        let angle = match (cos, sin) {
366            (1, 0) => Rotation::R0,
367            (0, 1) => Rotation::R90,
368            (-1, 0) => Rotation::R180,
369            (0, -1) => Rotation::R270,
370            _ => panic!("transformation did not represent a valid Manhattan transformation"),
371        };
372        Orientation {
373            reflect_vert,
374            angle,
375        }
376    }
377
378    /// Returns the inverse [`Transformation`] of `self`.
379    ///
380    /// # Examples
381    ///
382    /// ```
383    /// use geometry::transform::Transformation;
384    /// use geometry::transform::Rotation;
385    ///
386    /// let trans = Transformation::cascade(
387    ///     Transformation::rotate(Rotation::R90),
388    ///     Transformation::translate(5, 10),
389    /// );
390    /// let inv = trans.inv();
391    ///
392    /// assert_eq!(Transformation::cascade(inv, trans), Transformation::identity());
393    /// assert_eq!(Transformation::cascade(trans, inv), Transformation::identity());
394    /// ```
395    pub fn inv(&self) -> Transformation {
396        let inv = self.mat.inverse();
397        let invb = inv * self.b;
398        Self { mat: inv, b: -invb }
399    }
400}
401
402impl<T> From<T> for Transformation
403where
404    T: Into<Orientation>,
405{
406    fn from(value: T) -> Self {
407        Self::builder().orientation(value).build()
408    }
409}
410
411/// A builder for creating transformations from translations and [`Orientation`]s.
412#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
413pub struct TransformationBuilder {
414    x: i64,
415    y: i64,
416    reflect_vert: bool,
417    angle: Rotation,
418}
419
420impl TransformationBuilder {
421    /// Specifies the x-y translation encoded by the transformation.
422    pub fn point(&mut self, point: impl Into<Point>) -> &mut Self {
423        let point = point.into();
424        self.x = point.x;
425        self.y = point.y;
426        self
427    }
428
429    /// Specifies the [`Orientation`] applied by this transformation.
430    ///
431    /// This overrides any angle and reflection settings previously applied.
432    pub fn orientation(&mut self, o: impl Into<Orientation>) -> &mut Self {
433        let o = o.into();
434        self.reflect_vert = o.reflect_vert;
435        self.angle = o.angle;
436        self
437    }
438
439    /// Specifies the angle of rotation encoded by this transformation.
440    pub fn angle(&mut self, angle: Rotation) -> &mut Self {
441        self.angle = angle;
442        self
443    }
444
445    /// Specifies whether the transformation results in a vertical reflection.
446    pub fn reflect_vert(&mut self, reflect_vert: bool) -> &mut Self {
447        self.reflect_vert = reflect_vert;
448        self
449    }
450
451    /// Builds a [`Transformation`] from the specified parameters.
452    pub fn build(&mut self) -> Transformation {
453        let mut mat = self.angle.transformation_matrix();
454        if self.reflect_vert {
455            mat[0][1] = -mat[0][1];
456            mat[1][1] = -mat[1][1];
457        }
458        Transformation {
459            mat,
460            b: Point::new(self.x, self.y),
461        }
462    }
463}
464
465/// Finds the inverse of the matrix.
466///
467/// The determinant factor is unecessary since all transformation matrices have determinant 1 (no
468/// scaling).
469fn unitary_matinv(a: &[[i8; 2]; 2]) -> [[i8; 2]; 2] {
470    [[a[1][1], -a[0][1]], [-a[1][0], a[0][0]]]
471}
472
473/// A trait for specifying how an object is changed by a [`Transformation`].
474pub trait TransformRef: TranslateRef {
475    /// Applies matrix-vector [`Transformation`] `trans`.
476    fn transform_ref(&self, trans: Transformation) -> Self;
477}
478
479impl TransformRef for () {
480    fn transform_ref(&self, _trans: Transformation) -> Self {}
481}
482
483impl<T: TransformRef> TransformRef for Vec<T> {
484    fn transform_ref(&self, trans: Transformation) -> Self {
485        self.iter().map(|elt| elt.transform_ref(trans)).collect()
486    }
487}
488
489impl<T: TransformRef> TransformRef for Option<T> {
490    fn transform_ref(&self, trans: Transformation) -> Self {
491        self.as_ref().map(move |elt| elt.transform_ref(trans))
492    }
493}
494
495/// A trait for specifying how an object is changed by a [`Transformation`].
496#[impl_for_tuples(32)]
497pub trait TransformMut: TranslateMut {
498    /// Applies matrix-vector [`Transformation`] `trans`.
499    fn transform_mut(&mut self, trans: Transformation);
500}
501
502impl<T: TransformMut> TransformMut for Vec<T> {
503    fn transform_mut(&mut self, trans: Transformation) {
504        for i in self.iter_mut() {
505            i.transform_mut(trans);
506        }
507    }
508}
509
510impl<T: TransformMut> TransformMut for Option<T> {
511    fn transform_mut(&mut self, trans: Transformation) {
512        if let Some(inner) = self.as_mut() {
513            inner.transform_mut(trans);
514        }
515    }
516}
517
518/// A trait for specifying how an object is changed by a [`Transformation`].
519///
520/// Takes in an owned copy of the shape and returns the transformed version.
521pub trait Transform: Translate + TransformMut + Sized {
522    /// Applies matrix-vector [`Transformation`] `trans`.
523    ///
524    /// Creates a new shape at a location equal to the transformation of the original.
525    #[inline]
526    fn transform(mut self, trans: Transformation) -> Self {
527        self.transform_mut(trans);
528        self
529    }
530}
531
532impl<T: TransformMut + Sized> Transform for T {}
533
534/// A trait for specifying how a shape is translated by a [`Point`].
535pub trait TranslateRef: Sized {
536    /// Translates the shape by [`Point`], returning a new shape.
537    fn translate_ref(&self, p: Point) -> Self;
538}
539
540impl TranslateRef for () {
541    fn translate_ref(&self, _p: Point) -> Self {}
542}
543
544impl<T: TranslateRef> TranslateRef for Vec<T> {
545    fn translate_ref(&self, p: Point) -> Self {
546        self.iter().map(|elt| elt.translate_ref(p)).collect()
547    }
548}
549
550impl<T: TranslateRef> TranslateRef for Option<T> {
551    fn translate_ref(&self, p: Point) -> Self {
552        self.as_ref().map(move |elt| elt.translate_ref(p))
553    }
554}
555
556/// A trait for specifying how a shape is translated by a [`Point`].
557#[impl_for_tuples(32)]
558pub trait TranslateMut {
559    /// Translates the shape by a [`Point`] through mutation.
560    fn translate_mut(&mut self, p: Point);
561}
562
563impl<T: TranslateMut> TranslateMut for Vec<T> {
564    fn translate_mut(&mut self, p: Point) {
565        for i in self.iter_mut() {
566            i.translate_mut(p);
567        }
568    }
569}
570
571impl<T: TranslateMut> TranslateMut for Option<T> {
572    fn translate_mut(&mut self, p: Point) {
573        if let Some(inner) = self.as_mut() {
574            inner.translate_mut(p);
575        }
576    }
577}
578
579/// A trait for specifying how a shape is translated by a [`Point`].
580///
581/// Takes in an owned copy of the shape and returns the translated version.
582pub trait Translate: TranslateMut + Sized {
583    /// Translates the shape by a [`Point`] through mutation.
584    ///
585    /// Creates a new shape at a location equal to the translation of the original.
586    fn translate(mut self, p: Point) -> Self {
587        self.translate_mut(p);
588        self
589    }
590}
591
592impl<T: TranslateMut + Sized> Translate for T {}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597    use crate::{orientation::NamedOrientation, rect::Rect};
598
599    #[test]
600    fn matvec_works() {
601        let a = [[1, 2], [3, 4]];
602        let b = [5, 6];
603        assert_eq!(matvec_i8_i64(&a, &b), [17, 39]);
604    }
605
606    #[test]
607    fn matmul_works() {
608        let a = [[1, 2], [3, 4]];
609        let b = [[5, 6], [7, 8]];
610        assert_eq!(matmul_i8(&a, &b), [[19, 22], [43, 50]]);
611    }
612
613    #[test]
614    fn unitary_matinv_works() {
615        let a = [[1, 1], [3, 4]];
616        let inv = unitary_matinv(&a);
617        let a_mul_inv = matmul_i8(&a, &inv);
618        assert_eq!(a_mul_inv[0][0], 1);
619        assert_eq!(a_mul_inv[0][1], 0);
620        assert_eq!(a_mul_inv[1][0], 0);
621        assert_eq!(a_mul_inv[1][1], 1);
622    }
623
624    #[test]
625    fn cascade_identity_preserves_transformation() {
626        for orientation in NamedOrientation::all_rectangular() {
627            let tf = Transformation::from_offset_and_orientation(Point::new(520, 130), orientation);
628            let casc = Transformation::cascade(tf, Transformation::identity());
629            assert_eq!(
630                tf, casc,
631                "Cascading with identity produced incorrect transformation for orientation {:?}",
632                orientation
633            );
634        }
635    }
636
637    #[test]
638    fn transformation_offset_and_orientation_preserves_components() {
639        let pt = Point::new(8930, 730);
640        for orientation in NamedOrientation::all_rectangular() {
641            println!("Testing orientation {:?}", orientation);
642            let tf = Transformation::from_offset_and_orientation(pt, orientation);
643            assert_eq!(tf.orientation(), orientation.into());
644            assert_eq!(tf.offset_point(), pt);
645        }
646    }
647
648    #[test]
649    fn transformation_equivalent_to_offset_and_orientation() {
650        for orientation in NamedOrientation::all_rectangular() {
651            println!("Testing orientation {:?}", orientation);
652            let tf1 =
653                Transformation::from_offset_and_orientation(Point::new(380, 340), orientation);
654            assert_eq!(tf1.orientation(), orientation.into());
655            let tf2 =
656                Transformation::from_offset_and_orientation(tf1.offset_point(), tf1.orientation());
657            assert_eq!(tf1, tf2);
658        }
659    }
660
661    #[test]
662    fn point_transformations_work() {
663        let pt = Point::new(2, 1);
664
665        let pt_reflect_vert = pt.transform(Transformation::from_offset_and_orientation(
666            Point::zero(),
667            NamedOrientation::ReflectVert,
668        ));
669        assert_eq!(pt_reflect_vert, Point::new(2, -1));
670
671        let pt_reflect_horiz = pt.transform(Transformation::from_offset_and_orientation(
672            Point::zero(),
673            NamedOrientation::ReflectHoriz,
674        ));
675        assert_eq!(pt_reflect_horiz, Point::new(-2, 1));
676
677        let pt_r90 = pt.transform(Transformation::from_offset_and_orientation(
678            Point::new(23, 11),
679            NamedOrientation::R90,
680        ));
681        assert_eq!(pt_r90, Point::new(22, 13));
682
683        let pt_r180 = pt.transform(Transformation::from_offset_and_orientation(
684            Point::new(-50, 10),
685            NamedOrientation::R180,
686        ));
687        assert_eq!(pt_r180, Point::new(-52, 9));
688
689        let pt_r270 = pt.transform(Transformation::from_offset_and_orientation(
690            Point::new(80, 90),
691            NamedOrientation::R270,
692        ));
693        assert_eq!(pt_r270, Point::new(81, 88));
694
695        let pt_r90cw = pt.transform(Transformation::from_offset_and_orientation(
696            Point::new(5, 13),
697            NamedOrientation::R90Cw,
698        ));
699        assert_eq!(pt_r90cw, Point::new(6, 11));
700
701        let pt_r180cw = pt.transform(Transformation::from_offset_and_orientation(
702            Point::zero(),
703            NamedOrientation::R180Cw,
704        ));
705        assert_eq!(pt_r180cw, Point::new(-2, -1));
706
707        let pt_r270cw = pt.transform(Transformation::from_offset_and_orientation(
708            Point::new(1, 100),
709            NamedOrientation::R270Cw,
710        ));
711        assert_eq!(pt_r270cw, Point::new(0, 102));
712
713        let pt_flip_yx = pt.transform(Transformation::from_offset_and_orientation(
714            Point::new(-65, -101),
715            NamedOrientation::FlipYx,
716        ));
717        assert_eq!(pt_flip_yx, Point::new(-64, -99));
718
719        let pt_flip_minus_yx = pt.transform(Transformation::from_offset_and_orientation(
720            Point::new(1, -5),
721            NamedOrientation::FlipMinusYx,
722        ));
723        assert_eq!(pt_flip_minus_yx, Point::new(0, -7));
724    }
725
726    #[test]
727    fn translate_works_for_tuples() {
728        let mut tuple = (
729            Rect::from_sides(0, 0, 100, 200),
730            Rect::from_sides(50, -50, 150, 0),
731        );
732        tuple.translate_mut(Point::new(5, 10));
733        assert_eq!(
734            tuple,
735            (
736                Rect::from_sides(5, 10, 105, 210),
737                Rect::from_sides(55, -40, 155, 10)
738            )
739        );
740    }
741
742    #[test]
743    fn translate_works_for_vecs() {
744        let mut v = vec![
745            Rect::from_sides(0, 0, 100, 200),
746            Rect::from_sides(50, -50, 150, 0),
747        ];
748        v.translate_mut(Point::new(5, 10));
749        assert_eq!(
750            v,
751            vec![
752                Rect::from_sides(5, 10, 105, 210),
753                Rect::from_sides(55, -40, 155, 10)
754            ]
755        );
756    }
757
758    #[test]
759    fn transform_works_for_tuples() {
760        let mut tuple = (
761            Rect::from_sides(0, 0, 100, 200),
762            Rect::from_sides(50, -50, 150, 0),
763        );
764        tuple.transform_mut(Transformation::from_offset_and_orientation(
765            Point::zero(),
766            NamedOrientation::R90,
767        ));
768        assert_eq!(
769            tuple,
770            (
771                Rect::from_sides(-200, 0, 0, 100),
772                Rect::from_sides(0, 50, 50, 150)
773            )
774        );
775    }
776
777    #[test]
778    fn transform_works_for_vecs() {
779        let mut v = vec![
780            Rect::from_sides(0, 0, 100, 200),
781            Rect::from_sides(50, -50, 150, 0),
782        ];
783        v.transform_mut(Transformation::from_offset_and_orientation(
784            Point::zero(),
785            NamedOrientation::R90,
786        ));
787        assert_eq!(
788            v,
789            vec![
790                Rect::from_sides(-200, 0, 0, 100),
791                Rect::from_sides(0, 50, 50, 150)
792            ]
793        );
794    }
795
796    #[test]
797    fn transform_macros_work() {
798        #[derive(Debug, Copy, Clone, Eq, PartialEq, TranslateMut, TransformMut)]
799        pub struct TwoPointGroup {
800            p1: Point,
801            p2: Point,
802        }
803
804        #[derive(Debug, Copy, Clone, Eq, PartialEq, TranslateMut, TransformMut)]
805        pub enum PointEnum {
806            First(Point),
807            Second { pt: Point },
808        }
809
810        let group = TwoPointGroup {
811            p1: Point::new(100, 200),
812            p2: Point::new(-400, 300),
813        };
814
815        let group = group.translate(Point::new(100, 50));
816        assert_eq!(
817            group,
818            TwoPointGroup {
819                p1: Point::new(200, 250),
820                p2: Point::new(-300, 350),
821            }
822        );
823
824        let mut group = PointEnum::First(Point::new(100, 200));
825        group = group.transform(NamedOrientation::ReflectVert.into());
826        assert_eq!(group, PointEnum::First(Point::new(100, -200)),);
827
828        let mut group = PointEnum::Second {
829            pt: Point::new(100, 200),
830        };
831        group = group.transform(NamedOrientation::ReflectVert.into());
832        assert_eq!(
833            group,
834            PointEnum::Second {
835                pt: Point::new(100, -200)
836            }
837        );
838    }
839}