use axiolid_core::{Interval, Scalar, Tolerance};
use crate::LayeredFieldError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SurfaceFacing {
AgainstNormal,
WithNormal,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SurfaceHit {
w: Scalar,
facing: SurfaceFacing,
}
impl SurfaceHit {
pub const fn new(w: Scalar, facing: SurfaceFacing) -> Self {
Self { w, facing }
}
pub const fn w(self) -> Scalar {
self.w
}
pub const fn facing(self) -> SurfaceFacing {
self.facing
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LayeredCell {
surfaces: Vec<SurfaceHit>,
occupancy: Vec<Interval>,
}
impl LayeredCell {
pub const fn empty() -> Self {
Self {
surfaces: Vec::new(),
occupancy: Vec::new(),
}
}
pub fn new(occupancy: Vec<Interval>) -> Result<Self, LayeredFieldError> {
Self::with_layers(Vec::new(), occupancy)
}
pub fn with_layers(
mut surfaces: Vec<SurfaceHit>,
mut occupancy: Vec<Interval>,
) -> Result<Self, LayeredFieldError> {
if surfaces.iter().any(|hit| !hit.w.is_finite()) {
return Err(LayeredFieldError::InvalidInterval);
}
if occupancy
.iter()
.any(|span| !span.start.is_finite() || !span.end.is_finite() || span.start >= span.end)
{
return Err(LayeredFieldError::InvalidInterval);
}
surfaces.sort_by(|left, right| {
left.w
.total_cmp(&right.w)
.then_with(|| left.facing.cmp(&right.facing))
});
occupancy.sort_by(|left, right| {
left.start
.total_cmp(&right.start)
.then_with(|| left.end.total_cmp(&right.end))
});
if occupancy
.windows(2)
.any(|pair| pair[0].end >= pair[1].start)
{
return Err(LayeredFieldError::NonDisjointIntervals);
}
Ok(Self {
surfaces,
occupancy,
})
}
pub fn surfaces(&self) -> &[SurfaceHit] {
&self.surfaces
}
pub fn occupancy(&self) -> &[Interval] {
&self.occupancy
}
pub fn layer_count(&self) -> usize {
self.surfaces.len() + self.occupancy.len()
}
pub fn is_empty(&self) -> bool {
self.surfaces.is_empty() && self.occupancy.is_empty()
}
pub fn derive_occupancy(&self, tolerance: Tolerance) -> Result<Self, LayeredFieldError> {
if self.surfaces.len() % 2 != 0 {
return Err(LayeredFieldError::UnbalancedCrossings);
}
let mut spans = Vec::with_capacity(self.surfaces.len() / 2);
for pair in self.surfaces.chunks_exact(2) {
if pair[0].facing != SurfaceFacing::AgainstNormal
|| pair[1].facing != SurfaceFacing::WithNormal
{
return Err(LayeredFieldError::UnbalancedCrossings);
}
if (pair[1].w - pair[0].w).abs() <= tolerance.linear() {
return Err(LayeredFieldError::DegenerateOccupancy);
}
spans.push(Interval::new(pair[0].w, pair[1].w));
}
Self::with_layers(self.surfaces.clone(), spans)
}
pub fn largest_free_span(&self, search: Interval) -> Option<Interval> {
let (low, high) = if search.start <= search.end {
(search.start, search.end)
} else {
(search.end, search.start)
};
let mut cursor = low;
let mut best: Option<Interval> = None;
for span in &self.occupancy {
let start = span.start.max(low);
let end = span.end.min(high);
if start >= end {
continue;
}
if start > cursor {
best = keep_longer(best, Interval::new(cursor, start));
}
cursor = cursor.max(end);
}
if cursor < high {
best = keep_longer(best, Interval::new(cursor, high));
}
best
}
}
fn keep_longer(best: Option<Interval>, candidate: Interval) -> Option<Interval> {
match best {
Some(current) if current.length() >= candidate.length() => Some(current),
_ => Some(candidate),
}
}