1use crate::grid_index::GridIndex;
11use crate::homography::{estimate_homography, Homography};
12use nalgebra::Point2;
13use std::collections::HashMap;
14
15#[non_exhaustive]
16#[derive(thiserror::Error, Debug)]
17pub enum GridMeshError {
18 #[error("not enough grid corners (need at least 2×2)")]
19 NotEnoughCorners,
20 #[error("no valid grid cells found (each cell needs all 4 corners)")]
21 NoValidCells,
22 #[error("homography estimation failed for cell ({ci}, {cj})")]
23 HomographyFailed { ci: usize, cj: usize },
24}
25
26#[derive(Clone, Copy, Debug)]
27struct CellHomography {
28 h_img_from_cellrect: Homography,
29 valid: bool,
30}
31
32#[derive(Clone, Debug)]
37pub struct GridHomographyMesh {
38 pub min_i: i32,
40 pub min_j: i32,
42 pub cells_x: usize,
44 pub cells_y: usize,
46 pub px_per_cell: f32,
48 pub valid_cells: usize,
50 pub rect_width: usize,
52 pub rect_height: usize,
54
55 cells: Vec<CellHomography>,
56}
57
58impl GridHomographyMesh {
59 pub fn from_corners(
64 corners: &HashMap<GridIndex, Point2<f32>>,
65 px_per_cell: f32,
66 ) -> Result<Self, GridMeshError> {
67 if corners.len() < 4 {
68 return Err(GridMeshError::NotEnoughCorners);
69 }
70
71 let (mut min_i, mut min_j) = (i32::MAX, i32::MAX);
72 let (mut max_i, mut max_j) = (i32::MIN, i32::MIN);
73 for g in corners.keys() {
74 min_i = min_i.min(g.i);
75 min_j = min_j.min(g.j);
76 max_i = max_i.max(g.i);
77 max_j = max_j.max(g.j);
78 }
79
80 if max_i - min_i < 1 || max_j - min_j < 1 {
81 return Err(GridMeshError::NoValidCells);
82 }
83
84 let cells_x = (max_i - min_i) as usize;
85 let cells_y = (max_j - min_j) as usize;
86
87 let rect_width = ((cells_x as f32) * px_per_cell).floor().max(1.0) as usize;
88 let rect_height = ((cells_y as f32) * px_per_cell).floor().max(1.0) as usize;
89
90 let s = px_per_cell;
91 let cell_rect = [
92 Point2::new(0.0, 0.0),
93 Point2::new(s, 0.0),
94 Point2::new(0.0, s),
95 Point2::new(s, s),
96 ];
97
98 let mut cells = vec![
99 CellHomography {
100 h_img_from_cellrect: Homography::zero(),
101 valid: false,
102 };
103 cells_x * cells_y
104 ];
105
106 let mut valid_cells = 0usize;
107
108 for cj in 0..cells_y {
109 for ci in 0..cells_x {
110 let i0 = min_i + ci as i32;
111 let j0 = min_j + cj as i32;
112
113 let g00 = GridIndex { i: i0, j: j0 };
114 let g10 = GridIndex { i: i0 + 1, j: j0 };
115 let g01 = GridIndex { i: i0, j: j0 + 1 };
116 let g11 = GridIndex {
117 i: i0 + 1,
118 j: j0 + 1,
119 };
120
121 let Some(p00) = corners.get(&g00).copied() else {
122 continue;
123 };
124 let Some(p10) = corners.get(&g10).copied() else {
125 continue;
126 };
127 let Some(p01) = corners.get(&g01).copied() else {
128 continue;
129 };
130 let Some(p11) = corners.get(&g11).copied() else {
131 continue;
132 };
133
134 let img_quad = [p00, p10, p01, p11];
135
136 let h = estimate_homography(&cell_rect, &img_quad)
137 .ok_or(GridMeshError::HomographyFailed { ci, cj })?;
138
139 let idx = cj * cells_x + ci;
140 cells[idx] = CellHomography {
141 h_img_from_cellrect: h,
142 valid: true,
143 };
144 valid_cells += 1;
145 }
146 }
147
148 if valid_cells == 0 {
149 return Err(GridMeshError::NoValidCells);
150 }
151
152 Ok(Self {
153 min_i,
154 min_j,
155 cells_x,
156 cells_y,
157 px_per_cell,
158 valid_cells,
159 rect_width,
160 rect_height,
161 cells,
162 })
163 }
164
165 pub fn rect_to_img(&self, p_rect: Point2<f32>) -> Option<Point2<f32>> {
169 let s = self.px_per_cell;
170 if s <= 0.0 {
171 return None;
172 }
173
174 let ci = (p_rect.x / s).floor() as i32;
175 let cj = (p_rect.y / s).floor() as i32;
176 if ci < 0 || cj < 0 || ci >= self.cells_x as i32 || cj >= self.cells_y as i32 {
177 return None;
178 }
179
180 let x_local = p_rect.x - (ci as f32) * s;
181 let y_local = p_rect.y - (cj as f32) * s;
182 self.cell_rect_to_img(ci as usize, cj as usize, Point2::new(x_local, y_local))
183 }
184
185 pub fn cell_rect_to_img(
190 &self,
191 ci: usize,
192 cj: usize,
193 p_cell: Point2<f32>,
194 ) -> Option<Point2<f32>> {
195 let idx = cj.checked_mul(self.cells_x)?.checked_add(ci)?;
196 let cell = *self.cells.get(idx)?;
197 if !cell.valid {
198 return None;
199 }
200 Some(cell.h_img_from_cellrect.apply(p_cell))
201 }
202
203 pub fn cell_corners_img(&self, ci: usize, cj: usize) -> Option<[Point2<f32>; 4]> {
205 let s = self.px_per_cell;
206 let pts = [
207 Point2::new(0.0, 0.0),
208 Point2::new(s, 0.0),
209 Point2::new(s, s),
210 Point2::new(0.0, s),
211 ];
212 Some([
213 self.cell_rect_to_img(ci, cj, pts[0])?,
214 self.cell_rect_to_img(ci, cj, pts[1])?,
215 self.cell_rect_to_img(ci, cj, pts[2])?,
216 self.cell_rect_to_img(ci, cj, pts[3])?,
217 ])
218 }
219}