use nom::{ IResult, bytes::complete::{tag}, number::complete::{le_u32}, }; use std::convert::TryInto; pub mod consts { use iota::iota; iota! { pub const VERSION: usize = iota; , FILESIZE , FLAGS , NUM_TEXT , OFS_TEXT , NUM_MESHES , OFS_MESHES , NUM_VERTEXARRAYS , NUM_VERTEXES , OFS_VERTEXARRAYS , NUM_TRIANGLES , OFS_TRIANGLES , OFS_ADJACENCY , NUM_JOINTS , OFS_JOINTS , NUM_POSES , OFS_POSES , NUM_ANIMS , OFS_ANIMS , NUM_FRAMES , NUM_FRAMECHANNELS , OFS_FRAMES , OFS_BOUNDS , NUM_COMMENT , OFS_COMMENT , NUM_EXTENSIONS , OFS_EXTENSIONS } } pub mod types { iota! { pub const POSITION: usize = iota; , TEXCOORD , NORMAL , TANGENT , BLENDINDEXES , BLENDWEIGHTS , COLOR } pub const CUSTOM: usize = 0x10; } pub mod formats { iota! { pub const BYTE: u32 = iota; , UBYTE , SHORT , USHORT , INT , UINT , HALF , FLOAT , DOUBLE } } #[derive (Debug)] pub struct Mesh { pub name: u32, pub material: u32, pub first_vertex: u32, pub num_vertexes: u32, pub first_triangle: u32, pub num_triangles: u32, } #[derive (Debug)] pub struct Header { pub fields: [u32; 27], } #[derive (Debug)] pub struct VertexArray { va_type: u32, va_flags: u32, va_format: u32, va_size: u32, va_offset: u32, } #[derive (Debug)] pub struct Model <'a> { data: &'a [u8], pub header: Header, text: Vec , pub meshes: Vec , vertexarrays: Vec , } impl Header { pub fn from_slice (input: &[u8]) -> IResult <&[u8], Header> { let (input, _) = tag (b"INTERQUAKEMODEL\0")(input)?; let (input, version) = le_u32 (input)?; // I only know how to parse version 2 assert_eq! (version, 2); let mut input = input; let mut fields = [0; 27]; fields [0] = version; for field in fields.iter_mut ().skip (1) { let (i, h) = le_u32 (input)?; input = i; *field = h; } Ok ((input, Header { fields, })) } } impl Mesh { pub fn from_slice (input: &[u8]) -> IResult <&[u8], Mesh> { let mut result = Mesh { name: 0, material: 0, first_vertex: 0, num_vertexes: 0, first_triangle: 0, num_triangles: 0, }; let mut input = input; for field in [ &mut result.name, &mut result.material, &mut result.first_vertex, &mut result.num_vertexes, &mut result.first_triangle, &mut result.num_triangles, ].iter_mut () { let (i, f) = le_u32 (input)?; input = i; **field = f; } Ok ((input, result)) } } impl VertexArray { pub fn from_slice (input: &[u8]) -> IResult <&[u8], VertexArray> { let mut result = VertexArray { va_type: 0, va_flags: 0, va_format: 0, va_size: 0, va_offset: 0, }; let mut input = input; for field in [ &mut result.va_type, &mut result.va_flags, &mut result.va_format, &mut result.va_size, &mut result.va_offset, ].iter_mut () { let (i, f) = le_u32 (input)?; input = i; **field = f; } Ok ((input, result)) } } #[derive (Debug)] pub enum ModelLoadErr { ParseHeaderFailed, ParseMeshFailed (usize), ParseVertexArrayFailed (usize), } impl <'a> Model <'a> { pub fn from_slice (data: &'a [u8]) -> Result , ModelLoadErr> { let header = match Header::from_slice (data) { Ok ((_, h)) => h, Err (_) => return Err (ModelLoadErr::ParseHeaderFailed), }; let text = { let offset: usize = header.fields [consts::OFS_TEXT].try_into ().unwrap (); let num: usize = header.fields [consts::NUM_TEXT].try_into ().unwrap (); Vec::from (&data [offset..offset + num]) }; let meshes = { let num: usize = header.fields [consts::NUM_MESHES].try_into ().unwrap (); let mut meshes = Vec::with_capacity (num); let mesh_size = 6 * 4; let meshes_offset: usize = header.fields [consts::OFS_MESHES].try_into ().unwrap (); for i in 0..num { let offset = meshes_offset + i * mesh_size; let mesh_slice = &data [offset..offset + mesh_size]; let mesh = match Mesh::from_slice (mesh_slice) { Ok ((_, m)) => m, Err (_) => return Err (ModelLoadErr::ParseMeshFailed (i)), }; meshes.push (mesh); } meshes }; let vertexarrays = { let num: usize = header.fields [consts::NUM_VERTEXARRAYS].try_into ().unwrap (); let mut vertexarrays = Vec::with_capacity (num); let vertexarray_size = 5 * 4; let vertexarrays_offset: usize = header.fields [consts::OFS_VERTEXARRAYS].try_into ().unwrap (); for i in 0..num { let offset = vertexarrays_offset + i * vertexarray_size; let vertexarray_slice = &data [offset..offset + vertexarray_size]; let vertexarray = match VertexArray::from_slice (vertexarray_slice) { Ok ((_, va)) => va, Err (_) => return Err (ModelLoadErr::ParseVertexArrayFailed (i)), }; vertexarrays.push (vertexarray); } vertexarrays }; Ok (Model { data, header, text, meshes, vertexarrays, }) } pub fn get_vertex_slice (&self, vertexarray_index: usize ) -> &[u8] { let vertexarray = &self.vertexarrays [vertexarray_index]; let bytes_per_float = 4; let stride = bytes_per_float * vertexarray.va_size; //assert_eq! (stride, 12); let offset: usize = (vertexarray.va_offset).try_into ().unwrap (); let num_bytes: usize = (stride * self.header.fields [consts::NUM_VERTEXES]).try_into ().unwrap (); &self.data [offset..offset + num_bytes] } pub fn get_index_slice (&self, mesh_index: usize) -> &[u8] { let mesh = &self.meshes [mesh_index]; let bytes_per_u32 = 4; let indexes_per_tri = 3; let stride = bytes_per_u32 * indexes_per_tri; let offset: usize = (self.header.fields [consts::OFS_TRIANGLES] + stride * mesh.first_triangle).try_into ().unwrap (); let num_bytes: usize = (stride * mesh.num_triangles).try_into ().unwrap (); &self.data [offset..offset + num_bytes] } pub fn get_all_indexes (&self) -> &[u8] { let bytes_per_u32 = 4; let indexes_per_tri = 3; let stride = bytes_per_u32 * indexes_per_tri; let offset: usize = self.header.fields [consts::OFS_TRIANGLES].try_into ().unwrap (); let num_bytes: usize = (stride * self.header.fields [consts::NUM_TRIANGLES]).try_into ().unwrap (); &self.data [offset..offset + num_bytes] } // I don't think IQM makes any guarantees about UTF-8 // so I will only say that the slice has no NULs pub fn get_mesh_name (&self, index: usize) -> &[u8] { let mesh = &self.meshes [index]; let ofs: usize = (self.header.fields [consts::OFS_TEXT] + mesh.name).try_into ().unwrap (); // There should be an easy way to do this with CString? let mut nul_index = None; for (j, c) in self.data [ofs..].iter ().enumerate () { if *c == 0 { nul_index = Some (j); break; } } let nul_index = nul_index.unwrap (); &self.data [ofs..ofs + nul_index] } }