mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-04 00:54:14 +02:00
vtx
This commit is contained in:
parent
f33101119c
commit
b0d3245e07
13 changed files with 510 additions and 55 deletions
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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue