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    /// Row-0, column-0 entry — the `i`-contribution to the new `i`.
14    pub a: i32,
15    /// Row-0, column-1 entry — the `j`-contribution to the new `i`.
16    pub b: i32,
17    /// Row-1, column-0 entry — the `i`-contribution to the new `j`.
18    pub c: i32,
19    /// Row-1, column-1 entry — the `j`-contribution to the new `j`.
20    pub d: i32,
21}
22
23impl GridTransform {
24    /// The identity transform — leaves `(i, j)` unchanged.
25    pub const IDENTITY: GridTransform = GridTransform {
26        a: 1,
27        b: 0,
28        c: 0,
29        d: 1,
30    };
31
32    /// Apply the transform to `(i, j)`, returning the result as [`GridCoords`].
33    #[inline]
34    pub fn apply(&self, i: i32, j: i32) -> GridCoords {
35        GridCoords {
36            i: self.a * i + self.b * j,
37            j: self.c * i + self.d * j,
38        }
39    }
40
41    /// Invert the transform if it is unimodular (det = ±1).
42    pub fn inverse(&self) -> Option<GridTransform> {
43        let det = self.a * self.d - self.b * self.c;
44        if det != 1 && det != -1 {
45            return None;
46        }
47        Some(GridTransform {
48            a: self.d / det,
49            b: -self.b / det,
50            c: -self.c / det,
51            d: self.a / det,
52        })
53    }
54}
55
56/// A grid alignment: `dst = transform(src) + translation`.
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
58pub struct GridAlignment {
59    /// Linear part — applied to the source coordinates before translation.
60    pub transform: GridTransform,
61    /// Integer `[Δi, Δj]` offset added after `transform`.
62    pub translation: [i32; 2],
63}
64
65impl GridAlignment {
66    /// The identity alignment — identity transform with zero translation.
67    pub const IDENTITY: GridAlignment = GridAlignment {
68        transform: GridTransform::IDENTITY,
69        translation: [0, 0],
70    };
71
72    /// Map grid coordinates `(i, j)` using this alignment, returning [`GridCoords`].
73    #[inline]
74    pub fn map(&self, i: i32, j: i32) -> GridCoords {
75        let g = self.transform.apply(i, j);
76        GridCoords {
77            i: g.i + self.translation[0],
78            j: g.j + self.translation[1],
79        }
80    }
81
82    /// Invert the alignment if its linear part is unimodular (det = ±1).
83    pub fn inverse(&self) -> Option<GridAlignment> {
84        let inv = self.transform.inverse()?;
85        let [tx, ty] = self.translation;
86        let g = inv.apply(-tx, -ty);
87        Some(GridAlignment {
88            transform: inv,
89            translation: [g.i, g.j],
90        })
91    }
92}
93
94/// The 8 dihedral transforms `D4` on the integer grid.
95pub const GRID_TRANSFORMS_D4: [GridTransform; 8] = [
96    // rotations: 0°, 90°, 180°, 270°
97    GridTransform {
98        a: 1,
99        b: 0,
100        c: 0,
101        d: 1,
102    },
103    GridTransform {
104        a: 0,
105        b: 1,
106        c: -1,
107        d: 0,
108    },
109    GridTransform {
110        a: -1,
111        b: 0,
112        c: 0,
113        d: -1,
114    },
115    GridTransform {
116        a: 0,
117        b: -1,
118        c: 1,
119        d: 0,
120    },
121    // reflections (and combinations)
122    GridTransform {
123        a: -1,
124        b: 0,
125        c: 0,
126        d: 1,
127    },
128    GridTransform {
129        a: 1,
130        b: 0,
131        c: 0,
132        d: -1,
133    },
134    GridTransform {
135        a: 0,
136        b: 1,
137        c: 1,
138        d: 0,
139    },
140    GridTransform {
141        a: 0,
142        b: -1,
143        c: -1,
144        d: 0,
145    },
146];