Skip to main content

projective_grid/square/
alignment.rs

1use crate::square::index::GridCoords;
2use serde::{Deserialize, Serialize};
3
4/// Integer 2D grid transform (a 2×2 matrix) for aligning detected grids to a board model.
5///
6/// Represents a linear transform on integer coordinates:
7/// `(i', j') = (a*i + b*j, c*i + d*j)`.
8///
9/// For most calibration targets, valid transforms are the 8 elements of the dihedral group `D4`
10/// (rotations/reflections on the square grid). Those are provided in [`GRID_TRANSFORMS_D4`].
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
12pub struct GridTransform {
13    pub a: i32,
14    pub b: i32,
15    pub c: i32,
16    pub d: i32,
17}
18
19impl GridTransform {
20    pub const IDENTITY: GridTransform = GridTransform {
21        a: 1,
22        b: 0,
23        c: 0,
24        d: 1,
25    };
26
27    /// Apply the transform to `(i, j)`, returning the result as [`GridCoords`].
28    #[inline]
29    pub fn apply(&self, i: i32, j: i32) -> GridCoords {
30        GridCoords {
31            i: self.a * i + self.b * j,
32            j: self.c * i + self.d * j,
33        }
34    }
35
36    /// Invert the transform if it is unimodular (det = ±1).
37    pub fn inverse(&self) -> Option<GridTransform> {
38        let det = self.a * self.d - self.b * self.c;
39        if det != 1 && det != -1 {
40            return None;
41        }
42        Some(GridTransform {
43            a: self.d / det,
44            b: -self.b / det,
45            c: -self.c / det,
46            d: self.a / det,
47        })
48    }
49}
50
51/// A grid alignment: `dst = transform(src) + translation`.
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
53pub struct GridAlignment {
54    pub transform: GridTransform,
55    pub translation: [i32; 2],
56}
57
58impl GridAlignment {
59    pub const IDENTITY: GridAlignment = GridAlignment {
60        transform: GridTransform::IDENTITY,
61        translation: [0, 0],
62    };
63
64    /// Map grid coordinates `(i, j)` using this alignment, returning [`GridCoords`].
65    #[inline]
66    pub fn map(&self, i: i32, j: i32) -> GridCoords {
67        let g = self.transform.apply(i, j);
68        GridCoords {
69            i: g.i + self.translation[0],
70            j: g.j + self.translation[1],
71        }
72    }
73
74    pub fn inverse(&self) -> Option<GridAlignment> {
75        let inv = self.transform.inverse()?;
76        let [tx, ty] = self.translation;
77        let g = inv.apply(-tx, -ty);
78        Some(GridAlignment {
79            transform: inv,
80            translation: [g.i, g.j],
81        })
82    }
83}
84
85/// The 8 dihedral transforms `D4` on the integer grid.
86pub const GRID_TRANSFORMS_D4: [GridTransform; 8] = [
87    // rotations: 0°, 90°, 180°, 270°
88    GridTransform {
89        a: 1,
90        b: 0,
91        c: 0,
92        d: 1,
93    },
94    GridTransform {
95        a: 0,
96        b: 1,
97        c: -1,
98        d: 0,
99    },
100    GridTransform {
101        a: -1,
102        b: 0,
103        c: 0,
104        d: -1,
105    },
106    GridTransform {
107        a: 0,
108        b: -1,
109        c: 1,
110        d: 0,
111    },
112    // reflections (and combinations)
113    GridTransform {
114        a: -1,
115        b: 0,
116        c: 0,
117        d: 1,
118    },
119    GridTransform {
120        a: 1,
121        b: 0,
122        c: 0,
123        d: -1,
124    },
125    GridTransform {
126        a: 0,
127        b: 1,
128        c: 1,
129        d: 0,
130    },
131    GridTransform {
132        a: 0,
133        b: -1,
134        c: -1,
135        d: 0,
136    },
137];