rpfm_lib/files/cs2_collision/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//! CS2 Collision file format support.
12//!
13//! CS2 Collision files (`.cs2.collision`) define collision meshes for 3D models in
14//! Total War games. These files contain triangular mesh data used for physics
15//! collision detection and pathfinding.
16//!
17//! # File Format
18//!
19//! CS2 Collision files are binary files containing:
20//! - Magic number and version header
21//! - Overall bounding box
22//! - One or more named collision meshes with vertices and triangles
23//! - Triangle adjacency information for efficient collision queries
24//!
25//! # Supported Versions
26//!
27//! - **Version 0**
28//! - **Version 11**
29//! - **Version 13**
30//! - **Version 20**
31//! - **Version 21**
32//!
33//! # File Contents
34//!
35//! - **Bounding Box**: Overall bounds containing all collision meshes
36//! - **Collision3d Objects**: Named collision meshes with triangle data
37//! - **Triangle Adjacency**: Edge connectivity for fast neighbor queries
38//!
39//! # Usage
40//!
41//! ```rust,ignore
42//! use rpfm_lib::files::cs2_collision::Cs2Collision;
43//! use rpfm_lib::files::Decodeable;
44//!
45//! // Decode from binary data
46//! let collision = Cs2Collision::decode(&mut data, &None)?;
47//!
48//! // Access collision meshes
49//! for mesh in collision.collisions_3d() {
50//! println!("Mesh: {} ({} vertices, {} triangles)",
51//! mesh.name(),
52//! mesh.vertices().len(),
53//! mesh.triangles().len()
54//! );
55//! }
56//! ```
57//!
58//! # File Location
59//!
60//! These files are typically found at:
61//! - `rigidmodels/buildings/*/*.cs2.collision`
62
63use getset::*;
64use serde_derive::{Serialize, Deserialize};
65
66use crate::error::{Result, RLibError};
67use crate::binary::{ReadBytes, WriteBytes};
68use crate::files::{DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable};
69use crate::files::bmd::common::*;
70use crate::utils::check_size_mismatch;
71
72/// File extension for CS2 Collision files.
73pub const EXTENSION: &str = ".cs2.collision";
74
75#[cfg(test)] mod cs2_collision_test;
76
77mod versions;
78
79//---------------------------------------------------------------------------//
80// Enum & Structs
81//---------------------------------------------------------------------------//
82
83/// Represents a CS2 Collision file decoded in memory.
84///
85/// Contains all collision mesh data for a 3D model, including vertices,
86/// triangles, and adjacency information for efficient collision detection.
87///
88/// # Fields
89///
90/// * `magic_number` - File format identifier
91/// * `version` - File format version (0, 11, 13, 20, or 21)
92/// * `bounding_box` - Overall bounds containing all collision meshes
93/// * `collisions_3d` - List of named collision meshes
94///
95/// # Examples
96///
97/// ```rust,ignore
98/// let collision = Cs2Collision::decode(&mut data, &None)?;
99/// println!("Version: {}", collision.version());
100/// println!("Meshes: {}", collision.collisions_3d().len());
101/// ```
102#[derive(PartialEq, Clone, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
103#[getset(get = "pub", get_mut = "pub(crate)")]
104pub struct Cs2Collision {
105 /// File format identifier.
106 magic_number: u32,
107
108 /// File format version number.
109 version: u32,
110
111 /// Bounding box containing all collision meshes.
112 bounding_box: Cube,
113
114 /// List of collision meshes in this file.
115 collisions_3d: Vec<Collision3d>,
116}
117
118/// A named 3D collision mesh with vertices and triangles.
119///
120/// Represents a single collision mesh within a CS2 Collision file. Each mesh
121/// has a name, vertex list, and triangle list with adjacency information.
122///
123/// # Fields
124///
125/// * `name` - Name identifier for this collision mesh (UTF-8)
126/// * `uk_1` - Unknown field (possibly an ID)
127/// * `uk_2` - Unknown field (appears to be 0 or 1)
128/// * `vertices` - List of 3D vertex positions
129/// * `triangles` - List of triangles with adjacency data
130/// * `zero_4` - Reserved field (always 0)
131/// * `bounding_box` - Bounding box for this mesh
132///
133/// # Examples
134///
135/// ```rust,ignore
136/// for mesh in collision.collisions_3d() {
137/// println!("Mesh '{}' has {} triangles", mesh.name(), mesh.triangles().len());
138/// }
139/// ```
140#[derive(PartialEq, Clone, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
141#[getset(get = "pub", get_mut = "pub(crate)")]
142pub struct Collision3d {
143 /// Name identifier for this collision mesh.
144 name: String,
145
146 /// Unknown field (possibly an ID).
147 uk_1: i32,
148
149 /// Unknown field (appears to be 0 or 1).
150 uk_2: i32,
151
152 /// List of vertex positions for this mesh.
153 vertices: Vec<Point3d>,
154
155 /// List of triangles with adjacency information.
156 triangles: Vec<CollisionTriangle>,
157
158 /// Reserved field (always 0).
159 zero_4: i32,
160
161 /// Bounding box for this collision mesh.
162 bounding_box: Cube,
163}
164
165/// A collision triangle with vertex indices and edge adjacency information.
166///
167/// Represents a single triangle in the collision mesh along with its three edges
168/// and information about adjacent triangles. This adjacency data enables efficient
169/// collision detection and mesh traversal.
170///
171/// # Triangle Structure
172///
173/// Each triangle has:
174/// - 3 vertex indices defining the triangle face
175/// - 3 edges, each with adjacency to neighboring triangles
176/// - Face index for identification
177///
178/// # Edge Adjacency
179///
180/// For each edge (1, 2, 3):
181/// - Two vertex indices defining the edge
182/// - Face index (redundant, same as main face_index)
183/// - Across face index (index of triangle sharing this edge, or -1 if boundary)
184///
185/// # Fields
186///
187/// ## Triangle Face
188/// * `face_index` - Index identifier for this triangle
189/// * `padding` - Alignment padding (always 0)
190/// * `vertex_1`, `vertex_2`, `vertex_3` - Indices into the vertex array
191///
192/// ## Edge 1
193/// * `edge_1_vertex_1`, `edge_1_vertex_2` - Vertex indices for edge 1
194/// * `face_index_1` - Face index (same as face_index)
195/// * `zero_1` - Reserved (always 0)
196/// * `across_face_index_1` - Index of adjacent triangle across edge 1 (-1 if none)
197///
198/// ## Edge 2
199/// * `edge_2_vertex_1`, `edge_2_vertex_2` - Vertex indices for edge 2
200/// * `face_index_2` - Face index (same as face_index)
201/// * `zero_2` - Reserved (always 0)
202/// * `across_face_index_2` - Index of adjacent triangle across edge 2 (-1 if none)
203///
204/// ## Edge 3
205/// * `edge_3_vertex_1`, `edge_3_vertex_2` - Vertex indices for edge 3
206/// * `face_index_3` - Face index (same as face_index)
207/// * `zero_3` - Reserved (always 0)
208/// * `across_face_index_3` - Index of adjacent triangle across edge 3 (-1 if none)
209///
210/// ## Terminator
211/// * `zero_4` - Reserved field (always 0)
212///
213/// # Examples
214///
215/// ```rust,ignore
216/// for triangle in mesh.triangles() {
217/// // Check if triangle has neighbors on all edges
218/// let has_neighbor_1 = triangle.across_face_index_1() >= 0;
219/// let has_neighbor_2 = triangle.across_face_index_2() >= 0;
220/// let has_neighbor_3 = triangle.across_face_index_3() >= 0;
221/// }
222/// ```
223#[derive(PartialEq, Clone, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)]
224#[getset(get = "pub", get_mut = "pub(crate)")]
225pub struct CollisionTriangle {
226 /// Index identifier for this triangle.
227 face_index: i32,
228
229 /// Alignment padding (always 0).
230 padding: i8,
231
232 /// First vertex index.
233 vertex_1: i32,
234
235 /// Second vertex index.
236 vertex_2: i32,
237
238 /// Third vertex index.
239 vertex_3: i32,
240
241 /// First vertex of edge 1.
242 edge_1_vertex_1: i32,
243
244 /// Second vertex of edge 1.
245 edge_1_vertex_2: i32,
246
247 /// Face index for edge 1 (same as face_index).
248 face_index_1: i32,
249
250 /// Reserved (always 0).
251 zero_1: i32,
252
253 /// Index of triangle adjacent across edge 1 (-1 if none).
254 across_face_index_1: i32,
255
256 /// First vertex of edge 2.
257 edge_2_vertex_1: i32,
258
259 /// Second vertex of edge 2.
260 edge_2_vertex_2: i32,
261
262 /// Face index for edge 2 (same as face_index).
263 face_index_2: i32,
264
265 /// Reserved (always 0).
266 zero_2: i32,
267
268 /// Index of triangle adjacent across edge 2 (-1 if none).
269 across_face_index_2: i32,
270
271 /// First vertex of edge 3.
272 edge_3_vertex_1: i32,
273
274 /// Second vertex of edge 3.
275 edge_3_vertex_2: i32,
276
277 /// Face index for edge 3 (same as face_index).
278 face_index_3: i32,
279
280 /// Reserved (always 0).
281 zero_3: i32,
282
283 /// Index of triangle adjacent across edge 3 (-1 if none).
284 across_face_index_3: i32,
285
286 /// Reserved terminator field (always 0).
287 zero_4: i32,
288}
289
290//---------------------------------------------------------------------------//
291// Implementation of Cs2Collision
292//---------------------------------------------------------------------------//
293
294impl Decodeable for Cs2Collision {
295
296 fn decode<R: ReadBytes>(data: &mut R, _extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
297 let mut decoded = Self::default();
298 decoded.magic_number = data.read_u32()?;
299 decoded.version = data.read_u32()?;
300
301 match decoded.version {
302 21 => decoded.read_v21(data)?,
303 20 => decoded.read_v20(data)?,
304 _ => return Err(RLibError::DecodingUnsupportedVersion(decoded.version as usize)),
305 }
306
307 // Trigger an error if there's left data on the source.
308 check_size_mismatch(data.stream_position()? as usize, data.len()? as usize)?;
309
310 Ok(decoded)
311 }
312}
313
314impl Encodeable for Cs2Collision {
315
316 fn encode<W: WriteBytes>(&mut self, buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
317 buffer.write_u32(self.magic_number)?;
318 buffer.write_u32(self.version)?;
319
320 match self.version {
321 21 => self.write_v21(buffer)?,
322 20 => self.write_v20(buffer)?,
323 _ => unimplemented!()
324 }
325
326
327 Ok(())
328 }
329}