use crate::{
Aabb3d,
compact_cell::CompactCell,
compact_span::CompactSpan,
heightfield::Heightfield,
math::{dir_offset_x, dir_offset_z},
region::RegionId,
span::AreaType,
};
use alloc::vec::Vec;
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct CompactHeightfield {
pub width: u16,
pub height: u16,
pub walkable_height: u16,
pub walkable_climb: u16,
pub border_size: u16,
pub max_distance: u16,
pub max_region: RegionId,
pub aabb: Aabb3d,
pub cell_size: f32,
pub cell_height: f32,
pub cells: Vec<CompactCell>,
pub spans: Vec<CompactSpan>,
pub dist: Vec<u16>,
pub areas: Vec<AreaType>,
}
impl Heightfield {
const MAX_HEIGHT: u16 = u16::MAX;
pub fn into_compact(
self,
walkable_height: u16,
walkable_climb: u16,
) -> Result<CompactHeightfield, CompactHeightfieldError> {
let walkable_span_count = self
.allocated_spans
.values()
.filter(|span| span.area.is_walkable())
.count();
let mut compact_heightfield = CompactHeightfield {
width: self.width,
height: self.height,
walkable_height,
walkable_climb,
border_size: 0,
aabb: self.aabb,
max_distance: 0,
max_region: RegionId::NONE,
cell_size: self.cell_size,
cell_height: self.cell_height,
cells: vec![CompactCell::default(); self.width as usize * self.height as usize],
spans: vec![CompactSpan::default(); walkable_span_count],
dist: vec![],
areas: vec![AreaType::NOT_WALKABLE; walkable_span_count],
};
compact_heightfield.aabb.max.y += walkable_height as f32 * compact_heightfield.cell_height;
let mut cell_index = 0_usize;
for z in 0..self.height {
for x in 0..self.width {
let Some(span_key) = self.span_key_at(x, z) else {
continue;
};
let mut span_key_iter = Some(span_key);
let column_index = self.column_index(x, z);
let cell = &mut compact_heightfield.cells[column_index];
cell.set_index(cell_index as u32);
cell.set_count(0);
while let Some(span_key) = span_key_iter {
let span = self.span(span_key);
span_key_iter = span.next;
if !span.area.is_walkable() {
continue;
}
let bot = span.max;
let top = span
.next
.map(|span| self.span(span).min)
.unwrap_or(Self::MAX_HEIGHT);
compact_heightfield.spans[cell_index].y = bot.clamp(0, Self::MAX_HEIGHT);
let height = (top.saturating_sub(bot)).min(u8::MAX.into()) as u8;
compact_heightfield.spans[cell_index].set_height(height);
compact_heightfield.areas[cell_index] = span.area;
cell_index += 1;
cell.inc_count();
}
}
}
const MAX_LAYERS: u8 = CompactSpan::NOT_CONNECTED - 1;
let mut max_layer_index = 0_u32;
for z in 0..self.height {
for x in 0..self.width {
let column_index = x as usize + z as usize * self.width as usize;
let cell = &mut compact_heightfield.cells[column_index];
let index_count = cell.index() as usize + cell.count() as usize;
for i in cell.index() as usize..index_count {
for dir in 0..4_u8 {
compact_heightfield.spans[i].set_con(dir, None);
let neighbor_x = x as i32 + dir_offset_x(dir) as i32;
let neighbor_z = z as i32 + dir_offset_z(dir) as i32;
if !self.contains(neighbor_x, neighbor_z) {
continue;
}
let neighbor_x = neighbor_x as u16;
let neighbor_z = neighbor_z as u16;
let column_index = self.column_index(neighbor_x, neighbor_z);
let neighbor_cell = &compact_heightfield.cells[column_index];
let neighbor_index_count =
neighbor_cell.index() as usize + neighbor_cell.count() as usize;
let span_clone = compact_heightfield.spans[i].clone();
for k in neighbor_cell.index() as usize..neighbor_index_count as usize {
let neighbor_span = &compact_heightfield.spans[k];
let bot = span_clone.y.max(neighbor_span.y);
let top = (span_clone.y + span_clone.height() as u16)
.min(neighbor_span.y + neighbor_span.height() as u16);
let is_walkable = (top as i32 - bot as i32) >= walkable_height as i32;
let is_climbable = (neighbor_span.y as i32 - span_clone.y as i32).abs()
<= walkable_climb as i32;
if !is_walkable || !is_climbable {
continue;
}
let layer_index = k as i32 - neighbor_cell.index() as i32;
if layer_index < 0 || layer_index >= MAX_LAYERS as i32 {
max_layer_index = max_layer_index.max(layer_index as u32);
continue;
}
let layer_index = layer_index as u8;
compact_heightfield.spans[i].set_con(dir, Some(layer_index));
break;
}
}
}
}
}
if max_layer_index > MAX_LAYERS as u32 {
return Err(CompactHeightfieldError::TooManyLayers {
max_layer_index: MAX_LAYERS,
layer_index: max_layer_index,
});
}
Ok(compact_heightfield)
}
}
impl CompactHeightfield {
#[inline]
pub(crate) fn column_index(&self, x: u16, z: u16) -> usize {
x as usize + z as usize * self.width as usize
}
#[inline]
pub fn get_cell_at(&self, x: u16, z: u16) -> Option<&CompactCell> {
let Some(cell) = self.cells.get(self.column_index(x, z)) else {
return None;
};
Some(cell)
}
#[inline]
pub fn cell_at(&self, x: u16, z: u16) -> &CompactCell {
&self.cells[self.column_index(x, z)]
}
#[inline]
pub fn get_cell_at_mut(&mut self, x: u16, z: u16) -> Option<&mut CompactCell> {
let index = self.column_index(x, z);
let Some(cell) = self.cells.get_mut(index) else {
return None;
};
Some(cell)
}
#[inline]
pub fn cell_at_mut(&mut self, x: u16, z: u16) -> &mut CompactCell {
let index = self.column_index(x, z);
&mut self.cells[index]
}
#[inline]
pub fn con_indices(&self, x: i32, z: i32, dir: u8, con: u8) -> (i32, i32, usize) {
let a_x = x + dir_offset_x(dir) as i32;
let a_z = z + dir_offset_z(dir) as i32;
let cell_index = (a_x + a_z * self.width as i32) as usize;
let a_i = self.cells[cell_index].index() as usize + con as usize;
(a_x, a_z, a_i)
}
}
#[derive(Debug, thiserror::Error)]
pub enum CompactHeightfieldError {
#[error(
"Heightfield has too many layers. Max layer index is {max_layer_index}, but got {layer_index}"
)]
TooManyLayers {
max_layer_index: u8,
layer_index: u32,
},
}