use rootcause::Report;
use thiserror::Error;
use winnow::Parser;
use winnow::binary::le_f32;
use winnow::binary::le_u16;
use winnow::binary::le_u32;
use winnow::combinator::repeat;
use winnow::error::ContextError;
use winnow::error::ErrMode;
use winnow::token::take;
use crate::data::parser_utils::WResult;
const CTABLE_MAGIC: &[u8; 15] = b"SpeedTreeSDK___";
const STDK_MAGIC: u32 = 0x4B445453;
const CTABLE_VERTEX_STRIDE: usize = 40;
const BW_SCALE: f32 = 1.0 / 30.0;
const DRAW_CALLS_OFFSET: usize = 0xA4;
#[derive(Debug, Error)]
pub enum SpeedTreeError {
#[error("invalid magic")]
InvalidMagic,
#[error("file too short at offset {offset:#x}: need {need}, have {have}")]
TooShort { offset: usize, need: usize, have: usize },
#[error("no draw calls in file")]
NoDrawCalls,
#[error("no vertices found")]
NoVertices,
#[error("no LODs in file")]
NoLods,
#[error("unsupported vertex stride {0} (expected {CTABLE_VERTEX_STRIDE})")]
UnsupportedStride(u32),
#[error("parse error: {0}")]
ParseError(String),
}
#[derive(Debug)]
pub struct SpeedTreeMesh {
pub positions: Vec<[f32; 3]>,
pub normals: Vec<[f32; 3]>,
pub uvs: Vec<[f32; 2]>,
pub indices: Vec<u32>,
}
fn read_relptr(data: &[u8], base: usize, offset: usize) -> Result<usize, Report<SpeedTreeError>> {
let pos = base + offset;
if pos + 4 > data.len() {
return Err(Report::new(SpeedTreeError::TooShort {
offset: pos,
need: 4,
have: data.len().saturating_sub(pos),
}));
}
let val = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
Ok(base + val)
}
fn read_u32(data: &[u8], offset: usize) -> Result<u32, Report<SpeedTreeError>> {
if offset + 4 > data.len() {
return Err(Report::new(SpeedTreeError::TooShort { offset, need: 4, have: data.len().saturating_sub(offset) }));
}
Ok(u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()))
}
fn parse_le_f16(input: &mut &[u8]) -> WResult<f32> {
let bits = le_u16.parse_next(input)?;
Ok(half::f16::from_bits(bits).to_f32())
}
fn parse_ctable_vertex(input: &mut &[u8]) -> WResult<([f32; 3], [f32; 3], [f32; 2])> {
let px = parse_le_f16(input)?;
let py = parse_le_f16(input)?;
let pz = parse_le_f16(input)?;
let u = parse_le_f16(input)?;
let nx = parse_le_f16(input)?;
let ny = parse_le_f16(input)?;
let nz = parse_le_f16(input)?;
let v = parse_le_f16(input)?;
let _rest: &[u8] = take(24usize).parse_next(input)?;
Ok(([px, py, -pz], [nx, ny, -nz], [u, v]))
}
fn parse_ctable_format(data: &[u8]) -> Result<SpeedTreeMesh, Report<SpeedTreeError>> {
let data_ptr = 15;
let draw_table = read_relptr(data, data_ptr, DRAW_CALLS_OFFSET)?;
let draw_count = read_u32(data, draw_table)? as usize;
if draw_count == 0 {
return Err(Report::new(SpeedTreeError::NoDrawCalls));
}
let dc0 = read_relptr(data, draw_table, 4)?;
let vert_sub = read_relptr(data, dc0, 4)?;
let vert_data = read_relptr(data, vert_sub, 4)?;
let vert_header = read_relptr(data, vert_data, 8)?;
let vertex_count = read_u32(data, vert_header)? as usize;
let vertex_stride = read_u32(data, vert_header + 4)?;
let vb_start = vert_header + 8;
if vertex_stride != CTABLE_VERTEX_STRIDE as u32 {
return Err(Report::new(SpeedTreeError::UnsupportedStride(vertex_stride)));
}
if vertex_count == 0 {
return Err(Report::new(SpeedTreeError::NoVertices));
}
let vb_end = vb_start + vertex_count * CTABLE_VERTEX_STRIDE;
if vb_end > data.len() {
return Err(Report::new(SpeedTreeError::TooShort {
offset: vb_start,
need: vertex_count * CTABLE_VERTEX_STRIDE,
have: data.len().saturating_sub(vb_start),
}));
}
let idx_sub = read_relptr(data, dc0, 8)?;
let index_count = read_u32(data, idx_sub)? as usize;
let ib_start = idx_sub + 8;
let ib_end = ib_start + index_count * 2;
if ib_end > data.len() {
return Err(Report::new(SpeedTreeError::TooShort {
offset: ib_start,
need: index_count * 2,
have: data.len().saturating_sub(ib_start),
}));
}
let vb_input = &mut &data[vb_start..vb_end];
let vertices: Vec<([f32; 3], [f32; 3], [f32; 2])> = repeat(vertex_count, parse_ctable_vertex)
.parse_next(vb_input)
.map_err(|e: ErrMode<ContextError>| Report::new(SpeedTreeError::ParseError(format!("vertex parse: {e}"))))?;
let ib_input = &mut &data[ib_start..ib_end];
let raw_indices: Vec<u16> = repeat(index_count, le_u16)
.parse_next(ib_input)
.map_err(|e: ErrMode<ContextError>| Report::new(SpeedTreeError::ParseError(format!("index parse: {e}"))))?;
let mut positions = Vec::with_capacity(vertex_count);
let mut normals = Vec::with_capacity(vertex_count);
let mut uvs = Vec::with_capacity(vertex_count);
for (pos, norm, uv) in &vertices {
positions.push(*pos);
normals.push(*norm);
uvs.push(*uv);
}
let indices: Vec<u32> = raw_indices.iter().map(|&i| i as u32).collect();
Ok(SpeedTreeMesh { positions, normals, uvs, indices })
}
fn parse_stdk_draw_call(
input: &mut &[u8],
positions: &mut Vec<[f32; 3]>,
normals: &mut Vec<[f32; 3]>,
uvs: &mut Vec<[f32; 2]>,
indices: &mut Vec<u32>,
) -> WResult<()> {
let vertex_count = le_u32.parse_next(input)? as usize;
let _vertex_stride = le_u32.parse_next(input)?; let index_count = le_u32.parse_next(input)? as usize;
let _material_id = le_u32.parse_next(input)?;
let base_vertex = positions.len() as u32;
for _ in 0..vertex_count {
let px = le_f32.parse_next(input)?;
let py = le_f32.parse_next(input)?;
let pz = le_f32.parse_next(input)?;
let nx = le_f32.parse_next(input)?;
let ny = le_f32.parse_next(input)?;
let nz = le_f32.parse_next(input)?;
let u = le_f32.parse_next(input)?;
let v = le_f32.parse_next(input)?;
positions.push([px, py, -pz]);
normals.push([nx, ny, -nz]);
uvs.push([u, v]);
}
let raw_indices: Vec<u16> = repeat(index_count, le_u16).parse_next(input)?;
for idx in raw_indices {
indices.push(idx as u32 + base_vertex);
}
Ok(())
}
fn parse_stdk_format(data: &[u8]) -> Result<SpeedTreeMesh, Report<SpeedTreeError>> {
let input = &mut &data[4..];
let _version = le_u32
.parse_next(input)
.map_err(|e: ErrMode<ContextError>| Report::new(SpeedTreeError::ParseError(format!("version: {e}"))))?;
let lod_count = le_u32
.parse_next(input)
.map_err(|e: ErrMode<ContextError>| Report::new(SpeedTreeError::ParseError(format!("lod_count: {e}"))))?
as usize;
let _has_billboard = le_u32
.parse_next(input)
.map_err(|e: ErrMode<ContextError>| Report::new(SpeedTreeError::ParseError(format!("billboard: {e}"))))?;
if lod_count == 0 {
return Err(Report::new(SpeedTreeError::NoLods));
}
let mut positions = Vec::new();
let mut normals = Vec::new();
let mut uvs = Vec::new();
let mut indices = Vec::new();
let dc_count = le_u32
.parse_next(input)
.map_err(|e: ErrMode<ContextError>| Report::new(SpeedTreeError::ParseError(format!("dc_count: {e}"))))?
as usize;
for _ in 0..dc_count {
parse_stdk_draw_call(input, &mut positions, &mut normals, &mut uvs, &mut indices)
.map_err(|e: ErrMode<ContextError>| Report::new(SpeedTreeError::ParseError(format!("draw_call: {e}"))))?;
}
Ok(SpeedTreeMesh { positions, normals, uvs, indices })
}
pub fn parse_stsdk(data: &[u8]) -> Result<SpeedTreeMesh, Report<SpeedTreeError>> {
let mut mesh = if data.len() >= 15 && &data[..15] == CTABLE_MAGIC {
parse_ctable_format(data)?
} else if data.len() >= 4 && u32::from_le_bytes(data[..4].try_into().unwrap()) == STDK_MAGIC {
parse_stdk_format(data)?
} else {
return Err(Report::new(SpeedTreeError::InvalidMagic));
};
for pos in &mut mesh.positions {
pos[0] *= BW_SCALE;
pos[1] *= BW_SCALE;
pos[2] *= BW_SCALE;
}
Ok(mesh)
}