🎉 Rust crate for parsing Inter Quake Models
						commit
						d68d835baf
					
				| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
/target
 | 
			
		||||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,336 @@
 | 
			
		|||
#[macro_use]
 | 
			
		||||
extern crate iota;
 | 
			
		||||
 | 
			
		||||
use std::convert::TryInto;
 | 
			
		||||
use std::io::Cursor;
 | 
			
		||||
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 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, Default)]
 | 
			
		||||
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, Default)]
 | 
			
		||||
pub struct Header {
 | 
			
		||||
	pub fields: [u32; 27],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive (Debug, Default)]
 | 
			
		||||
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 <u8>,
 | 
			
		||||
	pub meshes: Vec <Mesh>,
 | 
			
		||||
	vertexarrays: Vec <VertexArray>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive (Debug)]
 | 
			
		||||
pub enum ModelLoadErr {
 | 
			
		||||
	BadMagic,
 | 
			
		||||
	UnsupportedVersion,
 | 
			
		||||
	ParseMeshFailed,
 | 
			
		||||
	ParseVertexArrayFailed,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Header {
 | 
			
		||||
	pub fn from_slice (input: &[u8]) -> Result <Header, ModelLoadErr> {
 | 
			
		||||
		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::<u32> ()], &mut header.fields);
 | 
			
		||||
		
 | 
			
		||||
		if header.fields [consts::VERSION] != 2 {
 | 
			
		||||
			return Err (ModelLoadErr::UnsupportedVersion);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		Ok (header)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Mesh {
 | 
			
		||||
	pub fn from_slice (input: &[u8]) -> Result <Mesh, ModelLoadErr> {
 | 
			
		||||
		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::<LittleEndian> ().map_err (|_| ModelLoadErr::ParseMeshFailed)?;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		Ok (mesh)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VertexArray {
 | 
			
		||||
	pub fn from_slice (input: &[u8]) -> Result <VertexArray, ModelLoadErr> {
 | 
			
		||||
		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::<LittleEndian> ().map_err (|_| ModelLoadErr::ParseVertexArrayFailed)?;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		Ok (va)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl <'a> Model <'a> {
 | 
			
		||||
	pub fn from_slice (data: &'a [u8]) -> Result <Model <'a>, 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 <P: AsRef <Path>> (filename: P) -> Vec <u8> {
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue