use axiolid_core::{Interval, Scalar};
use crate::{FieldConfig, LayeredField, LayeredFieldError};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ClearanceReport {
pub distance: Scalar,
pub blocked_at: Option<Scalar>,
pub bounded_by_field: bool,
}
pub fn clearance_above(
field: &LayeredField,
config: &FieldConfig,
x: usize,
y: usize,
w: Scalar,
) -> Result<ClearanceReport, LayeredFieldError> {
directional_clearance(field, config, x, y, w, true)
}
pub fn clearance_below(
field: &LayeredField,
config: &FieldConfig,
x: usize,
y: usize,
w: Scalar,
) -> Result<ClearanceReport, LayeredFieldError> {
directional_clearance(field, config, x, y, w, false)
}
pub fn largest_free_span(
field: &LayeredField,
x: usize,
y: usize,
search: Interval,
) -> Result<Option<Interval>, LayeredFieldError> {
let cell = field
.cell(x, y)
.ok_or(LayeredFieldError::NodeOutsideField)?;
Ok(cell.largest_free_span(search))
}
fn directional_clearance(
field: &LayeredField,
config: &FieldConfig,
x: usize,
y: usize,
w: Scalar,
upward: bool,
) -> Result<ClearanceReport, LayeredFieldError> {
if !w.is_finite() {
return Err(LayeredFieldError::InvalidInterval);
}
let cell = field
.cell(x, y)
.ok_or(LayeredFieldError::NodeOutsideField)?;
let linear = config.tolerance().linear();
let span = config.bounds().normal_span();
let bound = if upward { span.end } else { span.start };
let blocker = cell
.surfaces()
.iter()
.map(|hit| hit.w())
.filter(|value| {
if upward {
*value > w + linear
} else {
*value < w - linear
}
})
.fold(None::<Scalar>, |best, value| match best {
Some(current) if (current - w).abs() <= (value - w).abs() => Some(current),
_ => Some(value),
});
Ok(match blocker {
Some(value) => ClearanceReport {
distance: (value - w).abs(),
blocked_at: Some(value),
bounded_by_field: false,
},
None => ClearanceReport {
distance: (bound - w).abs(),
blocked_at: None,
bounded_by_field: true,
},
})
}