sightloom_core/zone/
polygon.rs1use crate::{CoreError, Point, Polygon, TrackId, VisionEvent, ZoneId};
4
5use super::slots::TrackSlot;
6
7pub struct PolygonZoneMonitor<'a, const N: usize> {
9 zone_id: ZoneId,
10 polygon: Polygon<'a>,
11 slots: [TrackSlot<bool>; N],
12}
13
14impl<'a, const N: usize> PolygonZoneMonitor<'a, N> {
15 #[must_use]
17 pub const fn new(zone_id: ZoneId, polygon: Polygon<'a>) -> Self {
18 Self {
19 zone_id,
20 polygon,
21 slots: [TrackSlot::Empty; N],
22 }
23 }
24
25 pub fn update(
32 &mut self,
33 track_id: TrackId,
34 point: Point,
35 output: &mut [VisionEvent],
36 ) -> Result<usize, CoreError> {
37 let (existing, free) = TrackSlot::find_slot(&self.slots, track_id);
38 let Some(index) = existing.or(free) else {
39 return Err(CoreError::InsufficientCapacity);
40 };
41 let inside = self.polygon.contains(point);
42 let (next, event) = match self.slots[index] {
43 TrackSlot::Empty => (
44 TrackSlot::Occupied {
45 track_id,
46 state: inside,
47 },
48 membership_event(track_id, self.zone_id, false, inside),
49 ),
50 TrackSlot::Occupied {
51 state: previous_inside,
52 ..
53 } => (
54 TrackSlot::Occupied {
55 track_id,
56 state: inside,
57 },
58 membership_event(track_id, self.zone_id, previous_inside, inside),
59 ),
60 };
61
62 if event.is_some() && output.is_empty() {
63 return Err(CoreError::InsufficientCapacity);
64 }
65
66 self.slots[index] = next;
67 if let Some(event) = event {
68 output[0] = event;
69 Ok(1)
70 } else {
71 Ok(0)
72 }
73 }
74
75 pub fn forget_track(&mut self, track_id: TrackId) -> bool {
77 TrackSlot::forget_track(&mut self.slots, track_id)
78 }
79}
80
81fn membership_event(
82 track_id: TrackId,
83 zone_id: ZoneId,
84 was_inside: bool,
85 is_inside: bool,
86) -> Option<VisionEvent> {
87 match (was_inside, is_inside) {
88 (false, true) => Some(VisionEvent::Entered { track_id, zone_id }),
89 (true, false) => Some(VisionEvent::Exited { track_id, zone_id }),
90 (false, false) | (true, true) => None,
91 }
92}