diff --git a/Cargo.lock b/Cargo.lock index d407d22..1e68efe 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,7 +121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" dependencies = [ "ciborium-io", - "half", + "half 1.8.2", ] [[package]] @@ -242,6 +242,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "either" version = "1.7.0" @@ -254,6 +260,15 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "half" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad6a9459c9c30b177b925162351f97e7d967c7ea8bab3b8352805327daf45554" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -401,6 +416,7 @@ dependencies = [ "bitfield-struct", "crc", "criterion", + "half 2.1.0", "libz-sys", "paste", ] diff --git a/Cargo.toml b/Cargo.toml index 70a44be..d81d533 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,4 @@ binrw = "0.9.2" libz-sys = { version = "1.1.8", default-features = false } bitfield-struct = "0.1.7" paste = "1.0.7" +half = "2.1.0" \ No newline at end of file diff --git a/src/dat.rs b/src/dat.rs index 6b6dfcd..807db7e 100755 --- a/src/dat.rs +++ b/src/dat.rs @@ -2,7 +2,7 @@ use std::io::{Cursor, Read, Seek, SeekFrom}; use binrw::BinRead; use binrw::binrw; use crate::gamedata::MemoryBuffer; -use crate::model::ModelHeader; +use crate::model::ModelFileHeader; use std::io::Write; use binrw::BinWrite; use crate::sqpack::read_data_block; @@ -284,7 +284,7 @@ impl DatFile { process_model_data(i, model_file_info.num.index_buffer_size[i] as u32, model_file_info.offset.index_buffer_size[i], &mut index_data_offsets, &mut index_data_sizes); } - let header = ModelHeader { + let header = ModelFileHeader { version: model_file_info.version, stack_size, runtime_size, diff --git a/src/lib.rs b/src/lib.rs index f6b575c..90aee86 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ pub mod index; mod dat; mod compression; -mod model; +pub mod model; /// All of the races in Eorzea in a nice enum package. pub mod race; diff --git a/src/model.rs b/src/model.rs index 9771959..dca2fb1 100755 --- a/src/model.rs +++ b/src/model.rs @@ -1,7 +1,14 @@ +use std::io::{Cursor, Seek, SeekFrom}; +use std::ops::Bound; use binrw::binrw; +use crate::gamedata::MemoryBuffer; +use binrw::BinRead; +use binrw::binread; +use half::f16; #[binrw] -pub struct ModelHeader { +#[derive(Debug)] +pub struct ModelFileHeader { pub(crate) version: u32, pub stack_size: u32, @@ -26,8 +33,388 @@ pub struct ModelHeader { pub has_edge_geometry: bool, } -pub struct Model {} +#[binread] +#[br(repr = u8)] +#[derive(Debug)] +enum ModelFlags1 { + DustOcclusionEnabled = 0x80, + SnowOcclusionEnabled = 0x40, + RainOcclusionEnabled = 0x20, + Unknown1 = 0x10, + LightingReflectionEnabled = 0x08, + WavingAnimationDisabled = 0x04, + LightShadowDisabled = 0x02, + ShadowDisabled = 0x01 +} -impl Model { - pub fn from_existing() {} +#[binread] +#[br(repr = u8)] +#[derive(Debug)] +enum ModelFlags2 { + None = 0x0, + Unknown2 = 0x80, + BgUvScrollEnabled = 0x40, + EnableForceNonResident = 0x20, + ExtraLodEnabled = 0x10, + ShadowMaskEnabled = 0x08, + ForceLodRangeEnabled = 0x04, + EdgeGeometryEnabled = 0x02, + Unknown3 = 0x01 +} + +#[binread] +#[derive(Debug)] +pub struct ModelHeader { + #[br(pad_after = 2)] + string_count : u16, + string_size : u32, + + #[br(count = string_size)] + strings : Vec, + + radius : f32, + + mesh_count : u16, + attribute_count : u16, + submesh_count : u16, + material_count : u16, + bone_count : u16, + bone_table_count : u16, + shape_count : u16, + shape_mesh_count : u16, + shape_value_count : u16, + + lod_count : u8, + + flags1 : ModelFlags1, + + element_id_count : u16, + terrain_shadow_mesh_count : u8, + + #[br(err_context("radius = {}", radius))] + flags2 : ModelFlags2, + + model_clip_out_of_distance : f32, + shadow_clip_out_of_distance : f32, + + #[br(pad_before = 2)] + #[br(pad_after = 2)] + terrain_shadow_submesh_count : u16, + + bg_change_material_index : u8, + #[br(pad_after = 12)] + bg_crest_change_material_index : u8, +} + +#[binread] +#[derive(Debug)] +struct MeshLod { + mesh_index : u16, + mesh_count : u16, + + model_lod_range : f32, + texture_lod_range : f32, + + water_mesh_index : u16, + water_mesh_count : u16, + + shadow_mesh_index : u16, + shadow_mesh_count : u16, + + terrain_shadow_mesh_count : u16, + terrain_shadow_mesh_index : u16, + + vertical_fog_mesh_index : u16, + vertical_fog_mesh_count : u16, + + // unused on win32 according to lumina devs + edge_geometry_size : u32, + edge_geometry_data_offset : u32, + + #[br(pad_after = 4)] + polygon_count : u32, + + vertex_buffer_size : u32, + index_buffer_size : u32, + vertex_data_offset : u32, + index_data_offset : u32 +} + +#[binread] +#[derive(Debug)] +struct Mesh { + #[br(pad_after = 2)] + vertex_count : u16, + index_count : u32, + + material_index : u16, + submesh_index : u16, + submesh_count : u16, + + bone_table_index : u16, + start_index : u32, + + vertex_buffer_offsets : [u32; 3], + vertex_buffer_strides : [u8; 3], + + vertex_stream_count : u8 +} + +#[binread] +#[derive(Debug)] +struct Submesh { + index_offset : i32, + index_count : i32, + + attribute_index_mask : u32, + + bone_start_index : u16, + bone_count : u16 +} + +#[binread] +#[derive(Debug)] +struct BoneTable { + bone_indices : [u16; 64], + + #[br(pad_after = 3)] + bone_count : u8 +} + +#[binread] +#[derive(Debug)] +struct BoundingBox { + min : [f32; 4], + max : [f32; 4] +} + +#[binread] +#[derive(Debug)] +struct ModelData { + header : ModelHeader, + + #[br(count = header.element_id_count)] + element_ids : Vec, + + #[br(count = 3)] + lods : Vec, + + #[br(count = header.mesh_count)] + meshes : Vec, + + #[br(count = header.attribute_count)] + attribute_name_offsets : Vec, + + // TODO: implement terrain shadow meshes + + #[br(count = header.submesh_count)] + submeshes : Vec, + + // TODO: implement terrain shadow submeshes + + #[br(count = header.material_count)] + material_name_offsets : Vec, + + #[br(count = header.bone_count)] + bone_name_offsets : Vec, + + #[br(count = header.bone_table_count)] + bone_tables : Vec, + + // TODO: implement shapes + + #[br(temp)] + submesh_bone_map_size : u32, + + #[br(count = submesh_bone_map_size / 2, err_context("lods = {:#?}", lods))] + submesh_bone_map : Vec, + + #[br(temp)] + padding_amount : u8, + + #[br(pad_before = padding_amount)] + bounding_box : BoundingBox, + model_bounding_box : BoundingBox, + water_bounding_box : BoundingBox, + vertical_fog_bounding_box : BoundingBox, + + #[br(count = header.bone_count)] + bone_bounding_boxes : Vec +} + +#[binread] +#[derive(Debug)] +struct ElementId { + element_id : u32, + parent_bone_name : u32, + translate : [f32; 3], + rotate : [f32; 3] +} + +#[binread] +#[br(repr = u8)] +#[derive(Copy, Clone, Debug, PartialEq)] +enum VertexType { + Invalid = 0, + Single3 = 2, + Single4 = 3, + UInt = 5, + ByteFloat4 = 8, + Half2 = 13, + Half4 = 14 +} + +#[binread] +#[br(repr = u8)] +#[derive(Copy, Clone, Debug)] +enum VertexUsage { + Position = 0, + BlendWeights = 1, + BlendIndices = 2, + Normal = 3, + UV = 4, + Tangent2 = 5, + Tangent1 = 6, + Color = 7, +} + +#[binread] +#[derive(Copy, Clone, Debug)] +struct VertexElement { + stream : u8, + offset : u8, + vertex_type : VertexType, + vertex_usage : VertexUsage, + #[br(pad_after = 3)] + usage_index: u8 +} + +#[derive(Clone)] +pub struct Vertex { + position : [f32; 3], + uv: [f32; 2], + normal: [f32; 3], + + bone_weight: [f32; 4], + bone_id : [u8; 4] +} + +pub struct Part { + pub vertices : Vec, + pub indices : Vec +} + +pub struct Lod { + pub parts : Vec +} + +pub struct MDL { + pub lods : Vec +} + +impl MDL { + pub fn from_existing(buffer : &MemoryBuffer) -> Option { + let mut cursor = Cursor::new(buffer); + let model_file_header = ModelFileHeader::read(&mut cursor).unwrap(); + + #[derive(Clone)] + struct VertexDeclaration { + elements : Vec + } + + let mut vertex_declarations: Vec = vec![VertexDeclaration{ elements : vec![] }; model_file_header.vertex_declaration_count as usize]; + for mut declaration in &mut vertex_declarations { + let mut element = VertexElement::read(&mut cursor).unwrap(); + + loop { + declaration.elements.push(element); + + element = VertexElement::read(&mut cursor).unwrap(); + + if element.stream == 255 { + break; + } + }; + + let to_seek = 17 * 8 - (declaration.elements.len() + 1) * 8; + println!("now seeking {to_seek}!"); + + cursor.seek(SeekFrom::Current(to_seek as i64)); + } + + let model = ModelData::read(&mut cursor).unwrap(); + + let mut lods = vec![]; + + for i in 0..model.header.lod_count { + let mut parts = vec![]; + + for j in model.lods[i as usize].mesh_index..model.lods[i as usize].mesh_index + model.lods[i as usize].mesh_count { + let declaration = &vertex_declarations[j as usize]; + let vertex_count = model.meshes[j as usize].vertex_count; + + let default_vertex = Vertex { + position: [0.0; 3], + uv: [0.0; 2], + normal: [0.0; 3], + bone_weight: [0.0; 4], + bone_id: [0u8; 4] + }; + + let mut vertices: Vec = vec![default_vertex; vertex_count as usize]; + + for k in 0..vertex_count { + for element in &declaration.elements { + cursor.seek(SeekFrom::Start((model.lods[i as usize].vertex_data_offset + + model.meshes[j as usize].vertex_buffer_offsets[element.stream as usize] + + element.offset as u32 + + model.meshes[i as usize].vertex_buffer_strides[element.stream as usize] as u32 * k as u32) as u64)); + + match element.vertex_usage { + VertexUsage::Position => { + vertices[k as usize].position = <[f32; 3]>::read(&mut cursor).unwrap(); + } + VertexUsage::BlendWeights => { + vertices[k as usize].bone_weight = <[f32; 4]>::read(&mut cursor).unwrap(); + } + VertexUsage::BlendIndices => { + vertices[k as usize].bone_id = <[u8; 4]>::read(&mut cursor).unwrap(); + } + VertexUsage::Normal => { + vertices[k as usize].normal = <[f32; 3]>::read(&mut cursor).unwrap(); + } + VertexUsage::UV => { + vertices[k as usize].uv[0] = f16::from_bits(::read(&mut cursor).unwrap()).to_f32(); + vertices[k as usize].uv[1] = f16::from_bits(::read(&mut cursor).unwrap()).to_f32(); + } + VertexUsage::Tangent2 => {} + VertexUsage::Tangent1 => {} + VertexUsage::Color => {} + } + } + } + + cursor.seek(SeekFrom::Start((model_file_header.index_offsets[i as usize] + (model.meshes[j as usize].start_index * 2)) as u64)); + + // TODO: optimize! + let mut indices : Vec = Vec::with_capacity(model.meshes[j as usize].index_count as usize); + for k in 0..model.meshes[j as usize].index_count { + indices.push(::read(&mut cursor).unwrap()); + } + + parts.push(Part { + vertices, + indices + }); + } + + lods.push(Lod { + parts + }); + } + + Some(MDL { + lods + }) + } } \ No newline at end of file