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