vmdl/
lib.rs

1mod compressed_vector;
2mod error;
3mod handle;
4pub mod mdl;
5mod shared;
6pub mod vtx;
7pub mod vvd;
8
9pub use crate::mdl::Mdl;
10use crate::mdl::{AnimationDescription, Bone, ModelFlags, PoseParameterDescription, TextureInfo};
11pub use crate::vtx::Vtx;
12use crate::vvd::Vertex;
13pub use crate::vvd::Vvd;
14use bytemuck::{pod_read_unaligned, Contiguous, Pod};
15use cgmath::{Matrix4, SquareMatrix, Transform, Vector3};
16pub use error::*;
17pub use handle::Handle;
18use itertools::Either;
19pub use shared::*;
20use std::any::type_name;
21use std::fs;
22use std::iter::once;
23use std::mem::size_of;
24use std::path::Path;
25
26pub struct Model {
27    #[allow(dead_code)]
28    mdl: Mdl,
29    vtx: Vtx,
30    vvd: Vvd,
31}
32
33impl Model {
34    pub fn from_parts(mdl: Mdl, vtx: Vtx, vvd: Vvd) -> Self {
35        Model { mdl, vtx, vvd }
36    }
37
38    /// Load the model from path
39    ///
40    /// Requires a path to the `.mdl` file and the `.dx90.vtx` and `.vvd` files for the model to be in the same directory.
41    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, ModelError> {
42        let path = path.as_ref();
43        let data = fs::read(path)?;
44        let mdl = Mdl::read(&data)?;
45        let data = fs::read(path.with_extension("dx90.vtx"))?;
46        let vtx = Vtx::read(&data)?;
47        let data = fs::read(path.with_extension("vvd"))?;
48        let vvd = Vvd::read(&data)?;
49
50        Ok(Model::from_parts(mdl, vtx, vvd))
51    }
52
53    pub fn vertices(&self) -> &[Vertex] {
54        &self.vvd.vertices
55    }
56
57    pub fn tangents(&self) -> &[[f32; 4]] {
58        &self.vvd.tangents
59    }
60
61    pub fn texture_directories(&self) -> &[String] {
62        &self.mdl.texture_paths
63    }
64
65    pub fn textures(&self) -> &[TextureInfo] {
66        &self.mdl.textures
67    }
68
69    pub fn skin_tables(&self) -> impl Iterator<Item = SkinTable> {
70        if self.mdl.header.skin_reference_count > 0 {
71            Either::Left(
72                self.mdl
73                    .skin_table
74                    .chunks(self.mdl.header.skin_reference_count as usize)
75                    .map(|chunk| SkinTable {
76                        table: chunk,
77                        textures: &self.mdl.textures,
78                    }),
79            )
80        } else {
81            Either::Right(once(SkinTable {
82                table: &[],
83                textures: &[],
84            }))
85        }
86    }
87
88    pub fn animations(&self) -> impl Iterator<Item = &AnimationDescription> {
89        self.mdl.local_animations.iter()
90    }
91
92    pub fn meshes(&self) -> impl Iterator<Item = Mesh> {
93        let mdl_meshes = self
94            .mdl
95            .body_parts
96            .iter()
97            .flat_map(|part| part.models.iter())
98            .flat_map(|model| {
99                model
100                    .meshes
101                    .iter()
102                    .map(|mesh| (mesh, model.name.as_str(), model.vertex_offset as usize))
103            });
104
105        let vtx_meshes = self
106            .vtx
107            .body_parts
108            .iter()
109            .flat_map(|part| part.models.iter())
110            .flat_map(|model| model.lods.first())
111            .flat_map(|lod| lod.meshes.iter());
112
113        mdl_meshes
114            .zip(vtx_meshes)
115            .map(|((mdl, model_name, model_vertex_offset), vtx)| Mesh {
116                model_vertex_offset,
117                model_name,
118                vertices: self.vertices(),
119                tangents: self.tangents(),
120                mdl,
121                vtx,
122            })
123    }
124
125    /// Calculate bounding coordinates of the model
126    pub fn bounding_box(&self) -> (Vector, Vector) {
127        (
128            self.mdl.header.bounding_box[0],
129            self.mdl.header.bounding_box[1],
130        )
131    }
132
133    pub fn name(&self) -> &str {
134        self.mdl.name.as_str()
135    }
136
137    pub fn bones(&self) -> impl Iterator<Item = &Bone> {
138        self.mdl.bones.iter()
139    }
140
141    pub fn root_transform(&self) -> Matrix4<f32> {
142        if self.mdl.header.flags.contains(ModelFlags::STATIC_PROP) {
143            return Matrix4::identity();
144        }
145
146        self.bones()
147            .next()
148            .map(|bone| Matrix4::from(bone.rot))
149            .unwrap_or_else(Matrix4::identity)
150    }
151
152    pub fn idle_transform(&self) -> Matrix4<f32> {
153        if self.mdl.header.flags.contains(ModelFlags::STATIC_PROP) {
154            return Matrix4::identity();
155        }
156
157        self.mdl
158            .local_animations
159            .iter()
160            .filter_map(|desc| desc.animations.iter().find(|animation| animation.bone == 0))
161            .find(|anim| anim.rotation_looks_valid())
162            .map(|animation| animation.rotation(0))
163            .map(Matrix4::from)
164            .unwrap_or_else(Matrix4::identity)
165    }
166
167    pub fn surface_prop(&self) -> &str {
168        self.mdl.surface_prop.as_str()
169    }
170
171    pub fn poses(&self) -> impl Iterator<Item = &PoseParameterDescription> {
172        self.mdl.pose_parameters.iter()
173    }
174
175    pub fn apply_root_transform(&self, vec: Vector) -> Vector {
176        let transform = self.idle_transform() * self.root_transform();
177        transform.transform_vector(Vector3::from(vec)).into()
178    }
179}
180
181pub struct SkinTable<'a> {
182    textures: &'a [TextureInfo],
183    table: &'a [u16],
184}
185
186impl<'a> SkinTable<'a> {
187    pub fn texture(&self, index: i32) -> Option<&'a str> {
188        self.texture_info(index).map(|info| info.name.as_str())
189    }
190
191    pub fn texture_index(&self, index: i32) -> Option<usize> {
192        let texture_index = self.table.get(index as usize)?;
193        Some(*texture_index as usize)
194    }
195    pub fn texture_info(&self, index: i32) -> Option<&'a TextureInfo> {
196        let texture_index = self.table.get(index as usize)?;
197        self.textures.get(*texture_index as usize)
198    }
199}
200
201pub struct Mesh<'a> {
202    pub model_name: &'a str,
203    model_vertex_offset: usize,
204    vertices: &'a [Vertex],
205    tangents: &'a [[f32; 4]],
206    mdl: &'a mdl::Mesh,
207    vtx: &'a vtx::Mesh,
208}
209
210impl<'a> Mesh<'a> {
211    /// Vertex indices into the model's vertex list
212    pub fn vertex_strip_indices(&self) -> impl Iterator<Item = impl Iterator<Item = usize> + 'a> {
213        let mdl_offset = self.mdl.vertex_offset as usize + self.model_vertex_offset;
214        self.vtx.strip_groups.iter().flat_map(move |strip_group| {
215            let group_indices = &strip_group.indices;
216            let vertices = &strip_group.vertices;
217            strip_group.strips.iter().map(move |strip| {
218                strip
219                    .indices()
220                    .map(move |index| group_indices[index] as usize)
221                    .map(move |index| vertices[index].original_mesh_vertex_id as usize + mdl_offset)
222            })
223        })
224    }
225
226    pub fn material_index(&self) -> i32 {
227        self.mdl.material
228    }
229
230    pub fn vertices(&self) -> impl Iterator<Item = &'a Vertex> + 'a {
231        self.vertex_strip_indices()
232            .flat_map(|strip| strip.map(|index| &self.vertices[index]))
233    }
234
235    pub fn tangents(&self) -> impl Iterator<Item = [f32; 4]> + '_ {
236        self.vertex_strip_indices()
237            .flat_map(|strip| strip.map(|index| self.tangents[index]))
238    }
239}
240
241fn index_range(index: i32, count: i32, size: usize) -> impl Iterator<Item = usize> {
242    (0..count as usize)
243        .map(move |i| i * size)
244        .map(move |i| index as usize + i)
245}
246
247fn read_relative_iter<'a, T: ReadRelative, I: 'a + Iterator<Item = usize>>(
248    data: &'a [u8],
249    indexes: I,
250) -> impl Iterator<Item = Result<T, ModelError>> + 'a {
251    indexes.map(|index| {
252        let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
253            data: type_name::<T>(),
254            offset: index,
255        })?;
256        let header = <T::Header as Readable>::read(data)?;
257        T::read(data, header)
258    })
259}
260
261fn read_relative<T: ReadRelative, I: Iterator<Item = usize>>(
262    data: &[u8],
263    indexes: I,
264) -> Result<Vec<T>, ModelError> {
265    read_relative_iter(data, indexes).collect()
266}
267
268fn read_single<T: ReadRelative, I: TryInto<usize>>(data: &[u8], index: I) -> Result<T, ModelError> {
269    let index = index.try_into().map_err(|_| ModelError::OutOfBounds {
270        data: type_name::<T>(),
271        offset: usize::MAX_VALUE,
272    })?;
273    let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
274        data: type_name::<T>(),
275        offset: index,
276    })?;
277    let header = <T::Header as Readable>::read(data)?;
278    T::read(data, header)
279}
280
281trait Readable: Sized {
282    fn read(data: &[u8]) -> Result<Self, ModelError>;
283}
284
285impl<T: Pod> Readable for T {
286    fn read(data: &[u8]) -> Result<Self, ModelError> {
287        let data = data
288            .get(0..size_of::<Self>())
289            .ok_or(ModelError::Eof(size_of::<Self>()))?;
290        Ok(pod_read_unaligned(data))
291    }
292}
293
294trait ReadRelative: Sized {
295    type Header: Readable;
296
297    fn read(data: &[u8], header: Self::Header) -> Result<Self, ModelError>;
298}
299
300trait ReadableRelative: Readable {}
301
302impl ReadableRelative for u8 {}
303impl ReadableRelative for u16 {}
304impl ReadableRelative for u32 {}
305impl ReadableRelative for i8 {}
306impl ReadableRelative for i16 {}
307impl ReadableRelative for i32 {}
308impl ReadableRelative for f32 {}
309impl<T: ReadableRelative + Pod> ReadableRelative for [T; 1] {}
310impl<T: ReadableRelative + Pod> ReadableRelative for [T; 2] {}
311impl<T: ReadableRelative + Pod> ReadableRelative for [T; 3] {}
312impl<T: ReadableRelative + Pod> ReadableRelative for [T; 4] {}
313
314impl<T: ReadableRelative> ReadRelative for T {
315    type Header = T;
316
317    fn read(_data: &[u8], header: Self::Header) -> Result<Self, ModelError> {
318        Ok(header)
319    }
320}
321
322impl ReadRelative for String {
323    type Header = ();
324
325    fn read(data: &[u8], _header: Self::Header) -> Result<Self, ModelError> {
326        let bytes = data.iter().copied().take_while(|byte| *byte != 0).collect();
327        String::from_utf8(bytes).map_err(ModelError::from)
328    }
329}