| 1 | // Copyright 2018 the Kurbo Authors |
| 2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
| 3 | |
| 4 | //! Affine transforms. |
| 5 | |
| 6 | use core::ops::{Mul, MulAssign}; |
| 7 | |
| 8 | use crate::{Point, Rect, Vec2}; |
| 9 | |
| 10 | #[cfg (not(feature = "std" ))] |
| 11 | use crate::common::FloatFuncs; |
| 12 | |
| 13 | /// A 2D affine transform. |
| 14 | #[derive (Clone, Copy, Debug, PartialEq)] |
| 15 | #[cfg_attr (feature = "schemars" , derive(schemars::JsonSchema))] |
| 16 | #[cfg_attr (feature = "serde" , derive(serde::Serialize, serde::Deserialize))] |
| 17 | pub struct Affine([f64; 6]); |
| 18 | |
| 19 | impl Affine { |
| 20 | /// The identity transform. |
| 21 | pub const IDENTITY: Affine = Affine::scale(1.0); |
| 22 | |
| 23 | /// A transform that is flipped on the y-axis. Useful for converting between |
| 24 | /// y-up and y-down spaces. |
| 25 | pub const FLIP_Y: Affine = Affine::new([1.0, 0., 0., -1.0, 0., 0.]); |
| 26 | |
| 27 | /// A transform that is flipped on the x-axis. |
| 28 | pub const FLIP_X: Affine = Affine::new([-1.0, 0., 0., 1.0, 0., 0.]); |
| 29 | |
| 30 | /// Construct an affine transform from coefficients. |
| 31 | /// |
| 32 | /// If the coefficients are `(a, b, c, d, e, f)`, then the resulting |
| 33 | /// transformation represents this augmented matrix: |
| 34 | /// |
| 35 | /// ```text |
| 36 | /// | a c e | |
| 37 | /// | b d f | |
| 38 | /// | 0 0 1 | |
| 39 | /// ``` |
| 40 | /// |
| 41 | /// Note that this convention is transposed from PostScript and |
| 42 | /// Direct2D, but is consistent with the |
| 43 | /// [Wikipedia](https://en.wikipedia.org/wiki/Affine_transformation) |
| 44 | /// formulation of affine transformation as augmented matrix. The |
| 45 | /// idea is that `(A * B) * v == A * (B * v)`, where `*` is the |
| 46 | /// [`Mul`](std::ops::Mul) trait. |
| 47 | #[inline ] |
| 48 | pub const fn new(c: [f64; 6]) -> Affine { |
| 49 | Affine(c) |
| 50 | } |
| 51 | |
| 52 | /// An affine transform representing uniform scaling. |
| 53 | #[inline ] |
| 54 | pub const fn scale(s: f64) -> Affine { |
| 55 | Affine([s, 0.0, 0.0, s, 0.0, 0.0]) |
| 56 | } |
| 57 | |
| 58 | /// An affine transform representing non-uniform scaling |
| 59 | /// with different scale values for x and y |
| 60 | #[inline ] |
| 61 | pub const fn scale_non_uniform(s_x: f64, s_y: f64) -> Affine { |
| 62 | Affine([s_x, 0.0, 0.0, s_y, 0.0, 0.0]) |
| 63 | } |
| 64 | |
| 65 | /// An affine transform representing rotation. |
| 66 | /// |
| 67 | /// The convention for rotation is that a positive angle rotates a |
| 68 | /// positive X direction into positive Y. Thus, in a Y-down coordinate |
| 69 | /// system (as is common for graphics), it is a clockwise rotation, and |
| 70 | /// in Y-up (traditional for math), it is anti-clockwise. |
| 71 | /// |
| 72 | /// The angle, `th`, is expressed in radians. |
| 73 | #[inline ] |
| 74 | pub fn rotate(th: f64) -> Affine { |
| 75 | let (s, c) = th.sin_cos(); |
| 76 | Affine([c, s, -s, c, 0.0, 0.0]) |
| 77 | } |
| 78 | |
| 79 | /// An affine transform representing a rotation of `th` radians about `center`. |
| 80 | /// |
| 81 | /// See [`Affine::rotate()`] for more info. |
| 82 | #[inline ] |
| 83 | pub fn rotate_about(th: f64, center: Point) -> Affine { |
| 84 | let center = center.to_vec2(); |
| 85 | Self::translate(-center) |
| 86 | .then_rotate(th) |
| 87 | .then_translate(center) |
| 88 | } |
| 89 | |
| 90 | /// An affine transform representing translation. |
| 91 | #[inline ] |
| 92 | pub fn translate<V: Into<Vec2>>(p: V) -> Affine { |
| 93 | let p = p.into(); |
| 94 | Affine([1.0, 0.0, 0.0, 1.0, p.x, p.y]) |
| 95 | } |
| 96 | |
| 97 | /// An affine transformation representing a skew. |
| 98 | /// |
| 99 | /// The `skew_x` and `skew_y` parameters represent skew factors for the |
| 100 | /// horizontal and vertical directions, respectively. |
| 101 | /// |
| 102 | /// This is commonly used to generate a faux oblique transform for |
| 103 | /// font rendering. In this case, you can slant the glyph 20 degrees |
| 104 | /// clockwise in the horizontal direction (assuming a Y-up coordinate |
| 105 | /// system): |
| 106 | /// |
| 107 | /// ``` |
| 108 | /// let oblique_transform = kurbo::Affine::skew(20f64.to_radians().tan(), 0.0); |
| 109 | /// ``` |
| 110 | #[inline ] |
| 111 | pub fn skew(skew_x: f64, skew_y: f64) -> Affine { |
| 112 | Affine([1.0, skew_y, skew_x, 1.0, 0.0, 0.0]) |
| 113 | } |
| 114 | |
| 115 | /// Create an affine transform that represents reflection about the line `point + direction * t, t in (-infty, infty)` |
| 116 | /// |
| 117 | /// # Examples |
| 118 | /// |
| 119 | /// ``` |
| 120 | /// # use kurbo::{Point, Vec2, Affine}; |
| 121 | /// # fn assert_near(p0: Point, p1: Point) { |
| 122 | /// # assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}" ); |
| 123 | /// # } |
| 124 | /// let point = Point::new(1., 0.); |
| 125 | /// let vec = Vec2::new(1., 1.); |
| 126 | /// let map = Affine::reflect(point, vec); |
| 127 | /// assert_near(map * Point::new(1., 0.), Point::new(1., 0.)); |
| 128 | /// assert_near(map * Point::new(2., 1.), Point::new(2., 1.)); |
| 129 | /// assert_near(map * Point::new(2., 2.), Point::new(3., 1.)); |
| 130 | /// ``` |
| 131 | #[inline ] |
| 132 | #[must_use ] |
| 133 | pub fn reflect(point: impl Into<Point>, direction: impl Into<Vec2>) -> Self { |
| 134 | let point = point.into(); |
| 135 | let direction = direction.into(); |
| 136 | |
| 137 | let n = Vec2 { |
| 138 | x: direction.y, |
| 139 | y: -direction.x, |
| 140 | } |
| 141 | .normalize(); |
| 142 | |
| 143 | // Compute Householder reflection matrix |
| 144 | let x2 = n.x * n.x; |
| 145 | let xy = n.x * n.y; |
| 146 | let y2 = n.y * n.y; |
| 147 | // Here we also add in the post translation, because it doesn't require any further calc. |
| 148 | let aff = Affine::new([ |
| 149 | 1. - 2. * x2, |
| 150 | -2. * xy, |
| 151 | -2. * xy, |
| 152 | 1. - 2. * y2, |
| 153 | point.x, |
| 154 | point.y, |
| 155 | ]); |
| 156 | aff.pre_translate(-point.to_vec2()) |
| 157 | } |
| 158 | |
| 159 | /// A [rotation] by `th` followed by `self`. |
| 160 | /// |
| 161 | /// Equivalent to `self * Affine::rotate(th)` |
| 162 | /// |
| 163 | /// [rotation]: Affine::rotate |
| 164 | #[inline ] |
| 165 | #[must_use ] |
| 166 | pub fn pre_rotate(self, th: f64) -> Self { |
| 167 | self * Affine::rotate(th) |
| 168 | } |
| 169 | |
| 170 | /// A [rotation] by `th` about `center` followed by `self`. |
| 171 | /// |
| 172 | /// Equivalent to `self * Affine::rotate_about(th, center)` |
| 173 | /// |
| 174 | /// [rotation]: Affine::rotate_about |
| 175 | #[inline ] |
| 176 | #[must_use ] |
| 177 | pub fn pre_rotate_about(self, th: f64, center: Point) -> Self { |
| 178 | Affine::rotate_about(th, center) * self |
| 179 | } |
| 180 | |
| 181 | /// A [scale] by `scale` followed by `self`. |
| 182 | /// |
| 183 | /// Equivalent to `self * Affine::scale(scale)` |
| 184 | /// |
| 185 | /// [scale]: Affine::scale |
| 186 | #[inline ] |
| 187 | #[must_use ] |
| 188 | pub fn pre_scale(self, scale: f64) -> Self { |
| 189 | self * Affine::scale(scale) |
| 190 | } |
| 191 | |
| 192 | /// A [scale] by `(scale_x, scale_y)` followed by `self`. |
| 193 | /// |
| 194 | /// Equivalent to `self * Affine::scale_non_uniform(scale_x, scale_y)` |
| 195 | /// |
| 196 | /// [scale]: Affine::scale_non_uniform |
| 197 | #[inline ] |
| 198 | #[must_use ] |
| 199 | pub fn pre_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self { |
| 200 | self * Affine::scale_non_uniform(scale_x, scale_y) |
| 201 | } |
| 202 | |
| 203 | /// A [translation] of `trans` followed by `self`. |
| 204 | /// |
| 205 | /// Equivalent to `self * Affine::translate(trans)` |
| 206 | /// |
| 207 | /// [translation]: Affine::translate |
| 208 | #[inline ] |
| 209 | #[must_use ] |
| 210 | pub fn pre_translate(self, trans: Vec2) -> Self { |
| 211 | self * Affine::translate(trans) |
| 212 | } |
| 213 | |
| 214 | /// `self` followed by a [rotation] of `th`. |
| 215 | /// |
| 216 | /// Equivalent to `Affine::rotate(th) * self` |
| 217 | /// |
| 218 | /// [rotation]: Affine::rotate |
| 219 | #[inline ] |
| 220 | #[must_use ] |
| 221 | pub fn then_rotate(self, th: f64) -> Self { |
| 222 | Affine::rotate(th) * self |
| 223 | } |
| 224 | |
| 225 | /// `self` followed by a [rotation] of `th` about `center`. |
| 226 | /// |
| 227 | /// Equivalent to `Affine::rotate_about(th, center) * self` |
| 228 | /// |
| 229 | /// [rotation]: Affine::rotate_about |
| 230 | #[inline ] |
| 231 | #[must_use ] |
| 232 | pub fn then_rotate_about(self, th: f64, center: Point) -> Self { |
| 233 | Affine::rotate_about(th, center) * self |
| 234 | } |
| 235 | |
| 236 | /// `self` followed by a [scale] of `scale`. |
| 237 | /// |
| 238 | /// Equivalent to `Affine::scale(scale) * self` |
| 239 | /// |
| 240 | /// [scale]: Affine::scale |
| 241 | #[inline ] |
| 242 | #[must_use ] |
| 243 | pub fn then_scale(self, scale: f64) -> Self { |
| 244 | Affine::scale(scale) * self |
| 245 | } |
| 246 | |
| 247 | /// `self` followed by a [scale] of `(scale_x, scale_y)`. |
| 248 | /// |
| 249 | /// Equivalent to `Affine::scale_non_uniform(scale_x, scale_y) * self` |
| 250 | /// |
| 251 | /// [scale]: Affine::scale_non_uniform |
| 252 | #[inline ] |
| 253 | #[must_use ] |
| 254 | pub fn then_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self { |
| 255 | Affine::scale_non_uniform(scale_x, scale_y) * self |
| 256 | } |
| 257 | |
| 258 | /// `self` followed by a translation of `trans`. |
| 259 | /// |
| 260 | /// Equivalent to `Affine::translate(trans) * self` |
| 261 | /// |
| 262 | /// [translation]: Affine::translate |
| 263 | #[inline ] |
| 264 | #[must_use ] |
| 265 | pub fn then_translate(mut self, trans: Vec2) -> Self { |
| 266 | self.0[4] += trans.x; |
| 267 | self.0[5] += trans.y; |
| 268 | self |
| 269 | } |
| 270 | |
| 271 | /// Creates an affine transformation that takes the unit square to the given rectangle. |
| 272 | /// |
| 273 | /// Useful when you want to draw into the unit square but have your output fill any rectangle. |
| 274 | /// In this case push the `Affine` onto the transform stack. |
| 275 | pub fn map_unit_square(rect: Rect) -> Affine { |
| 276 | Affine([rect.width(), 0., 0., rect.height(), rect.x0, rect.y0]) |
| 277 | } |
| 278 | |
| 279 | /// Get the coefficients of the transform. |
| 280 | #[inline ] |
| 281 | pub fn as_coeffs(self) -> [f64; 6] { |
| 282 | self.0 |
| 283 | } |
| 284 | |
| 285 | /// Compute the determinant of this transform. |
| 286 | pub fn determinant(self) -> f64 { |
| 287 | self.0[0] * self.0[3] - self.0[1] * self.0[2] |
| 288 | } |
| 289 | |
| 290 | /// Compute the inverse transform. |
| 291 | /// |
| 292 | /// Produces NaN values when the determinant is zero. |
| 293 | pub fn inverse(self) -> Affine { |
| 294 | let inv_det = self.determinant().recip(); |
| 295 | Affine([ |
| 296 | inv_det * self.0[3], |
| 297 | -inv_det * self.0[1], |
| 298 | -inv_det * self.0[2], |
| 299 | inv_det * self.0[0], |
| 300 | inv_det * (self.0[2] * self.0[5] - self.0[3] * self.0[4]), |
| 301 | inv_det * (self.0[1] * self.0[4] - self.0[0] * self.0[5]), |
| 302 | ]) |
| 303 | } |
| 304 | |
| 305 | /// Compute the bounding box of a transformed rectangle. |
| 306 | /// |
| 307 | /// Returns the minimal `Rect` that encloses the given `Rect` after affine transformation. |
| 308 | /// If the transform is axis-aligned, then this bounding box is "tight", in other words the |
| 309 | /// returned `Rect` is the transformed rectangle. |
| 310 | /// |
| 311 | /// The returned rectangle always has non-negative width and height. |
| 312 | pub fn transform_rect_bbox(self, rect: Rect) -> Rect { |
| 313 | let p00 = self * Point::new(rect.x0, rect.y0); |
| 314 | let p01 = self * Point::new(rect.x0, rect.y1); |
| 315 | let p10 = self * Point::new(rect.x1, rect.y0); |
| 316 | let p11 = self * Point::new(rect.x1, rect.y1); |
| 317 | Rect::from_points(p00, p01).union(Rect::from_points(p10, p11)) |
| 318 | } |
| 319 | |
| 320 | /// Is this map [finite]? |
| 321 | /// |
| 322 | /// [finite]: f64::is_finite |
| 323 | #[inline ] |
| 324 | pub fn is_finite(&self) -> bool { |
| 325 | self.0[0].is_finite() |
| 326 | && self.0[1].is_finite() |
| 327 | && self.0[2].is_finite() |
| 328 | && self.0[3].is_finite() |
| 329 | && self.0[4].is_finite() |
| 330 | && self.0[5].is_finite() |
| 331 | } |
| 332 | |
| 333 | /// Is this map [NaN]? |
| 334 | /// |
| 335 | /// [NaN]: f64::is_nan |
| 336 | #[inline ] |
| 337 | pub fn is_nan(&self) -> bool { |
| 338 | self.0[0].is_nan() |
| 339 | || self.0[1].is_nan() |
| 340 | || self.0[2].is_nan() |
| 341 | || self.0[3].is_nan() |
| 342 | || self.0[4].is_nan() |
| 343 | || self.0[5].is_nan() |
| 344 | } |
| 345 | |
| 346 | /// Compute the singular value decomposition of the linear transformation (ignoring the |
| 347 | /// translation). |
| 348 | /// |
| 349 | /// All non-degenerate linear transformations can be represented as |
| 350 | /// |
| 351 | /// 1. a rotation about the origin. |
| 352 | /// 2. a scaling along the x and y axes |
| 353 | /// 3. another rotation about the origin |
| 354 | /// |
| 355 | /// composed together. Decomposing a 2x2 matrix in this way is called a "singular value |
| 356 | /// decomposition" and is written `U Σ V^T`, where U and V^T are orthogonal (rotations) and Σ |
| 357 | /// is a diagonal matrix (a scaling). |
| 358 | /// |
| 359 | /// Since currently this function is used to calculate ellipse radii and rotation from an |
| 360 | /// affine map on the unit circle, we don't calculate V^T, since a rotation of the unit (or |
| 361 | /// any) circle about its center always results in the same circle. This is the reason that an |
| 362 | /// ellipse mapped using an affine map is always an ellipse. |
| 363 | /// |
| 364 | /// Will return NaNs if the matrix (or equivalently the linear map) is singular. |
| 365 | /// |
| 366 | /// First part of the return tuple is the scaling, second part is the angle of rotation (in |
| 367 | /// radians) |
| 368 | #[inline ] |
| 369 | pub(crate) fn svd(self) -> (Vec2, f64) { |
| 370 | let a = self.0[0]; |
| 371 | let a2 = a * a; |
| 372 | let b = self.0[1]; |
| 373 | let b2 = b * b; |
| 374 | let c = self.0[2]; |
| 375 | let c2 = c * c; |
| 376 | let d = self.0[3]; |
| 377 | let d2 = d * d; |
| 378 | let ab = a * b; |
| 379 | let cd = c * d; |
| 380 | let angle = 0.5 * (2.0 * (ab + cd)).atan2(a2 - b2 + c2 - d2); |
| 381 | let s1 = a2 + b2 + c2 + d2; |
| 382 | let s2 = ((a2 - b2 + c2 - d2).powi(2) + 4.0 * (ab + cd).powi(2)).sqrt(); |
| 383 | ( |
| 384 | Vec2 { |
| 385 | x: (0.5 * (s1 + s2)).sqrt(), |
| 386 | y: (0.5 * (s1 - s2)).sqrt(), |
| 387 | }, |
| 388 | angle, |
| 389 | ) |
| 390 | } |
| 391 | |
| 392 | /// Returns the translation part of this affine map (`(self.0[4], self.0[5])`). |
| 393 | #[inline ] |
| 394 | pub fn translation(self) -> Vec2 { |
| 395 | Vec2 { |
| 396 | x: self.0[4], |
| 397 | y: self.0[5], |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | /// Replaces the translation portion of this affine map |
| 402 | /// |
| 403 | /// The translation can be seen as being applied after the linear part of the map. |
| 404 | #[must_use ] |
| 405 | #[inline ] |
| 406 | pub fn with_translation(mut self, trans: Vec2) -> Affine { |
| 407 | self.0[4] = trans.x; |
| 408 | self.0[5] = trans.y; |
| 409 | self |
| 410 | } |
| 411 | } |
| 412 | |
| 413 | impl Default for Affine { |
| 414 | #[inline ] |
| 415 | fn default() -> Affine { |
| 416 | Affine::IDENTITY |
| 417 | } |
| 418 | } |
| 419 | |
| 420 | impl Mul<Point> for Affine { |
| 421 | type Output = Point; |
| 422 | |
| 423 | #[inline ] |
| 424 | fn mul(self, other: Point) -> Point { |
| 425 | Point::new( |
| 426 | self.0[0] * other.x + self.0[2] * other.y + self.0[4], |
| 427 | self.0[1] * other.x + self.0[3] * other.y + self.0[5], |
| 428 | ) |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | impl Mul for Affine { |
| 433 | type Output = Affine; |
| 434 | |
| 435 | #[inline ] |
| 436 | fn mul(self, other: Affine) -> Affine { |
| 437 | Affine([ |
| 438 | self.0[0] * other.0[0] + self.0[2] * other.0[1], |
| 439 | self.0[1] * other.0[0] + self.0[3] * other.0[1], |
| 440 | self.0[0] * other.0[2] + self.0[2] * other.0[3], |
| 441 | self.0[1] * other.0[2] + self.0[3] * other.0[3], |
| 442 | self.0[0] * other.0[4] + self.0[2] * other.0[5] + self.0[4], |
| 443 | self.0[1] * other.0[4] + self.0[3] * other.0[5] + self.0[5], |
| 444 | ]) |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | impl MulAssign for Affine { |
| 449 | #[inline ] |
| 450 | fn mul_assign(&mut self, other: Affine) { |
| 451 | *self = self.mul(other); |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | impl Mul<Affine> for f64 { |
| 456 | type Output = Affine; |
| 457 | |
| 458 | #[inline ] |
| 459 | fn mul(self, other: Affine) -> Affine { |
| 460 | Affine([ |
| 461 | self * other.0[0], |
| 462 | self * other.0[1], |
| 463 | self * other.0[2], |
| 464 | self * other.0[3], |
| 465 | self * other.0[4], |
| 466 | self * other.0[5], |
| 467 | ]) |
| 468 | } |
| 469 | } |
| 470 | |
| 471 | // Conversions to and from mint |
| 472 | #[cfg (feature = "mint" )] |
| 473 | impl From<Affine> for mint::ColumnMatrix2x3<f64> { |
| 474 | #[inline ] |
| 475 | fn from(a: Affine) -> mint::ColumnMatrix2x3<f64> { |
| 476 | mint::ColumnMatrix2x3 { |
| 477 | x: mint::Vector2 { |
| 478 | x: a.0[0], |
| 479 | y: a.0[1], |
| 480 | }, |
| 481 | y: mint::Vector2 { |
| 482 | x: a.0[2], |
| 483 | y: a.0[3], |
| 484 | }, |
| 485 | z: mint::Vector2 { |
| 486 | x: a.0[4], |
| 487 | y: a.0[5], |
| 488 | }, |
| 489 | } |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | #[cfg (feature = "mint" )] |
| 494 | impl From<mint::ColumnMatrix2x3<f64>> for Affine { |
| 495 | #[inline ] |
| 496 | fn from(m: mint::ColumnMatrix2x3<f64>) -> Affine { |
| 497 | Affine([m.x.x, m.x.y, m.y.x, m.y.y, m.z.x, m.z.y]) |
| 498 | } |
| 499 | } |
| 500 | |
| 501 | #[cfg (test)] |
| 502 | mod tests { |
| 503 | use crate::{Affine, Point, Vec2}; |
| 504 | use std::f64::consts::PI; |
| 505 | |
| 506 | fn assert_near(p0: Point, p1: Point) { |
| 507 | assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}" ); |
| 508 | } |
| 509 | |
| 510 | fn affine_assert_near(a0: Affine, a1: Affine) { |
| 511 | for i in 0..6 { |
| 512 | assert!((a0.0[i] - a1.0[i]).abs() < 1e-9, "{a0:?} != {a1:?}" ); |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | #[test ] |
| 517 | fn affine_basic() { |
| 518 | let p = Point::new(3.0, 4.0); |
| 519 | |
| 520 | assert_near(Affine::default() * p, p); |
| 521 | assert_near(Affine::scale(2.0) * p, Point::new(6.0, 8.0)); |
| 522 | assert_near(Affine::rotate(0.0) * p, p); |
| 523 | assert_near(Affine::rotate(PI / 2.0) * p, Point::new(-4.0, 3.0)); |
| 524 | assert_near(Affine::translate((5.0, 6.0)) * p, Point::new(8.0, 10.0)); |
| 525 | assert_near(Affine::skew(0.0, 0.0) * p, p); |
| 526 | assert_near(Affine::skew(2.0, 4.0) * p, Point::new(11.0, 16.0)); |
| 527 | } |
| 528 | |
| 529 | #[test ] |
| 530 | fn affine_mul() { |
| 531 | let a1 = Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); |
| 532 | let a2 = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]); |
| 533 | |
| 534 | let px = Point::new(1.0, 0.0); |
| 535 | let py = Point::new(0.0, 1.0); |
| 536 | let pxy = Point::new(1.0, 1.0); |
| 537 | assert_near(a1 * (a2 * px), (a1 * a2) * px); |
| 538 | assert_near(a1 * (a2 * py), (a1 * a2) * py); |
| 539 | assert_near(a1 * (a2 * pxy), (a1 * a2) * pxy); |
| 540 | } |
| 541 | |
| 542 | #[test ] |
| 543 | fn affine_inv() { |
| 544 | let a = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]); |
| 545 | let a_inv = a.inverse(); |
| 546 | |
| 547 | let px = Point::new(1.0, 0.0); |
| 548 | let py = Point::new(0.0, 1.0); |
| 549 | let pxy = Point::new(1.0, 1.0); |
| 550 | assert_near(a * (a_inv * px), px); |
| 551 | assert_near(a * (a_inv * py), py); |
| 552 | assert_near(a * (a_inv * pxy), pxy); |
| 553 | assert_near(a_inv * (a * px), px); |
| 554 | assert_near(a_inv * (a * py), py); |
| 555 | assert_near(a_inv * (a * pxy), pxy); |
| 556 | } |
| 557 | |
| 558 | #[test ] |
| 559 | fn reflection() { |
| 560 | affine_assert_near( |
| 561 | Affine::reflect(Point::ZERO, (1., 0.)), |
| 562 | Affine::new([1., 0., 0., -1., 0., 0.]), |
| 563 | ); |
| 564 | affine_assert_near( |
| 565 | Affine::reflect(Point::ZERO, (0., 1.)), |
| 566 | Affine::new([-1., 0., 0., 1., 0., 0.]), |
| 567 | ); |
| 568 | // y = x |
| 569 | affine_assert_near( |
| 570 | Affine::reflect(Point::ZERO, (1., 1.)), |
| 571 | Affine::new([0., 1., 1., 0., 0., 0.]), |
| 572 | ); |
| 573 | |
| 574 | // no translate |
| 575 | let point = Point::new(0., 0.); |
| 576 | let vec = Vec2::new(1., 1.); |
| 577 | let map = Affine::reflect(point, vec); |
| 578 | assert_near(map * Point::new(0., 0.), Point::new(0., 0.)); |
| 579 | assert_near(map * Point::new(1., 1.), Point::new(1., 1.)); |
| 580 | assert_near(map * Point::new(1., 2.), Point::new(2., 1.)); |
| 581 | |
| 582 | // with translate |
| 583 | let point = Point::new(1., 0.); |
| 584 | let vec = Vec2::new(1., 1.); |
| 585 | let map = Affine::reflect(point, vec); |
| 586 | assert_near(map * Point::new(1., 0.), Point::new(1., 0.)); |
| 587 | assert_near(map * Point::new(2., 1.), Point::new(2., 1.)); |
| 588 | assert_near(map * Point::new(2., 2.), Point::new(3., 1.)); |
| 589 | } |
| 590 | } |
| 591 | |