Skip to main content

ezu_features/ops/
transform.rs

1//! Affine transform (translate + rotate + scale) on the crate's
2//! integer-coord geometry. Rotation is in radians, applied around an
3//! optional pivot before translation.
4
5use crate::Polygon;
6
7/// Apply `(scale, rotation_rad, translate)` around `pivot` to a single
8/// point. Order: translate to origin → scale → rotate → translate back +
9/// translate.
10fn apply(
11    p: (i32, i32),
12    scale: (f64, f64),
13    rot: f64,
14    pivot: (f64, f64),
15    tx: (f64, f64),
16) -> (i32, i32) {
17    let x = p.0 as f64 - pivot.0;
18    let y = p.1 as f64 - pivot.1;
19    let sx = x * scale.0;
20    let sy = y * scale.1;
21    let (sin_t, cos_t) = rot.sin_cos();
22    let rx = sx * cos_t - sy * sin_t;
23    let ry = sx * sin_t + sy * cos_t;
24    let fx = rx + pivot.0 + tx.0;
25    let fy = ry + pivot.1 + tx.1;
26    (fx.round() as i32, fy.round() as i32)
27}
28
29/// Apply `(scale, rotation, translate)` to every vertex of the inputs.
30/// `pivot` is the centre of rotation / scale in feature-space coords.
31pub fn transform(
32    points: &mut [(i32, i32)],
33    lines: &mut [Vec<(i32, i32)>],
34    polygons: &mut [Polygon],
35    scale: (f64, f64),
36    rotation_rad: f64,
37    pivot: (f64, f64),
38    translate: (f64, f64),
39) {
40    for p in points.iter_mut() {
41        *p = apply(*p, scale, rotation_rad, pivot, translate);
42    }
43    for line in lines.iter_mut() {
44        for v in line.iter_mut() {
45            *v = apply(*v, scale, rotation_rad, pivot, translate);
46        }
47    }
48    for poly in polygons.iter_mut() {
49        for v in poly.exterior.iter_mut() {
50            *v = apply(*v, scale, rotation_rad, pivot, translate);
51        }
52        for hole in poly.holes.iter_mut() {
53            for v in hole.iter_mut() {
54                *v = apply(*v, scale, rotation_rad, pivot, translate);
55            }
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn translate_only_shifts_all_vertices() {
66        let mut points = vec![(0, 0)];
67        let mut lines: Vec<Vec<(i32, i32)>> = vec![];
68        let mut polys = vec![Polygon {
69            exterior: vec![(0, 0), (10, 0), (10, 10), (0, 10)],
70            holes: vec![],
71        }];
72        transform(
73            &mut points,
74            &mut lines,
75            &mut polys,
76            (1.0, 1.0),
77            0.0,
78            (0.0, 0.0),
79            (5.0, -3.0),
80        );
81        assert_eq!(points[0], (5, -3));
82        assert_eq!(polys[0].exterior[0], (5, -3));
83        assert_eq!(polys[0].exterior[2], (15, 7));
84    }
85
86    #[test]
87    fn rotate_90deg_around_origin_swaps_axes() {
88        let mut points = vec![(10, 0)];
89        let mut lines: Vec<Vec<(i32, i32)>> = vec![];
90        let mut polys: Vec<Polygon> = vec![];
91        transform(
92            &mut points,
93            &mut lines,
94            &mut polys,
95            (1.0, 1.0),
96            std::f64::consts::FRAC_PI_2,
97            (0.0, 0.0),
98            (0.0, 0.0),
99        );
100        // (10, 0) rotated 90° CCW → (0, 10).
101        assert_eq!(points[0], (0, 10));
102    }
103}