1use axiolid_core::{Frame3, Interval, Point3, Scalar, SpaceFrame, Tolerance};
8
9use crate::LayeredFieldError;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct FieldResourceBudget {
15 pub max_cells: usize,
17 pub max_intervals: usize,
19}
20
21impl FieldResourceBudget {
22 pub const fn new(max_cells: usize, max_intervals: usize) -> Self {
24 Self {
25 max_cells,
26 max_intervals,
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq)]
33pub struct FieldBounds {
34 min: Point3,
35 max: Point3,
36}
37
38impl FieldBounds {
39 pub fn new(min: Point3, max: Point3) -> Result<Self, LayeredFieldError> {
41 if !min.is_finite() || !max.is_finite() {
42 return Err(LayeredFieldError::InvalidBounds);
43 }
44 if min.x >= max.x || min.y >= max.y || min.z >= max.z {
45 return Err(LayeredFieldError::InvalidBounds);
46 }
47 Ok(Self { min, max })
48 }
49
50 pub const fn min(self) -> Point3 {
52 self.min
53 }
54
55 pub const fn max(self) -> Point3 {
57 self.max
58 }
59
60 pub fn normal_span(self) -> Interval {
62 Interval::new(self.min.z, self.max.z)
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq)]
68pub struct FieldConfig {
69 frame: Frame3,
70 bounds: FieldBounds,
71 cell_size: Scalar,
72 tolerance: Tolerance,
73 budget: FieldResourceBudget,
74 width: usize,
75 height: usize,
76}
77
78impl FieldConfig {
79 pub fn new(
86 frame: Frame3,
87 bounds: FieldBounds,
88 cell_size: Scalar,
89 tolerance: Tolerance,
90 budget: FieldResourceBudget,
91 ) -> Result<Self, LayeredFieldError> {
92 if !cell_size.is_finite() || cell_size <= 0.0 {
93 return Err(LayeredFieldError::InvalidCellSize);
94 }
95 if !frame_is_orthonormal(frame, tolerance) {
96 return Err(LayeredFieldError::InvalidFrame);
97 }
98 let span = bounds.max() - bounds.min();
99 let width = ceil_cells(span.x, cell_size)?;
100 let height = ceil_cells(span.y, cell_size)?;
101 let cells = width
102 .checked_mul(height)
103 .ok_or(LayeredFieldError::CellBudgetExceeded)?;
104 if cells == 0 {
105 return Err(LayeredFieldError::InvalidDimensions);
106 }
107 if cells > budget.max_cells {
108 return Err(LayeredFieldError::CellBudgetExceeded);
109 }
110 Ok(Self {
111 frame,
112 bounds,
113 cell_size,
114 tolerance,
115 budget,
116 width,
117 height,
118 })
119 }
120
121 pub const fn frame(&self) -> Frame3 {
123 self.frame
124 }
125
126 pub const fn bounds(&self) -> FieldBounds {
128 self.bounds
129 }
130
131 pub const fn cell_size(&self) -> Scalar {
133 self.cell_size
134 }
135
136 pub const fn tolerance(&self) -> Tolerance {
138 self.tolerance
139 }
140
141 pub const fn budget(&self) -> FieldResourceBudget {
143 self.budget
144 }
145
146 pub const fn dimensions(&self) -> (usize, usize) {
148 (self.width, self.height)
149 }
150
151 pub fn cell_center(&self, x: usize, y: usize) -> Point3 {
153 let local_x = self.bounds.min().x + (x as Scalar + 0.5) * self.cell_size;
154 let local_y = self.bounds.min().y + (y as Scalar + 0.5) * self.cell_size;
155 self.frame.origin + self.frame.x * local_x + self.frame.y * local_y
156 }
157
158 pub fn sample_point(&self, x: usize, y: usize, w: Scalar) -> Point3 {
160 self.cell_center(x, y) + self.frame.z * w
161 }
162}
163
164fn ceil_cells(span: Scalar, cell_size: Scalar) -> Result<usize, LayeredFieldError> {
165 let count = (span / cell_size).ceil();
166 if !count.is_finite() || count <= 0.0 || count > usize::MAX as Scalar {
167 return Err(LayeredFieldError::CellBudgetExceeded);
168 }
169 Ok(count as usize)
170}
171
172fn frame_is_orthonormal(frame: Frame3, tolerance: Tolerance) -> bool {
178 SpaceFrame::new(frame.origin, frame.x, frame.y, frame.z, tolerance).is_ok()
179}