🎉 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