/rust/registry/src/index.crates.io-1949cf8c6b5b557f/kurbo-0.13.0/src/translate_scale.rs
Line | Count | Source |
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 consisting of a uniform scaling followed by a 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 | | /// The translation component of this transformation |
46 | | pub translation: Vec2, |
47 | | /// The scale component of this transformation |
48 | | pub scale: f64, |
49 | | } |
50 | | |
51 | | impl TranslateScale { |
52 | | /// Create a new transformation from translation and scale. |
53 | | #[inline(always)] |
54 | 0 | pub const fn new(translation: Vec2, scale: f64) -> TranslateScale { |
55 | 0 | TranslateScale { translation, scale } |
56 | 0 | } |
57 | | |
58 | | /// Create a new transformation with scale only. |
59 | | #[inline(always)] |
60 | 0 | pub const fn scale(s: f64) -> TranslateScale { |
61 | 0 | TranslateScale::new(Vec2::ZERO, s) |
62 | 0 | } |
63 | | |
64 | | /// Create a new transformation with translation only. |
65 | | #[inline(always)] |
66 | 0 | pub fn translate(translation: impl Into<Vec2>) -> TranslateScale { |
67 | 0 | TranslateScale::new(translation.into(), 1.0) |
68 | 0 | } |
69 | | |
70 | | /// Create a transform that scales about a point other than the origin. |
71 | | /// |
72 | | /// # Examples |
73 | | /// |
74 | | /// ``` |
75 | | /// # use kurbo::{Point, TranslateScale}; |
76 | | /// # fn assert_near(p0: Point, p1: Point) { |
77 | | /// # assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}"); |
78 | | /// # } |
79 | | /// let center = Point::new(1., 1.); |
80 | | /// let ts = TranslateScale::from_scale_about(2., center); |
81 | | /// // Should keep the point (1., 1.) stationary |
82 | | /// assert_near(ts * center, center); |
83 | | /// // (2., 2.) -> (3., 3.) |
84 | | /// assert_near(ts * Point::new(2., 2.), Point::new(3., 3.)); |
85 | | /// ``` |
86 | | #[inline] |
87 | 0 | pub fn from_scale_about(scale: f64, focus: impl Into<Point>) -> Self { |
88 | | // We need to create a transform that is equivalent to translating `focus` |
89 | | // to the origin, followed by a normal scale, followed by reversing the translation. |
90 | | // We need to find the (translation ∘ scale) that matches this. |
91 | 0 | let focus = focus.into().to_vec2(); |
92 | 0 | let translation = focus - focus * scale; |
93 | 0 | Self::new(translation, scale) |
94 | 0 | } |
95 | | |
96 | | /// Compute the inverse transform. |
97 | | /// |
98 | | /// Multiplying a transform with its inverse (either on the |
99 | | /// left or right) results in the identity transform |
100 | | /// (modulo floating point rounding errors). |
101 | | /// |
102 | | /// Produces NaN values when scale is zero. |
103 | | #[inline] |
104 | 0 | pub fn inverse(self) -> TranslateScale { |
105 | 0 | let scale_recip = self.scale.recip(); |
106 | 0 | TranslateScale { |
107 | 0 | translation: self.translation * -scale_recip, |
108 | 0 | scale: scale_recip, |
109 | 0 | } |
110 | 0 | } |
111 | | |
112 | | /// Is this translate/scale [finite]? |
113 | | /// |
114 | | /// [finite]: f64::is_finite |
115 | | #[inline] |
116 | 0 | pub fn is_finite(&self) -> bool { |
117 | 0 | self.translation.is_finite() && self.scale.is_finite() |
118 | 0 | } |
119 | | |
120 | | /// Is this translate/scale [NaN]? |
121 | | /// |
122 | | /// [NaN]: f64::is_nan |
123 | | #[inline] |
124 | 0 | pub fn is_nan(&self) -> bool { |
125 | 0 | self.translation.is_nan() || self.scale.is_nan() |
126 | 0 | } |
127 | | } |
128 | | |
129 | | impl Default for TranslateScale { |
130 | | #[inline(always)] |
131 | 0 | fn default() -> TranslateScale { |
132 | 0 | TranslateScale::new(Vec2::ZERO, 1.0) |
133 | 0 | } |
134 | | } |
135 | | |
136 | | impl From<TranslateScale> for Affine { |
137 | | #[inline(always)] |
138 | 0 | fn from(ts: TranslateScale) -> Affine { |
139 | 0 | let TranslateScale { translation, scale } = ts; |
140 | 0 | Affine::new([scale, 0.0, 0.0, scale, translation.x, translation.y]) |
141 | 0 | } |
142 | | } |
143 | | |
144 | | impl Mul<Point> for TranslateScale { |
145 | | type Output = Point; |
146 | | |
147 | | #[inline] |
148 | 0 | fn mul(self, other: Point) -> Point { |
149 | 0 | (self.scale * other.to_vec2()).to_point() + self.translation |
150 | 0 | } |
151 | | } |
152 | | |
153 | | impl Mul for TranslateScale { |
154 | | type Output = TranslateScale; |
155 | | |
156 | | #[inline] |
157 | 0 | fn mul(self, other: TranslateScale) -> TranslateScale { |
158 | 0 | TranslateScale { |
159 | 0 | translation: self.translation + self.scale * other.translation, |
160 | 0 | scale: self.scale * other.scale, |
161 | 0 | } |
162 | 0 | } |
163 | | } |
164 | | |
165 | | impl MulAssign for TranslateScale { |
166 | | #[inline] |
167 | 0 | fn mul_assign(&mut self, other: TranslateScale) { |
168 | 0 | *self = self.mul(other); |
169 | 0 | } |
170 | | } |
171 | | |
172 | | impl Mul<TranslateScale> for f64 { |
173 | | type Output = TranslateScale; |
174 | | |
175 | | #[inline] |
176 | 0 | fn mul(self, other: TranslateScale) -> TranslateScale { |
177 | 0 | TranslateScale { |
178 | 0 | translation: other.translation * self, |
179 | 0 | scale: other.scale * self, |
180 | 0 | } |
181 | 0 | } |
182 | | } |
183 | | |
184 | | impl Add<Vec2> for TranslateScale { |
185 | | type Output = TranslateScale; |
186 | | |
187 | | #[inline] |
188 | 0 | fn add(self, other: Vec2) -> TranslateScale { |
189 | 0 | TranslateScale { |
190 | 0 | translation: self.translation + other, |
191 | 0 | scale: self.scale, |
192 | 0 | } |
193 | 0 | } |
194 | | } |
195 | | |
196 | | impl Add<TranslateScale> for Vec2 { |
197 | | type Output = TranslateScale; |
198 | | |
199 | | #[inline] |
200 | 0 | fn add(self, other: TranslateScale) -> TranslateScale { |
201 | 0 | other + self |
202 | 0 | } |
203 | | } |
204 | | |
205 | | impl AddAssign<Vec2> for TranslateScale { |
206 | | #[inline] |
207 | 0 | fn add_assign(&mut self, other: Vec2) { |
208 | 0 | *self = self.add(other); |
209 | 0 | } |
210 | | } |
211 | | |
212 | | impl Sub<Vec2> for TranslateScale { |
213 | | type Output = TranslateScale; |
214 | | |
215 | | #[inline] |
216 | 0 | fn sub(self, other: Vec2) -> TranslateScale { |
217 | 0 | TranslateScale { |
218 | 0 | translation: self.translation - other, |
219 | 0 | scale: self.scale, |
220 | 0 | } |
221 | 0 | } |
222 | | } |
223 | | |
224 | | impl SubAssign<Vec2> for TranslateScale { |
225 | | #[inline] |
226 | 0 | fn sub_assign(&mut self, other: Vec2) { |
227 | 0 | *self = self.sub(other); |
228 | 0 | } |
229 | | } |
230 | | |
231 | | impl Mul<Circle> for TranslateScale { |
232 | | type Output = Circle; |
233 | | |
234 | | #[inline] |
235 | 0 | fn mul(self, other: Circle) -> Circle { |
236 | 0 | Circle::new(self * other.center, self.scale * other.radius) |
237 | 0 | } |
238 | | } |
239 | | |
240 | | impl Mul<Line> for TranslateScale { |
241 | | type Output = Line; |
242 | | |
243 | | #[inline] |
244 | 0 | fn mul(self, other: Line) -> Line { |
245 | 0 | Line::new(self * other.p0, self * other.p1) |
246 | 0 | } |
247 | | } |
248 | | |
249 | | impl Mul<Rect> for TranslateScale { |
250 | | type Output = Rect; |
251 | | |
252 | | #[inline] |
253 | 0 | fn mul(self, other: Rect) -> Rect { |
254 | 0 | let pt0 = self * Point::new(other.x0, other.y0); |
255 | 0 | let pt1 = self * Point::new(other.x1, other.y1); |
256 | 0 | (pt0, pt1).into() |
257 | 0 | } |
258 | | } |
259 | | |
260 | | impl Mul<RoundedRect> for TranslateScale { |
261 | | type Output = RoundedRect; |
262 | | |
263 | | #[inline] |
264 | 0 | fn mul(self, other: RoundedRect) -> RoundedRect { |
265 | 0 | RoundedRect::from_rect(self * other.rect(), self * other.radii()) |
266 | 0 | } |
267 | | } |
268 | | |
269 | | impl Mul<RoundedRectRadii> for TranslateScale { |
270 | | type Output = RoundedRectRadii; |
271 | | |
272 | | #[inline] |
273 | 0 | fn mul(self, other: RoundedRectRadii) -> RoundedRectRadii { |
274 | 0 | RoundedRectRadii::new( |
275 | 0 | self.scale * other.top_left, |
276 | 0 | self.scale * other.top_right, |
277 | 0 | self.scale * other.bottom_right, |
278 | 0 | self.scale * other.bottom_left, |
279 | | ) |
280 | 0 | } |
281 | | } |
282 | | |
283 | | impl Mul<QuadBez> for TranslateScale { |
284 | | type Output = QuadBez; |
285 | | |
286 | | #[inline] |
287 | 0 | fn mul(self, other: QuadBez) -> QuadBez { |
288 | 0 | QuadBez::new(self * other.p0, self * other.p1, self * other.p2) |
289 | 0 | } |
290 | | } |
291 | | |
292 | | impl Mul<CubicBez> for TranslateScale { |
293 | | type Output = CubicBez; |
294 | | |
295 | | #[inline] |
296 | 0 | fn mul(self, other: CubicBez) -> CubicBez { |
297 | 0 | CubicBez::new( |
298 | 0 | self * other.p0, |
299 | 0 | self * other.p1, |
300 | 0 | self * other.p2, |
301 | 0 | self * other.p3, |
302 | | ) |
303 | 0 | } |
304 | | } |
305 | | |
306 | | #[cfg(test)] |
307 | | mod tests { |
308 | | use crate::{Affine, Point, TranslateScale, Vec2}; |
309 | | |
310 | | fn assert_near(p0: Point, p1: Point) { |
311 | | assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}"); |
312 | | } |
313 | | |
314 | | #[test] |
315 | | fn translate_scale() { |
316 | | let p = Point::new(3.0, 4.0); |
317 | | let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0); |
318 | | |
319 | | assert_near(ts * p, Point::new(11.0, 14.0)); |
320 | | } |
321 | | |
322 | | #[test] |
323 | | fn conversions() { |
324 | | let p = Point::new(3.0, 4.0); |
325 | | let s = 2.0; |
326 | | let t = Vec2::new(5.0, 6.0); |
327 | | let ts = TranslateScale::new(t, s); |
328 | | |
329 | | // Test that conversion to affine is consistent. |
330 | | let a: Affine = ts.into(); |
331 | | assert_near(ts * p, a * p); |
332 | | |
333 | | assert_near((s * p.to_vec2()).to_point(), TranslateScale::scale(s) * p); |
334 | | assert_near(p + t, TranslateScale::translate(t) * p); |
335 | | } |
336 | | |
337 | | #[test] |
338 | | fn inverse() { |
339 | | let p = Point::new(3.0, 4.0); |
340 | | let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0); |
341 | | |
342 | | assert_near(p, (ts * ts.inverse()) * p); |
343 | | assert_near(p, (ts.inverse() * ts) * p); |
344 | | } |
345 | | } |