mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 16:44:11 +02:00
vtx
This commit is contained in:
parent
f33101119c
commit
b0d3245e07
13 changed files with 510 additions and 55 deletions
|
|
@ -1,10 +1,14 @@
|
||||||
fn main() -> Result<(), vmdl::MdlError> {
|
use std::env::args;
|
||||||
let mut args = std::env::args();
|
use std::fs;
|
||||||
let _ = args.next();
|
use vmdl::mdl::Mdl;
|
||||||
let data = std::fs::read(args.next().expect("No demo file provided"))?;
|
|
||||||
let mdl = vmdl::Mdl::read(&data)?;
|
|
||||||
|
|
||||||
dbg!(mdl.bones);
|
fn main() -> Result<(), vmdl::ModelError> {
|
||||||
|
let mut args = args();
|
||||||
|
let _ = args.next();
|
||||||
|
let data = fs::read(args.next().expect("No demo file provided"))?;
|
||||||
|
let mdl = Mdl::read(&data)?;
|
||||||
|
|
||||||
|
dbg!(mdl.header);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/error.rs
14
src/error.rs
|
|
@ -2,32 +2,32 @@ use thiserror::Error;
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum MdlError {
|
pub enum ModelError {
|
||||||
#[error("io error while reading data: {0}")]
|
#[error("io error while reading data: {0}")]
|
||||||
IO(#[from] std::io::Error),
|
IO(#[from] std::io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
String(#[from] StringError),
|
String(#[from] StringError),
|
||||||
#[error("Malformed field found while parsing: {0:#}")]
|
#[error("Malformed field found while parsing: {0:#}")]
|
||||||
MalformedData(binrw::Error),
|
MalformedData(binrw::Error),
|
||||||
#[error("referenced data is out of bounds")]
|
#[error("referenced data to {data} is out of bounds at {offset}")]
|
||||||
OutOfBounds,
|
OutOfBounds { data: &'static str, offset: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<binrw::Error> for MdlError {
|
impl From<binrw::Error> for ModelError {
|
||||||
fn from(e: binrw::Error) -> Self {
|
fn from(e: binrw::Error) -> Self {
|
||||||
use binrw::Error;
|
use binrw::Error;
|
||||||
|
|
||||||
// only a few error types should be generated by our code
|
// only a few error types should be generated by our code
|
||||||
match e {
|
match e {
|
||||||
Error::Io(e) => MdlError::IO(e),
|
Error::Io(e) => ModelError::IO(e),
|
||||||
Error::Custom { err, .. } => {
|
Error::Custom { err, .. } => {
|
||||||
if err.is::<StringError>() {
|
if err.is::<StringError>() {
|
||||||
MdlError::String(*err.downcast::<StringError>().unwrap())
|
ModelError::String(*err.downcast::<StringError>().unwrap())
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected custom error")
|
panic!("unexpected custom error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
e => MdlError::MalformedData(e),
|
e => ModelError::MalformedData(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::Mdl;
|
use crate::mdl::Mdl;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
/// A handle represents a data structure in the mdl file and the mdl file containing it.
|
/// A handle represents a mdl 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
|
/// Keeping a reference of the mdl file with the mdl is required since a lot of mdl types
|
||||||
/// reference parts from other structures in the mdl file
|
/// reference parts from other structures in the mdl file
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Handle<'a, T> {
|
pub struct Handle<'a, T> {
|
||||||
|
|
|
||||||
39
src/lib.rs
39
src/lib.rs
|
|
@ -1,41 +1,40 @@
|
||||||
mod data;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod handle;
|
mod handle;
|
||||||
|
pub mod mdl;
|
||||||
|
mod shared;
|
||||||
|
pub mod vtx;
|
||||||
|
pub mod vvd;
|
||||||
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::{BinRead, BinReaderExt};
|
||||||
pub use data::*;
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use handle::Handle;
|
pub use handle::Handle;
|
||||||
|
pub use shared::*;
|
||||||
use std::io::Cursor;
|
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>(
|
fn read_indexes<'a, I: Iterator<Item = usize> + 'static, T: BinRead>(
|
||||||
indexes: I,
|
indexes: I,
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
) -> impl Iterator<Item = Result<T, MdlError>> + 'a
|
) -> impl Iterator<Item = Result<T, ModelError>> + 'a
|
||||||
where
|
where
|
||||||
T::Args: Default,
|
T::Args: Default,
|
||||||
{
|
{
|
||||||
indexes
|
indexes
|
||||||
.map(|index| data.get(index..).ok_or(MdlError::OutOfBounds))
|
.map(|index| {
|
||||||
|
data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "Bone",
|
||||||
|
offset: index,
|
||||||
|
})
|
||||||
|
})
|
||||||
.map(|data| {
|
.map(|data| {
|
||||||
data.and_then(|data| {
|
data.and_then(|data| {
|
||||||
let mut cursor = Cursor::new(data);
|
let mut cursor = Cursor::new(data);
|
||||||
cursor.read_le().map_err(MdlError::from)
|
cursor.read_le().map_err(ModelError::from)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{Bone, FixedString, Vector};
|
use crate::mdl::Bone;
|
||||||
|
use crate::{index_range, FixedString, Vector};
|
||||||
use binrw::BinRead;
|
use binrw::BinRead;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
|
@ -33,8 +34,8 @@ pub struct StudioHeader {
|
||||||
* Note that indexes/counts are not always paired and ordered consistently.
|
* Note that indexes/counts are not always paired and ordered consistently.
|
||||||
*/
|
*/
|
||||||
// mstudiobone_t
|
// mstudiobone_t
|
||||||
bone_count: i32, // Number of data sections (of type mstudiobone_t)
|
bone_count: i32, // Number of mdl sections (of type mstudiobone_t)
|
||||||
bone_offset: i32, // Offset of first data section
|
bone_offset: i32, // Offset of first mdl section
|
||||||
|
|
||||||
// mstudiobonecontroller_t
|
// mstudiobonecontroller_t
|
||||||
bone_controller_count: i32,
|
bone_controller_count: i32,
|
||||||
|
|
@ -121,7 +122,7 @@ pub struct StudioHeader {
|
||||||
pub surface_prop_index: i32,
|
pub surface_prop_index: i32,
|
||||||
|
|
||||||
// Unusual: In this one index comes first, then count.
|
// Unusual: In this one index comes first, then count.
|
||||||
// Key-value data is a series of strings. If you can't find
|
// Key-value mdl is a series of strings. If you can't find
|
||||||
// what you're interested in, check the associated PHY file as well.
|
// what you're interested in, check the associated PHY file as well.
|
||||||
key_value_index: i32,
|
key_value_index: i32,
|
||||||
key_value_count: i32,
|
key_value_count: i32,
|
||||||
|
|
@ -286,10 +287,4 @@ impl StudioHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
static_assertions::const_assert_eq!(size_of::<StudioHeader>() - size_of::<FixedString<0>>(), 408);
|
||||||
26
src/mdl/mod.rs
Normal file
26
src/mdl/mod.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
mod bone;
|
||||||
|
mod header;
|
||||||
|
mod header2;
|
||||||
|
|
||||||
|
pub use bone::*;
|
||||||
|
pub use header::*;
|
||||||
|
pub use header2::*;
|
||||||
|
|
||||||
|
use crate::{read_indexes, ModelError};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
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, ModelError> {
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
mod bone;
|
use crate::{BinRead, StringError};
|
||||||
mod header;
|
|
||||||
mod header2;
|
|
||||||
|
|
||||||
pub use bone::*;
|
|
||||||
pub use header::*;
|
|
||||||
pub use header2::*;
|
|
||||||
|
|
||||||
use crate::error::StringError;
|
|
||||||
use arrayvec::ArrayString;
|
use arrayvec::ArrayString;
|
||||||
use binrw::{BinRead, BinResult, ReadOptions};
|
use binrw::{BinResult, ReadOptions};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
206
src/vtx/mod.rs
Normal file
206
src/vtx/mod.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
mod raw;
|
||||||
|
|
||||||
|
use crate::ModelError;
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
use raw::*;
|
||||||
|
pub use raw::{MeshFlags, StripFlags, StripGroupFlags, Vertex};
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
pub const MDL_VERSION: i32 = 7;
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, ModelError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Vtx {
|
||||||
|
pub header: VtxHeader,
|
||||||
|
pub body_parts: Vec<BodyPart>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vtx {
|
||||||
|
pub fn read(data: &[u8]) -> Result<Self> {
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
let header: VtxHeader = reader.read_le()?;
|
||||||
|
Ok(Vtx {
|
||||||
|
body_parts: header
|
||||||
|
.body_indexes()
|
||||||
|
.map(|index| {
|
||||||
|
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "BodyPart",
|
||||||
|
offset: index,
|
||||||
|
})?;
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
let header = reader.read_le()?;
|
||||||
|
BodyPart::read(data, header)
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
header,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BodyPart {
|
||||||
|
pub models: Vec<Model>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyPart {
|
||||||
|
fn read(data: &[u8], header: BodyPartHeader) -> Result<Self> {
|
||||||
|
Ok(BodyPart {
|
||||||
|
models: header
|
||||||
|
.model_indexes()
|
||||||
|
.map(|index| {
|
||||||
|
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "Model",
|
||||||
|
offset: index,
|
||||||
|
})?;
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
let header = reader.read_le()?;
|
||||||
|
Model::read(data, header)
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Model {
|
||||||
|
pub lods: Vec<ModelLod>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
fn read(data: &[u8], header: ModelHeader) -> Result<Self> {
|
||||||
|
Ok(Model {
|
||||||
|
lods: header
|
||||||
|
.lod_indexes()
|
||||||
|
.map(|index| {
|
||||||
|
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "ModelLod",
|
||||||
|
offset: index,
|
||||||
|
})?;
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
let header = reader.read_le()?;
|
||||||
|
ModelLod::read(data, header)
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ModelLod {
|
||||||
|
pub meshes: Vec<Mesh>,
|
||||||
|
pub switch_point: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModelLod {
|
||||||
|
fn read(data: &[u8], header: ModelLodHeader) -> Result<Self> {
|
||||||
|
Ok(ModelLod {
|
||||||
|
meshes: header
|
||||||
|
.mesh_indexes()
|
||||||
|
.map(|index| {
|
||||||
|
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "Mesh",
|
||||||
|
offset: index,
|
||||||
|
})?;
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
let header = reader.read_le()?;
|
||||||
|
Mesh::read(data, header)
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
switch_point: header.switch_point,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Mesh {
|
||||||
|
pub strip_groups: Vec<StripGroup>,
|
||||||
|
pub flags: MeshFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mesh {
|
||||||
|
fn read(data: &[u8], header: MeshHeader) -> Result<Self> {
|
||||||
|
Ok(Mesh {
|
||||||
|
strip_groups: header
|
||||||
|
.strip_group_indexes()
|
||||||
|
.map(|index| {
|
||||||
|
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "StripGroup",
|
||||||
|
offset: index,
|
||||||
|
})?;
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
let header = reader.read_le()?;
|
||||||
|
StripGroup::read(data, header)
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
flags: header.flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StripGroup {
|
||||||
|
// todo vertex indexes
|
||||||
|
// todo topologies
|
||||||
|
pub vertices: Vec<Vertex>,
|
||||||
|
pub strips: Vec<Strip>,
|
||||||
|
pub flags: StripGroupFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StripGroup {
|
||||||
|
fn read(data: &[u8], header: StripGroupHeader) -> Result<Self> {
|
||||||
|
Ok(StripGroup {
|
||||||
|
vertices: header
|
||||||
|
.vertex_indexes()
|
||||||
|
.map(|index| {
|
||||||
|
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "Vertex",
|
||||||
|
offset: index,
|
||||||
|
})?;
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
reader.read_le().map_err(ModelError::from)
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
strips: header
|
||||||
|
.strip_indexes()
|
||||||
|
.map(|index| {
|
||||||
|
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "Strip",
|
||||||
|
offset: index,
|
||||||
|
})?;
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
let header = reader.read_le()?;
|
||||||
|
Strip::read(data, header)
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
flags: header.flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Strip {
|
||||||
|
// todo vertex indexes
|
||||||
|
// todo bone state changes
|
||||||
|
pub vertices: Vec<Vertex>,
|
||||||
|
pub flags: StripFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Strip {
|
||||||
|
fn read(data: &[u8], header: StripHeader) -> Result<Self> {
|
||||||
|
Ok(Strip {
|
||||||
|
vertices: header
|
||||||
|
.vertex_indexes()
|
||||||
|
.map(|index| {
|
||||||
|
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
data: "Vertex",
|
||||||
|
offset: index,
|
||||||
|
})?;
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
reader.read_le().map_err(ModelError::from)
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?,
|
||||||
|
flags: header.flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
202
src/vtx/raw.rs
Normal file
202
src/vtx/raw.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
use crate::index_range;
|
||||||
|
use binrw::BinRead;
|
||||||
|
use bitflags::bitflags;
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct VtxHeader {
|
||||||
|
pub version: i32,
|
||||||
|
pub vertex_cache_size: i32,
|
||||||
|
pub max_bones_per_strip: u16,
|
||||||
|
pub max_bones_per_triangle: u16,
|
||||||
|
pub max_bones_per_vertex: i32,
|
||||||
|
pub checksum: [u8; 4],
|
||||||
|
pub lod_count: i32,
|
||||||
|
pub material_replacement_list: i32,
|
||||||
|
body_part_count: i32,
|
||||||
|
body_part_offset: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(size_of::<VtxHeader>(), 36);
|
||||||
|
|
||||||
|
impl VtxHeader {
|
||||||
|
pub fn body_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(
|
||||||
|
self.body_part_offset,
|
||||||
|
self.body_part_count,
|
||||||
|
size_of::<BodyPartHeader>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct BodyPartHeader {
|
||||||
|
model_count: i32,
|
||||||
|
model_offset: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyPartHeader {
|
||||||
|
pub fn model_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(
|
||||||
|
self.model_offset,
|
||||||
|
self.model_count,
|
||||||
|
size_of::<ModelHeader>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct ModelHeader {
|
||||||
|
lod_count: i32,
|
||||||
|
lod_offset: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModelHeader {
|
||||||
|
pub fn lod_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.lod_offset, self.lod_count, size_of::<ModelLodHeader>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct ModelLodHeader {
|
||||||
|
mesh_count: i32,
|
||||||
|
mesh_offset: i32,
|
||||||
|
pub switch_point: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(size_of::<ModelLodHeader>(), 12);
|
||||||
|
|
||||||
|
impl ModelLodHeader {
|
||||||
|
pub fn mesh_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.mesh_offset, self.mesh_count, size_of::<MeshHeader>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, BinRead)]
|
||||||
|
#[repr(packed)]
|
||||||
|
pub struct MeshHeader {
|
||||||
|
strip_group_count: i32,
|
||||||
|
strip_group_offset: i32,
|
||||||
|
pub flags: MeshFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshHeader {
|
||||||
|
pub fn strip_group_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(
|
||||||
|
self.strip_group_offset,
|
||||||
|
self.strip_group_count,
|
||||||
|
size_of::<StripGroupHeader>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct MeshFlags: u8 {
|
||||||
|
const IS_TEETH = 0x01;
|
||||||
|
const IS_EYES = 0x02;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, BinRead)]
|
||||||
|
#[repr(packed)]
|
||||||
|
pub struct StripGroupHeader {
|
||||||
|
vertex_count: i32,
|
||||||
|
vertex_offset: i32,
|
||||||
|
index_count: i32,
|
||||||
|
index_offset: i32,
|
||||||
|
strip_count: i32,
|
||||||
|
strip_offset: i32,
|
||||||
|
pub flags: StripGroupFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(size_of::<StripGroupHeader>(), 25);
|
||||||
|
|
||||||
|
impl StripGroupHeader {
|
||||||
|
/// Index into the VVD file vertexes
|
||||||
|
pub fn vertex_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(
|
||||||
|
self.vertex_offset,
|
||||||
|
self.vertex_count,
|
||||||
|
size_of::<u16>(), // Vertex index from .VVD's vertex array
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.index_offset, self.index_count, size_of::<Vertex>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strip_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(
|
||||||
|
self.strip_offset,
|
||||||
|
self.strip_count,
|
||||||
|
size_of::<StripHeader>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct StripGroupFlags: u8 {
|
||||||
|
const IS_FLEXED = 0x01;
|
||||||
|
const IS_HWSKINNED = 0x02;
|
||||||
|
const IS_DELTA_FLEXED = 0x04;
|
||||||
|
const SUPPRESS_HW_MORPH = 0x08;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, BinRead)]
|
||||||
|
#[repr(packed)]
|
||||||
|
pub struct StripHeader {
|
||||||
|
index_count: i32,
|
||||||
|
index_offset: i32,
|
||||||
|
vertex_count: i32,
|
||||||
|
vertex_offset: i32,
|
||||||
|
pub flags: StripFlags,
|
||||||
|
bone_state_change_count: i32,
|
||||||
|
bone_state_change_offset: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(size_of::<StripHeader>(), 25);
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct StripFlags: u8 {
|
||||||
|
const IS_TRI_LIST = 0x01;
|
||||||
|
const IS_TRI_STRIP = 0x02;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StripHeader {
|
||||||
|
/// Index into the VVD file vertexes
|
||||||
|
pub fn vertex_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(
|
||||||
|
self.vertex_offset,
|
||||||
|
self.vertex_count,
|
||||||
|
size_of::<u16>(), // Vertex index from .VVD's vertex array
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(self.index_offset, self.index_count, size_of::<Vertex>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bone_state_change_indexes(&self) -> impl Iterator<Item = usize> {
|
||||||
|
index_range(
|
||||||
|
self.bone_state_change_offset,
|
||||||
|
self.bone_state_change_count,
|
||||||
|
todo!(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, BinRead)]
|
||||||
|
#[repr(packed)]
|
||||||
|
pub struct Vertex {
|
||||||
|
pub bone_weight_indexes: [u8; 3],
|
||||||
|
pub bone_count: u8,
|
||||||
|
pub original_mesh_vertex_id: u16,
|
||||||
|
pub bone_id: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(size_of::<Vertex>(), 9);
|
||||||
16
src/vvd.rs
Normal file
16
src/vvd.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
pub const MDL_VERSION: i32 = 7;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
pub id: i32,
|
||||||
|
pub version: i32,
|
||||||
|
pub checksum: [u8; 4],
|
||||||
|
pub lod_count: i32,
|
||||||
|
pub lod_vertex_count: [i32; 8],
|
||||||
|
pub fixup_count: i32,
|
||||||
|
pub fixup_index: i32,
|
||||||
|
pub vertex_index: i32,
|
||||||
|
pub tangent_index: i32,
|
||||||
|
}
|
||||||
15
tests/parse.rs
Normal file
15
tests/parse.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
use std::fs::read;
|
||||||
|
use vmdl::mdl::Mdl;
|
||||||
|
use vmdl::vtx::Vtx;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_mdl() {
|
||||||
|
let data = read("data/barrel01.mdl").unwrap();
|
||||||
|
Mdl::read(&data).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_vtx() {
|
||||||
|
let data = read("data/barrel01.dx90.vtx").unwrap();
|
||||||
|
Vtx::read(&data).unwrap();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue