good_web_game/graphics/
drawparam.rs

1use crate::graphics::{Color, Rect};
2use cgmath::Matrix;
3
4/// A struct that represents where to put a `Drawable`.
5///
6/// This can either be a set of individual components, or
7/// a single `Matrix4` transform.
8#[derive(Debug, Copy, Clone, PartialEq)]
9pub enum Transform {
10    /// Transform made of individual values
11    Values {
12        /// The position to draw the graphic expressed as a `Point2`.
13        dest: mint::Point2<f32>,
14        /// The orientation of the graphic in radians.
15        rotation: f32,
16        /// The x/y scale factors expressed as a `Vector2`.
17        scale: mint::Vector2<f32>,
18        /// An offset from the center for transform operations like scale/rotation,
19        /// with `0,0` meaning the origin and `1,1` meaning the opposite corner from the origin.
20        /// By default these operations are done from the top-left corner, so to rotate something
21        /// from the center specify `Point2::new(0.5, 0.5)` here.
22        offset: mint::Point2<f32>,
23    },
24    /// Transform made of an arbitrary matrix.
25    ///
26    /// It should represent the final model matrix of the given drawable.  This is useful for
27    /// situations where, for example, you build your own hierarchy system, where you calculate
28    /// matrices of each hierarchy item and store a calculated world-space model matrix of an item.
29    /// This lets you implement transform stacks, skeletal animations, etc.
30    Matrix(mint::ColumnMatrix4<f32>),
31}
32
33impl Default for Transform {
34    fn default() -> Self {
35        Transform::Values {
36            dest: mint::Point2 { x: 0.0, y: 0.0 },
37            rotation: 0.0,
38            scale: mint::Vector2 { x: 1.0, y: 1.0 },
39            offset: mint::Point2 { x: 0.0, y: 0.0 },
40        }
41    }
42}
43
44impl Transform {
45    /// Crunches the transform down to a single matrix, if it's not one already.
46    pub fn to_matrix(self) -> Self {
47        Transform::Matrix(self.to_bare_matrix())
48    }
49
50    /// Same as `to_matrix()` but just returns a bare `mint` matrix.
51    pub fn to_bare_matrix(self) -> mint::ColumnMatrix4<f32> {
52        match self {
53            Transform::Matrix(m) => m,
54            Transform::Values {
55                dest,
56                rotation,
57                scale,
58                offset,
59            } => {
60                // Calculate a matrix equivalent to doing this:
61                // type Vec3 = na::Vector3<f32>;
62                // let o = offset;
63                // let translate = na::Matrix4::new_translation(&Vec3::new(dest.x, dest.y, 0.0));
64                // let offset = na::Matrix4::new_translation(&Vec3::new(offset.x, offset.y, 0.0));
65                // let offset_inverse =
66                //     na::Matrix4::new_translation(&Vec3::new(-o.x, -o.y, 0.0));
67                // let axis_angle = Vec3::z() * *rotation;
68                // let rotation = na::Matrix4::new_rotation(axis_angle);
69                // let scale = na::Matrix4::new_nonuniform_scaling(&Vec3::new(scale.x, scale.y, 1.0));
70                // translate * rotation * scale * offset_inverse
71                //
72                // Doing the bits manually is faster though, or at least was last I checked.
73                let (sinr, cosr) = rotation.sin_cos();
74                let m00 = cosr * scale.x;
75                let m01 = -sinr * scale.y;
76                let m10 = sinr * scale.x;
77                let m11 = cosr * scale.y;
78                let m03 = offset.x * (-m00) - offset.y * m01 + dest.x;
79                let m13 = offset.y * (-m11) - offset.x * m10 + dest.y;
80                // Welp, this transpose fixes some bug that makes nothing draw,
81                // that was introduced in commit 2c6b3cc03f34fb240f4246f5a68c75bd85b60eae.
82                // The best part is, I don't know if this code is wrong, or whether there's
83                // some reversed matrix multiply or such somewhere else that this cancel
84                // out.  Probably the former though.
85                cgmath::Matrix4::new(
86                    m00, m01, 0.0, m03, // oh rustfmt you so fine
87                    m10, m11, 0.0, m13, // you so fine you blow my mind
88                    0.0, 0.0, 1.0, 0.0, // but leave my matrix formatting alone
89                    0.0, 0.0, 0.0, 1.0, // plz
90                )
91                .transpose()
92                .into()
93            }
94        }
95    }
96}
97
98#[derive(Debug, Copy, Clone, PartialEq)]
99pub struct DrawParam {
100    /// A portion of the drawable to clip, as a fraction of the whole image.
101    /// Defaults to the whole image `(0,0 to 1,1)` if omitted.
102    pub src: Rect,
103    /// A color to draw the target with.
104    /// Default: white.
105    pub color: Color,
106    /// Where to put the `Drawable`.
107    pub trans: Transform,
108}
109
110impl Default for DrawParam {
111    fn default() -> Self {
112        DrawParam {
113            src: Rect::one(),
114            color: Color::WHITE,
115            trans: Transform::default(),
116        }
117    }
118}
119
120impl DrawParam {
121    /// Create a new DrawParam with default values.
122    pub fn new() -> Self {
123        Self::default()
124    }
125
126    /// Set the source rect
127    pub fn src(mut self, src: Rect) -> Self {
128        self.src = src;
129        self
130    }
131
132    /// Set the dest point
133    pub fn dest<P>(mut self, dest_: P) -> Self
134    where
135        P: Into<mint::Point2<f32>>,
136    {
137        if let Transform::Values { ref mut dest, .. } = self.trans {
138            let p: mint::Point2<f32> = dest_.into();
139            *dest = p;
140            self
141        } else {
142            panic!("Cannot set values for a DrawParam matrix")
143        }
144    }
145
146    /// Set the drawable color.  This will be blended with whatever
147    /// color the drawn object already is.
148    pub fn color(mut self, color: Color) -> Self {
149        self.color = color;
150        self
151    }
152
153    /// Set the rotation of the drawable.
154    pub fn rotation(mut self, rot: f32) -> Self {
155        if let Transform::Values {
156            ref mut rotation, ..
157        } = self.trans
158        {
159            *rotation = rot;
160            self
161        } else {
162            panic!("Cannot set values for a DrawParam matrix")
163        }
164    }
165
166    /// Set the scaling factors of the drawable.
167    pub fn scale<V>(mut self, scale_: V) -> Self
168    where
169        V: Into<mint::Vector2<f32>>,
170    {
171        if let Transform::Values { ref mut scale, .. } = self.trans {
172            let p: mint::Vector2<f32> = scale_.into();
173            *scale = p;
174            self
175        } else {
176            panic!("Cannot set values for a DrawParam matrix")
177        }
178    }
179
180    /// Set the transformation offset of the drawable.
181    pub fn offset<P>(mut self, offset_: P) -> Self
182    where
183        P: Into<mint::Point2<f32>>,
184    {
185        if let Transform::Values { ref mut offset, .. } = self.trans {
186            let p: mint::Point2<f32> = offset_.into();
187            *offset = p;
188            self
189        } else {
190            panic!("Cannot set values for a DrawParam matrix")
191        }
192    }
193
194    /// Set the transformation matrix of the drawable.
195    pub fn transform<M>(mut self, transform: M) -> Self
196    where
197        M: Into<mint::ColumnMatrix4<f32>>,
198    {
199        self.trans = Transform::Matrix(transform.into());
200        self
201    }
202}
203
204/// Create a `DrawParam` from a location.
205/// Note that this takes a single-element tuple.
206/// It's a little weird but keeps the trait implementations
207/// from clashing.
208impl<P> From<(P,)> for DrawParam
209where
210    P: Into<mint::Point2<f32>>,
211{
212    fn from(location: (P,)) -> Self {
213        DrawParam::new().dest(location.0)
214    }
215}
216
217/// Create a `DrawParam` from a location and color
218impl<P> From<(P, Color)> for DrawParam
219where
220    P: Into<mint::Point2<f32>>,
221{
222    fn from((location, color): (P, Color)) -> Self {
223        DrawParam::new().dest(location).color(color)
224    }
225}
226
227/// Create a `DrawParam` from a location, rotation and color
228impl<P> From<(P, f32, Color)> for DrawParam
229where
230    P: Into<mint::Point2<f32>>,
231{
232    fn from((location, rotation, color): (P, f32, Color)) -> Self {
233        DrawParam::new()
234            .dest(location)
235            .rotation(rotation)
236            .color(color)
237    }
238}
239
240/// Create a `DrawParam` from a location, rotation, offset and color
241impl<P> From<(P, f32, P, Color)> for DrawParam
242where
243    P: Into<mint::Point2<f32>>,
244{
245    fn from((location, rotation, offset, color): (P, f32, P, Color)) -> Self {
246        DrawParam::new()
247            .dest(location)
248            .rotation(rotation)
249            .offset(offset)
250            .color(color)
251    }
252}
253
254/// Create a `DrawParam` from a location, rotation, offset, scale and color
255impl<P, V> From<(P, f32, P, V, Color)> for DrawParam
256where
257    P: Into<mint::Point2<f32>>,
258    V: Into<mint::Vector2<f32>>,
259{
260    fn from((location, rotation, offset, scale, color): (P, f32, P, V, Color)) -> Self {
261        DrawParam::new()
262            .dest(location)
263            .rotation(rotation)
264            .offset(offset)
265            .scale(scale)
266            .color(color)
267    }
268}