use crate::{
CoreError, Direction, LineSegment, LineSide, Point, TrackId, VisionEvent, ZoneId,
crosses_segment, line_side,
};
use super::slots::TrackSlot;
#[derive(Clone, Copy)]
struct LineTrackState {
previous: Point,
last_non_on: Option<LineSide>,
}
pub struct LineZoneMonitor<const N: usize> {
zone_id: ZoneId,
segment: LineSegment,
slots: [TrackSlot<LineTrackState>; N],
}
impl<const N: usize> LineZoneMonitor<N> {
#[must_use]
pub const fn new(zone_id: ZoneId, segment: LineSegment) -> Self {
Self {
zone_id,
segment,
slots: [TrackSlot::Empty; N],
}
}
pub fn update(
&mut self,
track_id: TrackId,
point: Point,
output: &mut [VisionEvent],
) -> Result<usize, CoreError> {
let (existing, free) = TrackSlot::find_slot(&self.slots, track_id);
let Some(index) = existing.or(free) else {
return Err(CoreError::InsufficientCapacity);
};
let side = line_side(self.segment, point);
let (next, event) = match self.slots[index] {
TrackSlot::Empty => (
TrackSlot::Occupied {
track_id,
state: LineTrackState {
previous: point,
last_non_on: non_on_side(side),
},
},
None,
),
TrackSlot::Occupied {
state:
LineTrackState {
previous,
last_non_on,
},
..
} => {
let event = crossing_event(
self.zone_id,
self.segment,
track_id,
previous,
last_non_on,
point,
side,
);
(
TrackSlot::Occupied {
track_id,
state: LineTrackState {
previous: point,
last_non_on: non_on_side(side).or(last_non_on),
},
},
event,
)
}
};
if event.is_some() && output.is_empty() {
return Err(CoreError::InsufficientCapacity);
}
self.slots[index] = next;
if let Some(event) = event {
output[0] = event;
Ok(1)
} else {
Ok(0)
}
}
pub fn forget_track(&mut self, track_id: TrackId) -> bool {
TrackSlot::forget_track(&mut self.slots, track_id)
}
}
fn non_on_side(side: LineSide) -> Option<LineSide> {
(side != LineSide::On).then_some(side)
}
#[allow(clippy::too_many_arguments)]
fn crossing_event(
zone_id: ZoneId,
segment: LineSegment,
track_id: TrackId,
previous: Point,
last_non_on: Option<LineSide>,
point: Point,
side: LineSide,
) -> Option<VisionEvent> {
let previous_side = last_non_on?;
if side == LineSide::On || side == previous_side || previous == point {
return None;
}
let motion = LineSegment::new(previous, point).ok()?;
if !crosses_segment(segment, motion) {
return None;
}
let direction = match (previous_side, side) {
(LineSide::Left, LineSide::Right) => Direction::LeftToRight,
(LineSide::Right, LineSide::Left) => Direction::RightToLeft,
_ => return None,
};
Some(VisionEvent::Crossed {
track_id,
zone_id,
direction,
})
}