1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use super::*;

/// An object in 2d space, bounded by a [Quad]
///
/// TODO: better name
pub trait Transform2d<F: Float> {
    /// Object's bounding [Quad]
    fn bounding_quad(&self) -> Quad<F>;

    /// Apply transformation matrix to this object
    fn apply_transform(&mut self, transform: mat3<F>);
}

impl<F: Float, T: Transform2d<F> + ?Sized> Transform2d<F> for Box<T> {
    fn bounding_quad(&self) -> Quad<F> {
        (**self).bounding_quad()
    }
    fn apply_transform(&mut self, transform: mat3<F>) {
        (**self).apply_transform(transform);
    }
}

/// A reference to a 2d object with additional transformation applied
///
// TODO: is this needed? should it be reference?
pub struct Transformed2d<'a, F: Float, T: Transform2d<F> + ?Sized> {
    /// Reference to the actual object
    pub inner: &'a T,

    /// Additional transformation
    pub transform: mat3<F>,
}

impl<'a, F: Float, T: Transform2d<F> + ?Sized> Transformed2d<'a, F, T> {
    /// Apply additional transformation to the given object
    pub fn new(inner: &'a T, transform: mat3<F>) -> Self {
        Self { inner, transform }
    }
}

impl<'a, F: Float, T: Transform2d<F> + ?Sized> Transform2d<F> for Transformed2d<'a, F, T> {
    fn bounding_quad(&self) -> Quad<F> {
        self.inner.bounding_quad().transform(self.transform)
    }
    fn apply_transform(&mut self, transform: mat3<F>) {
        self.transform = transform * self.transform;
    }
}

/// Extra methods for [Transform2d] types
pub trait Transform2dExt<F: Float>: Transform2d<F> {
    /// Apply transformation to the object, returning a modified value to allow chaining methods
    fn transform(self, transform: mat3<F>) -> Self
    where
        Self: Sized,
    {
        let mut result = self;
        result.apply_transform(transform);
        result
    }

    /// Align bounding box of this object, making its origin located at (0, 0)
    ///
    /// # Examples
    /// ```
    /// # use batbox_la::*;
    /// # use batbox_lapp::*;
    /// let quad = Quad::unit();
    /// assert_eq!(quad.bounding_box(), Aabb2::from_corners(vec2(-1.0, -1.0), vec2(1.0, 1.0)));
    /// let quad = quad.align_bounding_box(vec2(0.0, 1.0));
    /// assert_eq!(quad.bounding_box(), Aabb2::from_corners(vec2(0.0, 0.0), vec2(2.0, -2.0)));
    /// ```
    fn align_bounding_box(self, alignment: vec2<F>) -> Self
    where
        Self: Sized,
    {
        let aabb = self.bounding_box();
        self.translate(-aabb.bottom_left() - aabb.size() * alignment)
    }

    /// Rotate object around (0, 0) by given angle
    fn rotate(self, angle: Angle<F>) -> Self
    where
        Self: Sized,
    {
        self.transform(mat3::rotate(angle))
    }

    /// Translate object by given vector
    fn translate(self, delta: vec2<F>) -> Self
    where
        Self: Sized,
    {
        self.transform(mat3::translate(delta))
    }

    /// Scale object around (0, 0) by given factor
    fn scale(self, factor: vec2<F>) -> Self
    where
        Self: Sized,
    {
        self.transform(mat3::scale(factor))
    }

    /// Scale object around (0, 0) by given factor uniformly along both axis
    fn scale_uniform(self, factor: F) -> Self
    where
        Self: Sized,
    {
        self.transform(mat3::scale_uniform(factor))
    }

    /// Create a reference to this object with additional transformation applied
    ///
    // TODO: bad naming
    fn transformed(&self) -> Transformed2d<F, Self> {
        Transformed2d::new(self, mat3::identity())
    }

    /// Calculate bounding box of this object, getting [Aabb2] instead of a [Quad]
    fn bounding_box(&self) -> Aabb2<F> {
        Aabb2::points_bounding_box(
            [
                vec2(-F::ONE, -F::ONE),
                vec2(F::ONE, -F::ONE),
                vec2(F::ONE, F::ONE),
                vec2(-F::ONE, F::ONE),
            ]
            .into_iter()
            .map(|p| (self.bounding_quad().transform * p.extend(F::ONE)).into_2d()),
        )
        .unwrap()
    }

    /// Make this object's bounding [Quad] fit into given target
    fn fit_into(self, target: impl FitTarget2d<F>) -> Self
    where
        Self: Sized,
    {
        let mut result = self;
        target.make_fit(&mut result);
        result
    }
}

impl<F: Float, T: Transform2d<F> + ?Sized> Transform2dExt<F> for T {}