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;
5#[cfg(feature = "serialization")]
6use serde::{Deserialize, Serialize};
7
8use crate::utils::{To01String, get_key_ref, take_and_parse_key, take_key_owned};
9use crate::{
10    VmfBlock, VmfSerializable,
11    errors::{VmfError, VmfResult},
12};
13
14/// Represents the camera data in a VMF file.
15#[derive(Debug, Default, Clone, PartialEq, Deref, DerefMut)]
16#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
17pub struct Cameras {
18    /// The index of the active camera.
19    pub active: i8,
20    /// The list of cameras.
21    #[deref]
22    #[deref_mut]
23    pub cams: Vec<Camera>,
24}
25
26impl TryFrom<VmfBlock> for Cameras {
27    type Error = VmfError;
28
29    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
30        let mut cams = Vec::with_capacity(block.blocks.len());
31        for group in block.blocks {
32            cams.push(Camera::try_from(group)?);
33        }
34
35        Ok(Self {
36            active: take_and_parse_key::<i8>(&mut block.key_values, "activecamera")?,
37            cams,
38        })
39    }
40}
41
42impl From<Cameras> for VmfBlock {
43    fn from(val: Cameras) -> Self {
44        let mut blocks = Vec::with_capacity(val.cams.len());
45
46        for cam in val.cams {
47            blocks.push(cam.into());
48        }
49
50        let mut key_values = IndexMap::new();
51        key_values.insert("active".to_string(), val.active.to_string());
52
53        VmfBlock {
54            name: "cameras".to_string(),
55            key_values,
56            blocks,
57        }
58    }
59}
60
61impl VmfSerializable for Cameras {
62    fn to_vmf_string(&self, indent_level: usize) -> String {
63        let indent: String = "\t".repeat(indent_level);
64        let mut output = String::with_capacity(64);
65
66        output.push_str(&format!("{0}cameras\n{0}{{\n", indent));
67        output.push_str(&format!(
68            "{}\t\"activecamera\" \"{}\"\n",
69            indent, self.active
70        ));
71
72        for cam in &self.cams {
73            output.push_str(&format!("{0}\tcamera\n{0}\t{{\n", indent));
74            output.push_str(&format!(
75                "{}\t\t\"position\" \"{}\"\n",
76                indent, cam.position
77            ));
78            output.push_str(&format!("{}\t\t\"look\" \"{}\"\n", indent, cam.look));
79            output.push_str(&format!("{}\t}}\n", indent));
80        }
81
82        output.push_str(&format!("{}}}\n", indent));
83        output
84    }
85}
86
87/// Represents a single camera in a VMF file.
88#[derive(Debug, Default, Clone, PartialEq)]
89#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
90pub struct Camera {
91    /// The position of the camera in the VMF coordinate system.
92    pub position: String, // vertex
93    /// The point at which the camera is looking, in the VMF coordinate system.
94    pub look: String, // vertex
95}
96
97impl TryFrom<VmfBlock> for Camera {
98    type Error = VmfError;
99
100    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
101        let kv = &mut block.key_values;
102        Ok(Self {
103            position: take_key_owned(kv, "position")?,
104            look: take_key_owned(kv, "look")?,
105        })
106    }
107}
108
109impl From<Camera> for VmfBlock {
110    fn from(val: Camera) -> Self {
111        let mut key_values = IndexMap::new();
112        key_values.insert("position".to_string(), val.position);
113        key_values.insert("look".to_string(), val.look);
114
115        VmfBlock {
116            name: "camera".to_string(),
117            key_values,
118            ..Default::default()
119        }
120    }
121}
122
123/// Represents the cordons data in a VMF file.
124#[derive(Debug, Default, Clone, PartialEq, Deref, DerefMut)]
125#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
126pub struct Cordons {
127    /// The index of the active cordon.
128    pub active: i8,
129    /// The list of cordons.
130    #[deref]
131    #[deref_mut]
132    pub cordons: Vec<Cordon>,
133}
134
135impl TryFrom<VmfBlock> for Cordons {
136    type Error = VmfError;
137
138    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
139        let mut cordons = Vec::with_capacity(block.blocks.len());
140        for group in block.blocks {
141            cordons.push(Cordon::try_from(group)?);
142        }
143
144        Ok(Self {
145            active: take_and_parse_key::<i8>(&mut block.key_values, "active")?,
146            cordons,
147        })
148    }
149}
150
151impl From<Cordons> for VmfBlock {
152    fn from(val: Cordons) -> Self {
153        let mut blocks = Vec::new();
154
155        // Converts each  Cordon to a VmfBlock and adds it to the `blocks` vector
156        for cordon in val.cordons {
157            blocks.push(cordon.into());
158        }
159
160        // Creates a VmfBlock for Cordons
161        let mut key_values = IndexMap::new();
162        key_values.insert("active".to_string(), val.active.to_string());
163
164        VmfBlock {
165            name: "cordons".to_string(),
166            key_values,
167            blocks,
168        }
169    }
170}
171
172impl VmfSerializable for Cordons {
173    fn to_vmf_string(&self, indent_level: usize) -> String {
174        let indent = "\t".repeat(indent_level);
175        let mut output = String::with_capacity(256);
176
177        // Start of Cordons block
178        output.push_str(&format!("{0}cordons\n{0}{{\n", indent));
179        output.push_str(&format!("{}\t\"active\" \"{}\"\n", indent, self.active));
180
181        // Iterates through all Cordons and adds their string representation
182        for cordon in &self.cordons {
183            output.push_str(&cordon.to_vmf_string(indent_level + 1));
184        }
185
186        output.push_str(&format!("{}}}\n", indent));
187
188        output
189    }
190}
191
192/// Represents a single cordon in a VMF file.
193#[derive(Debug, Default, Clone, PartialEq)]
194#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
195pub struct Cordon {
196    /// The name of the cordon.
197    pub name: String,
198    /// Whether the cordon is active.
199    pub active: bool,
200    /// The minimum point of the cordon's bounding box.
201    pub min: String, // vertex
202    /// The maximum point of the cordon's bounding box.
203    pub max: String, // vertex
204}
205
206impl TryFrom<VmfBlock> for Cordon {
207    type Error = VmfError;
208
209    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
210        // Attempt #1: Try to take ownership of "mins" and "maxs" from the first sub-block.
211        let sub_block_result: Option<(String, String)> =
212            block.blocks.get_mut(0).and_then(|sub_block| {
213                // Try removing both keys using swap_remove (O(1)) and zip the Options.
214                // zip returns Some only if *both* removals were successful.
215                let maybe_min = sub_block.key_values.swap_remove("mins");
216                let maybe_max = sub_block.key_values.swap_remove("maxs");
217                maybe_min.zip(maybe_max)
218            });
219
220        // Decide where the final values come from
221        let (min_string, max_string) = match sub_block_result {
222            // Case 1: Successfully got both from the sub-block
223            Some((min_val, max_val)) => Ok((min_val, max_val)),
224            // Case 2: Failed to get both from sub-block (it didn't exist, or lacked one/both keys)
225            None => {
226                // Attempt #2: Take ownership from the parent block's key_values
227                let min_res = take_key_owned(&mut block.key_values, "mins").map_err(|_| {
228                    VmfError::InvalidFormat(
229                        "Missing 'mins' key in Cordon block or its 'box' sub-block".to_string(),
230                    )
231                });
232                let max_res = take_key_owned(&mut block.key_values, "maxs").map_err(|_| {
233                    VmfError::InvalidFormat(
234                        "Missing 'maxs' key in Cordon block or its 'box' sub-block".to_string(),
235                    )
236                });
237
238                // Combine results, returning the first error encountered if any
239                match (min_res, max_res) {
240                    (Ok(min), Ok(max)) => Ok((min, max)),
241                    (Err(e), _) => Err(e),
242                    (_, Err(e)) => Err(e),
243                }
244            }
245        }?;
246
247        // Take ownership of 'name' and check 'active' from the parent block
248        let name = take_key_owned(&mut block.key_values, "name")?;
249        let active = get_key_ref(&block.key_values, "active")? == "1";
250
251        Ok(Self {
252            name,
253            active,
254            min: min_string,
255            max: max_string,
256        })
257    }
258}
259
260impl From<Cordon> for VmfBlock {
261    fn from(val: Cordon) -> Self {
262        // Creates key_values for Cordon
263        let mut key_values = IndexMap::new();
264        key_values.insert("name".to_string(), val.name);
265        key_values.insert("active".to_string(), val.active.to_01_string());
266
267        // Creates a block for the box with `mins/maxs`
268        let mut box_block_key_values = IndexMap::new();
269        box_block_key_values.insert("mins".to_string(), val.min);
270        box_block_key_values.insert("maxs".to_string(), val.max);
271
272        // Creates a VmfBlock for the box
273        let box_block = VmfBlock {
274            name: "box".to_string(),
275            key_values: box_block_key_values,
276            blocks: vec![],
277        };
278
279        // Creates the main VmfBlock for Cordon
280        VmfBlock {
281            name: "cordon".to_string(),
282            key_values,
283            blocks: vec![box_block],
284        }
285    }
286}
287
288impl VmfSerializable for Cordon {
289    fn to_vmf_string(&self, indent_level: usize) -> String {
290        let indent: String = "\t".repeat(indent_level);
291        let mut output = String::with_capacity(64);
292
293        // Start of Cordon block
294        output.push_str(&format!("{0}cordon\n{0}{{\n", indent));
295        output.push_str(&format!("{}\t\"name\" \"{}\"\n", indent, self.name));
296        output.push_str(&format!(
297            "{}\t\"active\" \"{}\"\n",
298            indent,
299            self.active.to_01_string()
300        ));
301
302        // Adds a nested block with coordinates
303        output.push_str(&format!("{0}\tbox\n{}\t{{\n", indent));
304        output.push_str(&format!("{}\t\t\"mins\" \"{}\"\n", indent, self.min));
305        output.push_str(&format!("{}\t\t\"maxs\" \"{}\"\n", indent, self.max));
306        output.push_str(&format!("{}\t}}\n", indent)); // end of `box``
307
308        // End of Cordon block
309        output.push_str(&format!("{}}}\n", indent));
310
311        output
312    }
313}