mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 16:44:11 +02:00
bones
This commit is contained in:
parent
03470ab7f4
commit
f33101119c
10 changed files with 635 additions and 8 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
data
|
||||||
|
|
@ -2,7 +2,11 @@
|
||||||
name = "vmdl"
|
name = "vmdl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
exclude = ["data"]
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
arrayvec = "0.7.2"
|
||||||
|
binrw = "0.8.0"
|
||||||
|
thiserror = "1.0.30"
|
||||||
|
static_assertions = "1.1.0"
|
||||||
|
bitflags = "1.0.4"
|
||||||
10
examples/parse.rs
Normal file
10
examples/parse.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
fn main() -> Result<(), vmdl::MdlError> {
|
||||||
|
let mut args = std::env::args();
|
||||||
|
let _ = args.next();
|
||||||
|
let data = std::fs::read(args.next().expect("No demo file provided"))?;
|
||||||
|
let mdl = vmdl::Mdl::read(&data)?;
|
||||||
|
|
||||||
|
dbg!(mdl.bones);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
58
src/data/bone.rs
Normal file
58
src/data/bone.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::{Quaternion, RadianEuler, Vector};
|
||||||
|
use binrw::BinRead;
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct Bone {
|
||||||
|
pub sz_name_index: i32,
|
||||||
|
pub parent: i32, // parent bone
|
||||||
|
pub bone_controller: [i32; 6], // bone controller index, -1 == none
|
||||||
|
|
||||||
|
pub pos: Vector,
|
||||||
|
pub quaternion: Quaternion,
|
||||||
|
pub rot: RadianEuler,
|
||||||
|
pub pos_scale: Vector,
|
||||||
|
pub rot_scale: Vector,
|
||||||
|
|
||||||
|
pub pose_to_bone: [[f32; 3]; 4], // 3x4 matrix
|
||||||
|
pub q_alignment: Quaternion,
|
||||||
|
pub flags: BoneFlags,
|
||||||
|
pub proc_type: i32,
|
||||||
|
pub proc_index: i32, // procedural rule
|
||||||
|
pub physics_bone: i32, // index into physically simulated bone
|
||||||
|
pub surface_prop_idx: i32, // index into string table for property name
|
||||||
|
pub contents: i32, // See BSPFlags.h for the contents flags
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
reserved: [i32; 8], // remove as appropriate
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct BoneFlags: u32 {
|
||||||
|
const BONE_PHYSICALLY_SIMULATED = 0x00000001;
|
||||||
|
const BONE_PHYSICS_PROCEDURAL = 0x00000002;
|
||||||
|
const BONE_ALWAYS_PROCEDURAL = 0x00000004;
|
||||||
|
const BONE_SCREEN_ALIGN_SPHERE = 0x00000008;
|
||||||
|
const BONE_SCREEN_ALIGN_CYLINDER = 0x00000010;
|
||||||
|
|
||||||
|
const BONE_USED_BY_HITBOX = 0x00000100;
|
||||||
|
const BONE_USED_BY_ATTACHMENT = 0x00000200;
|
||||||
|
|
||||||
|
const BONE_USED_BY_VERTEX_LOD0 = 0x00000400;
|
||||||
|
const BONE_USED_BY_VERTEX_LOD1 = 0x00000800;
|
||||||
|
const BONE_USED_BY_VERTEX_LOD2 = 0x00001000;
|
||||||
|
const BONE_USED_BY_VERTEX_LOD3 = 0x00002000;
|
||||||
|
const BONE_USED_BY_VERTEX_LOD4 = 0x00004000;
|
||||||
|
const BONE_USED_BY_VERTEX_LOD5 = 0x00008000;
|
||||||
|
const BONE_USED_BY_VERTEX_LOD6 = 0x00010000;
|
||||||
|
const BONE_USED_BY_VERTEX_LOD7 = 0x00020000;
|
||||||
|
const BONE_USED_BY_BONE_MERGE = 0x00040000;
|
||||||
|
|
||||||
|
const BONE_TYPE_MASK = 0x00F00000;
|
||||||
|
const BONE_FIXED_ALIGNMENT = 0x00100000;
|
||||||
|
|
||||||
|
const BONE_HAS_SAVEFRAME_POS = 0x00200000;
|
||||||
|
const BONE_HAS_SAVEFRAME_ROT = 0x00400000;
|
||||||
|
}
|
||||||
|
}
|
||||||
295
src/data/header.rs
Normal file
295
src/data/header.rs
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
use crate::{Bone, FixedString, Vector};
|
||||||
|
use binrw::BinRead;
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
pub const FILETYPE_ID: i32 = i32::from_be_bytes(*b"IDST");
|
||||||
|
pub const MDL_VERSION: i32 = 48;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct StudioHeader {
|
||||||
|
pub id: i32,
|
||||||
|
pub version: i32,
|
||||||
|
pub checksum: [u8; 4], // This has to be the same in the phy and vtx files to load!
|
||||||
|
pub name: FixedString<64>,
|
||||||
|
pub data_length: i32,
|
||||||
|
|
||||||
|
pub eye_position: Vector, // Position of player viewpoint relative to model origin
|
||||||
|
pub illumination_position: Vector, // Position (relative to model origin) used to calculate ambient light contribution and cubemap reflections for the entire model.
|
||||||
|
pub hull_min: Vector, // Corner of model hull box with the least X/Y/Z values
|
||||||
|
pub hull_max: Vector, // Opposite corner of model hull box
|
||||||
|
pub view_bb_min: Vector,
|
||||||
|
pub view_bb_max: Vector,
|
||||||
|
|
||||||
|
pub flags: i32, // Binary flags in little-endian order.
|
||||||
|
// ex (00000001,00000000,00000000,11000000) means flags for position 0, 30, and 31 are set.
|
||||||
|
// Set model flags section for more information
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After this point, the header contains many references to offsets
|
||||||
|
* within the MDL file and the number of items at those offsets.
|
||||||
|
*
|
||||||
|
* Offsets are from the very beginning of the file.
|
||||||
|
*
|
||||||
|
* Note that indexes/counts are not always paired and ordered consistently.
|
||||||
|
*/
|
||||||
|
// mstudiobone_t
|
||||||
|
bone_count: i32, // Number of data sections (of type mstudiobone_t)
|
||||||
|
bone_offset: i32, // Offset of first data section
|
||||||
|
|
||||||
|
// mstudiobonecontroller_t
|
||||||
|
bone_controller_count: i32,
|
||||||
|
bone_controller_offset: i32,
|
||||||
|
|
||||||
|
// mstudiohitboxset_t
|
||||||
|
hitbox_count: i32,
|
||||||
|
hitbox_offset: i32,
|
||||||
|
|
||||||
|
// mstudioanimdesc_t
|
||||||
|
local_animation_count: i32,
|
||||||
|
local_animation_offset: i32,
|
||||||
|
|
||||||
|
// mstudioseqdesc_t
|
||||||
|
local_seq_count: i32,
|
||||||
|
local_seq_offset: i32,
|
||||||
|
|
||||||
|
pub activity_list_version: i32, // ??
|
||||||
|
pub events_indexed: i32, // ??
|
||||||
|
|
||||||
|
// VMT texture filenames
|
||||||
|
// mstudiotexture_t
|
||||||
|
texture_count: i32,
|
||||||
|
texture_offset: i32,
|
||||||
|
|
||||||
|
// This offset points to a series of ints.
|
||||||
|
// Each int value, in turn, is an offset relative to the start of this header/the-file,
|
||||||
|
// At which there is a null-terminated string.
|
||||||
|
texture_dir_count: i32,
|
||||||
|
texture_dir_offset: i32,
|
||||||
|
|
||||||
|
// Each skin-family assigns a texture-id to a skin location
|
||||||
|
pub skin_reference_count: i32,
|
||||||
|
pub skin_r_family_count: i32,
|
||||||
|
pub skin_reference_index: i32,
|
||||||
|
|
||||||
|
// mstudiobodyparts_t
|
||||||
|
body_part_count: i32,
|
||||||
|
body_part_offset: i32,
|
||||||
|
|
||||||
|
// Local attachment points
|
||||||
|
// mstudioattachment_t
|
||||||
|
attachment_count: i32,
|
||||||
|
attachment_offset: i32,
|
||||||
|
|
||||||
|
// Node values appear to be single bytes, while their names are null-terminated strings.
|
||||||
|
local_node_count: i32,
|
||||||
|
local_node_index: i32,
|
||||||
|
local_node_name_index: i32,
|
||||||
|
|
||||||
|
// mstudioflexdesc_t
|
||||||
|
flex_desc_count: i32,
|
||||||
|
flex_desc_index: i32,
|
||||||
|
|
||||||
|
// mstudioflexcontroller_t
|
||||||
|
flex_controller_count: i32,
|
||||||
|
flex_controller_index: i32,
|
||||||
|
|
||||||
|
// mstudioflexrule_t
|
||||||
|
flex_rules_count: i32,
|
||||||
|
flex_rules_index: i32,
|
||||||
|
|
||||||
|
// IK probably referse to inverse kinematics
|
||||||
|
// mstudioikchain_t
|
||||||
|
ik_chain_count: i32,
|
||||||
|
ik_chain_index: i32,
|
||||||
|
|
||||||
|
// Information about any "mouth" on the model for speech animation
|
||||||
|
// More than one sounds pretty creepy.
|
||||||
|
// mstudiomouth_t
|
||||||
|
mouths_count: i32,
|
||||||
|
mouths_index: i32,
|
||||||
|
|
||||||
|
// mstudioposeparamdesc_t
|
||||||
|
local_pose_param_count: i32,
|
||||||
|
local_pose_param_index: i32,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For anyone trying to follow along, as of this writing,
|
||||||
|
* the next "surfaceprop_index" value is at position 0x0134 (308)
|
||||||
|
* from the start of the file.
|
||||||
|
*/
|
||||||
|
// Surface property value (single null-terminated string)
|
||||||
|
pub surface_prop_index: i32,
|
||||||
|
|
||||||
|
// Unusual: In this one index comes first, then count.
|
||||||
|
// Key-value data is a series of strings. If you can't find
|
||||||
|
// what you're interested in, check the associated PHY file as well.
|
||||||
|
key_value_index: i32,
|
||||||
|
key_value_count: i32,
|
||||||
|
|
||||||
|
// More inverse-kinematics
|
||||||
|
// mstudioiklock_t
|
||||||
|
ik_lock_count: i32,
|
||||||
|
ik_lock_index: i32,
|
||||||
|
|
||||||
|
pub mass: f32, // Mass of object (4-bytes)
|
||||||
|
pub contents: i32, // ??
|
||||||
|
|
||||||
|
// Other models can be referenced for re-used sequences and animations
|
||||||
|
// (See also: The $includemodel QC option.)
|
||||||
|
// mstudiomodelgroup_t
|
||||||
|
include_model_count: i32,
|
||||||
|
include_model_index: i32,
|
||||||
|
|
||||||
|
pub virtual_model: i32, // Placeholder for mutable-void*
|
||||||
|
// Note that the SDK only compiles as 32-bit, so an int and a pointer are the same size (4 bytes)
|
||||||
|
|
||||||
|
// mstudioanimblock_t
|
||||||
|
anim_blocks_name_index: i32,
|
||||||
|
anim_blocks_count: i32,
|
||||||
|
anim_blocks_index: i32,
|
||||||
|
|
||||||
|
pub anim_block_model: i32, // Placeholder for mutable-void*
|
||||||
|
|
||||||
|
// Points to a series of bytes?
|
||||||
|
pub bone_table_name_index: i32,
|
||||||
|
|
||||||
|
pub vertex_base: i32, // Placeholder for void*
|
||||||
|
pub offset_base: i32, // Placeholder for void*
|
||||||
|
|
||||||
|
// Used with $constantdirectionallight from the QC
|
||||||
|
// Model should have flag #13 set if enabled
|
||||||
|
pub directional_dot_product: u8,
|
||||||
|
|
||||||
|
pub root_lod: u8, // Preferred rather than clamped
|
||||||
|
|
||||||
|
// 0 means any allowed, N means Lod 0 -> (N-1)
|
||||||
|
pub num_allowed_root_lods: u8,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
unused0: u8, // ??
|
||||||
|
#[allow(dead_code)]
|
||||||
|
unused1: i32, // ??
|
||||||
|
|
||||||
|
pub flex_controller_ui_count: i32,
|
||||||
|
pub flex_controller_ui_index: i32,
|
||||||
|
|
||||||
|
pub vert_anim_fixed_point_scale: f32,
|
||||||
|
pub unused2: i32,
|
||||||
|
|
||||||
|
pub studio_hdr2_index: i32,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
unused3: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StudioHeader {
|
||||||
|
pub fn header2_index(&self) -> Option<usize> {
|
||||||
|
(self.studio_hdr2_index > 0)
|
||||||
|
.then(|| self.studio_hdr2_index)
|
||||||
|
.and_then(|index| usize::try_from(index).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bone_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.bone_offset, self.bone_count, size_of::<Bone>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bone_controller_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.bone_controller_offset, self.bone_controller_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hitbox_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.hitbox_offset, self.hitbox_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_animation_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.local_animation_offset, self.local_animation_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_sequence_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.local_seq_offset, self.local_seq_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.texture_offset, self.texture_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture_dir_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.texture_dir_offset, self.texture_dir_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn body_part_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.body_part_offset, self.body_part_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attachment_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.attachment_offset, self.attachment_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_node_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.local_node_index, self.local_node_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_node_name_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.local_node_name_index, self.local_node_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flex_descriptor_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.flex_desc_index, self.flex_desc_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flex_controller_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.flex_controller_index, self.flex_controller_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flex_rule_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.flex_rules_index, self.flex_rules_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ik_chain_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.ik_chain_index, self.ik_chain_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouth_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.mouths_index, self.mouths_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_pose_param_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.local_pose_param_index, self.local_pose_param_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn key_value_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.key_value_index, self.key_value_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ik_lock_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.ik_lock_index, self.ik_lock_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn include_model_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.include_model_index, self.include_model_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn animation_block_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.anim_blocks_index, self.anim_blocks_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn animation_block_name_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.anim_blocks_name_index, self.anim_blocks_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flex_controller_ui_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(
|
||||||
|
self.flex_controller_ui_index,
|
||||||
|
self.flex_controller_ui_count,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_range(index: i32, count: i32, size: usize) -> impl Iterator<Item = usize> {
|
||||||
|
(0..count as usize)
|
||||||
|
.map(move |i| i * size)
|
||||||
|
.map(move |i| index as usize + i)
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(size_of::<StudioHeader>() - size_of::<FixedString<0>>(), 408);
|
||||||
39
src/data/header2.rs
Normal file
39
src/data/header2.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
pub struct StudioHHeader2 {
|
||||||
|
source_bone_transform_count: i32,
|
||||||
|
source_bone_transform_index: i32,
|
||||||
|
|
||||||
|
pub illumination_position_attachment_index: i32,
|
||||||
|
|
||||||
|
fl_max_exe_deflection: f32,
|
||||||
|
|
||||||
|
pub linear_bone_index: i32,
|
||||||
|
|
||||||
|
pub sz_name_index: i32,
|
||||||
|
|
||||||
|
bone_flex_driver_count: i32,
|
||||||
|
bone_flex_driver_index: i32,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
reserved: [i32; 56],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StudioHHeader2 {
|
||||||
|
pub fn source_bone_transforms(&self) -> Range<i32> {
|
||||||
|
self.source_bone_transform_index
|
||||||
|
..(self.source_bone_transform_index + self.source_bone_transform_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bone_flex_drivers(&self) -> Range<i32> {
|
||||||
|
self.bone_flex_driver_index..(self.bone_flex_driver_index + self.bone_flex_driver_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_eye_deflection(&self) -> f32 {
|
||||||
|
if self.fl_max_exe_deflection == 0.0 {
|
||||||
|
(30.0f32).cos()
|
||||||
|
} else {
|
||||||
|
self.fl_max_exe_deflection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/data/mod.rs
Normal file
114
src/data/mod.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
mod bone;
|
||||||
|
mod header;
|
||||||
|
mod header2;
|
||||||
|
|
||||||
|
pub use bone::*;
|
||||||
|
pub use header::*;
|
||||||
|
pub use header2::*;
|
||||||
|
|
||||||
|
use crate::error::StringError;
|
||||||
|
use arrayvec::ArrayString;
|
||||||
|
use binrw::{BinRead, BinResult, ReadOptions};
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, BinRead)]
|
||||||
|
pub struct Vector {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector> for [f32; 3] {
|
||||||
|
fn from(vector: Vector) -> Self {
|
||||||
|
[vector.x, vector.y, vector.z]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[f32; 3]> for Vector {
|
||||||
|
fn from(vector: [f32; 3]) -> Self {
|
||||||
|
Vector {
|
||||||
|
x: vector[0],
|
||||||
|
y: vector[1],
|
||||||
|
z: vector[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Vector> for [f32; 3] {
|
||||||
|
fn from(vector: &Vector) -> Self {
|
||||||
|
[vector.x, vector.y, vector.z]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct Quaternion {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
pub w: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct RadianEuler {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fixed length, null-terminated string
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FixedString<const LEN: usize>(ArrayString<LEN>);
|
||||||
|
|
||||||
|
impl<const N: usize> AsRef<str> for FixedString<N> {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> FixedString<N> {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const LEN: usize> Display for FixedString<LEN> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const LEN: usize> BinRead for FixedString<LEN> {
|
||||||
|
type Args = ();
|
||||||
|
|
||||||
|
fn read_options<R: binrw::io::Read + binrw::io::Seek>(
|
||||||
|
reader: &mut R,
|
||||||
|
options: &ReadOptions,
|
||||||
|
args: Self::Args,
|
||||||
|
) -> BinResult<Self> {
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
let name_buf = <[u8; LEN]>::read_options(reader, options, args)?;
|
||||||
|
|
||||||
|
let zero_pos =
|
||||||
|
name_buf
|
||||||
|
.iter()
|
||||||
|
.position(|c| *c == 0)
|
||||||
|
.ok_or_else(|| binrw::Error::Custom {
|
||||||
|
pos: reader.stream_position().unwrap(),
|
||||||
|
err: Box::new(StringError::NotNullTerminated),
|
||||||
|
})?;
|
||||||
|
let name = &name_buf[..zero_pos];
|
||||||
|
Ok(FixedString(
|
||||||
|
ArrayString::from(
|
||||||
|
str::from_utf8(name)
|
||||||
|
.map_err(StringError::NonUTF8)
|
||||||
|
.map_err(|e| binrw::Error::Custom {
|
||||||
|
pos: reader.stream_position().unwrap(),
|
||||||
|
err: Box::new(e),
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/error.rs
Normal file
41
src/error.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum MdlError {
|
||||||
|
#[error("io error while reading data: {0}")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
String(#[from] StringError),
|
||||||
|
#[error("Malformed field found while parsing: {0:#}")]
|
||||||
|
MalformedData(binrw::Error),
|
||||||
|
#[error("referenced data is out of bounds")]
|
||||||
|
OutOfBounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<binrw::Error> for MdlError {
|
||||||
|
fn from(e: binrw::Error) -> Self {
|
||||||
|
use binrw::Error;
|
||||||
|
|
||||||
|
// only a few error types should be generated by our code
|
||||||
|
match e {
|
||||||
|
Error::Io(e) => MdlError::IO(e),
|
||||||
|
Error::Custom { err, .. } => {
|
||||||
|
if err.is::<StringError>() {
|
||||||
|
MdlError::String(*err.downcast::<StringError>().unwrap())
|
||||||
|
} else {
|
||||||
|
panic!("unexpected custom error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => MdlError::MalformedData(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum StringError {
|
||||||
|
#[error(transparent)]
|
||||||
|
NonUTF8(#[from] std::str::Utf8Error),
|
||||||
|
#[error("String is not null-terminated")]
|
||||||
|
NotNullTerminated,
|
||||||
|
}
|
||||||
32
src/handle/mod.rs
Normal file
32
src/handle/mod.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use crate::Mdl;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
/// A handle represents a data structure in the mdl file and the mdl file containing it.
|
||||||
|
///
|
||||||
|
/// Keeping a reference of the mdl file with the data is required since a lot of data types
|
||||||
|
/// reference parts from other structures in the mdl file
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Handle<'a, T> {
|
||||||
|
mdl: &'a Mdl,
|
||||||
|
data: &'a T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for Handle<'_, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Handle { ..*self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> AsRef<T> for Handle<'a, T> {
|
||||||
|
fn as_ref(&self) -> &'a T {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for Handle<'_, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/lib.rs
45
src/lib.rs
|
|
@ -1,8 +1,41 @@
|
||||||
#[cfg(test)]
|
mod data;
|
||||||
mod tests {
|
mod error;
|
||||||
#[test]
|
mod handle;
|
||||||
fn it_works() {
|
|
||||||
let result = 2 + 2;
|
use binrw::{BinRead, BinReaderExt};
|
||||||
assert_eq!(result, 4);
|
pub use data::*;
|
||||||
|
pub use error::*;
|
||||||
|
pub use handle::Handle;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Mdl {
|
||||||
|
pub header: StudioHeader,
|
||||||
|
pub bones: Vec<Bone>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mdl {
|
||||||
|
pub fn read(data: &[u8]) -> Result<Self, MdlError> {
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
let header: StudioHeader = reader.read_le()?;
|
||||||
|
let bones = read_indexes(header.bone_indexes(), data).collect::<Result<_, _>>()?;
|
||||||
|
Ok(Mdl { header, bones })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_indexes<'a, I: Iterator<Item = usize> + 'static, T: BinRead>(
|
||||||
|
indexes: I,
|
||||||
|
data: &'a [u8],
|
||||||
|
) -> impl Iterator<Item = Result<T, MdlError>> + 'a
|
||||||
|
where
|
||||||
|
T::Args: Default,
|
||||||
|
{
|
||||||
|
indexes
|
||||||
|
.map(|index| data.get(index..).ok_or(MdlError::OutOfBounds))
|
||||||
|
.map(|data| {
|
||||||
|
data.and_then(|data| {
|
||||||
|
let mut cursor = Cursor::new(data);
|
||||||
|
cursor.read_le().map_err(MdlError::from)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue