vmf_forge/vmf/
regions.rs

1//! This module provides structures for representing region-specific data in a VMF file, such as cameras and cordons.
2
3use derive_more::{Deref, DerefMut};
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6
7use crate::utils::{get_key, parse_hs_key, To01String};
8use crate::{
9    errors::{VmfError, VmfResult},
10    VmfBlock, VmfSerializable,
11};
12
13/// Represents the camera data in a VMF file.
14#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Deref, DerefMut)]
15pub struct Cameras {
16    /// The index of the active camera.
17    pub active: i8,
18    /// The list of cameras.
19    #[deref]
20    #[deref_mut]
21    pub cams: Vec<Camera>,
22}
23
24impl TryFrom<VmfBlock> for Cameras {
25    type Error = VmfError;
26
27    fn try_from(block: VmfBlock) -> VmfResult<Self> {
28        let mut cams = Vec::with_capacity(12);
29        for group in block.blocks {
30            cams.push(Camera::try_from(group)?);
31        }
32
33        Ok(Self {
34            active: parse_hs_key!(&block.key_values, "activecamera", i8)?,
35            cams,
36        })
37    }
38}
39
40impl From<Cameras> for VmfBlock {
41    fn from(val: Cameras) -> Self {
42        let mut blocks = Vec::with_capacity(val.cams.len());
43
44        for cam in val.cams {
45            blocks.push(cam.into());
46        }
47
48        let mut key_values = IndexMap::new();
49        key_values.insert("active".to_string(), val.active.to_string());
50
51        VmfBlock {
52            name: "cameras".to_string(),
53            key_values,
54            blocks,
55        }
56    }
57}
58
59impl VmfSerializable for Cameras {
60    fn to_vmf_string(&self, indent_level: usize) -> String {
61        let indent: String = "\t".repeat(indent_level);
62        let mut output = String::with_capacity(64);
63
64        output.push_str(&format!("{0}cameras\n{0}{{\n", indent));
65        output.push_str(&format!(
66            "{}\t\"activecamera\" \"{}\"\n",
67            indent, self.active
68        ));
69
70        for cam in &self.cams {
71            output.push_str(&format!("{0}\tcamera\n{0}\t{{\n", indent));
72            output.push_str(&format!(
73                "{}\t\t\"position\" \"{}\"\n",
74                indent, cam.position
75            ));
76            output.push_str(&format!("{}\t\t\"look\" \"{}\"\n", indent, cam.look));
77            output.push_str(&format!("{}\t}}\n", indent));
78        }
79
80        output.push_str(&format!("{}}}\n", indent));
81        output
82    }
83}
84
85/// Represents a single camera in a VMF file.
86#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
87pub struct Camera {
88    /// The position of the camera in the VMF coordinate system.
89    pub position: String, // vertex
90    /// The point at which the camera is looking, in the VMF coordinate system.
91    pub look: String, // vertex
92}
93
94impl TryFrom<VmfBlock> for Camera {
95    type Error = VmfError;
96
97    fn try_from(block: VmfBlock) -> VmfResult<Self> {
98        Ok(Self {
99            position: get_key!(&block.key_values, "position")?.to_owned(),
100            look: get_key!(&block.key_values, "look")?.to_owned(),
101        })
102    }
103}
104
105impl From<Camera> for VmfBlock {
106    fn from(val: Camera) -> Self {
107        let mut key_values = IndexMap::new();
108        key_values.insert("position".to_string(), val.position);
109        key_values.insert("look".to_string(), val.look);
110
111        VmfBlock {
112            name: "camera".to_string(),
113            key_values,
114            ..Default::default()
115        }
116    }
117}
118
119/// Represents the cordons data in a VMF file.
120#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Deref, DerefMut)]
121pub struct Cordons {
122    /// The index of the active cordon.
123    pub active: i8,
124    /// The list of cordons.
125    #[deref]
126    #[deref_mut]
127    pub cordons: Vec<Cordon>,
128}
129
130impl TryFrom<VmfBlock> for Cordons {
131    type Error = VmfError;
132
133    fn try_from(block: VmfBlock) -> VmfResult<Self> {
134        let mut cordons = Vec::with_capacity(12);
135        for group in block.blocks {
136            cordons.push(Cordon::try_from(group)?);
137        }
138
139        Ok(Self {
140            active: parse_hs_key!(&block.key_values, "active", i8)?,
141            cordons,
142        })
143    }
144}
145
146impl From<Cordons> for VmfBlock {
147    fn from(val: Cordons) -> Self {
148        let mut blocks = Vec::new();
149
150        // Converts each  Cordon to a VmfBlock and adds it to the `blocks` vector
151        for cordon in val.cordons {
152            blocks.push(cordon.into());
153        }
154
155        // Creates a VmfBlock for Cordons
156        let mut key_values = IndexMap::new();
157        key_values.insert("active".to_string(), val.active.to_string());
158
159        VmfBlock {
160            name: "cordons".to_string(),
161            key_values,
162            blocks,
163        }
164    }
165}
166
167impl VmfSerializable for Cordons {
168    fn to_vmf_string(&self, indent_level: usize) -> String {
169        let indent = "\t".repeat(indent_level);
170        let mut output = String::with_capacity(256);
171
172        // Start of Cordons block
173        output.push_str(&format!("{0}cordons\n{0}{{\n", indent));
174        output.push_str(&format!("{}\t\"active\" \"{}\"\n", indent, self.active));
175
176        // Iterates through all Cordons and adds their string representation
177        for cordon in &self.cordons {
178            output.push_str(&cordon.to_vmf_string(indent_level + 1));
179        }
180
181        output.push_str(&format!("{}}}\n", indent));
182
183        output
184    }
185}
186
187/// Represents a single cordon in a VMF file.
188#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
189pub struct Cordon {
190    /// The name of the cordon.
191    pub name: String,
192    /// Whether the cordon is active.
193    pub active: bool,
194    /// The minimum point of the cordon's bounding box.
195    pub min: String, // vertex
196    /// The maximum point of the cordon's bounding box.
197    pub max: String, // vertex
198}
199
200impl TryFrom<VmfBlock> for Cordon {
201    type Error = VmfError;
202
203    fn try_from(block: VmfBlock) -> VmfResult<Self> {
204        let (min, max) = block
205            .blocks
206            .first()
207            .ok_or_else(|| VmfError::InvalidFormat("Missing 'box' block in Cordon".to_string()))
208            .and_then(|sub_block| {
209                Ok((
210                    get_key!(&sub_block.key_values, "mins")?,
211                    get_key!(&sub_block.key_values, "maxs")?,
212                ))
213            })
214            .or_else(|_| {
215                Ok::<(_, _), VmfError>((
216                    get_key!(&block.key_values, "mins")?,
217                    get_key!(&block.key_values, "maxs")?,
218                ))
219            })?;
220
221        Ok(Self {
222            name: get_key!(&block.key_values, "name")?.to_owned(),
223            active: get_key!(&block.key_values, "active")? == "1",
224            min: min.to_owned(),
225            max: max.to_owned(),
226        })
227    }
228}
229
230impl From<Cordon> for VmfBlock {
231    fn from(val: Cordon) -> Self {
232        // Creates key_values for Cordon
233        let mut key_values = IndexMap::new();
234        key_values.insert("name".to_string(), val.name);
235        key_values.insert("active".to_string(), val.active.to_01_string());
236
237        // Creates a block for the box with `mins/maxs`
238        let mut box_block_key_values = IndexMap::new();
239        box_block_key_values.insert("mins".to_string(), val.min);
240        box_block_key_values.insert("maxs".to_string(), val.max);
241
242        // Creates a VmfBlock for the box
243        let box_block = VmfBlock {
244            name: "box".to_string(),
245            key_values: box_block_key_values,
246            blocks: vec![],
247        };
248
249        // Creates the main VmfBlock for Cordon
250        VmfBlock {
251            name: "cordon".to_string(),
252            key_values,
253            blocks: vec![box_block],
254        }
255    }
256}
257
258impl VmfSerializable for Cordon {
259    fn to_vmf_string(&self, indent_level: usize) -> String {
260        let indent: String = "\t".repeat(indent_level);
261        let mut output = String::with_capacity(64);
262
263        // Start of Cordon block
264        output.push_str(&format!("{0}cordon\n{0}{{\n", indent));
265        output.push_str(&format!("{}\t\"name\" \"{}\"\n", indent, self.name));
266        output.push_str(&format!(
267            "{}\t\"active\" \"{}\"\n",
268            indent,
269            self.active.to_01_string()
270        ));
271
272        // Adds a nested block with coordinates
273        output.push_str(&format!("{0}\tbox\n{}\t{{\n", indent));
274        output.push_str(&format!("{}\t\t\"mins\" \"{}\"\n", indent, self.min));
275        output.push_str(&format!("{}\t\t\"maxs\" \"{}\"\n", indent, self.max));
276        output.push_str(&format!("{}\t}}\n", indent)); // end of `box``
277
278        // End of Cordon block
279        output.push_str(&format!("{}}}\n", indent));
280
281        output
282    }
283}