1use axiolid_core::Scalar;
8
9use crate::{FieldConfig, LayeredField, LayeredFieldError};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum FieldChannel {
14 SurfacePresence,
16 OccupancyPresence,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct PlanarMask {
23 width: usize,
24 height: usize,
25 bits: Vec<bool>,
26}
27
28impl PlanarMask {
29 pub fn empty(width: usize, height: usize) -> Result<Self, LayeredFieldError> {
31 let count = width
32 .checked_mul(height)
33 .ok_or(LayeredFieldError::InvalidDimensions)?;
34 if count == 0 {
35 return Err(LayeredFieldError::InvalidDimensions);
36 }
37 Ok(Self {
38 width,
39 height,
40 bits: vec![false; count],
41 })
42 }
43
44 pub fn from_field(field: &LayeredField, channel: FieldChannel) -> Self {
46 let (width, height) = field.dimensions();
47 let bits = field
48 .cells()
49 .iter()
50 .map(|cell| match channel {
51 FieldChannel::SurfacePresence => !cell.surfaces().is_empty(),
52 FieldChannel::OccupancyPresence => !cell.occupancy().is_empty(),
53 })
54 .collect();
55 Self {
56 width,
57 height,
58 bits,
59 }
60 }
61
62 pub const fn dimensions(&self) -> (usize, usize) {
64 (self.width, self.height)
65 }
66
67 pub fn count(&self) -> usize {
69 self.bits.iter().filter(|bit| **bit).count()
70 }
71
72 pub fn get(&self, x: usize, y: usize) -> Option<bool> {
74 self.index(x, y).map(|index| self.bits[index])
75 }
76
77 pub fn set(&mut self, x: usize, y: usize, value: bool) -> Result<(), LayeredFieldError> {
79 let index = self
80 .index(x, y)
81 .ok_or(LayeredFieldError::NodeOutsideField)?;
82 self.bits[index] = value;
83 Ok(())
84 }
85
86 pub fn inverted(&self) -> Self {
88 Self {
89 width: self.width,
90 height: self.height,
91 bits: self.bits.iter().map(|bit| !bit).collect(),
92 }
93 }
94
95 pub fn intersect(&self, other: &Self) -> Result<Self, LayeredFieldError> {
97 if self.width != other.width || self.height != other.height {
98 return Err(LayeredFieldError::DimensionMismatch);
99 }
100 Ok(Self {
101 width: self.width,
102 height: self.height,
103 bits: self
104 .bits
105 .iter()
106 .zip(&other.bits)
107 .map(|(left, right)| *left && *right)
108 .collect(),
109 })
110 }
111
112 pub fn dilate(&self, config: &FieldConfig, radius: Scalar) -> Result<Self, LayeredFieldError> {
118 self.morph(config, radius, true)
119 }
120
121 pub fn erode(&self, config: &FieldConfig, radius: Scalar) -> Result<Self, LayeredFieldError> {
123 self.morph(config, radius, false)
124 }
125
126 fn morph(
127 &self,
128 config: &FieldConfig,
129 radius: Scalar,
130 dilate: bool,
131 ) -> Result<Self, LayeredFieldError> {
132 if !radius.is_finite() || radius < 0.0 {
133 return Err(LayeredFieldError::InvalidEnvelope);
134 }
135 let steps = radius_in_cells(config, radius)?;
136 if steps == 0 {
137 return Ok(self.clone());
138 }
139 let reach = radius / config.cell_size();
140 let reach_squared = reach * reach;
141
142 let steps_isize = steps as isize;
154 let half_widths: Vec<isize> = (-steps_isize..=steps_isize)
155 .map(|dy| {
156 let remaining = reach_squared - (dy * dy) as Scalar;
157 if remaining < 0.0 {
158 -1
159 } else {
160 remaining.sqrt().floor() as isize
161 }
162 })
163 .collect();
164
165 let mut out = Self::empty(self.width, self.height)?;
166 for y in 0..self.height {
167 for x in 0..self.width {
168 let mut value = !dilate;
169 'window: for (index, dy) in (-steps_isize..=steps_isize).enumerate() {
170 let half = half_widths[index];
171 if half < 0 {
172 continue;
173 }
174 let ny = y as isize + dy;
175 if ny < 0 || ny as usize >= self.height {
181 if !dilate {
182 value = false;
183 break 'window;
184 }
185 continue;
186 }
187 let row = ny as usize * self.width;
188 for dx in -half..=half {
189 let nx = x as isize + dx;
190 let neighbor = if nx < 0 || nx as usize >= self.width {
191 false
192 } else {
193 self.bits[row + nx as usize]
194 };
195 if dilate && neighbor {
196 value = true;
197 break 'window;
198 }
199 if !dilate && !neighbor {
200 value = false;
201 break 'window;
202 }
203 }
204 }
205 out.bits[y * self.width + x] = value;
206 }
207 }
208 Ok(out)
209 }
210
211 pub fn connected_components(&self) -> ComponentLabels {
216 let mut labels = vec![None; self.bits.len()];
217 let mut next = 0usize;
218 let mut stack = Vec::new();
219 for start in 0..self.bits.len() {
220 if !self.bits[start] || labels[start].is_some() {
221 continue;
222 }
223 let label = next;
224 next += 1;
225 labels[start] = Some(label);
226 stack.push(start);
227 while let Some(index) = stack.pop() {
228 let x = index % self.width;
229 let y = index / self.width;
230 for (nx, ny) in neighbors(x, y, self.width, self.height) {
231 let neighbor = ny * self.width + nx;
232 if self.bits[neighbor] && labels[neighbor].is_none() {
233 labels[neighbor] = Some(label);
234 stack.push(neighbor);
235 }
236 }
237 }
238 }
239 ComponentLabels {
240 width: self.width,
241 height: self.height,
242 labels,
243 count: next,
244 }
245 }
246
247 fn index(&self, x: usize, y: usize) -> Option<usize> {
248 (x < self.width && y < self.height).then_some(y * self.width + x)
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct ComponentLabels {
255 width: usize,
256 height: usize,
257 labels: Vec<Option<usize>>,
258 count: usize,
259}
260
261impl ComponentLabels {
262 pub const fn count(&self) -> usize {
264 self.count
265 }
266
267 pub fn label(&self, x: usize, y: usize) -> Option<usize> {
269 if x >= self.width || y >= self.height {
270 return None;
271 }
272 self.labels[y * self.width + x]
273 }
274
275 pub fn same_component(&self, from: (usize, usize), to: (usize, usize)) -> bool {
277 match (self.label(from.0, from.1), self.label(to.0, to.1)) {
278 (Some(left), Some(right)) => left == right,
279 _ => false,
280 }
281 }
282}
283
284pub(crate) fn radius_in_cells(
285 config: &FieldConfig,
286 radius: Scalar,
287) -> Result<usize, LayeredFieldError> {
288 if !radius.is_finite() || radius < 0.0 {
289 return Err(LayeredFieldError::InvalidEnvelope);
290 }
291 let steps = (radius / config.cell_size()).ceil();
292 if !steps.is_finite() || steps > usize::MAX as Scalar {
293 return Err(LayeredFieldError::InvalidEnvelope);
294 }
295 Ok(steps as usize)
296}
297
298pub(crate) fn neighbors(
299 x: usize,
300 y: usize,
301 width: usize,
302 height: usize,
303) -> impl Iterator<Item = (usize, usize)> {
304 let mut out = Vec::with_capacity(4);
305 if x > 0 {
306 out.push((x - 1, y));
307 }
308 if y > 0 {
309 out.push((x, y - 1));
310 }
311 if x + 1 < width {
312 out.push((x + 1, y));
313 }
314 if y + 1 < height {
315 out.push((x, y + 1));
316 }
317 out.into_iter()
318}