Skip to main content

agg_rust/
trans_perspective.rs

1//! Perspective 2D transformations.
2//!
3//! Port of `agg_trans_perspective.h` — full 3×3 projective transformation
4//! matrix supporting perspective divide, quadrilateral mappings, and
5//! incremental scanline iteration.
6
7use crate::basics::is_equal_eps;
8use crate::trans_affine::{TransAffine, AFFINE_EPSILON};
9
10// ============================================================================
11// TransPerspective
12// ============================================================================
13
14/// Perspective 2D transformation (3×3 projective matrix).
15///
16/// ```text
17/// | sx  shy  w0 |
18/// | shx  sy  w1 |
19/// | tx   ty  w2 |
20/// ```
21///
22/// Transform: `m = 1/(x*w0 + y*w1 + w2)`, then
23/// `x' = m*(x*sx + y*shx + tx)`, `y' = m*(x*shy + y*sy + ty)`.
24///
25/// Port of C++ `trans_perspective`.
26#[derive(Clone, Copy)]
27pub struct TransPerspective {
28    pub sx: f64,
29    pub shy: f64,
30    pub w0: f64,
31    pub shx: f64,
32    pub sy: f64,
33    pub w1: f64,
34    pub tx: f64,
35    pub ty: f64,
36    pub w2: f64,
37}
38
39impl TransPerspective {
40    /// Identity matrix.
41    pub fn new() -> Self {
42        Self {
43            sx: 1.0,
44            shy: 0.0,
45            w0: 0.0,
46            shx: 0.0,
47            sy: 1.0,
48            w1: 0.0,
49            tx: 0.0,
50            ty: 0.0,
51            w2: 1.0,
52        }
53    }
54
55    /// Custom matrix from 9 values.
56    #[allow(clippy::too_many_arguments)]
57    pub fn new_from_values(
58        v0: f64,
59        v1: f64,
60        v2: f64,
61        v3: f64,
62        v4: f64,
63        v5: f64,
64        v6: f64,
65        v7: f64,
66        v8: f64,
67    ) -> Self {
68        Self {
69            sx: v0,
70            shy: v1,
71            w0: v2,
72            shx: v3,
73            sy: v4,
74            w1: v5,
75            tx: v6,
76            ty: v7,
77            w2: v8,
78        }
79    }
80
81    /// Custom matrix from array of 9 doubles.
82    pub fn new_from_array(m: &[f64; 9]) -> Self {
83        Self {
84            sx: m[0],
85            shy: m[1],
86            w0: m[2],
87            shx: m[3],
88            sy: m[4],
89            w1: m[5],
90            tx: m[6],
91            ty: m[7],
92            w2: m[8],
93        }
94    }
95
96    /// From an affine transformation (w0=0, w1=0, w2=1).
97    pub fn new_from_affine(a: &TransAffine) -> Self {
98        Self {
99            sx: a.sx,
100            shy: a.shy,
101            w0: 0.0,
102            shx: a.shx,
103            sy: a.sy,
104            w1: 0.0,
105            tx: a.tx,
106            ty: a.ty,
107            w2: 1.0,
108        }
109    }
110
111    // -----------------------------------------------------------------------
112    // Quadrilateral transformations
113    // -----------------------------------------------------------------------
114
115    /// Map unit square (0,0,1,1) to the quadrilateral.
116    pub fn square_to_quad(&mut self, q: &[f64; 8]) -> bool {
117        let dx = q[0] - q[2] + q[4] - q[6];
118        let dy = q[1] - q[3] + q[5] - q[7];
119
120        if dx == 0.0 && dy == 0.0 {
121            // Affine case (parallelogram)
122            self.sx = q[2] - q[0];
123            self.shy = q[3] - q[1];
124            self.w0 = 0.0;
125            self.shx = q[4] - q[2];
126            self.sy = q[5] - q[3];
127            self.w1 = 0.0;
128            self.tx = q[0];
129            self.ty = q[1];
130            self.w2 = 1.0;
131        } else {
132            let dx1 = q[2] - q[4];
133            let dy1 = q[3] - q[5];
134            let dx2 = q[6] - q[4];
135            let dy2 = q[7] - q[5];
136            let den = dx1 * dy2 - dx2 * dy1;
137            if den == 0.0 {
138                // Singular case
139                self.sx = 0.0;
140                self.shy = 0.0;
141                self.w0 = 0.0;
142                self.shx = 0.0;
143                self.sy = 0.0;
144                self.w1 = 0.0;
145                self.tx = 0.0;
146                self.ty = 0.0;
147                self.w2 = 0.0;
148                return false;
149            }
150            // General case
151            let u = (dx * dy2 - dy * dx2) / den;
152            let v = (dy * dx1 - dx * dy1) / den;
153            self.sx = q[2] - q[0] + u * q[2];
154            self.shy = q[3] - q[1] + u * q[3];
155            self.w0 = u;
156            self.shx = q[6] - q[0] + v * q[6];
157            self.sy = q[7] - q[1] + v * q[7];
158            self.w1 = v;
159            self.tx = q[0];
160            self.ty = q[1];
161            self.w2 = 1.0;
162        }
163        true
164    }
165
166    /// Map the quadrilateral to unit square (inverse of square_to_quad).
167    pub fn quad_to_square(&mut self, q: &[f64; 8]) -> bool {
168        if !self.square_to_quad(q) {
169            return false;
170        }
171        self.invert()
172    }
173
174    /// Map quadrilateral `src` to quadrilateral `dst`.
175    pub fn quad_to_quad(&mut self, qs: &[f64; 8], qd: &[f64; 8]) -> bool {
176        let mut p = TransPerspective::new();
177        if !self.quad_to_square(qs) {
178            return false;
179        }
180        if !p.square_to_quad(qd) {
181            return false;
182        }
183        self.multiply(&p);
184        true
185    }
186
187    /// Rectangle → quadrilateral.
188    pub fn rect_to_quad(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, q: &[f64; 8]) -> bool {
189        let r = [x1, y1, x2, y1, x2, y2, x1, y2];
190        self.quad_to_quad(&r, q)
191    }
192
193    /// Quadrilateral → rectangle.
194    pub fn quad_to_rect(&mut self, q: &[f64; 8], x1: f64, y1: f64, x2: f64, y2: f64) -> bool {
195        let r = [x1, y1, x2, y1, x2, y2, x1, y2];
196        self.quad_to_quad(q, &r)
197    }
198
199    // -----------------------------------------------------------------------
200    // Operations
201    // -----------------------------------------------------------------------
202
203    /// Reset to identity matrix.
204    pub fn reset(&mut self) {
205        self.sx = 1.0;
206        self.shy = 0.0;
207        self.w0 = 0.0;
208        self.shx = 0.0;
209        self.sy = 1.0;
210        self.w1 = 0.0;
211        self.tx = 0.0;
212        self.ty = 0.0;
213        self.w2 = 1.0;
214    }
215
216    /// Invert the matrix. Returns false if degenerate.
217    pub fn invert(&mut self) -> bool {
218        let d0 = self.sy * self.w2 - self.w1 * self.ty;
219        let d1 = self.w0 * self.ty - self.shy * self.w2;
220        let d2 = self.shy * self.w1 - self.w0 * self.sy;
221        let d = self.sx * d0 + self.shx * d1 + self.tx * d2;
222        if d == 0.0 {
223            self.sx = 0.0;
224            self.shy = 0.0;
225            self.w0 = 0.0;
226            self.shx = 0.0;
227            self.sy = 0.0;
228            self.w1 = 0.0;
229            self.tx = 0.0;
230            self.ty = 0.0;
231            self.w2 = 0.0;
232            return false;
233        }
234        let d = 1.0 / d;
235        let a = *self;
236        self.sx = d * d0;
237        self.shy = d * d1;
238        self.w0 = d * d2;
239        self.shx = d * (a.w1 * a.tx - a.shx * a.w2);
240        self.sy = d * (a.sx * a.w2 - a.w0 * a.tx);
241        self.w1 = d * (a.w0 * a.shx - a.sx * a.w1);
242        self.tx = d * (a.shx * a.ty - a.sy * a.tx);
243        self.ty = d * (a.shy * a.tx - a.sx * a.ty);
244        self.w2 = d * (a.sx * a.sy - a.shy * a.shx);
245        true
246    }
247
248    /// Translate the matrix.
249    pub fn translate(&mut self, x: f64, y: f64) {
250        self.tx += x;
251        self.ty += y;
252    }
253
254    /// Rotate the matrix.
255    pub fn rotate(&mut self, a: f64) {
256        self.multiply_affine(&TransAffine::new_rotation(a));
257    }
258
259    /// Scale uniformly.
260    pub fn scale_uniform(&mut self, s: f64) {
261        self.multiply_affine(&TransAffine::new_scaling_uniform(s));
262    }
263
264    /// Scale non-uniformly.
265    pub fn scale_xy(&mut self, x: f64, y: f64) {
266        self.multiply_affine(&TransAffine::new_scaling(x, y));
267    }
268
269    /// Multiply by another perspective matrix: self = a * self.
270    pub fn multiply(&mut self, a: &TransPerspective) {
271        let b = *self;
272        self.sx = a.sx * b.sx + a.shx * b.shy + a.tx * b.w0;
273        self.shx = a.sx * b.shx + a.shx * b.sy + a.tx * b.w1;
274        self.tx = a.sx * b.tx + a.shx * b.ty + a.tx * b.w2;
275        self.shy = a.shy * b.sx + a.sy * b.shy + a.ty * b.w0;
276        self.sy = a.shy * b.shx + a.sy * b.sy + a.ty * b.w1;
277        self.ty = a.shy * b.tx + a.sy * b.ty + a.ty * b.w2;
278        self.w0 = a.w0 * b.sx + a.w1 * b.shy + a.w2 * b.w0;
279        self.w1 = a.w0 * b.shx + a.w1 * b.sy + a.w2 * b.w1;
280        self.w2 = a.w0 * b.tx + a.w1 * b.ty + a.w2 * b.w2;
281    }
282
283    /// Premultiply: self = self * b.
284    pub fn premultiply(&mut self, b: &TransPerspective) {
285        let a = *self;
286        self.sx = a.sx * b.sx + a.shx * b.shy + a.tx * b.w0;
287        self.shx = a.sx * b.shx + a.shx * b.sy + a.tx * b.w1;
288        self.tx = a.sx * b.tx + a.shx * b.ty + a.tx * b.w2;
289        self.shy = a.shy * b.sx + a.sy * b.shy + a.ty * b.w0;
290        self.sy = a.shy * b.shx + a.sy * b.sy + a.ty * b.w1;
291        self.ty = a.shy * b.tx + a.sy * b.ty + a.ty * b.w2;
292        self.w0 = a.w0 * b.sx + a.w1 * b.shy + a.w2 * b.w0;
293        self.w1 = a.w0 * b.shx + a.w1 * b.sy + a.w2 * b.w1;
294        self.w2 = a.w0 * b.tx + a.w1 * b.ty + a.w2 * b.w2;
295    }
296
297    /// Multiply by an affine matrix: self = a * self.
298    pub fn multiply_affine(&mut self, a: &TransAffine) {
299        let b = *self;
300        self.sx = a.sx * b.sx + a.shx * b.shy + a.tx * b.w0;
301        self.shx = a.sx * b.shx + a.shx * b.sy + a.tx * b.w1;
302        self.tx = a.sx * b.tx + a.shx * b.ty + a.tx * b.w2;
303        self.shy = a.shy * b.sx + a.sy * b.shy + a.ty * b.w0;
304        self.sy = a.shy * b.shx + a.sy * b.sy + a.ty * b.w1;
305        self.ty = a.shy * b.tx + a.sy * b.ty + a.ty * b.w2;
306    }
307
308    /// Premultiply by an affine matrix: self = self * b.
309    pub fn premultiply_affine(&mut self, b: &TransAffine) {
310        let a = *self;
311        self.sx = a.sx * b.sx + a.shx * b.shy;
312        self.shx = a.sx * b.shx + a.shx * b.sy;
313        self.tx = a.sx * b.tx + a.shx * b.ty + a.tx;
314        self.shy = a.shy * b.sx + a.sy * b.shy;
315        self.sy = a.shy * b.shx + a.sy * b.sy;
316        self.ty = a.shy * b.tx + a.sy * b.ty + a.ty;
317        self.w0 = a.w0 * b.sx + a.w1 * b.shy;
318        self.w1 = a.w0 * b.shx + a.w1 * b.sy;
319        self.w2 = a.w0 * b.tx + a.w1 * b.ty + a.w2;
320    }
321
322    /// Multiply by inverse of another perspective matrix.
323    pub fn multiply_inv(&mut self, m: &TransPerspective) {
324        let mut t = *m;
325        t.invert();
326        self.multiply(&t);
327    }
328
329    /// Premultiply by inverse of another perspective matrix.
330    pub fn premultiply_inv(&mut self, m: &TransPerspective) {
331        let mut t = *m;
332        t.invert();
333        *self = t;
334        // Actually the C++ is: *this = t.multiply(*this);
335        // We need: result = t * this
336        // But t.multiply(this) means t = this * t in C++ AGG's convention
337        // Let me re-check: C++ multiply(a) does self = a * self
338        // So t.multiply(*this) means t = (*this) * t... no wait.
339        // C++ code: return *this = t.multiply(*this);
340        // t.multiply(*this) changes t to be: *this * t (since multiply(a) sets self = a * self)
341        // Actually no: multiply(a) in C++ is: b=*this; sx = a.sx*b.sx + ...
342        // So after multiply(a), self = a * old_self
343        // So t.multiply(*this) means: t_new = (*this) * t_old
344        // And then *this = t_new
345        // So the result is: *this = old_this * t_inv
346        // Which is premultiply(t_inv)
347    }
348
349    /// Multiply by inverse of an affine matrix.
350    pub fn multiply_inv_affine(&mut self, m: &TransAffine) {
351        let mut t = *m;
352        t.invert();
353        self.multiply_affine(&t);
354    }
355
356    /// Premultiply by inverse of an affine matrix.
357    pub fn premultiply_inv_affine(&mut self, m: &TransAffine) {
358        let mut t = TransPerspective::new_from_affine(m);
359        t.invert();
360        let old_self = *self;
361        *self = t;
362        self.multiply(&old_self);
363    }
364
365    // -----------------------------------------------------------------------
366    // Transformations
367    // -----------------------------------------------------------------------
368
369    /// Direct transformation of x and y with perspective divide.
370    pub fn transform(&self, x: &mut f64, y: &mut f64) {
371        let tx = *x;
372        let ty = *y;
373        let m = 1.0 / (tx * self.w0 + ty * self.w1 + self.w2);
374        *x = m * (tx * self.sx + ty * self.shx + self.tx);
375        *y = m * (tx * self.shy + ty * self.sy + self.ty);
376    }
377
378    /// Direct transformation, affine part only (no perspective divide).
379    pub fn transform_affine(&self, x: &mut f64, y: &mut f64) {
380        let tmp = *x;
381        *x = tmp * self.sx + *y * self.shx + self.tx;
382        *y = tmp * self.shy + *y * self.sy + self.ty;
383    }
384
385    /// Direct transformation, 2×2 matrix only (no translation).
386    pub fn transform_2x2(&self, x: &mut f64, y: &mut f64) {
387        let tmp = *x;
388        *x = tmp * self.sx + *y * self.shx;
389        *y = tmp * self.shy + *y * self.sy;
390    }
391
392    /// Inverse transformation (slow — inverts on every call).
393    pub fn inverse_transform(&self, x: &mut f64, y: &mut f64) {
394        let mut t = *self;
395        if t.invert() {
396            t.transform(x, y);
397        }
398    }
399
400    // -----------------------------------------------------------------------
401    // Load/Store
402    // -----------------------------------------------------------------------
403
404    /// Store matrix to array of 9 doubles.
405    pub fn store_to(&self, m: &mut [f64; 9]) {
406        m[0] = self.sx;
407        m[1] = self.shy;
408        m[2] = self.w0;
409        m[3] = self.shx;
410        m[4] = self.sy;
411        m[5] = self.w1;
412        m[6] = self.tx;
413        m[7] = self.ty;
414        m[8] = self.w2;
415    }
416
417    /// Load matrix from array of 9 doubles.
418    pub fn load_from(&mut self, m: &[f64; 9]) {
419        self.sx = m[0];
420        self.shy = m[1];
421        self.w0 = m[2];
422        self.shx = m[3];
423        self.sy = m[4];
424        self.w1 = m[5];
425        self.tx = m[6];
426        self.ty = m[7];
427        self.w2 = m[8];
428    }
429
430    /// Load from an affine transformation.
431    pub fn from_affine(&mut self, a: &TransAffine) {
432        self.sx = a.sx;
433        self.shy = a.shy;
434        self.w0 = 0.0;
435        self.shx = a.shx;
436        self.sy = a.sy;
437        self.w1 = 0.0;
438        self.tx = a.tx;
439        self.ty = a.ty;
440        self.w2 = 1.0;
441    }
442
443    // -----------------------------------------------------------------------
444    // Auxiliary queries
445    // -----------------------------------------------------------------------
446
447    /// Determinant of the 3×3 matrix.
448    pub fn determinant(&self) -> f64 {
449        self.sx * (self.sy * self.w2 - self.ty * self.w1)
450            + self.shx * (self.ty * self.w0 - self.shy * self.w2)
451            + self.tx * (self.shy * self.w1 - self.sy * self.w0)
452    }
453
454    /// Reciprocal of determinant.
455    pub fn determinant_reciprocal(&self) -> f64 {
456        1.0 / self.determinant()
457    }
458
459    /// Check if matrix is valid (non-degenerate).
460    pub fn is_valid(&self) -> bool {
461        self.is_valid_eps(AFFINE_EPSILON)
462    }
463
464    /// Check if matrix is valid with custom epsilon.
465    pub fn is_valid_eps(&self, epsilon: f64) -> bool {
466        self.sx.abs() > epsilon && self.sy.abs() > epsilon && self.w2.abs() > epsilon
467    }
468
469    /// Check if matrix is identity.
470    pub fn is_identity(&self) -> bool {
471        self.is_identity_eps(AFFINE_EPSILON)
472    }
473
474    /// Check if matrix is identity with custom epsilon.
475    pub fn is_identity_eps(&self, epsilon: f64) -> bool {
476        is_equal_eps(self.sx, 1.0, epsilon)
477            && is_equal_eps(self.shy, 0.0, epsilon)
478            && is_equal_eps(self.w0, 0.0, epsilon)
479            && is_equal_eps(self.shx, 0.0, epsilon)
480            && is_equal_eps(self.sy, 1.0, epsilon)
481            && is_equal_eps(self.w1, 0.0, epsilon)
482            && is_equal_eps(self.tx, 0.0, epsilon)
483            && is_equal_eps(self.ty, 0.0, epsilon)
484            && is_equal_eps(self.w2, 1.0, epsilon)
485    }
486
487    /// Check equality with another matrix.
488    pub fn is_equal(&self, m: &TransPerspective) -> bool {
489        self.is_equal_eps(m, AFFINE_EPSILON)
490    }
491
492    /// Check equality with custom epsilon.
493    pub fn is_equal_eps(&self, m: &TransPerspective, epsilon: f64) -> bool {
494        is_equal_eps(self.sx, m.sx, epsilon)
495            && is_equal_eps(self.shy, m.shy, epsilon)
496            && is_equal_eps(self.w0, m.w0, epsilon)
497            && is_equal_eps(self.shx, m.shx, epsilon)
498            && is_equal_eps(self.sy, m.sy, epsilon)
499            && is_equal_eps(self.w1, m.w1, epsilon)
500            && is_equal_eps(self.tx, m.tx, epsilon)
501            && is_equal_eps(self.ty, m.ty, epsilon)
502            && is_equal_eps(self.w2, m.w2, epsilon)
503    }
504
505    /// Determine overall scale factor.
506    pub fn scale(&self) -> f64 {
507        let x =
508            std::f64::consts::FRAC_1_SQRT_2 * self.sx + std::f64::consts::FRAC_1_SQRT_2 * self.shx;
509        let y =
510            std::f64::consts::FRAC_1_SQRT_2 * self.shy + std::f64::consts::FRAC_1_SQRT_2 * self.sy;
511        (x * x + y * y).sqrt()
512    }
513
514    /// Determine rotation angle.
515    pub fn rotation(&self) -> f64 {
516        let mut x1 = 0.0;
517        let mut y1 = 0.0;
518        let mut x2 = 1.0;
519        let mut y2 = 0.0;
520        self.transform(&mut x1, &mut y1);
521        self.transform(&mut x2, &mut y2);
522        (y2 - y1).atan2(x2 - x1)
523    }
524
525    /// Get translation components.
526    pub fn translation(&self) -> (f64, f64) {
527        (self.tx, self.ty)
528    }
529
530    /// Determine scaling components.
531    pub fn scaling(&self) -> (f64, f64) {
532        let mut x1 = 0.0;
533        let mut y1 = 0.0;
534        let mut x2 = 1.0;
535        let mut y2 = 1.0;
536        let mut t = *self;
537        t.multiply_affine(&TransAffine::new_rotation(-self.rotation()));
538        t.transform(&mut x1, &mut y1);
539        t.transform(&mut x2, &mut y2);
540        (x2 - x1, y2 - y1)
541    }
542
543    /// Absolute scaling components.
544    pub fn scaling_abs(&self) -> (f64, f64) {
545        (
546            (self.sx * self.sx + self.shx * self.shx).sqrt(),
547            (self.shy * self.shy + self.sy * self.sy).sqrt(),
548        )
549    }
550
551    // -----------------------------------------------------------------------
552    // Iterator
553    // -----------------------------------------------------------------------
554
555    /// Create an incremental scanline iterator.
556    pub fn begin(&self, x: f64, y: f64, step: f64) -> PerspectiveIteratorX {
557        PerspectiveIteratorX::new(x, y, step, self)
558    }
559}
560
561impl Default for TransPerspective {
562    fn default() -> Self {
563        Self::new()
564    }
565}
566
567// ============================================================================
568// PerspectiveIteratorX
569// ============================================================================
570
571/// Incremental iterator for scanline walking with perspective transform.
572///
573/// Maintains running numerator/denominator for perspective divide
574/// without full division at each pixel (only one divide per step).
575///
576/// Port of C++ `trans_perspective::iterator_x`.
577pub struct PerspectiveIteratorX {
578    den: f64,
579    den_step: f64,
580    nom_x: f64,
581    nom_x_step: f64,
582    nom_y: f64,
583    nom_y_step: f64,
584    pub x: f64,
585    pub y: f64,
586}
587
588impl PerspectiveIteratorX {
589    /// Create a default (identity-like) iterator at the origin.
590    pub fn default_new() -> Self {
591        Self {
592            den: 1.0,
593            den_step: 0.0,
594            nom_x: 0.0,
595            nom_x_step: 0.0,
596            nom_y: 0.0,
597            nom_y_step: 0.0,
598            x: 0.0,
599            y: 0.0,
600        }
601    }
602
603    fn new(px: f64, py: f64, step: f64, m: &TransPerspective) -> Self {
604        let den = px * m.w0 + py * m.w1 + m.w2;
605        let nom_x = px * m.sx + py * m.shx + m.tx;
606        let nom_y = px * m.shy + py * m.sy + m.ty;
607        Self {
608            den,
609            den_step: m.w0 * step,
610            nom_x,
611            nom_x_step: step * m.sx,
612            nom_y,
613            nom_y_step: step * m.shy,
614            x: nom_x / den,
615            y: nom_y / den,
616        }
617    }
618
619    /// Advance to the next step.
620    pub fn next(&mut self) {
621        self.den += self.den_step;
622        self.nom_x += self.nom_x_step;
623        self.nom_y += self.nom_y_step;
624        let d = 1.0 / self.den;
625        self.x = self.nom_x * d;
626        self.y = self.nom_y * d;
627    }
628}
629
630// ============================================================================
631// Tests
632// ============================================================================
633
634#[cfg(test)]
635mod tests {
636    use super::*;
637
638    #[test]
639    fn test_identity() {
640        let t = TransPerspective::new();
641        assert!(t.is_identity());
642        assert!(t.is_valid());
643
644        let mut x = 5.0;
645        let mut y = 10.0;
646        t.transform(&mut x, &mut y);
647        assert!((x - 5.0).abs() < 1e-10);
648        assert!((y - 10.0).abs() < 1e-10);
649    }
650
651    #[test]
652    fn test_translate() {
653        let mut t = TransPerspective::new();
654        t.translate(10.0, 20.0);
655        let mut x = 0.0;
656        let mut y = 0.0;
657        t.transform(&mut x, &mut y);
658        assert!((x - 10.0).abs() < 1e-10);
659        assert!((y - 20.0).abs() < 1e-10);
660    }
661
662    #[test]
663    fn test_from_affine() {
664        let a = TransAffine::new_scaling(2.0, 3.0);
665        let t = TransPerspective::new_from_affine(&a);
666        let mut x = 5.0;
667        let mut y = 10.0;
668        t.transform(&mut x, &mut y);
669        assert!((x - 10.0).abs() < 1e-10);
670        assert!((y - 30.0).abs() < 1e-10);
671    }
672
673    #[test]
674    fn test_invert() {
675        let mut t = TransPerspective::new();
676        t.translate(10.0, 20.0);
677        assert!(t.invert());
678
679        let mut x = 10.0;
680        let mut y = 20.0;
681        t.transform(&mut x, &mut y);
682        assert!((x - 0.0).abs() < 1e-10);
683        assert!((y - 0.0).abs() < 1e-10);
684    }
685
686    #[test]
687    fn test_square_to_quad_parallelogram() {
688        let mut t = TransPerspective::new();
689        // Parallelogram case: no perspective
690        let q = [0.0, 0.0, 10.0, 0.0, 10.0, 10.0, 0.0, 10.0];
691        assert!(t.square_to_quad(&q));
692        assert_eq!(t.w0, 0.0);
693        assert_eq!(t.w1, 0.0);
694
695        let mut x = 0.5;
696        let mut y = 0.5;
697        t.transform(&mut x, &mut y);
698        assert!((x - 5.0).abs() < 1e-10);
699        assert!((y - 5.0).abs() < 1e-10);
700    }
701
702    #[test]
703    fn test_quad_to_quad() {
704        let mut t = TransPerspective::new();
705        let src = [0.0, 0.0, 10.0, 0.0, 10.0, 10.0, 0.0, 10.0];
706        let dst = [0.0, 0.0, 20.0, 0.0, 20.0, 20.0, 0.0, 20.0];
707        assert!(t.quad_to_quad(&src, &dst));
708
709        let mut x = 5.0;
710        let mut y = 5.0;
711        t.transform(&mut x, &mut y);
712        assert!((x - 10.0).abs() < 1e-8);
713        assert!((y - 10.0).abs() < 1e-8);
714    }
715
716    #[test]
717    fn test_rect_to_quad() {
718        let mut t = TransPerspective::new();
719        let q = [0.0, 0.0, 20.0, 0.0, 20.0, 20.0, 0.0, 20.0];
720        assert!(t.rect_to_quad(0.0, 0.0, 10.0, 10.0, &q));
721
722        let mut x = 5.0;
723        let mut y = 5.0;
724        t.transform(&mut x, &mut y);
725        assert!((x - 10.0).abs() < 1e-8);
726        assert!((y - 10.0).abs() < 1e-8);
727    }
728
729    #[test]
730    fn test_transform_inverse_round_trip() {
731        let mut t = TransPerspective::new();
732        t.translate(5.0, 10.0);
733        t.rotate(0.3);
734        t.scale_uniform(2.0);
735
736        let mut x = 3.0;
737        let mut y = 7.0;
738        t.transform(&mut x, &mut y);
739        t.inverse_transform(&mut x, &mut y);
740        assert!((x - 3.0).abs() < 1e-8);
741        assert!((y - 7.0).abs() < 1e-8);
742    }
743
744    #[test]
745    fn test_store_load() {
746        let t = TransPerspective::new_from_values(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
747        let mut arr = [0.0; 9];
748        t.store_to(&mut arr);
749        assert_eq!(arr, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
750
751        let mut t2 = TransPerspective::new();
752        t2.load_from(&arr);
753        assert!(t.is_equal(&t2));
754    }
755
756    #[test]
757    fn test_determinant() {
758        let t = TransPerspective::new();
759        assert!((t.determinant() - 1.0).abs() < 1e-10);
760    }
761
762    #[test]
763    fn test_scale_query() {
764        let mut t = TransPerspective::new();
765        t.scale_uniform(3.0);
766        let s = t.scale();
767        assert!((s - 3.0).abs() < 1e-8);
768    }
769
770    #[test]
771    fn test_rotation_query() {
772        let mut t = TransPerspective::new();
773        t.rotate(0.5);
774        assert!((t.rotation() - 0.5).abs() < 1e-8);
775    }
776
777    #[test]
778    fn test_iterator() {
779        let mut t = TransPerspective::new();
780        t.scale_xy(2.0, 3.0);
781
782        let mut it = t.begin(0.0, 0.0, 1.0);
783        assert!((it.x - 0.0).abs() < 1e-10);
784        assert!((it.y - 0.0).abs() < 1e-10);
785
786        it.next();
787        assert!((it.x - 2.0).abs() < 1e-10);
788        assert!((it.y - 0.0).abs() < 1e-10);
789    }
790
791    #[test]
792    fn test_perspective_quad() {
793        // Non-parallelogram quad → should have w0, w1 != 0
794        let mut t = TransPerspective::new();
795        let q = [0.0, 0.0, 10.0, 1.0, 9.0, 10.0, 1.0, 9.0];
796        assert!(t.square_to_quad(&q));
797        // w0 or w1 should be non-zero for perspective case
798        assert!(t.w0 != 0.0 || t.w1 != 0.0);
799    }
800
801    #[test]
802    fn test_scaling_abs() {
803        let t = TransPerspective::new_from_affine(&TransAffine::new_scaling(3.0, 5.0));
804        let (sx, sy) = t.scaling_abs();
805        assert!((sx - 3.0).abs() < 1e-10);
806        assert!((sy - 5.0).abs() < 1e-10);
807    }
808}