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