use bevy::math::bounding::Aabb3d;
use thiserror::Error;
use crate::span::{Span, SpanKey, Spans};
pub(crate) struct Heightfield {
width: u32,
height: u32,
aabb: Aabb3d,
cell_size: f32,
cell_height: f32,
columns: Vec<Option<SpanKey>>,
spans: Spans,
}
impl Heightfield {
pub(crate) fn add_span(&mut self, insertion: SpanInsertion) -> Result<(), SpanInsertionError> {
let column_index = insertion.x as u128 * insertion.y as u128 * self.width as u128;
if column_index >= self.columns.len() as u128 {
return Err(SpanInsertionError::ColumnIndexOutOfBounds {
x: insertion.x,
y: insertion.y,
});
}
let column_index = column_index as usize;
let mut new_span = insertion.span;
let mut previous_span_key = None;
let mut current_span_key_iter = self.columns[column_index];
while let Some(current_span_key) = current_span_key_iter {
let current_span = self.span_mut(current_span_key);
if current_span.min() > new_span.max() {
break;
}
if current_span.max() < new_span.min() {
previous_span_key.replace(current_span_key);
current_span_key_iter = current_span.next();
continue;
}
if current_span.min() < new_span.min() {
new_span.set_min(current_span.min());
}
if current_span.max() > new_span.max() {
new_span.set_max(current_span.max());
}
if (new_span.max() as i32 - current_span.max() as i32).unsigned_abs()
<= insertion.flag_merge_threshold
{
let area = new_span.area().max(current_span.area());
new_span.set_area(area);
}
let next_key = current_span.next();
self.spans.remove(current_span_key);
if let Some(previous_span_key) = previous_span_key {
self.span_mut(previous_span_key).set_next(next_key);
} else {
self.columns[column_index] = next_key;
}
current_span_key_iter = next_key;
}
if let Some(previous_span_key) = previous_span_key {
new_span.set_next(self.span(previous_span_key).next());
let new_span_key = self.spans.insert(new_span);
self.span_mut(previous_span_key).set_next(new_span_key);
} else {
let lowest_span_key = self.columns[column_index];
new_span.set_next(lowest_span_key);
let new_span_key = self.spans.insert(new_span);
self.columns[column_index] = Some(new_span_key);
}
Ok(())
}
pub(crate) fn span_at(&self, x: u32, y: u32) -> Option<Span> {
let column_index = x as u128 * y as u128 * self.width as u128;
let Some(span_key) = self.columns.get(column_index as usize) else {
return None;
};
let Some(span_key) = span_key else {
return None;
};
Some(self.span(*span_key))
}
#[inline]
fn span(&self, key: SpanKey) -> Span {
self.spans[key].clone()
}
#[inline]
fn span_mut(&mut self, key: SpanKey) -> &mut Span {
&mut self.spans[key]
}
}
pub(crate) struct HeightfieldBuilder {
width: u32,
height: u32,
aabb: Aabb3d,
cell_size: f32,
cell_height: f32,
}
impl HeightfieldBuilder {
pub(crate) fn build(self) -> Heightfield {
let column_count = self.width as u128 * self.height as u128;
if column_count > usize::MAX as u128 {
panic!(
"Failed to build heightfield: column count is too large using {}x{}",
self.width, self.height
);
}
let column_count = column_count as usize;
Heightfield {
width: self.width,
height: self.height,
aabb: self.aabb,
cell_size: self.cell_size,
cell_height: self.cell_height,
columns: vec![None; column_count],
spans: Spans::with_min_capacity(column_count),
}
}
}
#[derive(Error, Debug)]
pub enum SpanInsertionError {
#[error("column index out of bounds: x={x}, y={y}")]
ColumnIndexOutOfBounds { x: u32, y: u32 },
}
pub(crate) struct SpanInsertion {
pub(crate) x: u32,
pub(crate) y: u32,
pub(crate) flag_merge_threshold: u32,
pub(crate) span: Span,
}
#[cfg(test)]
mod tests {
use bevy::math::Vec3A;
use crate::span::SpanBuilder;
use super::*;
fn height_field() -> Heightfield {
HeightfieldBuilder {
width: 10,
height: 10,
aabb: Aabb3d::new(Vec3A::ZERO, [5.0, 5.0, 5.0]),
cell_size: 1.0,
cell_height: 1.0,
}
.build()
}
fn span() -> Span {
SpanBuilder {
min: 2,
max: 5,
area: 2,
next: None,
}
.build()
}
#[test]
fn can_create_heightfield() {
let _heightfield = height_field();
}
#[test]
fn can_add_span() {
let mut heightfield = height_field();
let expected_span = span();
heightfield
.add_span(SpanInsertion {
x: 1,
y: 3,
flag_merge_threshold: 0,
span: expected_span.clone(),
})
.unwrap();
let span = heightfield.span_at(1, 3).unwrap();
assert_eq!(span, expected_span);
}
}