axiolid_field_ops/
clearance.rs1use axiolid_core::{Interval, Scalar};
8
9use crate::{FieldConfig, LayeredField, LayeredFieldError};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct ClearanceReport {
14 pub distance: Scalar,
16 pub blocked_at: Option<Scalar>,
18 pub bounded_by_field: bool,
20}
21
22pub fn clearance_above(
28 field: &LayeredField,
29 config: &FieldConfig,
30 x: usize,
31 y: usize,
32 w: Scalar,
33) -> Result<ClearanceReport, LayeredFieldError> {
34 directional_clearance(field, config, x, y, w, true)
35}
36
37pub fn clearance_below(
39 field: &LayeredField,
40 config: &FieldConfig,
41 x: usize,
42 y: usize,
43 w: Scalar,
44) -> Result<ClearanceReport, LayeredFieldError> {
45 directional_clearance(field, config, x, y, w, false)
46}
47
48pub fn largest_free_span(
50 field: &LayeredField,
51 x: usize,
52 y: usize,
53 search: Interval,
54) -> Result<Option<Interval>, LayeredFieldError> {
55 let cell = field
56 .cell(x, y)
57 .ok_or(LayeredFieldError::NodeOutsideField)?;
58 Ok(cell.largest_free_span(search))
59}
60
61fn directional_clearance(
62 field: &LayeredField,
63 config: &FieldConfig,
64 x: usize,
65 y: usize,
66 w: Scalar,
67 upward: bool,
68) -> Result<ClearanceReport, LayeredFieldError> {
69 if !w.is_finite() {
70 return Err(LayeredFieldError::InvalidInterval);
71 }
72 let cell = field
73 .cell(x, y)
74 .ok_or(LayeredFieldError::NodeOutsideField)?;
75 let linear = config.tolerance().linear();
76 let span = config.bounds().normal_span();
77 let bound = if upward { span.end } else { span.start };
78
79 let blocker = cell
80 .surfaces()
81 .iter()
82 .map(|hit| hit.w())
83 .filter(|value| {
84 if upward {
85 *value > w + linear
86 } else {
87 *value < w - linear
88 }
89 })
90 .fold(None::<Scalar>, |best, value| match best {
91 Some(current) if (current - w).abs() <= (value - w).abs() => Some(current),
92 _ => Some(value),
93 });
94
95 Ok(match blocker {
96 Some(value) => ClearanceReport {
97 distance: (value - w).abs(),
98 blocked_at: Some(value),
99 bounded_by_field: false,
100 },
101 None => ClearanceReport {
102 distance: (bound - w).abs(),
103 blocked_at: None,
104 bounded_by_field: true,
105 },
106 })
107}