use std::{ fs::File, io::Write, str::FromStr, }; use glam::{ Mat4, Vec3, }; fn main () { // The `clap` crate is very popular but also very slow to compile, // so I'll do my own CLI parsing let mut args = std::env::args (); let _exe_name = args.next ().unwrap (); let mut input_path = None; let mut output_path = None; let mut scale = 1.0; while let Some (arg) = args.next () { if arg == "--input" || arg == "-i" { input_path = args.next (); } else if arg == "--output" || arg == "-o" { output_path = args.next (); } else if arg == "--scale" { scale = f32::from_str (&args.next ().unwrap ()).unwrap (); } } let input_path = input_path.unwrap (); let output_path = output_path.unwrap (); let (document, buffers, _images) = gltf::import (input_path).unwrap (); let buffer = buffers.get (0).unwrap (); let mut vertexes = Vec::default (); let mut triangles = Vec::default (); let mut meshes = Vec::default (); let mut texts = String::from ("\0"); for node in document.nodes () { let mesh = match node.mesh () { None => continue, Some (x) => x, }; let m = gltf_node_get_mat4 (&node); for (i, prim) in mesh.primitives ().enumerate () { assert_eq! (prim.mode (), gltf::mesh::Mode::Triangles); let indices = prim.indices ().unwrap (); let indices_view = indices.view ().unwrap (); let indices_offset = indices.offset () + indices_view.offset (); let indices_slice = &buffer [indices_offset..(indices_offset + indices.count () * 2)]; let pos = prim.get (&gltf::Semantic::Positions).unwrap (); let pos_view = pos.view ().unwrap (); let pos_offset = pos.offset () + pos_view.offset (); let pos_slice = &buffer [pos_offset..(pos_offset + pos.count () * 4 * 3)]; let norm = prim.get (&gltf::Semantic::Normals).unwrap (); let norm_view = norm.view ().unwrap (); let norm_offset = norm.offset () + norm_view.offset (); let norm_slice = &buffer [norm_offset..(norm_offset + norm.count () * 4 * 3)]; let first_vertex = vertexes.len (); let mesh = iqm::Mesh { name: u32::try_from (texts.len ()).unwrap (), material: 0, first_vertex: u32::try_from (first_vertex).unwrap (), num_vertexes: u32::try_from (indices_slice.len () / 2).unwrap (), // num_vertexes: u32::try_from (3).unwrap (), first_triangle: u32::try_from (triangles.len ()).unwrap (), num_triangles: u32::try_from (indices_slice.len () / 6).unwrap (), //num_triangles: u32::try_from (1).unwrap (), }; texts.push_str (&format! ("{}/{}\0", node.name ().unwrap (), i)); meshes.push (mesh); for chunk in indices_slice.chunks_exact (2 * 3) { let read_idx = |i: usize| u16::from_le_bytes ([chunk [i * 2 + 0], chunk [i * 2 + 1]]); let idxs = [ read_idx (0), read_idx (1), read_idx (2), ]; let read_pos_coord = |i| f32::from_le_bytes ([ pos_slice [i * 4 + 0], pos_slice [i * 4 + 1], pos_slice [i * 4 + 2], pos_slice [i * 4 + 3], ]); let read_norm_coord = |i| f32::from_le_bytes ([ norm_slice [i * 4 + 0], norm_slice [i * 4 + 1], norm_slice [i * 4 + 2], norm_slice [i * 4 + 3], ]); let read_pos = move |i| { let i = usize::try_from (i).unwrap (); m.transform_point3 (Vec3::new ( read_pos_coord (i * 3 + 0), read_pos_coord (i * 3 + 1), read_pos_coord (i * 3 + 2), )) }; let read_norm = move |i| { let i = usize::try_from (i).unwrap (); m.transform_vector3 (Vec3::new ( read_norm_coord (i * 3 + 0), read_norm_coord (i * 3 + 1), read_norm_coord (i * 3 + 2), )) }; let verts = [ read_pos (idxs [0]), read_pos (idxs [2]), read_pos (idxs [1]), ]; let tri_start = u32::try_from (vertexes.len ()).unwrap (); triangles.push ([ tri_start + 0, tri_start + 1, tri_start + 2, ]); for i in [idxs [0], idxs [2], idxs [1]] { let pos = read_pos (i); let norm = read_norm (i); vertexes.push (iqm::Vertex { position: pos.to_array (), normal: norm.to_array (), }); } } } } let vertexarrays_unplaced = [ iqm::VertexArrayUnplaced { r#type: iqm::POSITION, flags: 0, format: iqm::FLOAT, size: 3, }, iqm::VertexArrayUnplaced { r#type: iqm::NORMAL, flags: 0, format: iqm::FLOAT, size: 3, }, iqm::VertexArrayUnplaced { r#type: iqm::TANGENT, flags: 0, format: iqm::FLOAT, size: 4, }, iqm::VertexArrayUnplaced { r#type: iqm::BLENDINDEXES, flags: 0, format: iqm::UBYTE, size: 4, }, iqm::VertexArrayUnplaced { r#type: iqm::BLENDWEIGHTS, flags: 0, format: iqm::UBYTE, size: 4, }, ]; let joints = [ iqm::Joint { name: u32::try_from (texts.len ()).unwrap (), parent: -1, translate: [0.0, 0.0, 0.0], rotate: [0.0, 0.0, 0.0, 1.0], scale: [1.0, 1.0, 1.0], }, ]; texts.push_str ("bogus_joint\0"); let poses = [ iqm::Pose { parent: -1, channelmask: 1, // Should be X translation channeloffset: [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, ], channelscale: [16.0 / 32768.0; 10], }, ]; let anims = [ iqm::Anim { name: u32::try_from (texts.len ()).unwrap (), first_frame: 0, num_frames: 2, framerate: 0.5, flags: 0, }, ]; texts.push_str ("bogus_anim\0"); let num_framechannels = 1; let frames = [ 0, 32768, ]; let vertexes = vertexes; let triangles = triangles; let meshes = meshes; let texts = texts; let num_vertexes = u32::try_from (vertexes.len ()).unwrap (); let num_triangles = u32::try_from (triangles.len ()).unwrap (); let num_joints = u32::try_from (joints.len ()).unwrap (); let num_poses = u32::try_from (poses.len ()).unwrap (); let num_anims = u32::try_from (anims.len ()).unwrap (); let num_frames = u32::try_from (frames.len ()).unwrap () / num_framechannels; let num_text = texts.len (); let num_text = u32::try_from (num_text).unwrap (); let num_meshes = u32::try_from (meshes.len ()).unwrap (); let num_vertexarrays = u32::try_from (vertexarrays_unplaced.len ()).unwrap (); let mut offset = 0; let header_len = 124u32; offset += header_len; let ofs_meshes = offset; offset += num_meshes * 6 * 4; let ofs_vertexarrays = offset; offset += num_vertexarrays * 5 * 4; let mut vertexarrays = Vec::default (); for va_in in vertexarrays_unplaced { let stride = va_in.stride (); vertexarrays.push (iqm::VertexArray { r#type: va_in.r#type, flags: va_in.flags, format: va_in.format, size: va_in.size, offset, }); offset += num_vertexes * stride; } let vertexarrays = vertexarrays; let ofs_triangles = offset; offset += num_triangles * 3 * 4; let ofs_joints = offset; offset += num_joints * 48; let ofs_poses = offset; offset += num_poses * 88; let ofs_anims = offset; offset += num_anims * 5 * 4; let ofs_frames = offset; offset += num_frames * num_framechannels * 2; let ofs_text = offset; offset += num_text; let filesize = offset; let _ = offset; let mut f = File::create (output_path).unwrap (); f.write_all (b"INTERQUAKEMODEL\0").unwrap (); f.write_all (&(2u32.to_le_bytes ())).unwrap (); // filesize f.write_all (&(filesize.to_le_bytes ())).unwrap (); // flags f.write_all (&(0u32.to_le_bytes ())).unwrap (); // num_text, ofs_text f.write_all (&(num_text.to_le_bytes ())).unwrap (); f.write_all (&(ofs_text.to_le_bytes ())).unwrap (); // num_meshes, ofs_meshes f.write_all (&(num_meshes.to_le_bytes ())).unwrap (); f.write_all (&(ofs_meshes.to_le_bytes ())).unwrap (); // num_vertexarrays, num_vertexes, ofs_vertexarrays f.write_all (&(num_vertexarrays.to_le_bytes ())).unwrap (); f.write_all (&(num_vertexes.to_le_bytes ())).unwrap (); f.write_all (&(ofs_vertexarrays.to_le_bytes ())).unwrap (); // num_triangles, ofs_triangles, ofs_adjacency f.write_all (&(num_triangles.to_le_bytes ())).unwrap (); f.write_all (&(ofs_triangles.to_le_bytes ())).unwrap (); f.write_all (&(0u32.to_le_bytes ())).unwrap (); // num_joints, ofs_joints f.write_all (&(num_joints.to_le_bytes ())).unwrap (); f.write_all (&(ofs_joints.to_le_bytes ())).unwrap (); // num_poses, ofs_poses f.write_all (&(num_poses.to_le_bytes ())).unwrap (); f.write_all (&(ofs_poses.to_le_bytes ())).unwrap (); // num_anims, ofs_anims f.write_all (&(num_anims.to_le_bytes ())).unwrap (); f.write_all (&(ofs_anims.to_le_bytes ())).unwrap (); // num_frames, num_framechannels, ofs_frames, ofs_bounds f.write_all (&(num_frames.to_le_bytes ())).unwrap (); f.write_all (&(num_framechannels.to_le_bytes ())).unwrap (); f.write_all (&(ofs_frames.to_le_bytes ())).unwrap (); f.write_all (&(0u32.to_le_bytes ())).unwrap (); // num_comment, ofs_comment f.write_all (&(0u32.to_le_bytes ())).unwrap (); f.write_all (&(0u32.to_le_bytes ())).unwrap (); // num_extensions, ofs_extensions f.write_all (&(0u32.to_le_bytes ())).unwrap (); f.write_all (&(0u32.to_le_bytes ())).unwrap (); // Meshes for mesh in meshes { f.write_all (&(mesh.name.to_le_bytes ())).unwrap (); f.write_all (&(mesh.material.to_le_bytes ())).unwrap (); f.write_all (&(mesh.first_vertex.to_le_bytes ())).unwrap (); f.write_all (&(mesh.num_vertexes.to_le_bytes ())).unwrap (); f.write_all (&(mesh.first_triangle.to_le_bytes ())).unwrap (); f.write_all (&(mesh.num_triangles.to_le_bytes ())).unwrap (); } // Vertex array meta-data for va in &vertexarrays { f.write_all (&(va.r#type.to_le_bytes ())).unwrap (); f.write_all (&(va.flags.to_le_bytes ())).unwrap (); f.write_all (&(va.format.to_le_bytes ())).unwrap (); f.write_all (&(va.size.to_le_bytes ())).unwrap (); f.write_all (&(va.offset.to_le_bytes ())).unwrap (); } // Vertex data for v in &vertexes { for x in v.position { f.write_all (&(f32::to_le_bytes (x * scale))).unwrap (); } } for v in &vertexes { for x in v.normal { f.write_all (&(f32::to_le_bytes (x))).unwrap (); } } for v in &vertexes { for x in [0.0, 0.0, 0.0, 1.0] { f.write_all (&(f32::to_le_bytes (x))).unwrap (); } } for v in &vertexes { f.write_all (&[0, 0, 0, 0]).unwrap (); } for v in &vertexes { f.write_all (&[255, 0, 0, 0]).unwrap (); } // Triangles for t in triangles { for x in t { f.write_all (&(u32::to_le_bytes (x))).unwrap (); } } // Joints for j in joints { f.write_all (&(u32::to_le_bytes (j.name))).unwrap (); f.write_all (&(i32::to_le_bytes (j.parent))).unwrap (); for x in j.translate { f.write_all (&(f32::to_le_bytes (x))).unwrap (); } for x in j.rotate { f.write_all (&(f32::to_le_bytes (x))).unwrap (); } for x in j.scale { f.write_all (&(f32::to_le_bytes (x))).unwrap (); } } for p in poses { f.write_all (&(i32::to_le_bytes (p.parent))).unwrap (); f.write_all (&(u32::to_le_bytes (p.channelmask))).unwrap (); for x in p.channeloffset { f.write_all (&(f32::to_le_bytes (x))).unwrap (); } for x in p.channelscale { f.write_all (&(f32::to_le_bytes (x))).unwrap (); } } for a in anims { f.write_all (&(u32::to_le_bytes (a.name))).unwrap (); f.write_all (&(u32::to_le_bytes (a.first_frame))).unwrap (); f.write_all (&(u32::to_le_bytes (a.num_frames))).unwrap (); f.write_all (&(f32::to_le_bytes (a.framerate))).unwrap (); f.write_all (&(u32::to_le_bytes (a.flags))).unwrap (); } for x in frames { f.write_all (&(u16::to_le_bytes (x))).unwrap (); } // Text f.write_all (texts.as_bytes ()).unwrap (); } fn gltf_node_get_mat4 (node: &gltf::Node) -> Mat4 { Mat4::from_cols_array_2d (&node.transform ().matrix ()) } /// IQM structures as written to disk mod iqm { 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, } pub const POSITION: u32 = 0; pub const NORMAL: u32 = 2; pub const TANGENT: u32 = 3; pub const BLENDINDEXES: u32 = 4; pub const BLENDWEIGHTS: u32 = 5; pub const UBYTE: u32 = 1; pub const FLOAT: u32 = 7; pub struct VertexArrayUnplaced { pub r#type: u32, pub flags: u32, pub format: u32, pub size: u32, } impl VertexArrayUnplaced { pub fn stride (&self) -> u32 { self.size * match self.format { UBYTE => 1, FLOAT => 4, _ => panic! ("Can't handle this vertexarray format yet"), } } } pub struct VertexArray { pub r#type: u32, pub flags: u32, pub format: u32, pub size: u32, pub offset: u32, } struct Triangle { vertex: [u32; 3], } pub struct Joint { pub name: u32, pub parent: i32, pub translate: [f32; 3], pub rotate: [f32; 4], pub scale: [f32; 3], } // Basically extra data about the joint pub struct Pose { pub parent: i32, pub channelmask: u32, pub channeloffset: [f32; 10], pub channelscale: [f32; 10], } pub struct Anim { pub name: u32, pub first_frame: u32, pub num_frames: u32, pub framerate: f32, pub flags: u32, } pub struct Vertex { pub position: [f32; 3], pub normal: [f32; 3], } }