Skip to main content

agg_rust/
trans_affine.rs

1//! Affine transformation matrix.
2//!
3//! Port of `agg_trans_affine.h` / `agg_trans_affine.cpp` — 2D affine
4//! transformations: rotation, scaling, translation, skewing, and
5//! arbitrary parallelogram mappings.
6
7use crate::basics::is_equal_eps;
8
9/// Epsilon for affine matrix comparisons.
10pub const AFFINE_EPSILON: f64 = 1e-14;
11
12/// 2D affine transformation matrix.
13///
14/// Stores six components: `[sx, shy, shx, sy, tx, ty]` representing the
15/// matrix:
16///
17/// ```text
18///   | sx  shx tx |
19///   | shy  sy ty |
20///   |  0    0  1 |
21/// ```
22///
23/// Transform: `x' = x*sx + y*shx + tx`, `y' = x*shy + y*sy + ty`.
24///
25/// Port of C++ `agg::trans_affine`.
26#[derive(Debug, Clone, Copy)]
27pub struct TransAffine {
28    pub sx: f64,
29    pub shy: f64,
30    pub shx: f64,
31    pub sy: f64,
32    pub tx: f64,
33    pub ty: f64,
34}
35
36impl TransAffine {
37    // ====================================================================
38    // Construction
39    // ====================================================================
40
41    /// Identity matrix.
42    pub fn new() -> Self {
43        Self {
44            sx: 1.0,
45            shy: 0.0,
46            shx: 0.0,
47            sy: 1.0,
48            tx: 0.0,
49            ty: 0.0,
50        }
51    }
52
53    /// Custom matrix from six components.
54    pub fn new_custom(sx: f64, shy: f64, shx: f64, sy: f64, tx: f64, ty: f64) -> Self {
55        Self {
56            sx,
57            shy,
58            shx,
59            sy,
60            tx,
61            ty,
62        }
63    }
64
65    /// Construct from a `[6]` array: `[sx, shy, shx, sy, tx, ty]`.
66    pub fn from_array(m: &[f64; 6]) -> Self {
67        Self {
68            sx: m[0],
69            shy: m[1],
70            shx: m[2],
71            sy: m[3],
72            tx: m[4],
73            ty: m[5],
74        }
75    }
76
77    // ====================================================================
78    // Named constructors (derived types in C++)
79    // ====================================================================
80
81    /// Rotation matrix.
82    pub fn new_rotation(a: f64) -> Self {
83        let (sa, ca) = a.sin_cos();
84        Self::new_custom(ca, sa, -sa, ca, 0.0, 0.0)
85    }
86
87    /// Non-uniform scaling matrix.
88    pub fn new_scaling(x: f64, y: f64) -> Self {
89        Self::new_custom(x, 0.0, 0.0, y, 0.0, 0.0)
90    }
91
92    /// Uniform scaling matrix.
93    pub fn new_scaling_uniform(s: f64) -> Self {
94        Self::new_custom(s, 0.0, 0.0, s, 0.0, 0.0)
95    }
96
97    /// Translation matrix.
98    pub fn new_translation(x: f64, y: f64) -> Self {
99        Self::new_custom(1.0, 0.0, 0.0, 1.0, x, y)
100    }
101
102    /// Skewing (shear) matrix.
103    pub fn new_skewing(x: f64, y: f64) -> Self {
104        Self::new_custom(1.0, y.tan(), x.tan(), 1.0, 0.0, 0.0)
105    }
106
107    /// Line segment transformation: maps `0..dist` to the segment `(x1,y1)-(x2,y2)`.
108    pub fn new_line_segment(x1: f64, y1: f64, x2: f64, y2: f64, dist: f64) -> Self {
109        let dx = x2 - x1;
110        let dy = y2 - y1;
111        let mut m = Self::new();
112        if dist > 0.0 {
113            m.multiply(&Self::new_scaling_uniform(
114                (dx * dx + dy * dy).sqrt() / dist,
115            ));
116        }
117        m.multiply(&Self::new_rotation(dy.atan2(dx)));
118        m.multiply(&Self::new_translation(x1, y1));
119        m
120    }
121
122    /// Reflection across a line through the origin at angle `a`.
123    pub fn new_reflection(a: f64) -> Self {
124        Self::new_reflection_unit(a.cos(), a.sin())
125    }
126
127    /// Reflection across a line through the origin containing the non-unit
128    /// vector `(x, y)`.
129    pub fn new_reflection_xy(x: f64, y: f64) -> Self {
130        let d = (x * x + y * y).sqrt();
131        Self::new_reflection_unit(x / d, y / d)
132    }
133
134    /// Reflection across a line through the origin containing unit vector
135    /// `(ux, uy)`.
136    pub fn new_reflection_unit(ux: f64, uy: f64) -> Self {
137        Self::new_custom(
138            2.0 * ux * ux - 1.0,
139            2.0 * ux * uy,
140            2.0 * ux * uy,
141            2.0 * uy * uy - 1.0,
142            0.0,
143            0.0,
144        )
145    }
146
147    // ====================================================================
148    // Parallelogram transformations
149    // ====================================================================
150
151    /// Map one parallelogram to another.
152    ///
153    /// `src` and `dst` are `[x1,y1, x2,y2, x3,y3]` — three corners,
154    /// the fourth is implicit.
155    pub fn parl_to_parl(&mut self, src: &[f64; 6], dst: &[f64; 6]) -> &mut Self {
156        self.sx = src[2] - src[0];
157        self.shy = src[3] - src[1];
158        self.shx = src[4] - src[0];
159        self.sy = src[5] - src[1];
160        self.tx = src[0];
161        self.ty = src[1];
162        self.invert();
163        self.multiply(&TransAffine::new_custom(
164            dst[2] - dst[0],
165            dst[3] - dst[1],
166            dst[4] - dst[0],
167            dst[5] - dst[1],
168            dst[0],
169            dst[1],
170        ));
171        self
172    }
173
174    /// Map a rectangle to a parallelogram.
175    pub fn rect_to_parl(
176        &mut self,
177        x1: f64,
178        y1: f64,
179        x2: f64,
180        y2: f64,
181        parl: &[f64; 6],
182    ) -> &mut Self {
183        let src = [x1, y1, x2, y1, x2, y2];
184        self.parl_to_parl(&src, parl);
185        self
186    }
187
188    /// Map a parallelogram to a rectangle.
189    pub fn parl_to_rect(
190        &mut self,
191        parl: &[f64; 6],
192        x1: f64,
193        y1: f64,
194        x2: f64,
195        y2: f64,
196    ) -> &mut Self {
197        let dst = [x1, y1, x2, y1, x2, y2];
198        self.parl_to_parl(parl, &dst);
199        self
200    }
201
202    // ====================================================================
203    // Operations (mutate self)
204    // ====================================================================
205
206    /// Reset to identity.
207    pub fn reset(&mut self) -> &mut Self {
208        self.sx = 1.0;
209        self.sy = 1.0;
210        self.shy = 0.0;
211        self.shx = 0.0;
212        self.tx = 0.0;
213        self.ty = 0.0;
214        self
215    }
216
217    /// Translate.
218    pub fn translate(&mut self, x: f64, y: f64) -> &mut Self {
219        self.tx += x;
220        self.ty += y;
221        self
222    }
223
224    /// Rotate by angle `a` (radians).
225    pub fn rotate(&mut self, a: f64) -> &mut Self {
226        let (sa, ca) = a.sin_cos();
227        let t0 = self.sx * ca - self.shy * sa;
228        let t2 = self.shx * ca - self.sy * sa;
229        let t4 = self.tx * ca - self.ty * sa;
230        self.shy = self.sx * sa + self.shy * ca;
231        self.sy = self.shx * sa + self.sy * ca;
232        self.ty = self.tx * sa + self.ty * ca;
233        self.sx = t0;
234        self.shx = t2;
235        self.tx = t4;
236        self
237    }
238
239    /// Non-uniform scale.
240    pub fn scale(&mut self, x: f64, y: f64) -> &mut Self {
241        self.sx *= x;
242        self.shx *= x;
243        self.tx *= x;
244        self.shy *= y;
245        self.sy *= y;
246        self.ty *= y;
247        self
248    }
249
250    /// Uniform scale.
251    pub fn scale_uniform(&mut self, s: f64) -> &mut Self {
252        self.scale(s, s)
253    }
254
255    /// Post-multiply: `self = self * m`.
256    pub fn multiply(&mut self, m: &TransAffine) -> &mut Self {
257        let t0 = self.sx * m.sx + self.shy * m.shx;
258        let t2 = self.shx * m.sx + self.sy * m.shx;
259        let t4 = self.tx * m.sx + self.ty * m.shx + m.tx;
260        self.shy = self.sx * m.shy + self.shy * m.sy;
261        self.sy = self.shx * m.shy + self.sy * m.sy;
262        self.ty = self.tx * m.shy + self.ty * m.sy + m.ty;
263        self.sx = t0;
264        self.shx = t2;
265        self.tx = t4;
266        self
267    }
268
269    /// Pre-multiply: `self = m * self`.
270    pub fn premultiply(&mut self, m: &TransAffine) -> &mut Self {
271        let mut t = *m;
272        t.multiply(self);
273        *self = t;
274        self
275    }
276
277    /// Post-multiply by inverse of `m`.
278    pub fn multiply_inv(&mut self, m: &TransAffine) -> &mut Self {
279        let mut t = *m;
280        t.invert();
281        self.multiply(&t);
282        self
283    }
284
285    /// Pre-multiply by inverse of `m`.
286    pub fn premultiply_inv(&mut self, m: &TransAffine) -> &mut Self {
287        let mut t = *m;
288        t.invert();
289        t.multiply(self);
290        *self = t;
291        self
292    }
293
294    /// Invert the matrix in place.
295    pub fn invert(&mut self) -> &mut Self {
296        let d = self.determinant_reciprocal();
297        let t0 = self.sy * d;
298        self.sy = self.sx * d;
299        self.shy = -self.shy * d;
300        self.shx = -self.shx * d;
301        let t4 = -self.tx * t0 - self.ty * self.shx;
302        self.ty = -self.tx * self.shy - self.ty * self.sy;
303        self.sx = t0;
304        self.tx = t4;
305        self
306    }
307
308    /// Mirror around X axis.
309    pub fn flip_x(&mut self) -> &mut Self {
310        self.sx = -self.sx;
311        self.shy = -self.shy;
312        self.tx = -self.tx;
313        self
314    }
315
316    /// Mirror around Y axis.
317    pub fn flip_y(&mut self) -> &mut Self {
318        self.shx = -self.shx;
319        self.sy = -self.sy;
320        self.ty = -self.ty;
321        self
322    }
323
324    // ====================================================================
325    // Store / Load
326    // ====================================================================
327
328    /// Store to a `[6]` array.
329    pub fn store_to(&self, m: &mut [f64; 6]) {
330        m[0] = self.sx;
331        m[1] = self.shy;
332        m[2] = self.shx;
333        m[3] = self.sy;
334        m[4] = self.tx;
335        m[5] = self.ty;
336    }
337
338    /// Load from a `[6]` array.
339    pub fn load_from(&mut self, m: &[f64; 6]) -> &mut Self {
340        self.sx = m[0];
341        self.shy = m[1];
342        self.shx = m[2];
343        self.sy = m[3];
344        self.tx = m[4];
345        self.ty = m[5];
346        self
347    }
348
349    // ====================================================================
350    // Transformations
351    // ====================================================================
352
353    /// Forward transform: `(x, y) -> (x', y')`.
354    #[inline]
355    pub fn transform(&self, x: &mut f64, y: &mut f64) {
356        let tmp = *x;
357        *x = tmp * self.sx + *y * self.shx + self.tx;
358        *y = tmp * self.shy + *y * self.sy + self.ty;
359    }
360
361    /// Forward transform (2x2 only, no translation).
362    #[inline]
363    pub fn transform_2x2(&self, x: &mut f64, y: &mut f64) {
364        let tmp = *x;
365        *x = tmp * self.sx + *y * self.shx;
366        *y = tmp * self.shy + *y * self.sy;
367    }
368
369    /// Inverse transform: `(x', y') -> (x, y)`.
370    #[inline]
371    pub fn inverse_transform(&self, x: &mut f64, y: &mut f64) {
372        let d = self.determinant_reciprocal();
373        let a = (*x - self.tx) * d;
374        let b = (*y - self.ty) * d;
375        *x = a * self.sy - b * self.shx;
376        *y = b * self.sx - a * self.shy;
377    }
378
379    // ====================================================================
380    // Auxiliary
381    // ====================================================================
382
383    /// Determinant of the 2x2 portion.
384    #[inline]
385    pub fn determinant(&self) -> f64 {
386        self.sx * self.sy - self.shy * self.shx
387    }
388
389    /// Reciprocal of the determinant.
390    #[inline]
391    pub fn determinant_reciprocal(&self) -> f64 {
392        1.0 / (self.sx * self.sy - self.shy * self.shx)
393    }
394
395    /// Average scale factor (useful for approximation_scale on curves).
396    ///
397    /// Uses the same truncated 1/sqrt(2) constant as C++ AGG (`0.707106781`)
398    /// to match its behavior exactly. Using full-precision FRAC_1_SQRT_2 would
399    /// produce a slightly different result that causes the line profile width
400    /// computation to take a different branch, amplifying the difference.
401    pub fn get_scale(&self) -> f64 {
402        let x = 0.707106781_f64 * self.sx + 0.707106781_f64 * self.shx;
403        let y = 0.707106781_f64 * self.shy + 0.707106781_f64 * self.sy;
404        (x * x + y * y).sqrt()
405    }
406
407    /// Check if the matrix is non-degenerate.
408    pub fn is_valid(&self, epsilon: f64) -> bool {
409        self.sx.abs() > epsilon && self.sy.abs() > epsilon
410    }
411
412    /// Check if this is an identity matrix.
413    pub fn is_identity(&self, epsilon: f64) -> bool {
414        is_equal_eps(self.sx, 1.0, epsilon)
415            && is_equal_eps(self.shy, 0.0, epsilon)
416            && is_equal_eps(self.shx, 0.0, epsilon)
417            && is_equal_eps(self.sy, 1.0, epsilon)
418            && is_equal_eps(self.tx, 0.0, epsilon)
419            && is_equal_eps(self.ty, 0.0, epsilon)
420    }
421
422    /// Check if two matrices are equal within epsilon.
423    pub fn is_equal(&self, m: &TransAffine, epsilon: f64) -> bool {
424        is_equal_eps(self.sx, m.sx, epsilon)
425            && is_equal_eps(self.shy, m.shy, epsilon)
426            && is_equal_eps(self.shx, m.shx, epsilon)
427            && is_equal_eps(self.sy, m.sy, epsilon)
428            && is_equal_eps(self.tx, m.tx, epsilon)
429            && is_equal_eps(self.ty, m.ty, epsilon)
430    }
431
432    /// Extract the rotation angle.
433    pub fn rotation(&self) -> f64 {
434        let mut x1 = 0.0;
435        let mut y1 = 0.0;
436        let mut x2 = 1.0;
437        let mut y2 = 0.0;
438        self.transform(&mut x1, &mut y1);
439        self.transform(&mut x2, &mut y2);
440        (y2 - y1).atan2(x2 - x1)
441    }
442
443    /// Extract the translation components.
444    pub fn translation(&self) -> (f64, f64) {
445        (self.tx, self.ty)
446    }
447
448    /// Extract scaling by removing rotation first.
449    pub fn scaling(&self) -> (f64, f64) {
450        let mut x1 = 0.0;
451        let mut y1 = 0.0;
452        let mut x2 = 1.0;
453        let mut y2 = 1.0;
454        let mut t = *self;
455        t.multiply(&TransAffine::new_rotation(-self.rotation()));
456        t.transform(&mut x1, &mut y1);
457        t.transform(&mut x2, &mut y2);
458        (x2 - x1, y2 - y1)
459    }
460
461    /// Absolute scaling (from matrix magnitudes).
462    pub fn scaling_abs(&self) -> (f64, f64) {
463        (
464            (self.sx * self.sx + self.shx * self.shx).sqrt(),
465            (self.shy * self.shy + self.sy * self.sy).sqrt(),
466        )
467    }
468}
469
470impl Default for TransAffine {
471    fn default() -> Self {
472        Self::new()
473    }
474}
475
476impl PartialEq for TransAffine {
477    fn eq(&self, other: &Self) -> bool {
478        self.is_equal(other, AFFINE_EPSILON)
479    }
480}
481
482impl std::ops::Mul for TransAffine {
483    type Output = TransAffine;
484    fn mul(self, rhs: TransAffine) -> TransAffine {
485        let mut result = self;
486        result.multiply(&rhs);
487        result
488    }
489}
490
491impl std::ops::MulAssign for TransAffine {
492    fn mul_assign(&mut self, rhs: TransAffine) {
493        self.multiply(&rhs);
494    }
495}
496
497// ============================================================================
498// Tests
499// ============================================================================
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504    use std::f64::consts::PI;
505
506    const EPS: f64 = 1e-10;
507
508    #[test]
509    fn test_identity() {
510        let m = TransAffine::new();
511        assert!(m.is_identity(AFFINE_EPSILON));
512        assert_eq!(m.determinant(), 1.0);
513    }
514
515    #[test]
516    fn test_translation() {
517        let m = TransAffine::new_translation(10.0, 20.0);
518        let mut x = 5.0;
519        let mut y = 3.0;
520        m.transform(&mut x, &mut y);
521        assert!((x - 15.0).abs() < EPS);
522        assert!((y - 23.0).abs() < EPS);
523    }
524
525    #[test]
526    fn test_scaling() {
527        let m = TransAffine::new_scaling(2.0, 3.0);
528        let mut x = 5.0;
529        let mut y = 4.0;
530        m.transform(&mut x, &mut y);
531        assert!((x - 10.0).abs() < EPS);
532        assert!((y - 12.0).abs() < EPS);
533    }
534
535    #[test]
536    fn test_uniform_scaling() {
537        let m = TransAffine::new_scaling_uniform(5.0);
538        let mut x = 2.0;
539        let mut y = 3.0;
540        m.transform(&mut x, &mut y);
541        assert!((x - 10.0).abs() < EPS);
542        assert!((y - 15.0).abs() < EPS);
543    }
544
545    #[test]
546    fn test_rotation_90() {
547        let m = TransAffine::new_rotation(PI / 2.0);
548        let mut x = 1.0;
549        let mut y = 0.0;
550        m.transform(&mut x, &mut y);
551        assert!(x.abs() < EPS);
552        assert!((y - 1.0).abs() < EPS);
553    }
554
555    #[test]
556    fn test_rotation_180() {
557        let m = TransAffine::new_rotation(PI);
558        let mut x = 1.0;
559        let mut y = 0.0;
560        m.transform(&mut x, &mut y);
561        assert!((x + 1.0).abs() < EPS);
562        assert!(y.abs() < EPS);
563    }
564
565    #[test]
566    fn test_multiply_translate_then_scale() {
567        // First translate, then scale
568        let mut m = TransAffine::new_translation(10.0, 0.0);
569        m.multiply(&TransAffine::new_scaling(2.0, 2.0));
570        let mut x = 0.0;
571        let mut y = 0.0;
572        m.transform(&mut x, &mut y);
573        assert!((x - 20.0).abs() < EPS); // (0+10)*2
574        assert!(y.abs() < EPS);
575    }
576
577    #[test]
578    fn test_multiply_scale_then_translate() {
579        let mut m = TransAffine::new_scaling(2.0, 2.0);
580        m.multiply(&TransAffine::new_translation(10.0, 0.0));
581        let mut x = 5.0;
582        let mut y = 0.0;
583        m.transform(&mut x, &mut y);
584        assert!((x - 20.0).abs() < EPS); // 5*2 + 10
585    }
586
587    #[test]
588    fn test_invert() {
589        let mut m = TransAffine::new_scaling(2.0, 3.0);
590        m.multiply(&TransAffine::new_translation(10.0, 20.0));
591
592        let mut inv = m;
593        inv.invert();
594
595        // m * inv should be identity
596        let result = m * inv;
597        assert!(result.is_identity(1e-10));
598    }
599
600    #[test]
601    fn test_inverse_transform() {
602        let m = TransAffine::new_scaling(2.0, 3.0);
603        let mut x = 5.0;
604        let mut y = 4.0;
605        m.transform(&mut x, &mut y);
606        m.inverse_transform(&mut x, &mut y);
607        assert!((x - 5.0).abs() < EPS);
608        assert!((y - 4.0).abs() < EPS);
609    }
610
611    #[test]
612    fn test_transform_2x2() {
613        let m = TransAffine::new_custom(2.0, 0.0, 0.0, 3.0, 100.0, 200.0);
614        let mut x = 5.0;
615        let mut y = 4.0;
616        m.transform_2x2(&mut x, &mut y);
617        // Should NOT include translation
618        assert!((x - 10.0).abs() < EPS);
619        assert!((y - 12.0).abs() < EPS);
620    }
621
622    #[test]
623    fn test_premultiply() {
624        let s = TransAffine::new_scaling(2.0, 2.0);
625        let t = TransAffine::new_translation(10.0, 0.0);
626
627        // premultiply(s) means self = s * self
628        let mut m = t;
629        m.premultiply(&s);
630
631        // Result: scale first, then translate
632        // s * t: for point (5,0) -> scale(5,0)=(10,0) -> translate -> (20,0)
633        let mut x = 5.0;
634        let mut y = 0.0;
635        m.transform(&mut x, &mut y);
636        assert!((x - 20.0).abs() < EPS);
637    }
638
639    #[test]
640    fn test_flip_x() {
641        let mut m = TransAffine::new();
642        m.flip_x();
643        let mut x = 5.0;
644        let mut y = 3.0;
645        m.transform(&mut x, &mut y);
646        assert!((x + 5.0).abs() < EPS);
647        assert!((y - 3.0).abs() < EPS);
648    }
649
650    #[test]
651    fn test_flip_y() {
652        let mut m = TransAffine::new();
653        m.flip_y();
654        let mut x = 5.0;
655        let mut y = 3.0;
656        m.transform(&mut x, &mut y);
657        assert!((x - 5.0).abs() < EPS);
658        assert!((y + 3.0).abs() < EPS);
659    }
660
661    #[test]
662    fn test_reset() {
663        let mut m = TransAffine::new_scaling(2.0, 3.0);
664        m.reset();
665        assert!(m.is_identity(AFFINE_EPSILON));
666    }
667
668    #[test]
669    fn test_store_load() {
670        let m = TransAffine::new_custom(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
671        let mut arr = [0.0; 6];
672        m.store_to(&mut arr);
673        assert_eq!(arr, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
674
675        let m2 = TransAffine::from_array(&arr);
676        assert!(m.is_equal(&m2, EPS));
677    }
678
679    #[test]
680    fn test_get_scale() {
681        let m = TransAffine::new_scaling_uniform(3.0);
682        // Uses C++-matching truncated 1/sqrt(2) constant, so tolerance is slightly wider
683        assert!((m.get_scale() - 3.0).abs() < 1e-8);
684    }
685
686    #[test]
687    fn test_is_valid() {
688        let m = TransAffine::new();
689        assert!(m.is_valid(AFFINE_EPSILON));
690
691        let m2 = TransAffine::new_custom(0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
692        assert!(!m2.is_valid(AFFINE_EPSILON));
693    }
694
695    #[test]
696    fn test_rotation_extraction() {
697        let m = TransAffine::new_rotation(PI / 4.0);
698        assert!((m.rotation() - PI / 4.0).abs() < EPS);
699    }
700
701    #[test]
702    fn test_translation_extraction() {
703        let m = TransAffine::new_translation(42.0, 17.0);
704        let (dx, dy) = m.translation();
705        assert!((dx - 42.0).abs() < EPS);
706        assert!((dy - 17.0).abs() < EPS);
707    }
708
709    #[test]
710    fn test_scaling_extraction() {
711        let m = TransAffine::new_scaling(3.0, 5.0);
712        let (sx, sy) = m.scaling();
713        assert!((sx - 3.0).abs() < EPS);
714        assert!((sy - 5.0).abs() < EPS);
715    }
716
717    #[test]
718    fn test_scaling_abs() {
719        let m = TransAffine::new_scaling(3.0, 5.0);
720        let (ax, ay) = m.scaling_abs();
721        assert!((ax - 3.0).abs() < EPS);
722        assert!((ay - 5.0).abs() < EPS);
723    }
724
725    #[test]
726    fn test_skewing() {
727        let m = TransAffine::new_skewing(PI / 4.0, 0.0);
728        let mut x = 0.0;
729        let mut y = 1.0;
730        m.transform(&mut x, &mut y);
731        // shx = tan(PI/4) = 1.0, so x = 0*1 + 1*1 = 1
732        assert!((x - 1.0).abs() < EPS);
733        assert!((y - 1.0).abs() < EPS);
734    }
735
736    #[test]
737    fn test_operator_mul() {
738        let a = TransAffine::new_translation(10.0, 0.0);
739        let b = TransAffine::new_scaling(2.0, 2.0);
740        let c = a * b;
741
742        let mut x = 1.0;
743        let mut y = 0.0;
744        c.transform(&mut x, &mut y);
745        // a then b: (1+10)*2 = 22
746        assert!((x - 22.0).abs() < EPS);
747    }
748
749    #[test]
750    fn test_operator_mul_assign() {
751        let mut m = TransAffine::new_translation(10.0, 0.0);
752        m *= TransAffine::new_scaling(2.0, 2.0);
753
754        let mut x = 1.0;
755        let mut y = 0.0;
756        m.transform(&mut x, &mut y);
757        assert!((x - 22.0).abs() < EPS);
758    }
759
760    #[test]
761    fn test_partial_eq() {
762        let a = TransAffine::new_translation(10.0, 20.0);
763        let b = TransAffine::new_translation(10.0, 20.0);
764        assert_eq!(a, b);
765
766        let c = TransAffine::new_translation(10.0, 21.0);
767        assert_ne!(a, c);
768    }
769
770    #[test]
771    fn test_determinant() {
772        let m = TransAffine::new_scaling(2.0, 3.0);
773        assert!((m.determinant() - 6.0).abs() < EPS);
774    }
775
776    #[test]
777    fn test_combined_transform() {
778        // Rotate 90, then translate (10, 0)
779        let mut m = TransAffine::new();
780        m.rotate(PI / 2.0);
781        m.translate(10.0, 0.0);
782
783        let mut x = 1.0;
784        let mut y = 0.0;
785        m.transform(&mut x, &mut y);
786        // rotate 90: (1,0) -> (0,1), then translate: (10, 1)
787        assert!((x - 10.0).abs() < EPS);
788        assert!((y - 1.0).abs() < EPS);
789    }
790
791    #[test]
792    fn test_chain_methods() {
793        let mut m = TransAffine::new();
794        m.scale(2.0, 2.0);
795        m.translate(5.0, 0.0);
796
797        let mut x = 3.0;
798        let mut y = 0.0;
799        m.transform(&mut x, &mut y);
800        // scale: (3*2, 0) = (6, 0), then translate: (6+5, 0) = (11, 0)
801        assert!((x - 11.0).abs() < EPS);
802    }
803
804    #[test]
805    fn test_parl_to_parl() {
806        // Identity: same parallelogram
807        let src = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0];
808        let dst = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0];
809        let mut m = TransAffine::new();
810        m.parl_to_parl(&src, &dst);
811        assert!(m.is_identity(1e-10));
812    }
813
814    #[test]
815    fn test_rect_to_parl() {
816        let parl = [0.0, 0.0, 2.0, 0.0, 2.0, 2.0];
817        let mut m = TransAffine::new();
818        m.rect_to_parl(0.0, 0.0, 1.0, 1.0, &parl);
819
820        let mut x = 0.5;
821        let mut y = 0.5;
822        m.transform(&mut x, &mut y);
823        assert!((x - 1.0).abs() < EPS);
824        assert!((y - 1.0).abs() < EPS);
825    }
826
827    #[test]
828    fn test_reflection() {
829        // Reflect across X axis (angle 0)
830        let m = TransAffine::new_reflection(0.0);
831        let mut x = 1.0;
832        let mut y = 1.0;
833        m.transform(&mut x, &mut y);
834        assert!((x - 1.0).abs() < EPS);
835        assert!((y + 1.0).abs() < EPS);
836    }
837
838    #[test]
839    fn test_line_segment() {
840        let m = TransAffine::new_line_segment(0.0, 0.0, 10.0, 0.0, 10.0);
841        let mut x = 5.0;
842        let mut y = 0.0;
843        m.transform(&mut x, &mut y);
844        assert!((x - 5.0).abs() < EPS);
845        assert!(y.abs() < EPS);
846    }
847
848    #[test]
849    fn test_multiply_inv() {
850        let m = TransAffine::new_scaling(2.0, 3.0);
851        let mut result = TransAffine::new_scaling(2.0, 3.0);
852        result.multiply_inv(&m);
853        assert!(result.is_identity(1e-10));
854    }
855
856    #[test]
857    fn test_default_trait() {
858        let m: TransAffine = Default::default();
859        assert!(m.is_identity(AFFINE_EPSILON));
860    }
861}