diff --git a/.gitignore b/.gitignore index ea8c4bf..4fffb2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/Cargo.lock diff --git a/src/lib.rs b/src/lib.rs index e28e614..7388df6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,65 +7,11 @@ use std::mem::size_of; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt}; -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 names; +pub mod zero_copy; -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 -} -} +pub use crate::names::consts; +pub use crate::names::types; #[derive (Debug, Default)] pub struct Mesh { @@ -109,13 +55,14 @@ pub enum ModelLoadErr { ParseVertexArrayFailed, } +use crate::names::MAGIC; + impl Header { pub fn from_slice (input: &[u8]) -> Result { - let magic = b"INTERQUAKEMODEL\0"; - if &input [0..magic.len ()] != magic { + if &input [0..MAGIC.len ()] != MAGIC { return Err (ModelLoadErr::BadMagic); } - let input = &input [magic.len ()..]; + let input = &input [MAGIC.len ()..]; let mut header = Header::default (); LittleEndian::read_u32_into (&input [0..header.fields.len () * size_of:: ()], &mut header.fields); @@ -288,18 +235,14 @@ impl <'a> Model <'a> { #[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 { + use std::convert::TryInto; + use std::fs::File; + use std::io::Read; + + 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 (); @@ -308,6 +251,8 @@ mod test { #[test] pub fn load_models () { + use super::*; + let airplane_bytes = load_file ("airplane.iqm"); let arrow_bytes = load_file ("arrow.iqm"); let truk_bytes = load_file ("truk.iqm"); @@ -316,7 +261,7 @@ mod test { let arrow = Model::from_slice (&arrow_bytes).unwrap (); let truk = Model::from_slice (&truk_bytes).unwrap (); - use consts::*; + use crate::names::consts::*; assert_eq! (airplane.header.fields [NUM_VERTEXES], 304); assert_eq! (airplane.header.fields [NUM_TRIANGLES], 156); @@ -333,4 +278,17 @@ mod test { assert_eq! (truk.meshes.len (), 5); assert_eq! (truk.vertexarrays.len (), 4); } + + #[test] + pub fn load_zero_copy () { + use crate::zero_copy::*; + + let airplane_bytes = load_file ("airplane.iqm"); + let arrow_bytes = load_file ("arrow.iqm"); + let truk_bytes = load_file ("truk.iqm"); + + let airplane = Model::get_root (&airplane_bytes); + let arrow = Model::get_root (&arrow_bytes); + let truk = Model::get_root (&truk_bytes); + } } diff --git a/src/names.rs b/src/names.rs new file mode 100644 index 0000000..2d17892 --- /dev/null +++ b/src/names.rs @@ -0,0 +1,82 @@ +pub const MAGIC: &[u8] = b"INTERQUAKEMODEL\0"; + +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 +} + +iota! { + pub const + VA_TYPE: usize = iota; + , VA_FLAGS + , VA_FORMAT + , VA_SIZE + , VA_OFFSET + , VA_FIELD_COUNT +} + +iota! { + pub const + MESH_NAME: usize = iota; + , MESH_MATERIAL + , MESH_FIRST_VERTEX + , MESH_NUM_VERTEXES + , MESH_FIRST_TRIANGLE + , MESH_NUM_TRIANGLES + , MESH_FIELD_COUNT +} +} + +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 +} +} diff --git a/src/zero_copy.rs b/src/zero_copy.rs new file mode 100644 index 0000000..a548adb --- /dev/null +++ b/src/zero_copy.rs @@ -0,0 +1,166 @@ +use std::convert::TryFrom; +use std::mem::size_of; + +use byteorder::{ByteOrder, LittleEndian}; + +pub struct Model <'data> { + pub data: &'data [u8], +} + +pub struct VertexArray <'data> { + pub model: &'data Model <'data>, + ofs: usize, +} + +pub struct Mesh <'data> { + pub model: &'data Model <'data>, + ofs: usize, +} + +pub struct VertexArrays <'data> { + model: &'data Model <'data>, + idx: usize, +} + +pub struct Meshes <'data> { + model: &'data Model <'data>, + idx: usize, +} + +#[derive (Debug)] +pub enum Error { + BadMagic, + UnsupportedVersion, + HeaderTooShort, + ParseMeshFailed, + ParseVertexArrayFailed, +} + +use crate::names; +use names::consts; +use names::MAGIC; + +pub const HEADER_FIELD_COUNT: usize = 27; + +// If you are using IQM models on an 8-bit or 16-bit microcontroller +// you're smarter than me anyway + +fn usize_from (x: u32) -> usize { + usize::try_from (x).unwrap () +} + +impl <'data> Iterator for VertexArrays <'data> { + type Item = VertexArray <'data>; + + fn next (&mut self) -> Option { + if self.idx >= usize_from (self.model.header_field (crate::names::consts::NUM_VERTEXARRAYS)) { + return None; + } + + let result = self.model.vertexarray (self.idx); + + self.idx += 1; + Some (result) + } +} + +impl <'data> Iterator for Meshes <'data> { + type Item = Mesh <'data>; + + fn next (&mut self) -> Option { + if self.idx >= usize_from (self.model.header_field (crate::names::consts::NUM_MESHES)) { + return None; + } + + let result = self.model.mesh (self.idx); + + self.idx += 1; + Some (result) + } +} + +impl <'data> Model <'data> { + pub fn get_root (data: &[u8]) -> Result { + if &data [0..MAGIC.len ()] != MAGIC { + return Err (Error::BadMagic); + } + + if data.len () < MAGIC.len () + size_of:: () * HEADER_FIELD_COUNT { + return Err (Error::HeaderTooShort); + } + + let m = Model { + data, + }; + + if m.header_field (consts::VERSION) != 2 { + return Err (Error::UnsupportedVersion); + } + + Ok (m) + } + + pub fn header_field (&self, idx: usize) -> u32 { + LittleEndian::read_u32 (&self.data [MAGIC.len () + idx * size_of:: ()..]) + } + + pub fn vertexarray (&self, idx: usize) -> VertexArray { + use crate::names::consts::*; + assert! (idx < usize_from (self.header_field (NUM_VERTEXARRAYS))); + + let vertexarray_stride = VA_FIELD_COUNT * size_of:: (); + + VertexArray { + model: self, + ofs: usize_from (self.header_field (OFS_VERTEXARRAYS)) + idx * vertexarray_stride, + } + } + + pub fn mesh (&self, idx: usize) -> Mesh { + use crate::names::consts::*; + assert! (idx < usize_from (self.header_field (NUM_MESHES))); + + let mesh_stride = MESH_FIELD_COUNT * size_of:: (); + + Mesh { + model: self, + ofs: usize_from (self.header_field (OFS_MESHES)) + idx * mesh_stride, + } + } + + pub fn vertexarrays (&self) -> VertexArrays { + VertexArrays { + model: self, + idx: 0, + } + } + + pub fn meshes (&self) -> Meshes { + Meshes { + model: self, + idx: 0, + } + } +} + +impl <'data> VertexArray <'data> { + pub fn field (&self, idx: usize) -> u32 { + LittleEndian::read_u32 (&self.model.data [self.ofs + idx * size_of::()..]) + } + + pub fn get_vertex_slice (&self) -> &[u8] { + use names::consts::*; + + let stride = size_of:: () * usize_from (self.field (VA_SIZE)); + let offset = usize_from (self.field (VA_OFFSET)); + let num_bytes = stride * usize_from (self.model.header_field (NUM_VERTEXES)); + + &self.model.data [offset..offset + num_bytes] + } +} + +impl <'data> Mesh <'data> { + pub fn field (&self, idx: usize) -> u32 { + LittleEndian::read_u32 (&self.model.data [self.ofs + idx * size_of::()..]) + } +}