2023-08-06 08:25:04 -04:00
|
|
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2024-04-20 13:17:11 -04:00
|
|
|
#![allow(clippy::unnecessary_fallible_conversions)] // This wrongly trips on binrw code
|
|
|
|
|
2023-12-13 17:23:53 -05:00
|
|
|
use std::io::{Cursor, Seek, SeekFrom};
|
2023-12-12 22:04:30 -05:00
|
|
|
use std::mem::size_of;
|
2023-08-06 08:25:04 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
use binrw::BinRead;
|
2022-10-20 11:45:55 -04:00
|
|
|
use binrw::BinReaderExt;
|
2024-04-20 13:18:03 -04:00
|
|
|
use binrw::{binrw, BinWrite, BinWriterExt};
|
2024-04-16 21:19:08 -04:00
|
|
|
|
2024-04-16 21:03:26 -04:00
|
|
|
use crate::common_file_operations::{read_bool_from, write_bool_as};
|
2024-04-20 13:18:03 -04:00
|
|
|
use crate::model_vertex_declarations::{
|
|
|
|
vertex_element_parser, vertex_element_writer, VertexDeclaration, VertexType, VertexUsage,
|
|
|
|
VERTEX_ELEMENT_SIZE,
|
|
|
|
};
|
|
|
|
use crate::{ByteBuffer, ByteSpan};
|
2023-11-24 08:25:45 -05:00
|
|
|
|
2024-02-25 09:01:59 -05:00
|
|
|
pub const NUM_VERTICES: u32 = 17;
|
|
|
|
|
2022-07-19 19:29:41 -04:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-10-13 16:03:46 -04:00
|
|
|
#[brw(little)]
|
2022-07-28 14:11:02 -04:00
|
|
|
pub struct ModelFileHeader {
|
2023-12-13 17:19:02 -05:00
|
|
|
pub version: u32,
|
2022-07-19 19:29:41 -04:00
|
|
|
|
|
|
|
pub stack_size: u32,
|
|
|
|
pub runtime_size: u32,
|
|
|
|
|
|
|
|
pub vertex_declaration_count: u16,
|
|
|
|
pub material_count: u16,
|
|
|
|
|
|
|
|
pub vertex_offsets: [u32; 3],
|
|
|
|
pub index_offsets: [u32; 3],
|
|
|
|
pub vertex_buffer_size: [u32; 3],
|
|
|
|
pub index_buffer_size: [u32; 3],
|
|
|
|
|
|
|
|
pub lod_count: u8,
|
|
|
|
|
2024-04-16 21:03:26 -04:00
|
|
|
#[br(map = read_bool_from::<u8>)]
|
|
|
|
#[bw(map = write_bool_as::<u8>)]
|
2022-07-19 19:29:41 -04:00
|
|
|
pub index_buffer_streaming_enabled: bool,
|
2024-04-16 21:03:26 -04:00
|
|
|
#[br(map = read_bool_from::<u8>)]
|
|
|
|
#[bw(map = write_bool_as::<u8>)]
|
2022-07-19 19:29:41 -04:00
|
|
|
#[brw(pad_after = 1)]
|
|
|
|
pub has_edge_geometry: bool,
|
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
|
|
|
#[brw(repr = u8)]
|
2024-04-20 13:18:03 -04:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-07-28 14:11:02 -04:00
|
|
|
enum ModelFlags1 {
|
|
|
|
DustOcclusionEnabled = 0x80,
|
|
|
|
SnowOcclusionEnabled = 0x40,
|
|
|
|
RainOcclusionEnabled = 0x20,
|
|
|
|
Unknown1 = 0x10,
|
|
|
|
LightingReflectionEnabled = 0x08,
|
|
|
|
WavingAnimationDisabled = 0x04,
|
|
|
|
LightShadowDisabled = 0x02,
|
2022-08-16 11:52:07 -04:00
|
|
|
ShadowDisabled = 0x01,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
|
|
|
#[brw(repr = u8)]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-07-28 14:11:02 -04:00
|
|
|
enum ModelFlags2 {
|
|
|
|
None = 0x0,
|
|
|
|
Unknown2 = 0x80,
|
|
|
|
BgUvScrollEnabled = 0x40,
|
|
|
|
EnableForceNonResident = 0x20,
|
|
|
|
ExtraLodEnabled = 0x10,
|
|
|
|
ShadowMaskEnabled = 0x08,
|
|
|
|
ForceLodRangeEnabled = 0x04,
|
|
|
|
EdgeGeometryEnabled = 0x02,
|
2022-08-16 11:52:07 -04:00
|
|
|
Unknown3 = 0x01,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2024-02-25 08:41:03 -05:00
|
|
|
#[br(import { vertex_declaration_count: u16 })]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-07-28 14:11:02 -04:00
|
|
|
pub struct ModelHeader {
|
2024-02-25 08:41:03 -05:00
|
|
|
#[br(args(vertex_declaration_count), parse_with = vertex_element_parser)]
|
|
|
|
#[bw(write_with = vertex_element_writer)]
|
|
|
|
pub vertex_declarations: Vec<VertexDeclaration>,
|
|
|
|
|
2023-11-22 21:21:01 -05:00
|
|
|
#[brw(pad_after = 2)]
|
2022-08-16 11:52:07 -04:00
|
|
|
string_count: u16,
|
|
|
|
string_size: u32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
#[br(count = string_size)]
|
2022-08-16 11:52:07 -04:00
|
|
|
strings: Vec<u8>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
radius: f32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
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,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
lod_count: u8,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
flags1: ModelFlags1,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
element_id_count: u16,
|
|
|
|
terrain_shadow_mesh_count: u8,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
flags2: ModelFlags2,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
model_clip_out_of_distance: f32,
|
|
|
|
shadow_clip_out_of_distance: f32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2024-02-25 08:54:27 -05:00
|
|
|
unknown4: u16,
|
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
terrain_shadow_submesh_count: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2024-02-25 08:54:27 -05:00
|
|
|
unknown5: u8,
|
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
bg_change_material_index: u8,
|
|
|
|
bg_crest_change_material_index: u8,
|
2024-02-25 08:54:27 -05:00
|
|
|
|
|
|
|
unknown6: u8,
|
|
|
|
unknown7: u16,
|
|
|
|
unknown8: u16,
|
|
|
|
#[brw(pad_after = 6)]
|
2024-04-20 13:18:03 -04:00
|
|
|
unknown9: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-07-28 14:11:02 -04:00
|
|
|
struct MeshLod {
|
2022-08-16 11:52:07 -04:00
|
|
|
mesh_index: u16,
|
|
|
|
mesh_count: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
model_lod_range: f32,
|
|
|
|
texture_lod_range: f32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
water_mesh_index: u16,
|
|
|
|
water_mesh_count: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
shadow_mesh_index: u16,
|
|
|
|
shadow_mesh_count: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
terrain_shadow_mesh_count: u16,
|
|
|
|
terrain_shadow_mesh_index: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
vertical_fog_mesh_index: u16,
|
|
|
|
vertical_fog_mesh_count: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
// unused on win32 according to lumina devs
|
2022-08-16 11:52:07 -04:00
|
|
|
edge_geometry_size: u32,
|
|
|
|
edge_geometry_data_offset: u32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2023-11-22 21:21:01 -05:00
|
|
|
#[brw(pad_after = 4)]
|
2022-08-16 11:52:07 -04:00
|
|
|
polygon_count: u32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
vertex_buffer_size: u32,
|
|
|
|
index_buffer_size: u32,
|
|
|
|
vertex_data_offset: u32,
|
|
|
|
index_data_offset: u32,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-07-28 14:11:02 -04:00
|
|
|
struct Mesh {
|
2023-11-22 21:21:01 -05:00
|
|
|
#[brw(pad_after = 2)]
|
2022-08-16 11:52:07 -04:00
|
|
|
vertex_count: u16,
|
|
|
|
index_count: u32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
material_index: u16,
|
|
|
|
submesh_index: u16,
|
|
|
|
submesh_count: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
bone_table_index: u16,
|
|
|
|
start_index: u32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
vertex_buffer_offsets: [u32; 3],
|
|
|
|
vertex_buffer_strides: [u8; 3],
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
vertex_stream_count: u8,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-07-28 14:11:02 -04:00
|
|
|
struct Submesh {
|
2023-12-12 21:43:04 -05:00
|
|
|
index_offset: u32,
|
|
|
|
index_count: u32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
attribute_index_mask: u32,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
bone_start_index: u16,
|
|
|
|
bone_count: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-07-28 14:11:02 -04:00
|
|
|
struct BoneTable {
|
2022-08-16 11:52:07 -04:00
|
|
|
bone_indices: [u16; 64],
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2023-11-22 21:21:01 -05:00
|
|
|
#[brw(pad_after = 3)]
|
2022-08-16 11:52:07 -04:00
|
|
|
bone_count: u8,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2024-04-17 21:26:50 -04:00
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
#[allow(dead_code)]
|
|
|
|
struct BoneTableV2 {
|
|
|
|
#[br(pad_before = 2)]
|
|
|
|
bone_count: u16,
|
|
|
|
|
|
|
|
#[br(count = bone_count)]
|
|
|
|
bone_indices: Vec<u16>,
|
|
|
|
|
|
|
|
// align to 4 bytes
|
|
|
|
// TODO: use br align_to?
|
|
|
|
#[br(if(bone_count % 2 == 0))]
|
2024-04-20 13:18:03 -04:00
|
|
|
padding: u16,
|
2024-04-17 21:26:50 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-07-28 14:11:02 -04:00
|
|
|
struct BoundingBox {
|
2022-08-16 11:52:07 -04:00
|
|
|
min: [f32; 4],
|
|
|
|
max: [f32; 4],
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2024-02-02 14:19:36 -05:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2024-02-02 14:19:36 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
struct TerrainShadowMesh {
|
|
|
|
index_count: u32,
|
|
|
|
start_index: u32,
|
|
|
|
vertex_buffer_offset: u32,
|
|
|
|
vertex_count: u16,
|
|
|
|
submesh_index: u16,
|
|
|
|
submesh_count: u16,
|
|
|
|
vertex_buffer_stride: u8,
|
2024-04-20 13:18:03 -04:00
|
|
|
padding: u8,
|
2024-02-02 14:19:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2024-02-02 14:19:36 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
struct TerrainShadowSubmesh {
|
|
|
|
index_offset: u32,
|
|
|
|
index_count: u32,
|
|
|
|
unknown1: u16,
|
2024-04-20 13:18:03 -04:00
|
|
|
unknown2: u16,
|
2024-02-02 14:19:36 -05:00
|
|
|
}
|
|
|
|
|
2024-02-25 09:35:18 -05:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2024-02-25 09:35:18 -05:00
|
|
|
#[allow(dead_code)]
|
2024-03-02 17:23:55 -05:00
|
|
|
struct ShapeStruct {
|
2024-02-25 09:35:18 -05:00
|
|
|
string_offset: u32,
|
|
|
|
shape_mesh_start_index: [u16; 3],
|
2024-04-20 13:18:03 -04:00
|
|
|
shape_mesh_count: [u16; 3],
|
2024-02-25 09:35:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
2024-04-20 13:18:03 -04:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2024-02-25 09:35:18 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
struct ShapeMesh {
|
|
|
|
mesh_index_offset: u32,
|
|
|
|
shape_value_count: u32,
|
2024-04-20 13:18:03 -04:00
|
|
|
shape_value_offset: u32,
|
2024-02-25 09:35:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2024-02-25 09:35:18 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
struct ShapeValue {
|
|
|
|
base_indices_index: u16,
|
2024-04-20 13:18:03 -04:00
|
|
|
replacing_vertex_index: u16,
|
2024-02-25 09:35:18 -05:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2024-02-25 08:41:03 -05:00
|
|
|
#[br(import {file_header: &ModelFileHeader})]
|
2022-10-13 17:11:03 -04:00
|
|
|
#[brw(little)]
|
2022-07-28 14:11:02 -04:00
|
|
|
struct ModelData {
|
2024-02-25 08:41:03 -05:00
|
|
|
#[br(args { vertex_declaration_count: file_header.vertex_declaration_count })]
|
2022-08-16 11:52:07 -04:00
|
|
|
header: ModelHeader,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
#[br(count = header.element_id_count)]
|
2022-08-16 11:52:07 -04:00
|
|
|
element_ids: Vec<ElementId>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
#[br(count = 3)]
|
2022-08-16 11:52:07 -04:00
|
|
|
lods: Vec<MeshLod>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
#[br(count = header.mesh_count)]
|
2022-08-16 11:52:07 -04:00
|
|
|
meshes: Vec<Mesh>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
#[br(count = header.attribute_count)]
|
2022-08-16 11:52:07 -04:00
|
|
|
attribute_name_offsets: Vec<u32>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2024-02-02 14:19:36 -05:00
|
|
|
#[br(count = header.terrain_shadow_mesh_count)]
|
|
|
|
terrain_shadow_meshes: Vec<TerrainShadowMesh>,
|
|
|
|
|
2022-07-28 14:11:02 -04:00
|
|
|
#[br(count = header.submesh_count)]
|
2022-08-16 11:52:07 -04:00
|
|
|
submeshes: Vec<Submesh>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2024-02-02 14:19:36 -05:00
|
|
|
#[br(count = header.terrain_shadow_submesh_count)]
|
|
|
|
terrain_shadow_submeshes: Vec<TerrainShadowSubmesh>,
|
|
|
|
|
2022-07-28 14:11:02 -04:00
|
|
|
#[br(count = header.material_count)]
|
2022-08-16 11:52:07 -04:00
|
|
|
material_name_offsets: Vec<u32>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
#[br(count = header.bone_count)]
|
2022-08-16 11:52:07 -04:00
|
|
|
bone_name_offsets: Vec<u32>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
#[br(count = header.bone_table_count)]
|
2024-04-17 21:26:50 -04:00
|
|
|
#[br(if(file_header.version <= 0x1000005))]
|
2022-08-16 11:52:07 -04:00
|
|
|
bone_tables: Vec<BoneTable>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2024-04-17 21:26:50 -04:00
|
|
|
#[br(count = header.bone_table_count)]
|
2024-04-17 21:59:23 -04:00
|
|
|
#[br(if(file_header.version >= 0x1000006))]
|
2024-04-17 21:26:50 -04:00
|
|
|
bone_tables_v2: Vec<BoneTableV2>,
|
|
|
|
|
2024-02-25 09:35:18 -05:00
|
|
|
#[br(count = header.shape_count)]
|
2024-03-02 17:23:55 -05:00
|
|
|
shapes: Vec<ShapeStruct>,
|
2024-02-25 09:35:18 -05:00
|
|
|
|
|
|
|
#[br(count = header.shape_mesh_count)]
|
|
|
|
shape_meshes: Vec<ShapeMesh>,
|
|
|
|
|
|
|
|
#[br(count = header.shape_value_count)]
|
|
|
|
shape_values: Vec<ShapeValue>,
|
|
|
|
|
2024-04-17 21:59:23 -04:00
|
|
|
// TODO: try to unify these fields?
|
|
|
|
#[br(if(file_header.version <= 0x1000005))]
|
|
|
|
submesh_bone_map_size: u32,
|
|
|
|
|
|
|
|
// hehe, Dawntrail made this u16 instead of u32. fun?
|
|
|
|
#[br(if(file_header.version >= 0x1000006))]
|
|
|
|
submesh_bone_map_size_v2: u16,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2024-04-17 21:59:23 -04:00
|
|
|
#[br(count = if file_header.version >= 0x1000006 { (submesh_bone_map_size_v2 / 2) as u32 } else { submesh_bone_map_size / 2 } )]
|
2022-08-16 11:52:07 -04:00
|
|
|
submesh_bone_map: Vec<u16>,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
padding_amount: u8,
|
2024-01-29 17:05:57 -05:00
|
|
|
#[br(count = padding_amount)]
|
|
|
|
unknown_padding: Vec<u8>,
|
|
|
|
|
2024-04-17 21:59:23 -04:00
|
|
|
#[br(dbg)]
|
|
|
|
// TODO: these are still wrong on Dawntrail!
|
2022-08-16 11:52:07 -04:00
|
|
|
bounding_box: BoundingBox,
|
|
|
|
model_bounding_box: BoundingBox,
|
|
|
|
water_bounding_box: BoundingBox,
|
|
|
|
vertical_fog_bounding_box: BoundingBox,
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
#[br(count = header.bone_count)]
|
2022-08-16 11:52:07 -04:00
|
|
|
bone_bounding_boxes: Vec<BoundingBox>,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-04-20 13:18:03 -04:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-07-28 14:11:02 -04:00
|
|
|
struct ElementId {
|
2022-08-16 11:52:07 -04:00
|
|
|
element_id: u32,
|
|
|
|
parent_bone_name: u32,
|
|
|
|
translate: [f32; 3],
|
|
|
|
rotate: [f32; 3],
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
2022-07-28 15:20:47 -04:00
|
|
|
#[repr(C)]
|
2022-07-28 14:11:02 -04:00
|
|
|
pub struct Vertex {
|
2022-08-16 11:52:07 -04:00
|
|
|
pub position: [f32; 3],
|
2023-11-24 08:07:30 -05:00
|
|
|
pub uv0: [f32; 2],
|
|
|
|
pub uv1: [f32; 2],
|
2022-07-28 15:20:47 -04:00
|
|
|
pub normal: [f32; 3],
|
2023-12-17 18:58:48 -05:00
|
|
|
pub bitangent: [f32; 4],
|
|
|
|
//pub bitangent1: [f32; 4], // TODO: need to figure out what the heck this could be
|
2023-11-24 08:07:30 -05:00
|
|
|
pub color: [f32; 4],
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2022-07-28 15:20:47 -04:00
|
|
|
pub bone_weight: [f32; 4],
|
2022-08-16 11:52:07 -04:00
|
|
|
pub bone_id: [u8; 4],
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2024-01-29 16:01:58 -05:00
|
|
|
impl Default for Vertex {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
position: [0.0; 3],
|
|
|
|
uv0: [0.0; 2],
|
|
|
|
uv1: [0.0; 2],
|
|
|
|
normal: [0.0; 3],
|
|
|
|
bitangent: [0.0; 4],
|
|
|
|
color: [0.0; 4],
|
|
|
|
bone_weight: [0.0; 4],
|
|
|
|
bone_id: [0u8; 4],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-05 17:08:07 -05:00
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
#[repr(C)]
|
|
|
|
pub struct NewShapeValue {
|
|
|
|
pub base_index: u32,
|
2024-04-20 13:18:03 -04:00
|
|
|
pub replacing_vertex: Vertex,
|
2024-03-05 17:08:07 -05:00
|
|
|
}
|
|
|
|
|
2023-12-12 21:43:04 -05:00
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
#[repr(C)]
|
|
|
|
pub struct SubMesh {
|
|
|
|
submesh_index: usize,
|
|
|
|
pub index_count: u32,
|
2024-04-20 13:18:03 -04:00
|
|
|
pub index_offset: u32,
|
2023-12-12 21:43:04 -05:00
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:55 -05:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Shape {
|
|
|
|
pub name: String,
|
2024-04-20 13:18:03 -04:00
|
|
|
pub morphed_vertices: Vec<Vertex>,
|
2024-03-02 17:23:55 -05:00
|
|
|
}
|
|
|
|
|
2024-03-01 15:47:47 -05:00
|
|
|
/// Corresponds to a "Mesh" in an LOD
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Clone)]
|
2022-07-28 14:11:02 -04:00
|
|
|
pub struct Part {
|
2023-09-22 17:42:04 -04:00
|
|
|
mesh_index: u16,
|
2022-08-16 11:52:07 -04:00
|
|
|
pub vertices: Vec<Vertex>,
|
|
|
|
pub indices: Vec<u16>,
|
2023-12-12 21:43:04 -05:00
|
|
|
pub material_index: u16,
|
2024-03-02 17:23:55 -05:00
|
|
|
pub submeshes: Vec<SubMesh>,
|
2024-04-20 13:18:03 -04:00
|
|
|
pub shapes: Vec<Shape>,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Clone)]
|
2022-07-28 14:11:02 -04:00
|
|
|
pub struct Lod {
|
2022-08-16 11:52:07 -04:00
|
|
|
pub parts: Vec<Part>,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2024-02-25 10:25:54 -05:00
|
|
|
#[derive(Clone)]
|
2022-07-28 14:11:02 -04:00
|
|
|
pub struct MDL {
|
2023-09-22 16:44:19 -04:00
|
|
|
file_header: ModelFileHeader,
|
2023-09-22 17:04:48 -04:00
|
|
|
model_data: ModelData,
|
2023-09-22 16:44:19 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
pub lods: Vec<Lod>,
|
|
|
|
pub affected_bone_names: Vec<String>,
|
2024-03-02 17:23:55 -05:00
|
|
|
pub material_names: Vec<String>,
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MDL {
|
2023-10-13 16:16:04 -04:00
|
|
|
pub fn from_existing(buffer: ByteSpan) -> Option<MDL> {
|
2022-07-28 14:11:02 -04:00
|
|
|
let mut cursor = Cursor::new(buffer);
|
2024-04-16 22:05:39 -04:00
|
|
|
let model_file_header = ModelFileHeader::read(&mut cursor).ok()?;
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
let model = ModelData::read_args(
|
|
|
|
&mut cursor,
|
|
|
|
binrw::args! { file_header: &model_file_header },
|
|
|
|
)
|
|
|
|
.ok()?;
|
|
|
|
|
2022-08-10 14:51:50 -04:00
|
|
|
let mut affected_bone_names = vec![];
|
|
|
|
|
2023-09-22 17:04:48 -04:00
|
|
|
for offset in &model.bone_name_offsets {
|
|
|
|
let mut offset = *offset;
|
2022-08-10 14:51:50 -04:00
|
|
|
let mut string = String::new();
|
|
|
|
|
|
|
|
let mut next_char = model.header.strings[offset as usize] as char;
|
|
|
|
while next_char != '\0' {
|
|
|
|
string.push(next_char);
|
|
|
|
offset += 1;
|
|
|
|
next_char = model.header.strings[offset as usize] as char;
|
|
|
|
}
|
|
|
|
|
|
|
|
affected_bone_names.push(string);
|
|
|
|
}
|
|
|
|
|
2023-03-31 21:31:03 -04:00
|
|
|
let mut material_names = vec![];
|
|
|
|
|
2023-09-22 17:04:48 -04:00
|
|
|
for offset in &model.material_name_offsets {
|
|
|
|
let mut offset = *offset;
|
2023-03-31 21:31:03 -04:00
|
|
|
let mut string = String::new();
|
|
|
|
|
|
|
|
let mut next_char = model.header.strings[offset as usize] as char;
|
|
|
|
while next_char != '\0' {
|
|
|
|
string.push(next_char);
|
|
|
|
offset += 1;
|
|
|
|
next_char = model.header.strings[offset as usize] as char;
|
|
|
|
}
|
|
|
|
|
|
|
|
material_names.push(string);
|
|
|
|
}
|
|
|
|
|
2022-07-28 14:11:02 -04:00
|
|
|
let mut lods = vec![];
|
|
|
|
|
|
|
|
for i in 0..model.header.lod_count {
|
|
|
|
let mut parts = vec![];
|
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
for j in model.lods[i as usize].mesh_index
|
|
|
|
..model.lods[i as usize].mesh_index + model.lods[i as usize].mesh_count
|
|
|
|
{
|
2024-02-25 08:41:03 -05:00
|
|
|
let declaration = &model.header.vertex_declarations[j as usize];
|
2022-07-28 14:11:02 -04:00
|
|
|
let vertex_count = model.meshes[j as usize].vertex_count;
|
2023-03-31 21:31:15 -04:00
|
|
|
let material_index = model.meshes[j as usize].material_index;
|
2022-07-28 14:11:02 -04:00
|
|
|
|
2024-01-29 16:01:58 -05:00
|
|
|
let mut vertices: Vec<Vertex> = vec![Vertex::default(); vertex_count as usize];
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
for k in 0..vertex_count {
|
|
|
|
for element in &declaration.elements {
|
2022-08-16 11:52:07 -04:00
|
|
|
cursor
|
|
|
|
.seek(SeekFrom::Start(
|
|
|
|
(model.lods[i as usize].vertex_data_offset
|
|
|
|
+ model.meshes[j as usize].vertex_buffer_offsets
|
2024-04-20 13:18:03 -04:00
|
|
|
[element.stream as usize]
|
2022-08-16 11:52:07 -04:00
|
|
|
+ element.offset as u32
|
2023-12-12 21:43:04 -05:00
|
|
|
+ model.meshes[j as usize].vertex_buffer_strides
|
2024-04-20 13:18:03 -04:00
|
|
|
[element.stream as usize]
|
|
|
|
as u32
|
|
|
|
* k as u32) as u64,
|
2022-08-16 11:52:07 -04:00
|
|
|
))
|
|
|
|
.ok()?;
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
match element.vertex_usage {
|
2024-04-20 13:18:03 -04:00
|
|
|
VertexUsage::Position => match element.vertex_type {
|
|
|
|
VertexType::Single4 => {
|
|
|
|
vertices[k as usize].position.clone_from_slice(
|
|
|
|
&MDL::read_single4(&mut cursor).unwrap()[0..3],
|
|
|
|
);
|
2023-07-05 19:42:53 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
VertexType::Half4 => {
|
|
|
|
vertices[k as usize].position.clone_from_slice(
|
|
|
|
&MDL::read_half4(&mut cursor).unwrap()[0..3],
|
|
|
|
);
|
2023-07-05 19:42:53 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
VertexType::Single3 => {
|
|
|
|
vertices[k as usize].position =
|
|
|
|
MDL::read_single3(&mut cursor).unwrap();
|
2023-07-05 19:42:53 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for position: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
2022-10-17 19:24:02 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
},
|
|
|
|
VertexUsage::BlendWeights => match element.vertex_type {
|
|
|
|
VertexType::ByteFloat4 => {
|
|
|
|
vertices[k as usize].bone_weight =
|
|
|
|
MDL::read_byte_float4(&mut cursor).unwrap();
|
|
|
|
}
|
|
|
|
VertexType::Byte4 => {
|
|
|
|
let bytes = MDL::read_byte4(&mut cursor).unwrap();
|
|
|
|
vertices[k as usize].bone_weight = [
|
|
|
|
f32::from(bytes[0]),
|
|
|
|
f32::from(bytes[1]),
|
|
|
|
f32::from(bytes[2]),
|
|
|
|
f32::from(bytes[3]),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
VertexType::UnsignedShort4 => {
|
|
|
|
let bytes = MDL::read_unsigned_short4(&mut cursor).unwrap();
|
|
|
|
vertices[k as usize].bone_weight = [
|
|
|
|
f32::from(bytes[0]),
|
|
|
|
f32::from(bytes[1]),
|
|
|
|
f32::from(bytes[2]),
|
|
|
|
f32::from(bytes[3]),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for blendweight: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
VertexUsage::BlendIndices => match element.vertex_type {
|
|
|
|
VertexType::Byte4 => {
|
|
|
|
vertices[k as usize].bone_id =
|
|
|
|
MDL::read_byte4(&mut cursor).unwrap();
|
|
|
|
}
|
|
|
|
VertexType::UnsignedShort4 => {
|
|
|
|
let shorts = MDL::read_unsigned_short4(&mut cursor).unwrap();
|
|
|
|
vertices[k as usize].bone_id = [
|
|
|
|
shorts[0] as u8,
|
|
|
|
shorts[1] as u8,
|
|
|
|
shorts[2] as u8,
|
|
|
|
shorts[3] as u8,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for blendindice: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
VertexUsage::Normal => match element.vertex_type {
|
|
|
|
VertexType::Half4 => {
|
|
|
|
vertices[k as usize].normal.clone_from_slice(
|
|
|
|
&MDL::read_half4(&mut cursor).unwrap()[0..3],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
VertexType::Single3 => {
|
|
|
|
vertices[k as usize].normal =
|
|
|
|
MDL::read_single3(&mut cursor).unwrap();
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for normal: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
VertexUsage::UV => match element.vertex_type {
|
|
|
|
VertexType::ByteFloat4 => {
|
|
|
|
let combined = MDL::read_byte_float4(&mut cursor).unwrap();
|
2024-02-02 14:19:36 -05:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
vertices[k as usize].uv0.clone_from_slice(&combined[0..2]);
|
|
|
|
vertices[k as usize].uv1.clone_from_slice(&combined[2..4]);
|
|
|
|
}
|
|
|
|
VertexType::Half4 => {
|
|
|
|
let combined = MDL::read_half4(&mut cursor).unwrap();
|
2023-11-24 08:07:30 -05:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
vertices[k as usize].uv0.clone_from_slice(&combined[0..2]);
|
|
|
|
vertices[k as usize].uv1.clone_from_slice(&combined[2..4]);
|
|
|
|
}
|
|
|
|
VertexType::Single4 => {
|
|
|
|
let combined = MDL::read_single4(&mut cursor).unwrap();
|
2023-11-24 08:07:30 -05:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
vertices[k as usize].uv0.clone_from_slice(&combined[0..2]);
|
|
|
|
vertices[k as usize].uv1.clone_from_slice(&combined[2..4]);
|
|
|
|
}
|
|
|
|
VertexType::Half2 => {
|
|
|
|
let combined = MDL::read_half2(&mut cursor).unwrap();
|
2024-02-02 14:19:36 -05:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
vertices[k as usize].uv0.clone_from_slice(&combined[0..2]);
|
2022-10-17 19:24:02 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for uv: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
2023-11-24 08:07:30 -05:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
},
|
|
|
|
VertexUsage::BiTangent => match element.vertex_type {
|
|
|
|
VertexType::ByteFloat4 => {
|
|
|
|
vertices[k as usize].bitangent =
|
|
|
|
MDL::read_tangent(&mut cursor).unwrap();
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for bitangent: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
2023-12-17 18:58:48 -05:00
|
|
|
VertexUsage::Tangent => {
|
2023-11-24 08:07:30 -05:00
|
|
|
match element.vertex_type {
|
2024-02-02 14:19:36 -05:00
|
|
|
// Used for... terrain..?
|
|
|
|
VertexType::ByteFloat4 => {}
|
2023-11-24 08:07:30 -05:00
|
|
|
_ => {
|
2024-04-20 13:18:03 -04:00
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for tangent: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
2023-11-24 08:07:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
VertexUsage::Color => match element.vertex_type {
|
|
|
|
VertexType::ByteFloat4 => {
|
|
|
|
vertices[k as usize].color =
|
|
|
|
MDL::read_byte_float4(&mut cursor).unwrap();
|
2023-11-24 08:07:30 -05:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for color: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
cursor
|
|
|
|
.seek(SeekFrom::Start(
|
|
|
|
(model_file_header.index_offsets[i as usize]
|
2023-12-12 22:04:30 -05:00
|
|
|
+ (model.meshes[j as usize].start_index * size_of::<u16>() as u32))
|
2022-08-16 11:52:07 -04:00
|
|
|
as u64,
|
|
|
|
))
|
|
|
|
.ok()?;
|
2022-07-28 14:11:02 -04:00
|
|
|
|
|
|
|
// TODO: optimize!
|
2022-08-16 11:52:07 -04:00
|
|
|
let mut indices: Vec<u16> =
|
|
|
|
Vec::with_capacity(model.meshes[j as usize].index_count as usize);
|
2022-08-06 18:07:42 -04:00
|
|
|
for _ in 0..model.meshes[j as usize].index_count {
|
2022-10-17 19:24:02 -04:00
|
|
|
indices.push(cursor.read_le::<u16>().ok()?);
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
let mut submeshes: Vec<SubMesh> =
|
|
|
|
Vec::with_capacity(model.meshes[j as usize].submesh_count as usize);
|
2023-12-12 21:43:04 -05:00
|
|
|
for i in 0..model.meshes[j as usize].submesh_count {
|
|
|
|
submeshes.push(SubMesh {
|
|
|
|
submesh_index: model.meshes[j as usize].submesh_index as usize + i as usize,
|
2024-04-20 13:18:03 -04:00
|
|
|
index_count: model.submeshes
|
|
|
|
[model.meshes[j as usize].submesh_index as usize + i as usize]
|
|
|
|
.index_count,
|
|
|
|
index_offset: model.submeshes
|
|
|
|
[model.meshes[j as usize].submesh_index as usize + i as usize]
|
|
|
|
.index_offset,
|
2023-12-12 21:43:04 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:55 -05:00
|
|
|
let mut shapes = vec![];
|
|
|
|
|
|
|
|
for shape in &model.shapes {
|
|
|
|
// Adapted from https://github.com/xivdev/Penumbra/blob/master/Penumbra/Import/Models/Export/MeshExporter.cs
|
2024-04-20 13:18:03 -04:00
|
|
|
let affected_shape_mesh: Vec<&ShapeMesh> = model
|
|
|
|
.shape_meshes
|
|
|
|
.iter()
|
2024-03-02 17:23:55 -05:00
|
|
|
.skip(shape.shape_mesh_start_index[i as usize] as usize)
|
|
|
|
.take(shape.shape_mesh_count[i as usize] as usize)
|
2024-04-20 13:18:03 -04:00
|
|
|
.filter(|shape_mesh| {
|
|
|
|
shape_mesh.mesh_index_offset == model.meshes[j as usize].start_index
|
|
|
|
})
|
|
|
|
.collect();
|
2024-03-02 17:23:55 -05:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
let shape_values: Vec<&ShapeValue> = affected_shape_mesh
|
|
|
|
.iter()
|
|
|
|
.flat_map(|shape_mesh| {
|
|
|
|
model
|
|
|
|
.shape_values
|
|
|
|
.iter()
|
|
|
|
.skip(shape_mesh.shape_value_offset as usize)
|
|
|
|
.take(shape_mesh.shape_value_count as usize)
|
|
|
|
})
|
|
|
|
.filter(|shape_value| {
|
|
|
|
shape_value.base_indices_index
|
|
|
|
>= model.meshes[j as usize].start_index as u16
|
|
|
|
&& shape_value.base_indices_index
|
|
|
|
< (model.meshes[j as usize].start_index
|
|
|
|
+ model.meshes[j as usize].index_count)
|
|
|
|
as u16
|
|
|
|
})
|
2024-03-02 17:23:55 -05:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
let mut morphed_vertices = vec![Vertex::default(); vertices.len()];
|
|
|
|
|
|
|
|
if !shape_values.is_empty() {
|
|
|
|
for shape_value in shape_values {
|
2024-04-20 13:18:03 -04:00
|
|
|
let old_vertex =
|
|
|
|
vertices[indices[shape_value.base_indices_index as usize] as usize];
|
|
|
|
let new_vertex = vertices[shape_value.replacing_vertex_index as usize
|
|
|
|
- model.meshes[j as usize].start_index as usize];
|
|
|
|
let vertex = &mut morphed_vertices
|
|
|
|
[indices[shape_value.base_indices_index as usize] as usize];
|
2024-03-02 17:23:55 -05:00
|
|
|
|
|
|
|
vertex.position[0] = new_vertex.position[0] - old_vertex.position[0];
|
|
|
|
vertex.position[1] = new_vertex.position[1] - old_vertex.position[1];
|
|
|
|
vertex.position[2] = new_vertex.position[2] - old_vertex.position[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut offset = shape.string_offset;
|
|
|
|
let mut string = String::new();
|
|
|
|
|
|
|
|
let mut next_char = model.header.strings[offset as usize] as char;
|
|
|
|
while next_char != '\0' {
|
|
|
|
string.push(next_char);
|
|
|
|
offset += 1;
|
|
|
|
next_char = model.header.strings[offset as usize] as char;
|
|
|
|
}
|
|
|
|
|
|
|
|
shapes.push(Shape {
|
|
|
|
name: string,
|
2024-04-20 13:18:03 -04:00
|
|
|
morphed_vertices,
|
2024-03-02 17:23:55 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
parts.push(Part {
|
|
|
|
mesh_index: j,
|
|
|
|
vertices,
|
|
|
|
indices,
|
|
|
|
material_index,
|
|
|
|
submeshes,
|
|
|
|
shapes,
|
|
|
|
});
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
lods.push(Lod { parts });
|
2022-07-28 14:11:02 -04:00
|
|
|
}
|
2022-07-19 19:29:41 -04:00
|
|
|
|
2022-07-28 14:11:02 -04:00
|
|
|
Some(MDL {
|
2023-09-22 16:44:19 -04:00
|
|
|
file_header: model_file_header,
|
2023-09-22 17:04:48 -04:00
|
|
|
model_data: model,
|
2022-08-10 14:51:50 -04:00
|
|
|
lods,
|
2022-08-16 11:52:07 -04:00
|
|
|
affected_bone_names,
|
2024-04-20 13:18:03 -04:00
|
|
|
material_names,
|
2022-07-28 14:11:02 -04:00
|
|
|
})
|
|
|
|
}
|
2023-07-05 19:42:53 -04:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
pub fn replace_vertices(
|
|
|
|
&mut self,
|
|
|
|
lod_index: usize,
|
|
|
|
part_index: usize,
|
|
|
|
vertices: &[Vertex],
|
|
|
|
indices: &[u16],
|
|
|
|
submeshes: &[SubMesh],
|
|
|
|
) {
|
2023-12-09 14:45:00 -05:00
|
|
|
let part = &mut self.lods[lod_index].parts[part_index];
|
|
|
|
|
2023-12-09 17:18:02 -05:00
|
|
|
part.vertices = Vec::from(vertices);
|
|
|
|
part.indices = Vec::from(indices);
|
|
|
|
|
2023-12-12 21:43:04 -05:00
|
|
|
for (i, submesh) in part.submeshes.iter().enumerate() {
|
|
|
|
if i < submeshes.len() {
|
2024-04-20 13:18:03 -04:00
|
|
|
self.model_data.submeshes[submesh.submesh_index].index_offset =
|
|
|
|
submeshes[i].index_offset;
|
|
|
|
self.model_data.submeshes[submesh.submesh_index].index_count =
|
|
|
|
submeshes[i].index_count;
|
2023-12-12 21:43:04 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-09 17:18:02 -05:00
|
|
|
// Update vertex count in header
|
|
|
|
self.model_data.meshes[part.mesh_index as usize].vertex_count = part.vertices.len() as u16;
|
2023-12-12 21:43:04 -05:00
|
|
|
self.model_data.meshes[part.mesh_index as usize].index_count = part.indices.len() as u32;
|
2023-12-10 14:52:49 -05:00
|
|
|
|
2024-02-25 10:25:54 -05:00
|
|
|
self.update_headers();
|
|
|
|
}
|
|
|
|
|
2024-03-05 17:08:07 -05:00
|
|
|
pub fn remove_shape_meshes(&mut self) {
|
|
|
|
self.model_data.shape_meshes.clear();
|
|
|
|
self.model_data.shape_values.clear();
|
|
|
|
|
|
|
|
for lod in 0..3 {
|
|
|
|
for shape in &mut self.model_data.shapes {
|
|
|
|
shape.shape_mesh_count[lod] = 0;
|
|
|
|
shape.shape_mesh_start_index[lod] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.update_headers();
|
|
|
|
}
|
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
pub fn add_shape_mesh(
|
|
|
|
&mut self,
|
|
|
|
lod_index: usize,
|
|
|
|
shape_index: usize,
|
|
|
|
shape_mesh_index: usize,
|
|
|
|
part_index: usize,
|
|
|
|
shape_values: &[NewShapeValue],
|
|
|
|
) {
|
2024-03-05 17:08:07 -05:00
|
|
|
let part = &mut self.lods[lod_index].parts[part_index];
|
|
|
|
|
|
|
|
// TODO: this is assuming they are added in order
|
|
|
|
if shape_mesh_index == 0 {
|
2024-04-20 13:18:03 -04:00
|
|
|
self.model_data.shapes[shape_index].shape_mesh_start_index[lod_index] =
|
|
|
|
self.model_data.shape_meshes.len() as u16;
|
2024-03-05 17:08:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
self.model_data.shape_meshes.push(ShapeMesh {
|
|
|
|
mesh_index_offset: self.model_data.meshes[part.mesh_index as usize].start_index,
|
|
|
|
shape_value_count: shape_values.len() as u32,
|
2024-04-20 13:18:03 -04:00
|
|
|
shape_value_offset: self.model_data.shape_values.len() as u32,
|
2024-03-05 17:08:07 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
for shape_value in shape_values {
|
|
|
|
part.vertices.push(shape_value.replacing_vertex);
|
|
|
|
|
|
|
|
self.model_data.shape_values.push(ShapeValue {
|
2024-04-20 13:18:03 -04:00
|
|
|
base_indices_index: self.model_data.meshes[part.mesh_index as usize].start_index
|
|
|
|
as u16
|
|
|
|
+ shape_value.base_index as u16,
|
|
|
|
replacing_vertex_index: self.model_data.meshes[part.mesh_index as usize].start_index
|
|
|
|
as u16
|
|
|
|
+ (part.vertices.len() - 1) as u16,
|
2024-03-05 17:08:07 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
self.model_data.shapes[shape_index].shape_mesh_count[lod_index] += 1;
|
|
|
|
|
|
|
|
self.update_headers();
|
|
|
|
}
|
|
|
|
|
2024-02-25 10:25:54 -05:00
|
|
|
pub(crate) fn update_headers(&mut self) {
|
2023-12-10 14:52:49 -05:00
|
|
|
// update values
|
|
|
|
for i in 0..self.file_header.lod_count {
|
|
|
|
let mut vertex_offset = 0;
|
|
|
|
|
|
|
|
for j in self.model_data.lods[i as usize].mesh_index
|
2024-04-20 13:18:03 -04:00
|
|
|
..self.model_data.lods[i as usize].mesh_index
|
|
|
|
+ self.model_data.lods[i as usize].mesh_count
|
2023-12-10 14:52:49 -05:00
|
|
|
{
|
|
|
|
let mesh = &mut self.model_data.meshes[j as usize];
|
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
mesh.start_index =
|
|
|
|
self.model_data.submeshes[mesh.submesh_index as usize].index_offset;
|
2023-12-12 21:43:04 -05:00
|
|
|
|
2023-12-10 14:52:49 -05:00
|
|
|
for i in 0..mesh.vertex_stream_count as usize {
|
|
|
|
mesh.vertex_buffer_offsets[i] = vertex_offset;
|
2024-04-20 13:18:03 -04:00
|
|
|
vertex_offset +=
|
|
|
|
mesh.vertex_count as u32 * mesh.vertex_buffer_strides[i] as u32;
|
2023-12-10 14:52:49 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for lod in &mut self.model_data.lods {
|
|
|
|
let mut total_vertex_buffer_size = 0;
|
2023-12-12 21:43:04 -05:00
|
|
|
let mut total_index_buffer_size = 0;
|
2023-12-10 14:52:49 -05:00
|
|
|
|
|
|
|
// still slightly off?
|
2024-04-20 13:18:03 -04:00
|
|
|
for j in lod.mesh_index..lod.mesh_index + lod.mesh_count {
|
2023-12-10 14:52:49 -05:00
|
|
|
let vertex_count = self.model_data.meshes[j as usize].vertex_count;
|
2023-12-12 21:43:04 -05:00
|
|
|
let index_count = self.model_data.meshes[j as usize].index_count;
|
2023-12-10 14:52:49 -05:00
|
|
|
|
|
|
|
let mut total_vertex_stride: u32 = 0;
|
|
|
|
for i in 0..self.model_data.meshes[j as usize].vertex_stream_count as usize {
|
2024-04-20 13:18:03 -04:00
|
|
|
total_vertex_stride +=
|
|
|
|
self.model_data.meshes[j as usize].vertex_buffer_strides[i] as u32;
|
2023-12-10 14:52:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
total_vertex_buffer_size += vertex_count as u32 * total_vertex_stride;
|
2023-12-12 22:04:30 -05:00
|
|
|
total_index_buffer_size += index_count * size_of::<u16>() as u32;
|
2023-12-12 21:43:04 -05:00
|
|
|
}
|
|
|
|
|
2024-02-25 10:25:54 -05:00
|
|
|
// TODO: this can definitely be written better
|
|
|
|
let mut index_padding = total_index_buffer_size % 16;
|
|
|
|
if index_padding == 0 {
|
|
|
|
index_padding = 16;
|
|
|
|
} else {
|
|
|
|
index_padding = 16 - index_padding;
|
2023-12-10 14:52:49 -05:00
|
|
|
}
|
|
|
|
|
2023-12-12 21:43:04 -05:00
|
|
|
lod.vertex_buffer_size = total_vertex_buffer_size;
|
2024-02-25 10:25:54 -05:00
|
|
|
lod.index_buffer_size = total_index_buffer_size.wrapping_add(index_padding);
|
2023-12-10 14:52:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// update lod values
|
2023-12-13 16:51:40 -05:00
|
|
|
self.file_header.stack_size = self.file_header.calculate_stack_size();
|
2024-01-29 15:57:00 -05:00
|
|
|
self.file_header.runtime_size = self.model_data.calculate_runtime_size();
|
2023-12-12 21:43:04 -05:00
|
|
|
|
2024-03-01 15:47:47 -05:00
|
|
|
let data_offset = self.file_header.runtime_size
|
2023-12-12 22:04:30 -05:00
|
|
|
+ size_of::<ModelFileHeader>() as u32
|
2023-12-12 21:43:04 -05:00
|
|
|
+ self.file_header.stack_size;
|
2023-12-10 14:52:49 -05:00
|
|
|
|
2024-03-01 15:47:47 -05:00
|
|
|
let mut overall_offset: u32 = 0;
|
2023-12-12 21:43:04 -05:00
|
|
|
|
2024-03-01 15:47:47 -05:00
|
|
|
for lod in &mut self.model_data.lods {
|
|
|
|
// vertex
|
|
|
|
lod.vertex_data_offset = data_offset + overall_offset;
|
|
|
|
overall_offset += lod.vertex_buffer_size;
|
2023-12-10 14:52:49 -05:00
|
|
|
|
2024-03-01 15:47:47 -05:00
|
|
|
// index
|
|
|
|
lod.index_data_offset = data_offset + overall_offset;
|
|
|
|
overall_offset += lod.index_buffer_size;
|
2023-12-10 14:52:49 -05:00
|
|
|
|
2024-03-01 15:47:47 -05:00
|
|
|
// edge, but unused?
|
|
|
|
//lod.edge_geometry_data_offset = data_offset + overall_offset;
|
|
|
|
//overall_offset += lod.edge_geometry_size;
|
2023-12-10 14:52:49 -05:00
|
|
|
|
2024-03-01 15:47:47 -05:00
|
|
|
lod.edge_geometry_data_offset = lod.index_data_offset;
|
2023-12-10 14:52:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for i in 0..self.lods.len() {
|
|
|
|
self.file_header.vertex_buffer_size[i] = self.model_data.lods[i].vertex_buffer_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
for i in 0..self.lods.len() {
|
|
|
|
self.file_header.vertex_offsets[i] = self.model_data.lods[i].vertex_data_offset;
|
|
|
|
}
|
2023-12-12 21:43:04 -05:00
|
|
|
|
|
|
|
for i in 0..self.lods.len() {
|
|
|
|
self.file_header.index_buffer_size[i] = self.model_data.lods[i].index_buffer_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
for i in 0..self.lods.len() {
|
|
|
|
self.file_header.index_offsets[i] = self.model_data.lods[i].index_data_offset;
|
|
|
|
}
|
2024-03-05 17:08:07 -05:00
|
|
|
|
|
|
|
self.model_data.header.shape_count = self.model_data.shapes.len() as u16;
|
|
|
|
self.model_data.header.shape_mesh_count = self.model_data.shape_meshes.len() as u16;
|
|
|
|
self.model_data.header.shape_value_count = self.model_data.shape_values.len() as u16;
|
2023-12-09 14:45:00 -05:00
|
|
|
}
|
|
|
|
|
2023-10-13 16:16:04 -04:00
|
|
|
pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
|
|
|
|
let mut buffer = ByteBuffer::new();
|
2023-09-22 16:44:19 -04:00
|
|
|
|
|
|
|
{
|
|
|
|
let mut cursor = Cursor::new(&mut buffer);
|
|
|
|
|
2023-09-22 17:04:48 -04:00
|
|
|
// write file header
|
|
|
|
self.file_header.write(&mut cursor).ok()?;
|
|
|
|
|
|
|
|
self.model_data.write(&mut cursor).ok()?;
|
2023-09-22 17:42:04 -04:00
|
|
|
|
|
|
|
for (l, lod) in self.lods.iter().enumerate() {
|
2023-11-24 08:25:45 -05:00
|
|
|
for part in lod.parts.iter() {
|
2024-04-20 13:18:03 -04:00
|
|
|
let declaration =
|
|
|
|
&self.model_data.header.vertex_declarations[part.mesh_index as usize];
|
2023-09-22 17:42:04 -04:00
|
|
|
|
2023-11-22 21:21:01 -05:00
|
|
|
for (k, vert) in part.vertices.iter().enumerate() {
|
2023-09-22 17:42:04 -04:00
|
|
|
for element in &declaration.elements {
|
|
|
|
cursor
|
|
|
|
.seek(SeekFrom::Start(
|
2023-09-22 19:17:24 -04:00
|
|
|
(self.model_data.lods[l].vertex_data_offset
|
2024-04-20 13:18:03 -04:00
|
|
|
+ self.model_data.meshes[part.mesh_index as usize]
|
|
|
|
.vertex_buffer_offsets
|
|
|
|
[element.stream as usize]
|
2023-09-22 17:42:04 -04:00
|
|
|
+ element.offset as u32
|
2024-04-20 13:18:03 -04:00
|
|
|
+ self.model_data.meshes[part.mesh_index as usize]
|
|
|
|
.vertex_buffer_strides
|
|
|
|
[element.stream as usize]
|
|
|
|
as u32
|
|
|
|
* k as u32) as u64,
|
2023-09-22 17:42:04 -04:00
|
|
|
))
|
|
|
|
.ok()?;
|
|
|
|
|
|
|
|
match element.vertex_usage {
|
2024-04-20 13:18:03 -04:00
|
|
|
VertexUsage::Position => match element.vertex_type {
|
|
|
|
VertexType::Half4 => {
|
|
|
|
MDL::write_half4(
|
|
|
|
&mut cursor,
|
|
|
|
&MDL::pad_slice(&vert.position, 1.0),
|
|
|
|
)
|
|
|
|
.ok()?;
|
2023-09-22 17:42:04 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
VertexType::Single3 => {
|
|
|
|
MDL::write_single3(&mut cursor, &vert.position).ok()?;
|
2023-09-22 17:42:04 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for position: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
2023-09-22 17:42:04 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
},
|
|
|
|
VertexUsage::BlendWeights => match element.vertex_type {
|
|
|
|
VertexType::ByteFloat4 => {
|
|
|
|
MDL::write_byte_float4(&mut cursor, &vert.bone_weight)
|
|
|
|
.ok()?;
|
2023-09-22 17:42:04 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for blendweight: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
VertexUsage::BlendIndices => match element.vertex_type {
|
|
|
|
VertexType::Byte4 => {
|
|
|
|
MDL::write_byte4(&mut cursor, &vert.bone_id).ok()?;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for blendindice: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
VertexUsage::Normal => match element.vertex_type {
|
|
|
|
VertexType::Half4 => {
|
|
|
|
MDL::write_half4(
|
|
|
|
&mut cursor,
|
|
|
|
&MDL::pad_slice(&vert.normal, 0.0),
|
|
|
|
)
|
|
|
|
.ok()?;
|
|
|
|
}
|
|
|
|
VertexType::Single3 => {
|
|
|
|
MDL::write_single3(&mut cursor, &vert.normal).ok()?;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for normal: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
VertexUsage::UV => match element.vertex_type {
|
|
|
|
VertexType::Half4 => {
|
|
|
|
let combined =
|
|
|
|
[vert.uv0[0], vert.uv0[1], vert.uv1[0], vert.uv1[1]];
|
2023-11-24 08:07:30 -05:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
MDL::write_half4(&mut cursor, &combined).ok()?;
|
|
|
|
}
|
|
|
|
VertexType::Single4 => {
|
|
|
|
let combined =
|
|
|
|
[vert.uv0[0], vert.uv0[1], vert.uv1[0], vert.uv1[1]];
|
2023-11-24 08:07:30 -05:00
|
|
|
|
2024-04-20 13:18:03 -04:00
|
|
|
MDL::write_single4(&mut cursor, &combined).ok()?;
|
2023-09-22 17:42:04 -04:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for uv: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
2023-11-24 08:07:30 -05:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
},
|
|
|
|
VertexUsage::BiTangent => match element.vertex_type {
|
|
|
|
VertexType::ByteFloat4 => {
|
|
|
|
MDL::write_tangent(&mut cursor, &vert.bitangent).ok()?;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for bitangent: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
2023-12-17 18:58:48 -05:00
|
|
|
VertexUsage::Tangent => {
|
2024-04-20 13:17:11 -04:00
|
|
|
#[allow(clippy::match_single_binding)] // TODO
|
2023-11-24 08:07:30 -05:00
|
|
|
match element.vertex_type {
|
2023-12-17 18:58:48 -05:00
|
|
|
/*VertexType::ByteFloat4 => {
|
|
|
|
MDL::write_tangent(&mut cursor, &vert.binormal).ok()?;
|
|
|
|
}*/
|
2023-11-24 08:07:30 -05:00
|
|
|
_ => {
|
2024-04-20 13:18:03 -04:00
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for tangent: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
2023-11-24 08:07:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
VertexUsage::Color => match element.vertex_type {
|
|
|
|
VertexType::ByteFloat4 => {
|
|
|
|
MDL::write_byte_float4(&mut cursor, &vert.color).ok()?;
|
2023-11-24 08:07:30 -05:00
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
_ => {
|
|
|
|
panic!(
|
|
|
|
"Unexpected vertex type for color: {:#?}",
|
|
|
|
element.vertex_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
2023-09-22 17:42:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor
|
|
|
|
.seek(SeekFrom::Start(
|
2023-11-22 21:21:01 -05:00
|
|
|
(self.file_header.index_offsets[l]
|
2024-04-20 13:18:03 -04:00
|
|
|
+ (self.model_data.meshes[part.mesh_index as usize].start_index
|
|
|
|
* size_of::<u16>() as u32)) as u64,
|
2023-09-22 17:42:04 -04:00
|
|
|
))
|
|
|
|
.ok()?;
|
|
|
|
|
2023-11-24 08:29:32 -05:00
|
|
|
cursor.write_le(&part.indices).ok()?;
|
2023-09-22 17:42:04 -04:00
|
|
|
}
|
|
|
|
}
|
2023-09-22 16:44:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Some(buffer)
|
|
|
|
}
|
2023-12-13 16:51:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ModelFileHeader {
|
|
|
|
pub fn calculate_stack_size(&self) -> u32 {
|
2024-02-25 08:41:03 -05:00
|
|
|
// From https://github.com/Ottermandias/Penumbra.GameData/blob/44021b93e6901c84b739bbf4d1c6350f4486cdbf/Files/MdlFile.cs#L11
|
|
|
|
self.vertex_declaration_count as u32 * NUM_VERTICES * VERTEX_ELEMENT_SIZE as u32
|
2023-12-13 16:51:40 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-29 16:02:56 -05:00
|
|
|
// TODO: From Xande, need to be cleaned up :)
|
2024-01-29 15:57:00 -05:00
|
|
|
impl ModelData {
|
|
|
|
pub fn calculate_runtime_size(&self) -> u32 {
|
|
|
|
2 //StringCount
|
|
|
|
+ 2 // Unknown
|
|
|
|
+ 4 //StringSize
|
|
|
|
+ self.header.string_size
|
|
|
|
+ 56 //ModelHeader
|
|
|
|
+ (self.element_ids.len() as u32 * 32)
|
|
|
|
+ (3 * 60) // 3 Lods
|
|
|
|
//+ ( /*file.ModelHeader.ExtraLodEnabled ? 40*/ 0 )
|
|
|
|
+ self.meshes.len() as u32 * 36
|
|
|
|
+ self.attribute_name_offsets.len() as u32 * size_of::<u32>() as u32
|
|
|
|
+ self.header.terrain_shadow_mesh_count as u32 * 20
|
|
|
|
+ self.header.submesh_count as u32 * 16
|
|
|
|
+ self.header.terrain_shadow_submesh_count as u32 * 10
|
|
|
|
+ self.material_name_offsets.len() as u32 * size_of::<u32>() as u32
|
|
|
|
+ self.bone_name_offsets.len() as u32 * size_of::<u32>() as u32
|
|
|
|
+ self.bone_tables.len() as u32 * 132
|
|
|
|
+ self.header.shape_count as u32 * 16
|
|
|
|
+ self.header.shape_mesh_count as u32 * 12
|
|
|
|
+ self.header.shape_value_count as u32 * 4
|
|
|
|
+ 4 // SubmeshBoneMapSize
|
2024-04-14 13:36:27 -04:00
|
|
|
+ self.submesh_bone_map.len() as u32 * 2
|
2024-02-25 09:35:18 -05:00
|
|
|
+ self.padding_amount as u32 + 1 // PaddingAmount and Padding
|
2024-01-29 15:57:00 -05:00
|
|
|
+ (4 * 32) // 4 BoundingBoxes
|
|
|
|
+ (self.header.bone_count as u32 * 32)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-13 16:51:40 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2024-02-25 10:25:54 -05:00
|
|
|
use std::fs::read;
|
2024-02-25 08:41:03 -05:00
|
|
|
use std::mem::size_of;
|
2024-02-25 10:25:54 -05:00
|
|
|
use std::path::PathBuf;
|
2024-04-16 21:19:08 -04:00
|
|
|
|
|
|
|
use crate::model_vertex_declarations::VERTEX_ELEMENT_SIZE;
|
|
|
|
|
|
|
|
use super::*;
|
2024-02-25 08:41:03 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_file_header_size() {
|
|
|
|
assert_eq!(0x44, size_of::<ModelFileHeader>());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_vertex_element_size() {
|
|
|
|
assert_eq!(8, VERTEX_ELEMENT_SIZE);
|
|
|
|
}
|
2023-12-13 16:51:40 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_stack_size() {
|
|
|
|
let example_header = ModelFileHeader {
|
|
|
|
version: 0,
|
|
|
|
stack_size: 0,
|
|
|
|
runtime_size: 0,
|
|
|
|
vertex_declaration_count: 6,
|
|
|
|
material_count: 0,
|
|
|
|
vertex_offsets: [0; 3],
|
|
|
|
index_offsets: [0; 3],
|
|
|
|
vertex_buffer_size: [0; 3],
|
|
|
|
index_buffer_size: [0; 3],
|
|
|
|
lod_count: 0,
|
|
|
|
index_buffer_streaming_enabled: false,
|
|
|
|
has_edge_geometry: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(816, example_header.calculate_stack_size());
|
|
|
|
|
|
|
|
let example_header2 = ModelFileHeader {
|
|
|
|
version: 0,
|
|
|
|
stack_size: 0,
|
|
|
|
runtime_size: 0,
|
|
|
|
vertex_declaration_count: 2,
|
|
|
|
material_count: 0,
|
|
|
|
vertex_offsets: [0; 3],
|
|
|
|
index_offsets: [0; 3],
|
|
|
|
vertex_buffer_size: [0; 3],
|
|
|
|
index_buffer_size: [0; 3],
|
|
|
|
lod_count: 0,
|
|
|
|
index_buffer_streaming_enabled: false,
|
|
|
|
has_edge_geometry: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(272, example_header2.calculate_stack_size());
|
|
|
|
}
|
2024-02-25 10:25:54 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_headers() {
|
|
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
|
|
d.push("resources/tests");
|
|
|
|
d.push("c0201e0038_top_zeroed.mdl");
|
|
|
|
|
|
|
|
let mut mdl = MDL::from_existing(&read(d).unwrap()).unwrap();
|
|
|
|
let old_mdl = mdl.clone();
|
|
|
|
|
|
|
|
mdl.update_headers();
|
|
|
|
|
|
|
|
// There should be no changes
|
|
|
|
assert_eq!(mdl.file_header, old_mdl.file_header);
|
|
|
|
assert_eq!(mdl.model_data, old_mdl.model_data);
|
|
|
|
}
|
2024-03-01 15:47:58 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_vertices() {
|
|
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
|
|
d.push("resources/tests");
|
|
|
|
d.push("c0201e0038_top_zeroed.mdl");
|
|
|
|
|
|
|
|
let mut mdl = MDL::from_existing(&read(d).unwrap()).unwrap();
|
|
|
|
let old_mdl = mdl.clone();
|
|
|
|
|
|
|
|
for l in 0..old_mdl.lods.len() {
|
|
|
|
for p in 0..old_mdl.lods[l].parts.len() {
|
2024-04-20 13:18:03 -04:00
|
|
|
mdl.replace_vertices(
|
|
|
|
l,
|
|
|
|
p,
|
|
|
|
&old_mdl.lods[l].parts[p].vertices,
|
|
|
|
&old_mdl.lods[l].parts[p].indices,
|
|
|
|
&old_mdl.lods[l].parts[p].submeshes,
|
|
|
|
);
|
2024-03-01 15:47:58 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// There should be no changes
|
|
|
|
assert_eq!(mdl.file_header, old_mdl.file_header);
|
|
|
|
assert_eq!(mdl.model_data, old_mdl.model_data);
|
|
|
|
}
|
2024-02-25 10:30:50 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parsing() {
|
|
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
|
|
d.push("resources/tests");
|
|
|
|
d.push("c0201e0038_top_zeroed.mdl");
|
|
|
|
|
2024-04-16 21:19:08 -04:00
|
|
|
let mdl = MDL::from_existing(&read(d).unwrap()).unwrap();
|
2024-02-25 10:30:50 -05:00
|
|
|
|
|
|
|
// file header
|
|
|
|
assert_eq!(mdl.file_header.version, 16777221);
|
|
|
|
assert_eq!(mdl.file_header.stack_size, 816);
|
2024-04-20 13:18:03 -04:00
|
|
|
assert_eq!(
|
|
|
|
mdl.file_header.stack_size,
|
|
|
|
mdl.file_header.calculate_stack_size()
|
|
|
|
);
|
2024-02-25 10:30:50 -05:00
|
|
|
assert_eq!(mdl.file_header.runtime_size, 12544);
|
2024-04-20 13:18:03 -04:00
|
|
|
assert_eq!(
|
|
|
|
mdl.file_header.runtime_size,
|
|
|
|
mdl.model_data.calculate_runtime_size()
|
|
|
|
);
|
2024-02-25 10:30:50 -05:00
|
|
|
assert_eq!(mdl.file_header.vertex_declaration_count, 6);
|
|
|
|
assert_eq!(mdl.file_header.material_count, 2);
|
|
|
|
assert_eq!(mdl.file_header.lod_count, 3);
|
|
|
|
assert_eq!(mdl.file_header.index_buffer_streaming_enabled, false);
|
|
|
|
assert_eq!(mdl.file_header.has_edge_geometry, false);
|
|
|
|
|
|
|
|
// model header
|
|
|
|
assert_eq!(mdl.model_data.header.radius, 1.5340779);
|
|
|
|
}
|
2024-04-16 22:05:39 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invalid() {
|
|
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
|
|
d.push("resources/tests");
|
|
|
|
d.push("random");
|
|
|
|
|
|
|
|
// Feeding it invalid data should not panic
|
|
|
|
MDL::from_existing(&read(d).unwrap());
|
|
|
|
}
|
2024-04-20 13:18:03 -04:00
|
|
|
}
|