emath/ts_transform.rs
1use crate::{Pos2, Rect, Vec2};
2
3/// Linearly transforms positions via a translation, then a scaling.
4///
5/// [`TSTransform`] first scales points with the scaling origin at `0, 0`
6/// (the top left corner), then translates them.
7#[repr(C)]
8#[derive(Clone, Copy, Debug, PartialEq)]
9#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
10#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
11pub struct TSTransform {
12 /// Scaling applied first, scaled around (0, 0).
13 pub scaling: f32,
14
15 /// Translation amount, applied after scaling.
16 pub translation: Vec2,
17}
18
19impl Eq for TSTransform {}
20
21impl Default for TSTransform {
22 #[inline]
23 fn default() -> Self {
24 Self::IDENTITY
25 }
26}
27
28impl TSTransform {
29 pub const IDENTITY: Self = Self {
30 translation: Vec2::ZERO,
31 scaling: 1.0,
32 };
33
34 #[inline]
35 /// Creates a new translation that first scales points around
36 /// `(0, 0)`, then translates them.
37 pub fn new(translation: Vec2, scaling: f32) -> Self {
38 Self {
39 translation,
40 scaling,
41 }
42 }
43
44 #[inline]
45 pub fn from_translation(translation: Vec2) -> Self {
46 Self::new(translation, 1.0)
47 }
48
49 #[inline]
50 pub fn from_scaling(scaling: f32) -> Self {
51 Self::new(Vec2::ZERO, scaling)
52 }
53
54 /// Inverts the transform.
55 ///
56 /// ```
57 /// # use emath::{pos2, vec2, TSTransform};
58 /// let p1 = pos2(2.0, 3.0);
59 /// let p2 = pos2(12.0, 5.0);
60 /// let ts = TSTransform::new(vec2(2.0, 3.0), 2.0);
61 /// let inv = ts.inverse();
62 /// assert_eq!(inv.mul_pos(p1), pos2(0.0, 0.0));
63 /// assert_eq!(inv.mul_pos(p2), pos2(5.0, 1.0));
64 ///
65 /// assert_eq!(ts.inverse().inverse(), ts);
66 /// ```
67 #[inline]
68 pub fn inverse(&self) -> Self {
69 Self::new(-self.translation / self.scaling, 1.0 / self.scaling)
70 }
71
72 /// Transforms the given coordinate.
73 ///
74 /// ```
75 /// # use emath::{pos2, vec2, TSTransform};
76 /// let p1 = pos2(0.0, 0.0);
77 /// let p2 = pos2(5.0, 1.0);
78 /// let ts = TSTransform::new(vec2(2.0, 3.0), 2.0);
79 /// assert_eq!(ts.mul_pos(p1), pos2(2.0, 3.0));
80 /// assert_eq!(ts.mul_pos(p2), pos2(12.0, 5.0));
81 /// ```
82 #[inline]
83 pub fn mul_pos(&self, pos: Pos2) -> Pos2 {
84 self.scaling * pos + self.translation
85 }
86
87 /// Transforms the given rectangle.
88 ///
89 /// ```
90 /// # use emath::{pos2, vec2, Rect, TSTransform};
91 /// let rect = Rect::from_min_max(pos2(5.0, 5.0), pos2(15.0, 10.0));
92 /// let ts = TSTransform::new(vec2(1.0, 0.0), 3.0);
93 /// let transformed = ts.mul_rect(rect);
94 /// assert_eq!(transformed.min, pos2(16.0, 15.0));
95 /// assert_eq!(transformed.max, pos2(46.0, 30.0));
96 /// ```
97 #[inline]
98 pub fn mul_rect(&self, rect: Rect) -> Rect {
99 Rect {
100 min: self.mul_pos(rect.min),
101 max: self.mul_pos(rect.max),
102 }
103 }
104}
105
106/// Transforms the position.
107impl std::ops::Mul<Pos2> for TSTransform {
108 type Output = Pos2;
109
110 #[inline]
111 fn mul(self, pos: Pos2) -> Pos2 {
112 self.mul_pos(pos)
113 }
114}
115
116/// Transforms the rectangle.
117impl std::ops::Mul<Rect> for TSTransform {
118 type Output = Rect;
119
120 #[inline]
121 fn mul(self, rect: Rect) -> Rect {
122 self.mul_rect(rect)
123 }
124}
125
126impl std::ops::Mul<Self> for TSTransform {
127 type Output = Self;
128
129 #[inline]
130 /// Applies the right hand side transform, then the left hand side.
131 ///
132 /// ```
133 /// # use emath::{TSTransform, vec2};
134 /// let ts1 = TSTransform::new(vec2(1.0, 0.0), 2.0);
135 /// let ts2 = TSTransform::new(vec2(-1.0, -1.0), 3.0);
136 /// let ts_combined = TSTransform::new(vec2(2.0, -1.0), 6.0);
137 /// assert_eq!(ts_combined, ts2 * ts1);
138 /// ```
139 fn mul(self, rhs: Self) -> Self::Output {
140 // Apply rhs first.
141 Self {
142 scaling: self.scaling * rhs.scaling,
143 translation: self.translation + self.scaling * rhs.translation,
144 }
145 }
146}