use projective_grid::LatticeKind;
use super::error::TargetValidationError;
use super::lattice::{point_set_invariant_under, rotational_symmetries};
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct OriginFiducials {
pub dot_radius_mm: f32,
pub dots_mm: Vec<[f32; 2]>,
}
impl OriginFiducials {
pub(crate) fn validate(
&self,
kind: LatticeKind,
cells_mm: &[[f32; 2]],
outer_draw_radius_mm: f32,
) -> Result<(), TargetValidationError> {
if !self.dot_radius_mm.is_finite() || self.dot_radius_mm <= 0.0 {
return Err(TargetValidationError::InvalidDotRadius {
dot_radius_mm: self.dot_radius_mm,
});
}
if self.dots_mm.is_empty() {
return Err(TargetValidationError::EmptyFiducialDots);
}
for (index, dot) in self.dots_mm.iter().enumerate() {
if !dot[0].is_finite() || !dot[1].is_finite() {
return Err(TargetValidationError::NonFiniteDot { index });
}
}
let min_clearance = f64::from(outer_draw_radius_mm) + f64::from(self.dot_radius_mm);
let min_clearance_sq = min_clearance * min_clearance;
for (index, dot) in self.dots_mm.iter().enumerate() {
let hit = cells_mm.iter().any(|cell| {
let dx = f64::from(dot[0]) - f64::from(cell[0]);
let dy = f64::from(dot[1]) - f64::from(cell[1]);
dx * dx + dy * dy < min_clearance_sq
});
if hit {
return Err(TargetValidationError::DotOverlapsMarker { index });
}
}
let dots: Vec<[f64; 2]> = self
.dots_mm
.iter()
.map(|&[x, y]| [f64::from(x), f64::from(y)])
.collect();
let tol = (f64::from(self.dot_radius_mm) * 0.1).max(1e-6);
for rotation in rotational_symmetries(kind, cells_mm) {
if point_set_invariant_under(&dots, &rotation, tol) {
return Err(TargetValidationError::FiducialsRotationallySymmetric {
angle_deg: rotation.angle_rad.to_degrees() as f32,
});
}
}
Ok(())
}
}