1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-04-21 12:17:45 +00:00

Implement MDL parsing

The actual vertex output is untested, but the headers parse fine it
seems. A lot of this is ported straight from C++, so more refactoring
is expected.
This commit is contained in:
Joshua Goins 2022-07-28 14:11:02 -04:00
parent c36c4b7470
commit aeed62fb4c
5 changed files with 412 additions and 8 deletions

18
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"

View file

@ -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,

View file

@ -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;

View file

@ -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 {}
impl Model {
pub fn from_existing() {}
#[binread]
#[br(repr = u8)]
#[derive(Debug)]
enum ModelFlags1 {
DustOcclusionEnabled = 0x80,
SnowOcclusionEnabled = 0x40,
RainOcclusionEnabled = 0x20,
Unknown1 = 0x10,
LightingReflectionEnabled = 0x08,
WavingAnimationDisabled = 0x04,
LightShadowDisabled = 0x02,
ShadowDisabled = 0x01
}
#[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<u8>,
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<ElementId>,
#[br(count = 3)]
lods : Vec<MeshLod>,
#[br(count = header.mesh_count)]
meshes : Vec<Mesh>,
#[br(count = header.attribute_count)]
attribute_name_offsets : Vec<u32>,
// TODO: implement terrain shadow meshes
#[br(count = header.submesh_count)]
submeshes : Vec<Submesh>,
// TODO: implement terrain shadow submeshes
#[br(count = header.material_count)]
material_name_offsets : Vec<u32>,
#[br(count = header.bone_count)]
bone_name_offsets : Vec<u32>,
#[br(count = header.bone_table_count)]
bone_tables : Vec<BoneTable>,
// 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<u16>,
#[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<BoundingBox>
}
#[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<Vertex>,
pub indices : Vec<u16>
}
pub struct Lod {
pub parts : Vec<Part>
}
pub struct MDL {
pub lods : Vec<Lod>
}
impl MDL {
pub fn from_existing(buffer : &MemoryBuffer) -> Option<MDL> {
let mut cursor = Cursor::new(buffer);
let model_file_header = ModelFileHeader::read(&mut cursor).unwrap();
#[derive(Clone)]
struct VertexDeclaration {
elements : Vec<VertexElement>
}
let mut vertex_declarations: Vec<VertexDeclaration> = 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<Vertex> = 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(<u16 as BinRead>::read(&mut cursor).unwrap()).to_f32();
vertices[k as usize].uv[1] = f16::from_bits(<u16 as BinRead>::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<u16> = 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(<u16 as BinRead>::read(&mut cursor).unwrap());
}
parts.push(Part {
vertices,
indices
});
}
lods.push(Lod {
parts
});
}
Some(MDL {
lods
})
}
}