1 | // Copyright 2019 the Kurbo Authors
|
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT
|
3 |
|
4 | //! A transformation that includes both scale and translation.
|
5 |
|
6 | use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
|
7 |
|
8 | use crate::{
|
9 | Affine, Circle, CubicBez, Line, Point, QuadBez, Rect, RoundedRect, RoundedRectRadii, Vec2,
|
10 | };
|
11 |
|
12 | /// A transformation including scaling and translation.
|
13 | ///
|
14 | /// If the translation is `(x, y)` and the scale is `s`, then this
|
15 | /// transformation represents this augmented matrix:
|
16 | ///
|
17 | /// ```text
|
18 | /// | s 0 x |
|
19 | /// | 0 s y |
|
20 | /// | 0 0 1 |
|
21 | /// ```
|
22 | ///
|
23 | /// See [`Affine`] for more details about the
|
24 | /// equivalence with augmented matrices.
|
25 | ///
|
26 | /// Various multiplication ops are defined, and these are all defined
|
27 | /// to be consistent with matrix multiplication. Therefore,
|
28 | /// `TranslateScale * Point` is defined but not the other way around.
|
29 | ///
|
30 | /// Also note that multiplication is not commutative. Thus,
|
31 | /// `TranslateScale::scale(2.0) * TranslateScale::translate(Vec2::new(1.0, 0.0))`
|
32 | /// has a translation of (2, 0), while
|
33 | /// `TranslateScale::translate(Vec2::new(1.0, 0.0)) * TranslateScale::scale(2.0)`
|
34 | /// has a translation of (1, 0). (Both have a scale of 2; also note that
|
35 | /// the first case can be written
|
36 | /// `2.0 * TranslateScale::translate(Vec2::new(1.0, 0.0))` as this case
|
37 | /// has an implicit conversion).
|
38 | ///
|
39 | /// This transformation is less powerful than `Affine`, but can be applied
|
40 | /// to more primitives, especially including [`Rect`].
|
41 | #[derive (Clone, Copy, Debug)]
|
42 | #[cfg_attr (feature = "schemars" , derive(schemars::JsonSchema))]
|
43 | #[cfg_attr (feature = "serde" , derive(serde::Serialize, serde::Deserialize))]
|
44 | pub struct TranslateScale {
|
45 | translation: Vec2,
|
46 | scale: f64,
|
47 | }
|
48 |
|
49 | impl TranslateScale {
|
50 | /// Create a new transformation from translation and scale.
|
51 | #[inline ]
|
52 | pub const fn new(translation: Vec2, scale: f64) -> TranslateScale {
|
53 | TranslateScale { translation, scale }
|
54 | }
|
55 |
|
56 | /// Create a new transformation with scale only.
|
57 | #[inline ]
|
58 | pub const fn scale(s: f64) -> TranslateScale {
|
59 | TranslateScale::new(Vec2::ZERO, s)
|
60 | }
|
61 |
|
62 | /// Create a new transformation with translation only.
|
63 | #[inline ]
|
64 | pub const fn translate(t: Vec2) -> TranslateScale {
|
65 | TranslateScale::new(t, 1.0)
|
66 | }
|
67 |
|
68 | /// Decompose transformation into translation and scale.
|
69 | pub fn as_tuple(self) -> (Vec2, f64) {
|
70 | (self.translation, self.scale)
|
71 | }
|
72 |
|
73 | /// Compute the inverse transform.
|
74 | ///
|
75 | /// Multiplying a transform with its inverse (either on the
|
76 | /// left or right) results in the identity transform
|
77 | /// (modulo floating point rounding errors).
|
78 | ///
|
79 | /// Produces NaN values when scale is zero.
|
80 | pub fn inverse(self) -> TranslateScale {
|
81 | let scale_recip = self.scale.recip();
|
82 | TranslateScale {
|
83 | translation: self.translation * -scale_recip,
|
84 | scale: scale_recip,
|
85 | }
|
86 | }
|
87 |
|
88 | /// Is this translate/scale finite?
|
89 | #[inline ]
|
90 | pub fn is_finite(&self) -> bool {
|
91 | self.translation.is_finite() && self.scale.is_finite()
|
92 | }
|
93 |
|
94 | /// Is this translate/scale NaN?
|
95 | #[inline ]
|
96 | pub fn is_nan(&self) -> bool {
|
97 | self.translation.is_nan() || self.scale.is_nan()
|
98 | }
|
99 | }
|
100 |
|
101 | impl Default for TranslateScale {
|
102 | #[inline ]
|
103 | fn default() -> TranslateScale {
|
104 | TranslateScale::scale(1.0)
|
105 | }
|
106 | }
|
107 |
|
108 | impl From<TranslateScale> for Affine {
|
109 | fn from(ts: TranslateScale) -> Affine {
|
110 | let TranslateScale { translation: Vec2, scale: f64 } = ts;
|
111 | Affine::new([scale, 0.0, 0.0, scale, translation.x, translation.y])
|
112 | }
|
113 | }
|
114 |
|
115 | impl Mul<Point> for TranslateScale {
|
116 | type Output = Point;
|
117 |
|
118 | #[inline ]
|
119 | fn mul(self, other: Point) -> Point {
|
120 | (self.scale * other.to_vec2()).to_point() + self.translation
|
121 | }
|
122 | }
|
123 |
|
124 | impl Mul for TranslateScale {
|
125 | type Output = TranslateScale;
|
126 |
|
127 | #[inline ]
|
128 | fn mul(self, other: TranslateScale) -> TranslateScale {
|
129 | TranslateScale {
|
130 | translation: self.translation + self.scale * other.translation,
|
131 | scale: self.scale * other.scale,
|
132 | }
|
133 | }
|
134 | }
|
135 |
|
136 | impl MulAssign for TranslateScale {
|
137 | #[inline ]
|
138 | fn mul_assign(&mut self, other: TranslateScale) {
|
139 | *self = self.mul(other);
|
140 | }
|
141 | }
|
142 |
|
143 | impl Mul<TranslateScale> for f64 {
|
144 | type Output = TranslateScale;
|
145 |
|
146 | #[inline ]
|
147 | fn mul(self, other: TranslateScale) -> TranslateScale {
|
148 | TranslateScale {
|
149 | translation: other.translation * self,
|
150 | scale: other.scale * self,
|
151 | }
|
152 | }
|
153 | }
|
154 |
|
155 | impl Add<Vec2> for TranslateScale {
|
156 | type Output = TranslateScale;
|
157 |
|
158 | #[inline ]
|
159 | fn add(self, other: Vec2) -> TranslateScale {
|
160 | TranslateScale {
|
161 | translation: self.translation + other,
|
162 | scale: self.scale,
|
163 | }
|
164 | }
|
165 | }
|
166 |
|
167 | impl Add<TranslateScale> for Vec2 {
|
168 | type Output = TranslateScale;
|
169 |
|
170 | #[inline ]
|
171 | fn add(self, other: TranslateScale) -> TranslateScale {
|
172 | other + self
|
173 | }
|
174 | }
|
175 |
|
176 | impl AddAssign<Vec2> for TranslateScale {
|
177 | #[inline ]
|
178 | fn add_assign(&mut self, other: Vec2) {
|
179 | *self = self.add(other);
|
180 | }
|
181 | }
|
182 |
|
183 | impl Sub<Vec2> for TranslateScale {
|
184 | type Output = TranslateScale;
|
185 |
|
186 | #[inline ]
|
187 | fn sub(self, other: Vec2) -> TranslateScale {
|
188 | TranslateScale {
|
189 | translation: self.translation - other,
|
190 | scale: self.scale,
|
191 | }
|
192 | }
|
193 | }
|
194 |
|
195 | impl SubAssign<Vec2> for TranslateScale {
|
196 | #[inline ]
|
197 | fn sub_assign(&mut self, other: Vec2) {
|
198 | *self = self.sub(other);
|
199 | }
|
200 | }
|
201 |
|
202 | impl Mul<Circle> for TranslateScale {
|
203 | type Output = Circle;
|
204 |
|
205 | #[inline ]
|
206 | fn mul(self, other: Circle) -> Circle {
|
207 | Circle::new(self * other.center, self.scale * other.radius)
|
208 | }
|
209 | }
|
210 |
|
211 | impl Mul<Line> for TranslateScale {
|
212 | type Output = Line;
|
213 |
|
214 | #[inline ]
|
215 | fn mul(self, other: Line) -> Line {
|
216 | Line::new(self * other.p0, self * other.p1)
|
217 | }
|
218 | }
|
219 |
|
220 | impl Mul<Rect> for TranslateScale {
|
221 | type Output = Rect;
|
222 |
|
223 | #[inline ]
|
224 | fn mul(self, other: Rect) -> Rect {
|
225 | let pt0: Point = self * Point::new(x:other.x0, y:other.y0);
|
226 | let pt1: Point = self * Point::new(x:other.x1, y:other.y1);
|
227 | (pt0, pt1).into()
|
228 | }
|
229 | }
|
230 |
|
231 | impl Mul<RoundedRect> for TranslateScale {
|
232 | type Output = RoundedRect;
|
233 |
|
234 | #[inline ]
|
235 | fn mul(self, other: RoundedRect) -> RoundedRect {
|
236 | RoundedRect::from_rect(self * other.rect(), self * other.radii())
|
237 | }
|
238 | }
|
239 |
|
240 | impl Mul<RoundedRectRadii> for TranslateScale {
|
241 | type Output = RoundedRectRadii;
|
242 |
|
243 | #[inline ]
|
244 | fn mul(self, other: RoundedRectRadii) -> RoundedRectRadii {
|
245 | RoundedRectRadii::new(
|
246 | self.scale * other.top_left,
|
247 | self.scale * other.top_right,
|
248 | self.scale * other.bottom_right,
|
249 | self.scale * other.bottom_left,
|
250 | )
|
251 | }
|
252 | }
|
253 |
|
254 | impl Mul<QuadBez> for TranslateScale {
|
255 | type Output = QuadBez;
|
256 |
|
257 | #[inline ]
|
258 | fn mul(self, other: QuadBez) -> QuadBez {
|
259 | QuadBez::new(self * other.p0, self * other.p1, self * other.p2)
|
260 | }
|
261 | }
|
262 |
|
263 | impl Mul<CubicBez> for TranslateScale {
|
264 | type Output = CubicBez;
|
265 |
|
266 | #[inline ]
|
267 | fn mul(self, other: CubicBez) -> CubicBez {
|
268 | CubicBez::new(
|
269 | self * other.p0,
|
270 | self * other.p1,
|
271 | self * other.p2,
|
272 | self * other.p3,
|
273 | )
|
274 | }
|
275 | }
|
276 |
|
277 | #[cfg (test)]
|
278 | mod tests {
|
279 | use crate::{Affine, Point, TranslateScale, Vec2};
|
280 |
|
281 | fn assert_near(p0: Point, p1: Point) {
|
282 | assert!((p1 - p0).hypot() < 1e-9, " {p0:?} != {p1:?}" );
|
283 | }
|
284 |
|
285 | #[test ]
|
286 | fn translate_scale() {
|
287 | let p = Point::new(3.0, 4.0);
|
288 | let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
|
289 |
|
290 | assert_near(ts * p, Point::new(11.0, 14.0));
|
291 | }
|
292 |
|
293 | #[test ]
|
294 | fn conversions() {
|
295 | let p = Point::new(3.0, 4.0);
|
296 | let s = 2.0;
|
297 | let t = Vec2::new(5.0, 6.0);
|
298 | let ts = TranslateScale::new(t, s);
|
299 |
|
300 | // Test that conversion to affine is consistent.
|
301 | let a: Affine = ts.into();
|
302 | assert_near(ts * p, a * p);
|
303 |
|
304 | assert_near((s * p.to_vec2()).to_point(), TranslateScale::scale(s) * p);
|
305 | assert_near(p + t, TranslateScale::translate(t) * p);
|
306 | }
|
307 |
|
308 | #[test ]
|
309 | fn inverse() {
|
310 | let p = Point::new(3.0, 4.0);
|
311 | let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
|
312 |
|
313 | assert_near(p, (ts * ts.inverse()) * p);
|
314 | assert_near(p, (ts.inverse() * ts) * p);
|
315 | }
|
316 | }
|
317 | |