From d68d835baf94c84e12e85a85c1fc5a0b9551f86b Mon Sep 17 00:00:00 2001 From: _ <> Date: Mon, 25 May 2020 18:16:03 +0000 Subject: [PATCH] :tada: Rust crate for parsing Inter Quake Models --- .gitignore | 1 + Cargo.toml | 12 ++ airplane.iqm | Bin 0 -> 18576 bytes arrow.iqm | Bin 0 -> 8276 bytes src/lib.rs | 336 +++++++++++++++++++++++++++++++++++++++++++++++++++ truk.iqm | Bin 0 -> 16996 bytes 6 files changed, 349 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 airplane.iqm create mode 100644 arrow.iqm create mode 100644 src/lib.rs create mode 100644 truk.iqm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0dc7fdd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "inter_quake_model" +version = "0.1.0" +authors = ["_"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +byteorder = "1" +iota = "0.2" diff --git a/airplane.iqm b/airplane.iqm new file mode 100644 index 0000000000000000000000000000000000000000..fb5a60a161a463a78538d5ab4d598b8cf689b243 GIT binary patch literal 18576 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 literal 0 HcmV?d00001 diff --git a/arrow.iqm b/arrow.iqm new file mode 100644 index 0000000000000000000000000000000000000000..c31f89f35a8a396c69ee1af520ab94a2f7037f35 GIT binary patch literal 8276 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 tvk2gsqD@Z5$ma!^OTNWga((KoVII#Pd6~ { + data: &'a [u8], + + pub header: Header, + text: Vec , + pub meshes: Vec , + vertexarrays: Vec , +} + +#[derive (Debug)] +pub enum ModelLoadErr { + BadMagic, + UnsupportedVersion, + ParseMeshFailed, + ParseVertexArrayFailed, +} + +impl Header { + pub fn from_slice (input: &[u8]) -> Result { + let magic = b"INTERQUAKEMODEL\0"; + if &input [0..magic.len ()] != magic { + return Err (ModelLoadErr::BadMagic); + } + let input = &input [magic.len ()..]; + + let mut header = Header::default (); + LittleEndian::read_u32_into (&input [0..header.fields.len () * size_of:: ()], &mut header.fields); + + if header.fields [consts::VERSION] != 2 { + return Err (ModelLoadErr::UnsupportedVersion); + } + + Ok (header) + } +} + +impl Mesh { + pub fn from_slice (input: &[u8]) -> Result { + let mut mesh = Mesh::default (); + + let mut rdr = Cursor::new (input); + + for field in [ + &mut mesh.name, + &mut mesh.material, + &mut mesh.first_vertex, + &mut mesh.num_vertexes, + &mut mesh.first_triangle, + &mut mesh.num_triangles, + ].iter_mut () { + **field = rdr.read_u32:: ().map_err (|_| ModelLoadErr::ParseMeshFailed)?; + } + + Ok (mesh) + } +} + +impl VertexArray { + pub fn from_slice (input: &[u8]) -> Result { + let mut va = VertexArray::default (); + + let mut rdr = Cursor::new (input); + + for field in [ + &mut va.va_type, + &mut va.va_flags, + &mut va.va_format, + &mut va.va_size, + &mut va.va_offset, + ].iter_mut () { + **field = rdr.read_u32:: ().map_err (|_| ModelLoadErr::ParseVertexArrayFailed)?; + } + + Ok (va) + } +} + +impl <'a> Model <'a> { + pub fn from_slice (data: &'a [u8]) -> Result , ModelLoadErr> { + let header = Header::from_slice (data)?; + + 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 = Mesh::from_slice (mesh_slice)?; + + 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 = VertexArray::from_slice (vertexarray_slice)?; + + 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] + } +} + +#[cfg (test)] +mod test { + use super::*; + + use std::fs::File; + use std::io::Read; + use std::path::Path; + + #[test] + pub fn nothing () { + + } + + fn load_file > (filename: P) -> Vec { + let mut f = File::open (filename).unwrap (); + let mut data = vec! [0u8; f.metadata ().unwrap ().len ().try_into ().unwrap ()]; + f.read_exact (&mut data).unwrap (); + data + } + + #[test] + pub fn load_models () { + let airplane_bytes = load_file ("airplane.iqm"); + let arrow_bytes = load_file ("arrow.iqm"); + let truk_bytes = load_file ("truk.iqm"); + + let airplane = Model::from_slice (&airplane_bytes).unwrap (); + let arrow = Model::from_slice (&arrow_bytes).unwrap (); + let truk = Model::from_slice (&truk_bytes).unwrap (); + + use consts::*; + + assert_eq! (airplane.header.fields [NUM_VERTEXES], 304); + assert_eq! (airplane.header.fields [NUM_TRIANGLES], 156); + assert_eq! (airplane.meshes.len (), 1); + assert_eq! (airplane.vertexarrays.len (), 4); + + assert_eq! (arrow.header.fields [NUM_VERTEXES], 109); + assert_eq! (arrow.header.fields [NUM_TRIANGLES], 117); + assert_eq! (arrow.meshes.len (), 1); + assert_eq! (arrow.vertexarrays.len (), 4); + + assert_eq! (truk.header.fields [NUM_VERTEXES], 256); + assert_eq! (truk.header.fields [NUM_TRIANGLES], 180); + assert_eq! (truk.meshes.len (), 5); + assert_eq! (truk.vertexarrays.len (), 4); + } +} diff --git a/truk.iqm b/truk.iqm new file mode 100644 index 0000000000000000000000000000000000000000..b5474a7b7c2eb0256c061456a1d229b193367fe0 GIT binary patch literal 16996 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 literal 0 HcmV?d00001