use crate::math_functions::{Aabb, Vec3, VEC3_ZERO};
pub const HEIGHT_FIELD_HOLE: u8 = 0xFF;
pub const HEIGHT_FIELD_VERSION: u64 = 0x8B18CBD138A6BC84;
pub const HEIGHT_FIELD_DATA_SIZE: usize = 88;
pub const CONCAVE_EDGE1: i32 = 0x01;
pub const CONCAVE_EDGE2: i32 = 0x02;
pub const CONCAVE_EDGE3: i32 = 0x04;
pub const INVERSE_CONCAVE_EDGE1: i32 = 0x10;
pub const INVERSE_CONCAVE_EDGE2: i32 = 0x20;
pub const INVERSE_CONCAVE_EDGE3: i32 = 0x40;
#[derive(Debug, Clone)]
pub struct HeightFieldDef {
pub heights: Vec<f32>,
pub material_indices: Vec<u8>,
pub scale: Vec3,
pub count_x: i32,
pub count_z: i32,
pub global_minimum_height: f32,
pub global_maximum_height: f32,
pub clockwise_winding: bool,
}
impl Default for HeightFieldDef {
fn default() -> Self {
Self {
heights: Vec::new(),
material_indices: Vec::new(),
scale: VEC3_ZERO,
count_x: 0,
count_z: 0,
global_minimum_height: 0.0,
global_maximum_height: 0.0,
clockwise_winding: false,
}
}
}
#[derive(Debug, Clone)]
pub struct HeightFieldData {
pub version: u64,
pub byte_count: i32,
pub hash: u32,
pub aabb: Aabb,
pub min_height: f32,
pub max_height: f32,
pub height_scale: f32,
pub scale: Vec3,
pub column_count: i32,
pub row_count: i32,
pub heights_offset: i32,
pub material_offset: i32,
pub flags_offset: i32,
pub clockwise: bool,
pub padding: [u8; 3],
pub compressed_heights: Vec<u16>,
pub material_indices: Vec<u8>,
pub flags: Vec<u8>,
}
impl Default for HeightFieldData {
fn default() -> Self {
Self {
version: HEIGHT_FIELD_VERSION,
byte_count: 0,
hash: 0,
aabb: Aabb::default(),
min_height: 0.0,
max_height: 0.0,
height_scale: 0.0,
scale: VEC3_ZERO,
column_count: 0,
row_count: 0,
heights_offset: 0,
material_offset: 0,
flags_offset: 0,
clockwise: false,
padding: [0; 3],
compressed_heights: Vec::new(),
material_indices: Vec::new(),
flags: Vec::new(),
}
}
}
pub fn get_height_field_compressed_heights(hf: &HeightFieldData) -> &[u16] {
&hf.compressed_heights
}
pub fn get_height_field_material_indices(hf: &HeightFieldData) -> &[u8] {
&hf.material_indices
}
pub fn get_height_field_flags(hf: &HeightFieldData) -> &[u8] {
&hf.flags
}
pub fn get_height_field_triangle_count(hf: &HeightFieldData) -> i32 {
2 * (hf.column_count - 1) * (hf.row_count - 1)
}
fn write_u64_le(buf: &mut Vec<u8>, v: u64) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn read_u64_le(buf: &[u8], off: usize) -> u64 {
u64::from_le_bytes(buf[off..off + 8].try_into().unwrap())
}
fn read_u32_le(buf: &[u8], off: usize) -> u32 {
u32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
}
fn read_i32_le(buf: &[u8], off: usize) -> i32 {
read_u32_le(buf, off) as i32
}
fn read_u16_le(buf: &[u8], off: usize) -> u16 {
u16::from_le_bytes(buf[off..off + 2].try_into().unwrap())
}
fn read_f32_le(buf: &[u8], off: usize) -> f32 {
f32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
}
fn read_vec3_at(buf: &[u8], off: usize) -> Vec3 {
Vec3 {
x: read_f32_le(buf, off),
y: read_f32_le(buf, off + 4),
z: read_f32_le(buf, off + 8),
}
}
fn read_aabb_at(buf: &[u8], off: usize) -> Aabb {
Aabb {
lower_bound: read_vec3_at(buf, off),
upper_bound: read_vec3_at(buf, off + 12),
}
}
pub fn convert_bytes_to_height_field(bytes: &[u8]) -> Option<HeightFieldData> {
if bytes.len() < HEIGHT_FIELD_DATA_SIZE {
return None;
}
let version = read_u64_le(bytes, 0);
if version != HEIGHT_FIELD_VERSION {
return None;
}
let byte_count = read_i32_le(bytes, 8);
if byte_count < HEIGHT_FIELD_DATA_SIZE as i32 || bytes.len() != byte_count as usize {
return None;
}
let hash = read_u32_le(bytes, 12);
let aabb = read_aabb_at(bytes, 16);
let min_height = read_f32_le(bytes, 40);
let max_height = read_f32_le(bytes, 44);
let height_scale = read_f32_le(bytes, 48);
let scale = read_vec3_at(bytes, 52);
let column_count = read_i32_le(bytes, 64);
let row_count = read_i32_le(bytes, 68);
let heights_offset = read_i32_le(bytes, 72);
let material_offset = read_i32_le(bytes, 76);
let flags_offset = read_i32_le(bytes, 80);
let clockwise = bytes[84] != 0;
let padding = [bytes[85], bytes[86], bytes[87]];
if column_count < 0 || row_count < 0 {
return None;
}
let height_count = (column_count as usize).checked_mul(row_count as usize)?;
let cell_count =
((column_count - 1).max(0) as usize).checked_mul((row_count - 1).max(0) as usize)?;
let hoff = heights_offset as usize;
if hoff + height_count * 2 > bytes.len() {
return None;
}
let mut compressed_heights = Vec::with_capacity(height_count);
for i in 0..height_count {
compressed_heights.push(read_u16_le(bytes, hoff + i * 2));
}
let moff = material_offset as usize;
if moff + cell_count > bytes.len() {
return None;
}
let material_indices = bytes[moff..moff + cell_count].to_vec();
let foff = flags_offset as usize;
let flag_count = cell_count * 2; if foff + flag_count > bytes.len() {
return None;
}
let flags = bytes[foff..foff + flag_count].to_vec();
Some(HeightFieldData {
version,
byte_count,
hash,
aabb,
min_height,
max_height,
height_scale,
scale,
column_count,
row_count,
heights_offset,
material_offset,
flags_offset,
clockwise,
padding,
compressed_heights,
material_indices,
flags,
})
}
fn write_u32_le(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn write_i32_le(buf: &mut Vec<u8>, v: i32) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn write_f32_le(buf: &mut Vec<u8>, v: f32) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn write_vec3(buf: &mut Vec<u8>, v: Vec3) {
write_f32_le(buf, v.x);
write_f32_le(buf, v.y);
write_f32_le(buf, v.z);
}
fn write_aabb(buf: &mut Vec<u8>, a: Aabb) {
write_vec3(buf, a.lower_bound);
write_vec3(buf, a.upper_bound);
}
fn pad_to(buf: &mut Vec<u8>, len: usize) {
if buf.len() < len {
buf.resize(len, 0);
}
}
fn write_header(buf: &mut Vec<u8>, h: &HeightFieldData, hash_override: Option<u32>) {
write_u64_le(buf, h.version);
write_i32_le(buf, h.byte_count);
write_u32_le(buf, hash_override.unwrap_or(h.hash));
write_aabb(buf, h.aabb);
write_f32_le(buf, h.min_height);
write_f32_le(buf, h.max_height);
write_f32_le(buf, h.height_scale);
write_vec3(buf, h.scale);
write_i32_le(buf, h.column_count);
write_i32_le(buf, h.row_count);
write_i32_le(buf, h.heights_offset);
write_i32_le(buf, h.material_offset);
write_i32_le(buf, h.flags_offset);
buf.push(u8::from(h.clockwise));
buf.extend_from_slice(&h.padding);
debug_assert_eq!(buf.len(), HEIGHT_FIELD_DATA_SIZE);
}
impl HeightFieldData {
pub fn from_bytes(bytes: &[u8]) -> Option<HeightFieldData> {
convert_bytes_to_height_field(bytes)
}
pub fn to_bytes(&self) -> Vec<u8> {
self.to_bytes_with_hash(self.hash)
}
pub fn to_bytes_with_hash(&self, hash: u32) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.byte_count as usize);
write_header(&mut buf, self, Some(hash));
pad_to(&mut buf, self.heights_offset as usize);
for h in &self.compressed_heights {
buf.extend_from_slice(&h.to_le_bytes());
}
pad_to(&mut buf, self.material_offset as usize);
buf.extend_from_slice(&self.material_indices);
pad_to(&mut buf, self.flags_offset as usize);
buf.extend_from_slice(&self.flags);
pad_to(&mut buf, self.byte_count as usize);
debug_assert_eq!(buf.len(), self.byte_count as usize);
buf
}
}