1use num_traits::{Float, FloatConst};
2
3use crate::geometry::{Dimension, Size};
4use crate::linalg::{Quaternion3D, Transform3D};
5use crate::physics::Angle;
6
7#[derive(Copy, Clone, Debug, Default, PartialEq)]
15pub struct TransformBuilder<T>
16where
17 T: Float,
18{
19 transforms: [Transform<T>; 8],
20 index: usize,
21}
22
23impl<T> TransformBuilder<T>
24where
25 T: Float,
26{
27 pub fn new() -> TransformBuilder<T>
30 where
31 T: Default,
32 {
33 Default::default()
34 }
35
36 pub fn push(
41 mut self,
42 transform: Transform<T>,
43 ) -> Result<TransformBuilder<T>, TransformBuilder<T>> {
44 match self.index {
45 0 => self.transforms[0] = transform,
46 n => match self.transforms[n - 1].concat(transform) {
47 Some(transform) => {
48 self.transforms[n - 1] = transform;
49 return Ok(self);
50 }
51 None if n == self.transforms.len() => return Err(self),
52 None => self.transforms[n] = transform,
53 },
54 }
55
56 self.index += 1;
57
58 Ok(self)
59 }
60
61 pub fn len(&self) -> usize {
63 self.index
64 }
65
66 pub fn into_transforms(self) -> [Transform<T>; 8] {
68 self.transforms
69 }
70}
71
72#[derive(Copy, Clone, Debug, PartialEq)]
75pub struct Transform<T>
76where
77 T: Float,
78{
79 pub matrix: Transform3D<T>,
82
83 pub relative_translation: (T, T),
87}
88
89impl<T> Transform<T>
90where
91 T: Float,
92{
93 pub fn with_transform(matrix: Transform3D<T>) -> Transform<T> {
95 Transform {
96 matrix,
97 ..Default::default()
98 }
99 }
100
101 pub fn with_matrix(matrix: [T; 6]) -> Transform<T> {
104 let [a, b, c, d, tx, ty] = matrix;
105
106 let mut transform = Transform::default();
107 transform.matrix.columns[0][0] = a;
108 transform.matrix.columns[0][1] = b;
109 transform.matrix.columns[1][0] = c;
110 transform.matrix.columns[1][1] = d;
111 transform.matrix.columns[3][0] = tx;
112 transform.matrix.columns[3][1] = ty;
113
114 transform
115 }
116
117 pub fn with_matrix3d(matrix: [T; 16]) -> Transform<T> {
120 let [m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44] =
121 matrix;
122
123 let mut transform = Transform::default();
124 transform.matrix.columns[0] = [m11, m12, m13, m14];
125 transform.matrix.columns[1] = [m21, m22, m23, m24];
126 transform.matrix.columns[2] = [m31, m32, m33, m34];
127 transform.matrix.columns[3] = [m41, m42, m43, m44];
128
129 transform
130 }
131
132 pub fn with_perspective(d: T) -> Transform<T> {
135 Transform {
136 matrix: Transform3D::with_perspective(d),
137 ..Default::default()
138 }
139 }
140
141 pub fn with_rotation(rx: T, ry: T, rz: T, angle: Angle<T>) -> Transform<T>
144 where
145 T: FloatConst,
146 {
147 let q = Quaternion3D::with_angle(angle.to_radians(), -rx, -ry, rz);
148 Transform {
149 matrix: Transform3D::with_rotation(q),
150 ..Default::default()
151 }
152 }
153
154 pub fn with_scale(sx: T, sy: T, sz: T) -> Transform<T> {
156 Transform {
157 matrix: Transform3D::with_scale(sx, sy, sz),
158 ..Default::default()
159 }
160 }
161
162 pub fn with_skew_x(sx: Angle<T>) -> Transform<T> {
165 Transform {
166 matrix: Transform3D::with_skew_x(sx.to_radians()),
167 ..Default::default()
168 }
169 }
170
171 pub fn with_skew_y(sy: Angle<T>) -> Transform<T> {
174 Transform {
175 matrix: Transform3D::with_skew_y(sy.to_radians()),
176 ..Default::default()
177 }
178 }
179
180 pub fn with_translation(tx: Dimension<T>, ty: Dimension<T>, tz: T) -> Transform<T> {
185 let (tx, rx) = match tx {
186 Dimension::Percentage(rx) => (T::zero(), rx),
187 Dimension::Points(tx) => (tx, T::zero()),
188 _ => (T::zero(), T::zero()),
189 };
190
191 let (ty, ry) = match ty {
192 Dimension::Percentage(ry) => (T::zero(), ry),
193 Dimension::Points(ty) => (ty, T::zero()),
194 _ => (T::zero(), T::zero()),
195 };
196
197 Transform {
198 matrix: Transform3D::with_translation(tx, ty, tz),
199 relative_translation: (rx, ry),
200 }
201 }
202
203 pub fn is_resolved(&self) -> bool {
206 let (tx, ty) = self.relative_translation;
207 tx.is_zero() && ty.is_zero()
208 }
209
210 pub fn resolve(&self, size: Size<T>) -> Transform3D<T> {
212 let (rtx, rty) = self.relative_translation;
213
214 self.matrix
215 .translate(rtx * size.width, rty * size.height, T::zero())
216 }
217
218 pub fn concat(&self, other: Transform<T>) -> Option<Transform<T>> {
223 match self.is_resolved() {
224 true => Some(Transform {
225 matrix: self.matrix.concat(other.matrix),
226 relative_translation: other.relative_translation,
227 }),
228 false => None,
229 }
230 }
231
232 pub fn squash(transforms: [Transform<T>; 8], size: Size<T>) -> Transform3D<T> {
235 transforms
236 .iter()
237 .fold(Transform3D::identity(), |current, transform| {
238 current.concat(transform.resolve(size))
239 })
240 }
241}
242
243impl<T> Default for Transform<T>
244where
245 T: Float,
246{
247 fn default() -> Self {
248 Transform {
249 matrix: Default::default(),
250 relative_translation: (T::zero(), T::zero()),
251 }
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::{Angle, Dimension, Quaternion3D, Size, Transform, Transform3D, TransformBuilder};
258
259 #[test]
260 fn test_transform() {
261 assert_eq!(
262 Transform::with_rotation(1.0, 0.0, 0.0, Angle::with_degrees(45.0)).is_resolved(),
263 true
264 );
265
266 assert_eq!(Transform::with_scale(1.0, 2.0, 3.0).is_resolved(), true);
267
268 assert_eq!(
269 Transform::with_translation(Dimension::Points(10.0), Dimension::Points(20.0), 30.0)
270 .is_resolved(),
271 true
272 );
273
274 assert_eq!(
275 Transform::with_translation(Dimension::Percentage(10.0), Dimension::Points(20.0), 30.0)
276 .is_resolved(),
277 false
278 );
279
280 assert_eq!(
281 Transform::with_translation(Dimension::Points(10.0), Dimension::Percentage(20.0), 30.0)
282 .is_resolved(),
283 false
284 );
285
286 assert_eq!(
287 Transform::with_translation(
288 Dimension::Percentage(0.0),
289 Dimension::Percentage(0.0),
290 30.0
291 )
292 .is_resolved(),
293 true
294 );
295 }
296
297 #[test]
298 fn test_transform_builder() {
299 let builder = TransformBuilder::new()
300 .push(Transform::with_scale(2.0, 3.0, 1.0))
301 .unwrap()
302 .push(Transform::with_translation(
303 Dimension::Percentage(0.5),
304 Dimension::Percentage(0.3),
305 20.0,
306 ))
307 .unwrap()
308 .push(Transform::with_rotation(
309 1.0,
310 0.0,
311 0.0,
312 Angle::with_degrees(45.0),
313 ))
314 .unwrap();
315
316 assert_eq!(builder.len(), 2);
317
318 let resolved = Transform::squash(builder.into_transforms(), Size::new(320.0, 480.0));
319
320 assert_eq!(
321 resolved,
322 Transform3D::with_scale(2.0, 3.0, 1.0)
323 .translate(160.0, 144.0, 20.0)
324 .rotate(Quaternion3D::with_angle(
325 Angle::with_degrees(45.0).to_radians(),
326 -1.0,
327 0.0,
328 0.0
329 ))
330 );
331 }
332}