use rootcause::Report;
use thiserror::Error;
use winnow::Parser;
use winnow::binary::le_u32;
use winnow::combinator::repeat;
use crate::data::parser_utils::WResult;
pub const TERRAIN_MAGIC: u32 = 0x00627274;
const RLE_THRESHOLD: u32 = 0x100000;
#[derive(Debug, Error)]
pub enum TerrainError {
#[error("data too short: need {need} bytes, have {have}")]
DataTooShort { need: usize, have: usize },
#[error("bad magic: expected 0x{:08X}, got 0x{got:08X}", TERRAIN_MAGIC)]
BadMagic { got: u32 },
#[error("RLE decode produced {decoded} values, expected at least {expected} (width*height)")]
SizeMismatch { decoded: usize, expected: usize },
#[error("parse error: {0}")]
ParseError(String),
}
#[derive(Debug)]
pub struct Terrain {
pub width: u32,
pub height: u32,
pub tile_size: u16,
pub tiles_per_axis: u16,
pub heightmap: Vec<f32>,
}
fn parse_header(input: &mut &[u8]) -> WResult<(u32, u32, u32, u32)> {
let magic = le_u32.parse_next(input)?;
let width = le_u32.parse_next(input)?;
let height = le_u32.parse_next(input)?;
let tile_info = le_u32.parse_next(input)?;
Ok((magic, width, height, tile_info))
}
pub fn parse_terrain(file_data: &[u8]) -> Result<Terrain, Report<TerrainError>> {
let input = &mut &file_data[..];
let (magic, width, height, tile_info) = parse_header(input).map_err(|e| {
if file_data.len() < 16 {
Report::new(TerrainError::DataTooShort { need: 16, have: file_data.len() })
} else {
Report::new(TerrainError::ParseError(format!("{e}")))
}
})?;
if magic != TERRAIN_MAGIC {
return Err(Report::new(TerrainError::BadMagic { got: magic }));
}
let tile_size = (tile_info & 0xFFFF) as u16;
let tiles_per_axis = ((tile_info >> 16) & 0xFFFF) as u16;
let body = *input;
let n_u32 = body.len() / 4;
let expected = (width as usize) * (height as usize);
let body_input = &mut &body[..n_u32 * 4]; let raw: Vec<u32> = repeat(n_u32, le_u32).parse_next(body_input).map_err(
|e: winnow::error::ErrMode<winnow::error::ContextError>| Report::new(TerrainError::ParseError(format!("{e}"))),
)?;
let mut values = Vec::with_capacity(expected + 1024);
let mut idx = 0;
while idx < raw.len() {
let val = raw[idx];
if idx + 2 < raw.len() && raw[idx + 1] == val && raw[idx + 2] > 0 && raw[idx + 2] < RLE_THRESHOLD {
let count = raw[idx + 2] as usize;
let f = f32::from_bits(val);
values.resize(values.len() + count, f);
idx += 3;
} else {
values.push(f32::from_bits(val));
idx += 1;
}
}
if values.len() < expected {
return Err(Report::new(TerrainError::SizeMismatch { decoded: values.len(), expected }));
}
let preamble = values.len() - expected;
let heightmap = values.split_off(preamble);
Ok(Terrain { width, height, tile_size, tiles_per_axis, heightmap })
}