1use axiolid_core::Tolerance;
4
5use crate::{FieldConfig, LayeredCell, LayeredFieldError};
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11#[non_exhaustive]
12pub struct FieldEvidence {
13 pub cells_sampled: usize,
15 pub surface_hits: usize,
17 pub occupancy_spans: usize,
19 pub empty_cells: usize,
21 pub multi_layer_cells: usize,
23 pub parallel_triangles_skipped: usize,
25 pub out_of_bounds_hits: usize,
27 pub boundary_contacts: usize,
29 pub coincident_hits_merged: usize,
35 pub degenerate_triangles: usize,
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub struct LayeredField {
42 width: usize,
43 height: usize,
44 cells: Vec<LayeredCell>,
45 evidence: FieldEvidence,
46}
47
48impl LayeredField {
49 pub fn with_config(config: &FieldConfig) -> Result<Self, LayeredFieldError> {
51 let (width, height) = config.dimensions();
52 Self::empty(width, height)
53 }
54
55 pub fn empty(width: usize, height: usize) -> Result<Self, LayeredFieldError> {
57 let cell_count = width
58 .checked_mul(height)
59 .ok_or(LayeredFieldError::InvalidDimensions)?;
60 if cell_count == 0 {
61 return Err(LayeredFieldError::InvalidDimensions);
62 }
63 Ok(Self {
64 width,
65 height,
66 cells: vec![LayeredCell::empty(); cell_count],
67 evidence: FieldEvidence::default(),
68 })
69 }
70
71 pub const fn dimensions(&self) -> (usize, usize) {
73 (self.width, self.height)
74 }
75
76 pub fn cell_count(&self) -> usize {
78 self.cells.len()
79 }
80
81 pub fn linear_index(&self, x: usize, y: usize) -> Option<usize> {
83 (x < self.width && y < self.height).then_some(y * self.width + x)
84 }
85
86 pub fn cell(&self, x: usize, y: usize) -> Option<&LayeredCell> {
88 self.linear_index(x, y).map(|index| &self.cells[index])
89 }
90
91 pub fn cells(&self) -> &[LayeredCell] {
93 &self.cells
94 }
95
96 pub const fn evidence(&self) -> FieldEvidence {
98 self.evidence
99 }
100
101 pub fn from_cells(
106 width: usize,
107 height: usize,
108 cells: Vec<LayeredCell>,
109 evidence: FieldEvidence,
110 ) -> Result<Self, LayeredFieldError> {
111 let expected = width
112 .checked_mul(height)
113 .ok_or(LayeredFieldError::InvalidDimensions)?;
114 if expected == 0 || cells.len() != expected {
115 return Err(LayeredFieldError::DimensionMismatch);
116 }
117 Ok(Self {
118 width,
119 height,
120 cells,
121 evidence,
122 })
123 }
124
125 pub fn derive_occupancy(&self, tolerance: Tolerance) -> Result<Self, LayeredFieldError> {
130 let mut derived = self.clone();
131 let mut evidence = self.evidence;
132 evidence.occupancy_spans = 0;
133 for (index, cell) in self.cells.iter().enumerate() {
134 let updated = cell.derive_occupancy(tolerance)?;
135 evidence.occupancy_spans += updated.occupancy().len();
136 derived.cells[index] = updated;
137 }
138 derived.evidence = evidence;
139 Ok(derived)
140 }
141}