use axiolid_core::{Frame3, Interval, Point3, Scalar, SpaceFrame, Tolerance};
use crate::LayeredFieldError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldResourceBudget {
pub max_cells: usize,
pub max_intervals: usize,
}
impl FieldResourceBudget {
pub const fn new(max_cells: usize, max_intervals: usize) -> Self {
Self {
max_cells,
max_intervals,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FieldBounds {
min: Point3,
max: Point3,
}
impl FieldBounds {
pub fn new(min: Point3, max: Point3) -> Result<Self, LayeredFieldError> {
if !min.is_finite() || !max.is_finite() {
return Err(LayeredFieldError::InvalidBounds);
}
if min.x >= max.x || min.y >= max.y || min.z >= max.z {
return Err(LayeredFieldError::InvalidBounds);
}
Ok(Self { min, max })
}
pub const fn min(self) -> Point3 {
self.min
}
pub const fn max(self) -> Point3 {
self.max
}
pub fn normal_span(self) -> Interval {
Interval::new(self.min.z, self.max.z)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FieldConfig {
frame: Frame3,
bounds: FieldBounds,
cell_size: Scalar,
tolerance: Tolerance,
budget: FieldResourceBudget,
width: usize,
height: usize,
}
impl FieldConfig {
pub fn new(
frame: Frame3,
bounds: FieldBounds,
cell_size: Scalar,
tolerance: Tolerance,
budget: FieldResourceBudget,
) -> Result<Self, LayeredFieldError> {
if !cell_size.is_finite() || cell_size <= 0.0 {
return Err(LayeredFieldError::InvalidCellSize);
}
if !frame_is_orthonormal(frame, tolerance) {
return Err(LayeredFieldError::InvalidFrame);
}
let span = bounds.max() - bounds.min();
let width = ceil_cells(span.x, cell_size)?;
let height = ceil_cells(span.y, cell_size)?;
let cells = width
.checked_mul(height)
.ok_or(LayeredFieldError::CellBudgetExceeded)?;
if cells == 0 {
return Err(LayeredFieldError::InvalidDimensions);
}
if cells > budget.max_cells {
return Err(LayeredFieldError::CellBudgetExceeded);
}
Ok(Self {
frame,
bounds,
cell_size,
tolerance,
budget,
width,
height,
})
}
pub const fn frame(&self) -> Frame3 {
self.frame
}
pub const fn bounds(&self) -> FieldBounds {
self.bounds
}
pub const fn cell_size(&self) -> Scalar {
self.cell_size
}
pub const fn tolerance(&self) -> Tolerance {
self.tolerance
}
pub const fn budget(&self) -> FieldResourceBudget {
self.budget
}
pub const fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
pub fn cell_center(&self, x: usize, y: usize) -> Point3 {
let local_x = self.bounds.min().x + (x as Scalar + 0.5) * self.cell_size;
let local_y = self.bounds.min().y + (y as Scalar + 0.5) * self.cell_size;
self.frame.origin + self.frame.x * local_x + self.frame.y * local_y
}
pub fn sample_point(&self, x: usize, y: usize, w: Scalar) -> Point3 {
self.cell_center(x, y) + self.frame.z * w
}
}
fn ceil_cells(span: Scalar, cell_size: Scalar) -> Result<usize, LayeredFieldError> {
let count = (span / cell_size).ceil();
if !count.is_finite() || count <= 0.0 || count > usize::MAX as Scalar {
return Err(LayeredFieldError::CellBudgetExceeded);
}
Ok(count as usize)
}
fn frame_is_orthonormal(frame: Frame3, tolerance: Tolerance) -> bool {
SpaceFrame::new(frame.origin, frame.x, frame.y, frame.z, tolerance).is_ok()
}