Skip to main content

rpfm_lib/files/rigidmodel/vertices/
mod.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! Vertex format definitions and I/O for RigidModel files.
12//!
13//! # Overview
14//!
15//! Vertices define the 3D geometry of meshes. RigidModel files support multiple vertex
16//! formats optimized for different use cases, with extensive use of data compression
17//! to reduce file size.
18//!
19//! # Vertex Formats
20//!
21//! | Format    | ID | Purpose                          | Bone Support |
22//! |-----------|---.|----------------------------------|--------------|
23//! | Static    | 0  | Standard non-animated geometry   | No           |
24//! | Collision | 1  | Simplified collision meshes      | No           |
25//! | Weighted  | 3  | Skeletal animation (2 bones)     | Yes (2)      |
26//! | Cinematic | 4  | High-quality cutscenes (4 bones) | Yes (4)      |
27//! | Grass     | 5  | Vegetation/grass rendering       | No           |
28//! | Uk8       | 8  | Unknown (seen in water planes)   | Unknown      |
29//! | Uk12      | 12 | Unknown (seen in coral shrubs)   | Unknown      |
30//! | ClothSim  | 25 | Cloth physics simulation         | Yes          |
31//!
32//! # Compression Techniques
33//!
34//! Vertices use multiple compression methods to reduce storage:
35//!
36//! ## Half-Precision Floats (f16)
37//! - Positions and UVs stored as 16-bit floats instead of 32-bit
38//! - Reduces size by 50% with minimal precision loss
39//!
40//! ## Normalized Vectors (u8)
41//! - Normals, tangents, bitangents stored as unsigned bytes
42//! - Converted from [-1.0, 1.0] range to [0, 255] range
43//! - Formula: `u8_value = (float_value + 1.0) * 127.5`
44//!
45//! ## Percentage Encoding (Bone Weights)
46//! - Bone weights stored as u8 representing percentages
47//! - Converted from [0.0, 1.0] to [0, 255]
48//! - Formula: `u8_value = float_value * 255.0`
49//!
50//! # Format-Specific Fields
51//!
52//! Different vertex formats read/write different subsets of vertex data:
53//!
54//! - **Static**: Position, UVs, normal, tangent, bitangent
55//! - **Weighted**: Static fields + bone indices + bone weights (2 bones)
56//! - **Cinematic**: Static fields + bone indices + bone weights (4 bones)
57//! - **Collision**: Position only (minimal data)
58//! - **Grass**: Position + UVs + normal + special grass data
59//! - **ClothSim**: Similar to weighted with cloth-specific data
60//!
61//! # Material-Dependent Variations
62//!
63//! The exact fields read also depend on the material type. For example:
64//! - RsTerrain materials read minimal vertex data
65//! - DefaultMaterial reads full vertex attributes
66//! - Cloth materials include additional physics data
67
68use getset::{Getters, MutGetters, Setters};
69use nalgebra::{Vector2, Vector4};
70use serde::{Deserialize, Serialize};
71
72use crate::binary::{ReadBytes, WriteBytes};
73use crate::error::{Result, RLibError};
74
75use super::materials::MaterialType;
76
77//---------------------------------------------------------------------------//
78//                              Enum & Structs
79//---------------------------------------------------------------------------//
80
81/// Vertex format identifier determining data layout and compression.
82///
83/// Different formats optimize for different use cases. The numeric value is stored
84/// as a u16 in the file format.
85#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
86#[repr(u16)]
87pub enum VertexFormat {
88    /// Standard static geometry without animation.
89    #[default]
90    Static = 0,
91    /// Simplified collision mesh vertices (position only).
92    Collision = 1,
93    /// Skeletal animation vertices with bone weights (2 bones per vertex).
94    Weighted = 3,
95    /// High-quality cinematic vertices (4 bones per vertex).
96    Cinematic = 4,
97    /// Grass/vegetation vertices with special rendering data.
98    Grass = 5,
99    /// Unknown format (observed in glb_water_planes models).
100    Uk8 = 8,
101    /// Unknown format (observed in sea_coral_shrubs_02 models).
102    Uk12 = 12,
103    /// Cloth simulation vertices with physics data.
104    ClothSim = 25,
105}
106
107/// Universal vertex structure containing all possible vertex attributes.
108///
109/// Not all fields are used by all vertex formats - the format and material type
110/// determine which fields contain valid data. Fields are stored with various
111/// compression techniques in the binary format.
112///
113/// # Field Usage by Format
114///
115/// - **Static**: position, UVs, normal, tangent, bitangent
116/// - **Weighted**: Static fields + bone_indices (2) + weights (2)
117/// - **Cinematic**: Static fields + bone_indices (4) + weights (4)
118/// - **Collision**: position only
119/// - **Grass**: position, UVs, normal, + grass-specific data
120/// - **ClothSim**: Similar to Weighted with cloth data
121///
122/// # Compression Notes
123///
124/// When reading from file:
125/// - Position is often stored as Vector4 of f16 (half-precision)
126/// - UVs stored as f16
127/// - Normals/tangents/bitangents stored as u8 normalized vectors
128/// - Bone weights stored as u8 percentages
129#[derive(Clone, Debug, Default, PartialEq, Getters, MutGetters, Setters, Serialize, Deserialize)]
130#[getset(get = "pub", get_mut = "pub", set = "pub")]
131pub struct Vertex {
132
133    /// 3D position of the vertex (x, y, z, w).
134    /// Often stored as f16 in file, expanded to f32 in memory.
135    position: Vector4<f32>,
136
137    /// Primary texture UV coordinates (u, v).
138    /// Often stored as f16 in file, expanded to f32 in memory.
139    texture_coordinate: Vector2<f32>,
140
141    /// Secondary texture UV coordinates (u, v) for multi-texturing.
142    /// Often stored as f16 in file, expanded to f32 in memory.
143    texture_coordinate2: Vector2<f32>,
144
145    /// Vertex normal vector for lighting calculations (x, y, z, w).
146    /// Often stored as u8 normalized in file, expanded to f32 in memory.
147    normal: Vector4<f32>,
148
149    /// Tangent vector for normal mapping (x, y, z, w).
150    /// Often stored as u8 normalized in file, expanded to f32 in memory.
151    tangent: Vector4<f32>,
152
153    /// Bitangent vector for normal mapping (x, y, z, w).
154    /// Often stored as u8 normalized in file, expanded to f32 in memory.
155    bitangent: Vector4<f32>,
156
157    /// Vertex color (r, g, b, a). Typically unused in modern rendering (textures used instead).
158    color: Vector4<f32>,
159
160    /// Bone indices for skeletal animation (up to 4 bones).
161    /// For Weighted format: only first 2 are used.
162    /// For Cinematic format: all 4 are used.
163    bone_indices: Vector4<u8>,
164
165    /// Bone weights for skeletal animation (up to 4 weights, should sum to 1.0).
166    /// Often stored as u8 percentages in file, expanded to f32 in memory.
167    weights: Vector4<f32>,
168
169    /// Unknown field (version 8+ only, purpose undocumented).
170    uk_1: Vector4<u8>,
171}
172
173//---------------------------------------------------------------------------//
174//                            Implementation
175//---------------------------------------------------------------------------//
176
177impl TryFrom<u16> for VertexFormat {
178    type Error = RLibError;
179    fn try_from(value: u16) -> Result<Self> {
180        match value {
181            _ if value == Self::Static as u16 => Ok(Self::Static),
182            _ if value == Self::Collision as u16 => Ok(Self::Collision),
183            _ if value == Self::Weighted as u16 => Ok(Self::Weighted),
184            _ if value == Self::Cinematic as u16 => Ok(Self::Cinematic),
185            _ if value == Self::Grass as u16 => Ok(Self::Grass),
186            _ if value == Self::Uk8 as u16 => Ok(Self::Uk8),
187            _ if value == Self::Uk12 as u16 => Ok(Self::Uk12),
188            _ if value == Self::ClothSim as u16 => Ok(Self::ClothSim),
189            _ => Err(RLibError::DecodingRigidModelUnknownVertexFormat(value))
190        }
191    }
192}
193
194impl From<VertexFormat> for u16 {
195    fn from(value: VertexFormat) -> u16 {
196        value as u16
197    }
198}
199
200impl Vertex {
201
202    /// Reads a vertex from binary data based on the vertex format and material type.
203    ///
204    /// Different combinations of vertex format and material type have different binary
205    /// layouts. This function dispatches to the appropriate reading logic based on
206    /// the format/material combination.
207    ///
208    /// # Arguments
209    ///
210    /// * `data` - Binary data source.
211    /// * `version` - RigidModel version (affects layout for some formats).
212    /// * `vtype` - Vertex format specifying the data layout.
213    /// * `mtype` - Material type (affects layout for static vertices).
214    ///
215    /// # Errors
216    ///
217    /// Returns an error if:
218    /// - The vertex format is unknown ([`RLibError::DecodingRigidModelUnknownVertexFormat`])
219    /// - The vertex format is unsupported for the material type
220    ///   ([`RLibError::DecodingRigidModelUnsupportedVertexFormatForMaterial`])
221    pub fn read<R: ReadBytes>(data: &mut R, version: u32, vtype: VertexFormat, mtype: MaterialType) -> Result<Self> {
222        let mut v = Self::default();
223        match vtype {
224            VertexFormat::Static => match mtype {
225                MaterialType::DefaultMaterial => {
226                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
227                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
228                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
229                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
230                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
231                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
232                    v.uk_1 = data.read_vector_4_u8()?;
233                },
234                MaterialType::RsTerrain => {
235                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
236                    v.normal = data.read_vector_4_f32_normal_from_vector_4_f16()?;
237                },
238                MaterialType::WeightedTextureBlend => {
239                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
240                    v.normal = data.read_vector_4_f32_normal_from_vector_4_f16()?;
241                },
242                MaterialType::ProjectedDecalV4 => {
243                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
244                },
245                MaterialType::RsRiver => {
246                    v.position = data.read_vector_4_f32()?;
247                    v.normal = data.read_vector_4_f32()?;
248                },
249                MaterialType::TerrainBlend => {
250                    v.position = data.read_vector_4_f32()?;
251                    v.normal = data.read_vector_4_f32()?;
252                },
253                MaterialType::TiledDirtmap => {
254                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
255                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
256                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
257                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
258                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
259                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
260
261                    v.uk_1 = data.read_vector_4_u8()?;
262                },
263                MaterialType::ShipAmbientmap => {
264                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
265                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
266                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
267                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
268                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
269                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
270
271                    v.uk_1 = data.read_vector_4_u8()?;
272                },
273                MaterialType::Decal => {
274                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
275                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
276                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
277                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
278                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
279                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
280
281                    v.uk_1 = data.read_vector_4_u8()?;
282                }
283                MaterialType::Dirtmap => {
284                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
285                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
286                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
287                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
288                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
289                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
290
291                    v.uk_1 = data.read_vector_4_u8()?;
292                }
293                MaterialType::AlphaBlend => {
294                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
295                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
296                }
297                MaterialType::Cloth => {
298                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
299                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
300                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
301                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
302                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
303                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
304
305                    v.uk_1 = data.read_vector_4_u8()?;
306                }
307                _ => return Err(RLibError::DecodingRigidModelUnsupportedVertexFormatForMaterial(vtype.into(), mtype.into()))
308            },
309
310            VertexFormat::Collision => {
311                v.position = data.read_vector_4_f32_from_vec_3_f32()?;
312                v.normal = data.read_vector_4_f32_from_vec_3_f32()?;
313            }
314
315            VertexFormat::Weighted => {
316                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
317
318                let bone_indices = data.read_vector_2_u8()?;
319                v.bone_indices = Vector4::new(bone_indices.x, bone_indices.y, 0, 0);
320
321                let weights = data.read_vector_2_f32_pct_from_vector_2_u8()?;
322                v.weights = Vector4::new(weights.x, weights.y, 0.0, 0.0);
323
324                v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
325                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
326                v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
327                v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
328
329                if version >= 8 {
330                    v.uk_1 = data.read_vector_4_u8()?;
331                }
332            }
333            VertexFormat::Cinematic => {
334                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
335
336                // w is not used in this one.
337                v.bone_indices = data.read_vector_4_u8()?;
338                v.weights = data.read_vector_4_f32_pct_from_vector_4_u8()?;
339                v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
340                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
341                v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
342                v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
343
344                if version >= 8 {
345                    v.uk_1 = data.read_vector_4_u8()?;
346                }
347            }
348            VertexFormat::Uk8 => {
349                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
350                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
351            }
352            VertexFormat::Uk12 => {
353                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
354                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
355                v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
356                v.uk_1 = data.read_vector_4_u8()?;
357            }
358            _ => return Err(RLibError::DecodingRigidModelUnknownVertexFormat(vtype.into()))
359        }
360
361        Ok(v)
362    }
363
364    /// Writes a vertex to binary data based on the vertex format and material type.
365    ///
366    /// Different combinations of vertex format and material type have different binary
367    /// layouts. This function dispatches to the appropriate writing logic based on
368    /// the format/material combination.
369    ///
370    /// # Arguments
371    ///
372    /// * `data` - Output buffer.
373    /// * `version` - RigidModel version (affects layout for some formats).
374    /// * `vtype` - Vertex format specifying the data layout.
375    /// * `mtype` - Material type (affects layout for static vertices).
376    ///
377    /// # Errors
378    ///
379    /// Returns an error if:
380    /// - The vertex format is unknown ([`RLibError::DecodingRigidModelUnknownVertexFormat`])
381    /// - The vertex format is unsupported for the material type
382    ///   ([`RLibError::DecodingRigidModelUnsupportedVertexFormatForMaterial`])
383    pub fn write<W: WriteBytes>(&self, data: &mut W, version: u32, vtype: VertexFormat, mtype: MaterialType) -> Result<()> {
384        match vtype {
385            VertexFormat::Static => match mtype {
386                MaterialType::DefaultMaterial => {
387                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
388                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
389                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
390                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
391                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
392                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
393
394                    // Color? Causes difference when processing if treated as color.
395                    data.write_vector_4_u8(self.uk_1)?;
396                }
397                MaterialType::RsTerrain => {
398                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
399                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.normal())?;
400                }
401                MaterialType::WeightedTextureBlend => {
402                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
403                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.normal())?;
404                }
405                MaterialType::ProjectedDecalV4 => {
406                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
407                }
408                MaterialType::RsRiver => {
409                    data.write_vector_4_f32(*self.position())?;
410                    data.write_vector_4_f32(*self.normal())?;
411                }
412                MaterialType::TerrainBlend => {
413                    data.write_vector_4_f32(*self.position())?;
414                    data.write_vector_4_f32(*self.normal())?;
415                }
416                MaterialType::TiledDirtmap => {
417                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
418                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
419                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
420                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
421                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
422                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
423                    data.write_vector_4_u8(self.uk_1)?;
424                }
425                MaterialType::ShipAmbientmap => {
426                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
427                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
428                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
429                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
430                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
431                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
432                    data.write_vector_4_u8(self.uk_1)?;
433                }
434                MaterialType::Decal => {
435                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
436                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
437                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
438                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
439                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
440                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
441                    data.write_vector_4_u8(self.uk_1)?;
442                }
443                MaterialType::Dirtmap => {
444                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
445                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
446                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
447                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
448                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
449                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
450                    data.write_vector_4_u8(self.uk_1)?;
451                }
452                MaterialType::AlphaBlend => {
453                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
454                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
455                }
456                MaterialType::Cloth => {
457                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
458                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
459                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
460                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
461                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
462                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
463                    data.write_vector_4_u8(self.uk_1)?;
464                }
465                _ => return Err(RLibError::DecodingRigidModelUnsupportedVertexFormatForMaterial(vtype.into(), mtype.into()))
466            },
467
468            VertexFormat::Collision => {
469                data.write_vector_4_f32_to_vector_3_f32(self.position)?;
470                data.write_vector_4_f32_to_vector_3_f32(self.normal)?;
471            }
472
473            VertexFormat::Weighted => {
474                data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
475                data.write_vector_2_u8(Vector2::new(self.bone_indices.x, self.bone_indices.y))?;
476                data.write_vector_2_f32_pct_as_vector_2_u8(Vector2::new(self.weights.x, self.weights.y))?;
477                data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
478                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
479                data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
480                data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
481
482                if version >= 8 {
483                    data.write_vector_4_u8(self.uk_1)?;
484                }
485            }
486            VertexFormat::Cinematic => {
487                data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
488                data.write_vector_4_u8(self.bone_indices)?;
489                data.write_vector_4_f32_pct_as_vector_4_u8(self.weights)?;
490                data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
491                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
492                data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
493                data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
494
495                if version >= 8 {
496                    data.write_vector_4_u8(self.uk_1)?;
497                }
498            }
499            VertexFormat::Uk8 => {
500                data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
501                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
502            }
503            VertexFormat::Uk12 => {
504                data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
505                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
506                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
507                data.write_vector_4_u8(self.uk_1)?;
508            }
509            _ => return Err(RLibError::DecodingRigidModelUnknownVertexFormat(vtype.into()))
510        }
511
512        Ok(())
513    }
514}
515/*
516/// Util to swap the x and z coordinates of a vector.
517fn swap_xz(input: &Vector4<f32>) -> Vector4<f32> {
518    let mut i = *input;
519    i.swap_rows(0, 2);
520    i
521}*/