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//! | Tree      | 6  | Tree rendering, with wind data   | No           |
29//! | Billboard | 7  | Tree billboards                  | No           |
30//! | Rope      | 11 | Ropes (meshes without vertices)  | No           |
31//! | Uk8       | 8  | Unknown (seen in water planes)   | Unknown      |
32//! | Uk12      | 12 | Unknown (coral shrubs; campaign vegetation in v6) | Unknown |
33//! | ClothSim  | 25 | Cloth physics simulation         | Yes          |
34//!
35//! # Compression Techniques
36//!
37//! Vertices use multiple compression methods to reduce storage:
38//!
39//! ## Half-Precision Floats (f16)
40//! - Positions and UVs stored as 16-bit floats instead of 32-bit
41//! - Reduces size by 50% with minimal precision loss
42//!
43//! ## Normalized Vectors (u8)
44//! - Normals, tangents, bitangents stored as unsigned bytes
45//! - Converted from [-1.0, 1.0] range to [0, 255] range
46//! - Formula: `u8_value = (float_value + 1.0) * 127.5`
47//!
48//! ## Percentage Encoding (Bone Weights)
49//! - Bone weights stored as u8 representing percentages
50//! - Converted from [0.0, 1.0] to [0, 255]
51//! - Formula: `u8_value = float_value * 255.0`
52//!
53//! # Format-Specific Fields
54//!
55//! Different vertex formats read/write different subsets of vertex data:
56//!
57//! - **Static**: Position, UVs, normal, tangent, bitangent
58//! - **Weighted**: Static fields + bone indices + bone weights (2 bones)
59//! - **Cinematic**: Static fields + bone indices + bone weights (4 bones)
60//! - **Collision**: Position only (minimal data)
61//! - **Grass**: Position + UVs + normal + special grass data
62//! - **ClothSim**: Similar to weighted with cloth-specific data
63//!
64//! # Material-Dependent Variations
65//!
66//! The exact fields read also depend on the material type. For example:
67//! - RsTerrain materials read minimal vertex data
68//! - DefaultMaterial reads full vertex attributes
69//! - Cloth materials include additional physics data
70
71use getset::{Getters, MutGetters, Setters};
72use half::f16;
73use nalgebra::{Vector2, Vector4};
74use serde::{Deserialize, Serialize};
75
76use crate::binary::{ReadBytes, WriteBytes};
77use crate::error::{Result, RLibError};
78
79use super::materials::MaterialType;
80
81//---------------------------------------------------------------------------//
82//                              Enum & Structs
83//---------------------------------------------------------------------------//
84
85/// Vertex format identifier determining data layout and compression.
86///
87/// Different formats optimize for different use cases. The numeric value is stored
88/// as a u16 in the file format.
89#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
90#[repr(u16)]
91pub enum VertexFormat {
92    /// Standard static geometry without animation.
93    #[default]
94    Static = 0,
95    /// Simplified collision mesh vertices (position only).
96    Collision = 1,
97    /// Skeletal animation vertices with bone weights (2 bones per vertex).
98    Weighted = 3,
99    /// High-quality cinematic vertices (4 bones per vertex).
100    Cinematic = 4,
101    /// Grass/vegetation vertices with special rendering data.
102    Grass = 5,
103    /// Tree vertices, with a second position and wind data.
104    Tree = 6,
105    /// Tree billboard vertices (position and UVs only).
106    Billboard = 7,
107    /// Format of rope meshes, which have no vertices of their own.
108    Rope = 11,
109    /// Unknown format (observed in glb_water_planes models).
110    Uk8 = 8,
111    /// Unknown format (observed in sea_coral_shrubs_02 models).
112    Uk12 = 12,
113    /// Cloth simulation vertices with physics data.
114    ClothSim = 25,
115}
116
117/// Universal vertex structure containing all possible vertex attributes.
118///
119/// Not all fields are used by all vertex formats - the format and material type
120/// determine which fields contain valid data. Fields are stored with various
121/// compression techniques in the binary format.
122///
123/// # Field Usage by Format
124///
125/// - **Static**: position, UVs, normal, tangent, bitangent
126/// - **Weighted**: Static fields + bone_indices (2) + weights (2)
127/// - **Cinematic**: Static fields + bone_indices (4) + weights (4)
128/// - **Collision**: position only
129/// - **Grass**: position, UVs, normal, + grass-specific data
130/// - **ClothSim**: Similar to Weighted with cloth data
131///
132/// # Compression Notes
133///
134/// When reading from file:
135/// - Position is often stored as Vector4 of f16 (half-precision)
136/// - UVs stored as f16
137/// - Normals/tangents/bitangents stored as u8 normalized vectors
138/// - Bone weights stored as u8 percentages
139#[derive(Clone, Debug, Default, PartialEq, Getters, MutGetters, Setters, Serialize, Deserialize)]
140#[getset(get = "pub", get_mut = "pub", set = "pub")]
141pub struct Vertex {
142
143    /// 3D position of the vertex (x, y, z, w).
144    /// Often stored as f16 in file, expanded to f32 in memory.
145    position: Vector4<f32>,
146
147    /// Primary texture UV coordinates (u, v).
148    /// Often stored as f16 in file, expanded to f32 in memory.
149    texture_coordinate: Vector2<f32>,
150
151    /// Secondary texture UV coordinates (u, v) for multi-texturing.
152    /// Often stored as f16 in file, expanded to f32 in memory.
153    ///
154    /// In tree vertices these are not uvs but the LOD blend weights: the game's tree vertex shaders (rigidbranchcommon_v3,
155    /// rigidleafcommon_v3) draw `x * position + y * position2`. Full detail vertices have (0, 1).
156    texture_coordinate2: Vector2<f32>,
157
158    /// Vertex normal vector for lighting calculations (x, y, z, w).
159    /// Often stored as u8 normalized in file, expanded to f32 in memory.
160    normal: Vector4<f32>,
161
162    /// Tangent vector for normal mapping (x, y, z, w).
163    /// Often stored as u8 normalized in file, expanded to f32 in memory.
164    tangent: Vector4<f32>,
165
166    /// Bitangent vector for normal mapping (x, y, z, w).
167    /// Often stored as u8 normalized in file, expanded to f32 in memory.
168    bitangent: Vector4<f32>,
169
170    /// Vertex color (r, g, b, a). Typically unused in modern rendering (textures used instead).
171    color: Vector4<f32>,
172
173    /// Bone indices for skeletal animation (up to 4 bones).
174    /// For Weighted format: only first 2 are used.
175    /// For Cinematic format: all 4 are used.
176    bone_indices: Vector4<u8>,
177
178    /// Bone weights for skeletal animation (up to 4 weights, should sum to 1.0).
179    /// Often stored as u8 percentages in file, expanded to f32 in memory.
180    weights: Vector4<f32>,
181
182    /// Unknown field (version 8+ only, purpose undocumented).
183    uk_1: Vector4<u8>,
184
185    /// Second position of the vertex (x, y, z, w). Seen in bow wave vertices and in tree vertices, where it's the LOD position:
186    /// the position the vertex is drawn at, blended with `position` by the weights in `texture_coordinate2` (full detail vertices
187    /// keep `position` at the origin and are drawn entirely at this one).
188    position2: Vector4<f32>,
189
190    /// Unknown pair of values of tree vertices. Small integers stored as f16. The game's tree shaders don't read it.
191    uk_2: Vector2<f32>,
192
193    /// Tree vertices: the vertex colour (r, g, b, values up to 2) and the wind weight (a: how much the vertex sways). Stored as f16.
194    tree_colour_wind: Vector4<f32>,
195
196    /// Unknown value of bow wave vertices, 0 or 1.
197    uk_4: f32,
198}
199
200//---------------------------------------------------------------------------//
201//                            Implementation
202//---------------------------------------------------------------------------//
203
204impl TryFrom<u16> for VertexFormat {
205    type Error = RLibError;
206    fn try_from(value: u16) -> Result<Self> {
207        match value {
208            _ if value == Self::Static as u16 => Ok(Self::Static),
209            _ if value == Self::Collision as u16 => Ok(Self::Collision),
210            _ if value == Self::Weighted as u16 => Ok(Self::Weighted),
211            _ if value == Self::Cinematic as u16 => Ok(Self::Cinematic),
212            _ if value == Self::Grass as u16 => Ok(Self::Grass),
213            _ if value == Self::Tree as u16 => Ok(Self::Tree),
214            _ if value == Self::Billboard as u16 => Ok(Self::Billboard),
215            _ if value == Self::Rope as u16 => Ok(Self::Rope),
216            _ if value == Self::Uk8 as u16 => Ok(Self::Uk8),
217            _ if value == Self::Uk12 as u16 => Ok(Self::Uk12),
218            _ if value == Self::ClothSim as u16 => Ok(Self::ClothSim),
219            _ => Err(RLibError::DecodingRigidModelUnknownVertexFormat(value))
220        }
221    }
222}
223
224impl From<VertexFormat> for u16 {
225    fn from(value: VertexFormat) -> u16 {
226        value as u16
227    }
228}
229
230impl Vertex {
231
232    /// Reads a vertex from binary data based on the vertex format and material type.
233    ///
234    /// Different combinations of vertex format and material type have different binary
235    /// layouts. This function dispatches to the appropriate reading logic based on
236    /// the format/material combination.
237    ///
238    /// # Arguments
239    ///
240    /// * `data` - Binary data source.
241    /// * `version` - RigidModel version (affects layout for some formats).
242    /// * `vtype` - Vertex format specifying the data layout.
243    /// * `mtype` - Material type (affects layout for static vertices).
244    ///
245    /// # Errors
246    ///
247    /// Returns an error if:
248    /// - The vertex format is unknown ([`RLibError::DecodingRigidModelUnknownVertexFormat`])
249    /// - The vertex format is unsupported for the material type
250    ///   ([`RLibError::DecodingRigidModelUnsupportedVertexFormatForMaterial`])
251    pub fn read<R: ReadBytes>(data: &mut R, version: u32, vtype: VertexFormat, mtype: MaterialType) -> Result<Self> {
252        let mut v = Self::default();
253        match vtype {
254            VertexFormat::Static => match static_layout(version, mtype) {
255                MaterialType::TiledDirtmap |
256                MaterialType::ShipAmbientmap |
257                MaterialType::Decal |
258                MaterialType::DecalDirtmap |
259                MaterialType::Dirtmap |
260                MaterialType::Cloth |
261                MaterialType::Unlit |
262                MaterialType::WaterDisplaceVolume |
263                MaterialType::Uk54 |
264                MaterialType::DefaultMaterial => {
265                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
266                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
267                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
268                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
269                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
270                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
271                    v.uk_1 = data.read_vector_4_u8()?;
272                },
273                MaterialType::RsTerrain => {
274                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
275                    v.normal = data.read_vector_4_f32_normal_from_vector_4_f16()?;
276                },
277                MaterialType::WeightedTextureBlend => {
278                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
279                    v.normal = data.read_vector_4_f32_normal_from_vector_4_f16()?;
280                },
281                MaterialType::ProjectedDecalV4 => {
282                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
283                },
284                MaterialType::RiverOutfield |
285                MaterialType::RsRiver => {
286                    v.position = data.read_vector_4_f32()?;
287                    v.normal = data.read_vector_4_f32()?;
288                },
289                MaterialType::ProjectedDecal |
290                MaterialType::ProjectedDecalV2 |
291                MaterialType::ProjectedDecalV3 => {
292                    v.position = data.read_vector_4_f32()?;
293                },
294                // These store no vertex data: their indices point to the vertices of another mesh.
295                MaterialType::StaticPointLight |
296                MaterialType::Uk56 |
297                MaterialType::Uk57 => {},
298                MaterialType::TextureCombo |
299                MaterialType::TextureComboVertexWind => {
300                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
301                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
302                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
303                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
304                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
305                    v.uk_1 = data.read_vector_4_u8()?;
306                },
307                MaterialType::Uk27 => {
308                    v.position = data.read_vector_4_f32_from_vec_3_f32()?;
309                    v.texture_coordinate = Vector2::new(data.read_f32()?, data.read_f32()?);
310                    v.texture_coordinate2 = Vector2::new(data.read_f32()?, data.read_f32()?);
311                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
312                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
313                    v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
314                    v.uk_1 = data.read_vector_4_u8()?;
315                },
316                MaterialType::DebugGeometry => {
317                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
318                    v.normal = data.read_vector_4_f32_normal_from_vector_4_f16()?;
319                    v.color = data.read_vector_4_f32_pct_from_vector_4_u8()?;
320                },
321                MaterialType::BowWave => {
322                    v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
323                    v.position2 = data.read_vector_4_f32_normal_from_vector_4_f16()?;
324                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
325                    v.uk_4 = data.read_f32()?;
326                },
327                MaterialType::CustomTerrain => {
328                    v.position = data.read_vector_4_f32()?;
329                    v.uk_1 = data.read_vector_4_u8()?;
330                    v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
331                    v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
332                    v.texture_coordinate = Vector2::new(data.read_f32()?, data.read_f32()?);
333                },
334                MaterialType::TerrainBlend => {
335                    v.position = data.read_vector_4_f32()?;
336                    v.normal = data.read_vector_4_f32()?;
337                },
338                MaterialType::AlphaBlend => {
339                    v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
340                    v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
341                }
342                _ => return Err(RLibError::DecodingRigidModelUnsupportedVertexFormatForMaterial(vtype.into(), mtype.into()))
343            },
344
345            VertexFormat::Collision => {
346                v.position = data.read_vector_4_f32_from_vec_3_f32()?;
347                v.normal = data.read_vector_4_f32_from_vec_3_f32()?;
348            }
349
350            VertexFormat::Weighted => {
351                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
352
353                let bone_indices = data.read_vector_2_u8()?;
354                v.bone_indices = Vector4::new(bone_indices.x, bone_indices.y, 0, 0);
355
356                let weights = data.read_vector_2_f32_pct_from_vector_2_u8()?;
357                v.weights = Vector4::new(weights.x, weights.y, 0.0, 0.0);
358
359                v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
360                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
361                v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
362                v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
363
364                if version >= 8 {
365                    v.uk_1 = data.read_vector_4_u8()?;
366                }
367            }
368            VertexFormat::Cinematic => {
369                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
370
371                // w is not used in this one.
372                v.bone_indices = data.read_vector_4_u8()?;
373                v.weights = data.read_vector_4_f32_pct_from_vector_4_u8()?;
374                v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
375                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
376                v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
377                v.bitangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
378
379                if version >= 8 {
380                    v.uk_1 = data.read_vector_4_u8()?;
381                }
382            }
383            VertexFormat::Grass => {
384                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
385                v.texture_coordinate = Vector2::new(data.read_f32()?, data.read_f32()?);
386                v.normal = data.read_vector_4_f32_normal_from_vector_4_u8()?;
387                v.tangent = data.read_vector_4_f32_normal_from_vector_4_u8()?;
388                v.uk_1 = data.read_vector_4_u8()?;
389            }
390            VertexFormat::Tree => {
391                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
392                v.position2 = data.read_vector_4_f32_normal_from_vector_4_f16()?;
393                v.normal = data.read_vector_4_f32_normal_from_vector_4_f16()?;
394                v.tangent = data.read_vector_4_f32_normal_from_vector_4_f16()?;
395                v.bitangent = data.read_vector_4_f32_normal_from_vector_4_f16()?;
396                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
397                v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
398                v.uk_2 = data.read_vector_2_f32_from_vector_2_f16()?;
399                v.tree_colour_wind = read_vector_4_f32_from_vector_4_f16(data)?;
400            }
401            VertexFormat::Billboard |
402            VertexFormat::Uk8 => {
403                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
404                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
405            }
406            VertexFormat::Uk12 if version <= 6 => {
407                v.position = read_vector_4_f32_from_vector_4_f16(data)?;
408                v.normal = read_vector_4_f32_from_vector_4_f16(data)?;
409            }
410            VertexFormat::Uk12 => {
411                v.position = data.read_vector_4_f32_normal_from_vector_4_f16()?;
412                v.texture_coordinate = data.read_vector_2_f32_from_vector_2_f16()?;
413                v.texture_coordinate2 = data.read_vector_2_f32_from_vector_2_f16()?;
414                v.uk_1 = data.read_vector_4_u8()?;
415            }
416            _ => return Err(RLibError::DecodingRigidModelUnknownVertexFormat(vtype.into()))
417        }
418
419        Ok(v)
420    }
421
422    /// Writes a vertex to binary data based on the vertex format and material type.
423    ///
424    /// Different combinations of vertex format and material type have different binary
425    /// layouts. This function dispatches to the appropriate writing logic based on
426    /// the format/material combination.
427    ///
428    /// # Arguments
429    ///
430    /// * `data` - Output buffer.
431    /// * `version` - RigidModel version (affects layout for some formats).
432    /// * `vtype` - Vertex format specifying the data layout.
433    /// * `mtype` - Material type (affects layout for static vertices).
434    ///
435    /// # Errors
436    ///
437    /// Returns an error if:
438    /// - The vertex format is unknown ([`RLibError::DecodingRigidModelUnknownVertexFormat`])
439    /// - The vertex format is unsupported for the material type
440    ///   ([`RLibError::DecodingRigidModelUnsupportedVertexFormatForMaterial`])
441    pub fn write<W: WriteBytes>(&self, data: &mut W, version: u32, vtype: VertexFormat, mtype: MaterialType) -> Result<()> {
442        match vtype {
443            VertexFormat::Static => match static_layout(version, mtype) {
444                MaterialType::TiledDirtmap |
445                MaterialType::ShipAmbientmap |
446                MaterialType::Decal |
447                MaterialType::DecalDirtmap |
448                MaterialType::Dirtmap |
449                MaterialType::Cloth |
450                MaterialType::Unlit |
451                MaterialType::WaterDisplaceVolume |
452                MaterialType::Uk54 |
453                MaterialType::DefaultMaterial => {
454                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
455                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
456                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
457                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
458                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
459                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
460
461                    // Color? Causes difference when processing if treated as color.
462                    data.write_vector_4_u8(self.uk_1)?;
463                }
464                MaterialType::RsTerrain => {
465                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
466                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.normal())?;
467                }
468                MaterialType::WeightedTextureBlend => {
469                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
470                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.normal())?;
471                }
472                MaterialType::ProjectedDecalV4 => {
473                    data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
474                }
475                MaterialType::RiverOutfield |
476                MaterialType::RsRiver => {
477                    data.write_vector_4_f32(*self.position())?;
478                    data.write_vector_4_f32(*self.normal())?;
479                }
480                MaterialType::ProjectedDecal |
481                MaterialType::ProjectedDecalV2 |
482                MaterialType::ProjectedDecalV3 => {
483                    data.write_vector_4_f32(*self.position())?;
484                }
485                MaterialType::StaticPointLight |
486                MaterialType::Uk56 |
487                MaterialType::Uk57 => {}
488                MaterialType::TextureCombo |
489                MaterialType::TextureComboVertexWind => {
490                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
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.normal)?;
493                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
494                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
495                    data.write_vector_4_u8(self.uk_1)?;
496                }
497                MaterialType::Uk27 => {
498                    data.write_vector_4_f32_to_vector_3_f32(self.position)?;
499                    data.write_f32(self.texture_coordinate.x)?;
500                    data.write_f32(self.texture_coordinate.y)?;
501                    data.write_f32(self.texture_coordinate2.x)?;
502                    data.write_f32(self.texture_coordinate2.y)?;
503                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
504                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
505                    data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
506                    data.write_vector_4_u8(self.uk_1)?;
507                }
508                MaterialType::DebugGeometry => {
509                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
510                    data.write_vector_4_f32_normal_as_vector_4_f16(self.normal)?;
511                    data.write_vector_4_f32_pct_as_vector_4_u8(self.color)?;
512                }
513                MaterialType::BowWave => {
514                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
515                    data.write_vector_4_f32_normal_as_vector_4_f16(self.position2)?;
516                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
517                    data.write_f32(self.uk_4)?;
518                }
519                MaterialType::CustomTerrain => {
520                    data.write_vector_4_f32(self.position)?;
521                    data.write_vector_4_u8(self.uk_1)?;
522                    data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
523                    data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
524                    data.write_f32(self.texture_coordinate.x)?;
525                    data.write_f32(self.texture_coordinate.y)?;
526                }
527                MaterialType::TerrainBlend => {
528                    data.write_vector_4_f32(*self.position())?;
529                    data.write_vector_4_f32(*self.normal())?;
530                }
531                MaterialType::AlphaBlend => {
532                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
533                    data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
534                }
535                _ => return Err(RLibError::DecodingRigidModelUnsupportedVertexFormatForMaterial(vtype.into(), mtype.into()))
536            },
537
538            VertexFormat::Collision => {
539                data.write_vector_4_f32_to_vector_3_f32(self.position)?;
540                data.write_vector_4_f32_to_vector_3_f32(self.normal)?;
541            }
542
543            VertexFormat::Weighted => {
544                data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
545                data.write_vector_2_u8(Vector2::new(self.bone_indices.x, self.bone_indices.y))?;
546                data.write_vector_2_f32_pct_as_vector_2_u8(Vector2::new(self.weights.x, self.weights.y))?;
547                data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
548                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
549                data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
550                data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
551
552                if version >= 8 {
553                    data.write_vector_4_u8(self.uk_1)?;
554                }
555            }
556            VertexFormat::Cinematic => {
557                data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
558                data.write_vector_4_u8(self.bone_indices)?;
559                data.write_vector_4_f32_pct_as_vector_4_u8(self.weights)?;
560                data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
561                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
562                data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
563                data.write_vector_4_f32_normal_as_vector_4_u8(self.bitangent)?;
564
565                if version >= 8 {
566                    data.write_vector_4_u8(self.uk_1)?;
567                }
568            }
569            VertexFormat::Grass => {
570                data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
571                data.write_f32(self.texture_coordinate.x)?;
572                data.write_f32(self.texture_coordinate.y)?;
573                data.write_vector_4_f32_normal_as_vector_4_u8(self.normal)?;
574                data.write_vector_4_f32_normal_as_vector_4_u8(self.tangent)?;
575                data.write_vector_4_u8(self.uk_1)?;
576            }
577            VertexFormat::Tree => {
578                data.write_vector_4_f32_normal_as_vector_4_f16(self.position)?;
579                data.write_vector_4_f32_normal_as_vector_4_f16(self.position2)?;
580                data.write_vector_4_f32_normal_as_vector_4_f16(self.normal)?;
581                data.write_vector_4_f32_normal_as_vector_4_f16(self.tangent)?;
582                data.write_vector_4_f32_normal_as_vector_4_f16(self.bitangent)?;
583                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
584                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
585                data.write_vector_2_f32_as_vector_2_f16(self.uk_2)?;
586                write_vector_4_f32_as_vector_4_f16(data, self.tree_colour_wind)?;
587            }
588            VertexFormat::Billboard |
589            VertexFormat::Uk8 => {
590                data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
591                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
592            }
593            VertexFormat::Uk12 if version <= 6 => {
594                write_vector_4_f32_as_vector_4_f16(data, self.position)?;
595                write_vector_4_f32_as_vector_4_f16(data, self.normal)?;
596            }
597            VertexFormat::Uk12 => {
598                data.write_vector_4_f32_normal_as_vector_4_f16(*self.position())?;
599                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate)?;
600                data.write_vector_2_f32_as_vector_2_f16(self.texture_coordinate2)?;
601                data.write_vector_4_u8(self.uk_1)?;
602            }
603            _ => return Err(RLibError::DecodingRigidModelUnknownVertexFormat(vtype.into()))
604        }
605
606        Ok(())
607    }
608}
609/// The material whose static vertex layout a material uses.
610///
611/// Up to version 6 (Attila), TerrainBlend vertices are laid out like DefaultMaterial ones (f16 position, two f16 uvs, u8 normal, tangent,
612/// bitangent and colour): read as two f32 vectors instead, the position's w (f16 1.0) shows up in the high half of y, and x reaches
613/// thousands. Later versions keep the f32 layout.
614fn static_layout(version: u32, mtype: MaterialType) -> MaterialType {
615    match mtype {
616        MaterialType::TerrainBlend if version <= 6 => MaterialType::DefaultMaterial,
617        _ => mtype,
618    }
619}
620
621/// Reads four f16 values as they are, without the w scaling of `read_vector_4_f32_normal_from_vector_4_f16`.
622fn read_vector_4_f32_from_vector_4_f16<R: ReadBytes>(data: &mut R) -> Result<Vector4<f32>> {
623    Ok(Vector4::new(data.read_f16()?.to_f32(), data.read_f16()?.to_f32(), data.read_f16()?.to_f32(), data.read_f16()?.to_f32()))
624}
625
626/// Writes four values as f16, without the w scaling of `write_vector_4_f32_normal_as_vector_4_f16`.
627fn write_vector_4_f32_as_vector_4_f16<W: WriteBytes>(data: &mut W, value: Vector4<f32>) -> Result<()> {
628    for component in value.iter() {
629        data.write_f16(f16::from_f32(*component))?;
630    }
631
632    Ok(())
633}
634
635/*
636/// Util to swap the x and z coordinates of a vector.
637fn swap_xz(input: &Vector4<f32>) -> Vector4<f32> {
638    let mut i = *input;
639    i.swap_rows(0, 2);
640    i
641}*/