use crate::float_helpers::lit;
use crate::local_step::LocalStep;
use crate::Float;
use crate::GridIndex;
use nalgebra::Point2;
use std::collections::HashMap;
pub fn predict_grid_position<F: Float>(
grid: &HashMap<GridIndex, Point2<F>>,
idx: GridIndex,
) -> Option<Point2<F>> {
let half: F = lit(0.5);
let mut pred_sum = Point2::new(F::zero(), F::zero());
let mut pred_count = 0u32;
let left = GridIndex {
i: idx.i - 1,
j: idx.j,
};
let right = GridIndex {
i: idx.i + 1,
j: idx.j,
};
if let (Some(&pl), Some(&pr)) = (grid.get(&left), grid.get(&right)) {
let mid = Point2::new(half * (pl.x + pr.x), half * (pl.y + pr.y));
pred_sum.x += mid.x;
pred_sum.y += mid.y;
pred_count += 1;
}
let up = GridIndex {
i: idx.i,
j: idx.j - 1,
};
let down = GridIndex {
i: idx.i,
j: idx.j + 1,
};
if let (Some(&pu), Some(&pd)) = (grid.get(&up), grid.get(&down)) {
let mid = Point2::new(half * (pu.x + pd.x), half * (pu.y + pd.y));
pred_sum.x += mid.x;
pred_sum.y += mid.y;
pred_count += 1;
}
if pred_count == 0 {
return None;
}
let n: F = lit(pred_count as f64);
Some(Point2::new(pred_sum.x / n, pred_sum.y / n))
}
pub fn find_inconsistent_corners<F: Float>(
grid: &HashMap<GridIndex, Point2<F>>,
threshold: F,
) -> Vec<(GridIndex, Point2<F>)> {
let threshold_sq = threshold * threshold;
let mut flagged = Vec::new();
for (&idx, &pos) in grid {
if let Some(predicted) = predict_grid_position(grid, idx) {
let dx = pos.x - predicted.x;
let dy = pos.y - predicted.y;
if dx * dx + dy * dy > threshold_sq {
flagged.push((idx, predicted));
}
}
}
flagged
}
pub fn find_inconsistent_corners_step_aware<F: Float>(
grid: &HashMap<GridIndex, Point2<F>>,
local_steps: &HashMap<GridIndex, LocalStep<F>>,
threshold_rel: F,
threshold_px_floor: F,
) -> Vec<(GridIndex, Point2<F>)> {
let half: F = lit(0.5);
let mut flagged = Vec::new();
let floor_sq = threshold_px_floor * threshold_px_floor;
for (&idx, &pos) in grid {
let Some(predicted) = predict_grid_position(grid, idx) else {
continue;
};
let dx = pos.x - predicted.x;
let dy = pos.y - predicted.y;
let err_sq = dx * dx + dy * dy;
let step_threshold_sq = match local_steps.get(&idx) {
Some(ls) if ls.confidence > F::zero() => {
let step_mean = (ls.step_u + ls.step_v) * half;
if step_mean <= F::zero() {
floor_sq
} else {
let t = threshold_rel * step_mean;
t * t
}
}
_ => floor_sq,
};
if err_sq > step_threshold_sq {
flagged.push((idx, predicted));
}
}
flagged
}
#[cfg(test)]
mod tests {
use super::*;
fn make_grid(rows: i32, cols: i32, spacing: f32) -> HashMap<GridIndex, Point2<f32>> {
let mut map = HashMap::new();
for j in 0..rows {
for i in 0..cols {
map.insert(
GridIndex { i, j },
Point2::new(i as f32 * spacing, j as f32 * spacing),
);
}
}
map
}
#[test]
fn clean_grid_has_no_inconsistencies() {
let grid = make_grid(5, 5, 60.0);
let flagged = find_inconsistent_corners(&grid, 3.0);
assert!(flagged.is_empty());
}
#[test]
fn displaced_corner_is_flagged() {
let mut grid = make_grid(3, 3, 60.0);
let center = GridIndex { i: 1, j: 1 };
grid.insert(center, Point2::new(69.0, 69.0));
let flagged = find_inconsistent_corners(&grid, 3.0);
assert_eq!(1, flagged.len());
assert_eq!(center, flagged[0].0);
let pred = flagged[0].1;
assert!((pred.x - 60.0).abs() < 0.01);
assert!((pred.y - 60.0).abs() < 0.01);
}
#[test]
fn perspective_distorted_grid_passes() {
let spacing = 60.0;
let mut grid = HashMap::new();
for j in 0..5 {
let scale = 1.0 + 0.02 * j as f32;
for i in 0..5 {
grid.insert(
GridIndex { i, j },
Point2::new(i as f32 * spacing * scale, j as f32 * spacing * scale),
);
}
}
let flagged = find_inconsistent_corners(&grid, 3.0);
assert!(flagged.is_empty());
}
#[test]
fn isolated_corners_are_skipped() {
let mut grid = HashMap::new();
grid.insert(GridIndex { i: 0, j: 0 }, Point2::new(0.0, 0.0));
grid.insert(GridIndex { i: 5, j: 5 }, Point2::new(300.0, 300.0));
let flagged = find_inconsistent_corners(&grid, 3.0);
assert!(flagged.is_empty());
}
fn local_step_map(
grid: &HashMap<GridIndex, Point2<f32>>,
step: f32,
) -> HashMap<GridIndex, LocalStep<f32>> {
grid.keys()
.map(|&idx| {
(
idx,
LocalStep {
step_u: step,
step_v: step,
confidence: 1.0,
supporters_u: 4,
supporters_v: 4,
},
)
})
.collect()
}
#[test]
fn step_aware_flags_wrong_relative_distance() {
let spacing = 60.0;
let mut grid = make_grid(3, 3, spacing);
let center = GridIndex { i: 1, j: 1 };
let displacement = 0.4 * spacing;
grid.insert(
center,
Point2::new(spacing + displacement, spacing + displacement),
);
let steps = local_step_map(&grid, spacing);
let flagged = find_inconsistent_corners_step_aware(&grid, &steps, 0.2, 2.0);
assert_eq!(flagged.len(), 1);
assert_eq!(flagged[0].0, center);
}
#[test]
fn step_aware_preserves_floor_when_step_missing() {
let spacing = 60.0;
let mut grid = make_grid(3, 3, spacing);
grid.insert(
GridIndex { i: 1, j: 1 },
Point2::new(spacing + 5.0, spacing),
);
let steps: HashMap<GridIndex, LocalStep<f32>> = HashMap::new();
let tight = find_inconsistent_corners_step_aware(&grid, &steps, 0.2, 3.0);
assert_eq!(tight.len(), 1);
let loose = find_inconsistent_corners_step_aware(&grid, &steps, 0.2, 10.0);
assert!(loose.is_empty());
}
#[test]
fn predict_from_single_pair() {
let mut grid = HashMap::new();
grid.insert(GridIndex { i: 0, j: 0 }, Point2::new(0.0, 0.0));
grid.insert(GridIndex { i: 1, j: 0 }, Point2::new(60.0, 0.0));
grid.insert(GridIndex { i: 2, j: 0 }, Point2::new(120.0, 0.0));
let pred = predict_grid_position(&grid, GridIndex { i: 1, j: 0 }).unwrap();
assert!((pred.x - 60.0f32).abs() < 0.01);
assert!((pred.y - 0.0f32).abs() < 0.01);
}
}