use crate::{CoreError, Point, Polygon, TrackId, VisionEvent, ZoneId};
use super::slots::TrackSlot;
pub struct PolygonZoneMonitor<'a, const N: usize> {
zone_id: ZoneId,
polygon: Polygon<'a>,
slots: [TrackSlot<bool>; N],
}
impl<'a, const N: usize> PolygonZoneMonitor<'a, N> {
#[must_use]
pub const fn new(zone_id: ZoneId, polygon: Polygon<'a>) -> Self {
Self {
zone_id,
polygon,
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 inside = self.polygon.contains(point);
let (next, event) = match self.slots[index] {
TrackSlot::Empty => (
TrackSlot::Occupied {
track_id,
state: inside,
},
membership_event(track_id, self.zone_id, false, inside),
),
TrackSlot::Occupied {
state: previous_inside,
..
} => (
TrackSlot::Occupied {
track_id,
state: inside,
},
membership_event(track_id, self.zone_id, previous_inside, inside),
),
};
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 membership_event(
track_id: TrackId,
zone_id: ZoneId,
was_inside: bool,
is_inside: bool,
) -> Option<VisionEvent> {
match (was_inside, is_inside) {
(false, true) => Some(VisionEvent::Entered { track_id, zone_id }),
(true, false) => Some(VisionEvent::Exited { track_id, zone_id }),
(false, false) | (true, true) => None,
}
}