Skip to main content

mnk_vmf/types/
camera.rs

1use chumsky::{IterParser, Parser as ChumskyParser};
2
3use crate::{
4    impl_block_properties_parser,
5    parser::{
6        close_block, key_value, key_value_boolean, key_value_numeric, open_block, InternalParser,
7        TokenError, TokenSource,
8    },
9    types::point::{key_value_point3d, Point3D},
10    Parser,
11};
12
13/// Represents a collection of cameras in the VMF file
14#[derive(Debug)]
15pub struct Cameras<'a> {
16    pub activecamera: i32,
17    pub cameras: Vec<Camera<'a>>,
18}
19
20impl<'a> Cameras<'a> {
21    pub fn new(activecamera: i32, cameras: Vec<Camera<'a>>) -> Self {
22        Self {
23            activecamera,
24            cameras,
25        }
26    }
27}
28
29/// Represents a camera entity in the VMF file
30#[derive(Debug, Default)]
31pub struct Camera<'a> {
32    pub id: u32,
33    pub classname: &'a str,
34    pub origin: Point3D,
35    pub angles: Point3D,
36    pub targetname: &'a str,
37
38    // Camera specific properties
39    pub spawnflags: Option<u32>,
40    pub wait: Option<f32>,
41    pub acceleration: Option<f32>,
42    pub deceleration: Option<f32>,
43    pub speed: Option<f32>,
44    pub fov: Option<f32>,
45    pub fov_rate: Option<f32>,
46    pub use_screen_aspect_ratio: Option<bool>,
47    pub interp_time: Option<f32>,
48}
49
50/// Internal [`Cameras`] Properties to be used in a parser impl
51#[derive(Debug, Clone)]
52enum CamerasProperty {
53    ActiveCamera(i32),
54}
55
56/// Internal [`Camera`] Properties to be used in a parser impl
57#[derive(Debug, Clone)]
58enum CameraProperty<'a> {
59    Id(u32),
60    Classname(&'a str),
61    Origin(Point3D),
62    Angles(Point3D),
63    Targetname(&'a str),
64    SpawnFlags(u32),
65    Wait(f32),
66    Acceleration(f32),
67    Deceleration(f32),
68    Speed(f32),
69    Fov(f32),
70    FovRate(f32),
71    UseScreenAspectRatio(bool),
72    InterpTime(f32),
73}
74
75/// Public parser trait implementation that allows [`Cameras`] to use ::parse(input) call.
76impl<'src> Parser<'src> for Cameras<'src> {}
77
78/// A [`InternalParser`] implementation for [`Cameras`].
79///
80/// usage: `let cameras = Cameras::parser().parse(input);`.
81///
82/// The format that is being parsed here is:
83/// ```ignore
84/// cameras
85/// {
86///     "activecamera" "-1"
87///     camera
88///     {
89///         ...
90///     }
91/// }
92/// ```
93impl<'src> InternalParser<'src> for Cameras<'src> {
94    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
95    where
96        I: TokenSource<'src>,
97    {
98        impl_block_properties_parser! {
99            property_list: CamerasProperty = {
100                p_activecamera = key_value_numeric("activecamera") => CamerasProperty::ActiveCamera,
101            }
102        }
103
104        open_block("cameras")
105            .ignore_then(
106                property_list
107                    .repeated()
108                    .collect::<Vec<CamerasProperty>>()
109                    .then(
110                        Camera::parser::<I>()
111                            .repeated()
112                            .collect::<Vec<Camera<'src>>>(),
113                    ),
114            )
115            .then_ignore(close_block())
116            .map(
117                |(properties, cameras): (Vec<CamerasProperty>, Vec<Camera<'src>>)| {
118                    let mut activecamera = -1;
119                    for prop in properties {
120                        match prop {
121                            CamerasProperty::ActiveCamera(val) => activecamera = val,
122                        }
123                    }
124                    Cameras::new(activecamera, cameras)
125                },
126            )
127            .boxed()
128    }
129}
130
131/// Public parser trait implementation that allows [`Camera`] to use ::parse(input) call.
132impl<'src> Parser<'src> for Camera<'src> {}
133
134/// A [`InternalParser`] implementation for [`Camera`].
135///
136/// usage: `let camera = Camera::parser().parse(input);`.
137///
138/// The format that is being parsed here is:
139/// ```ignore
140/// camera
141/// {
142///     "id" "1"
143///     "classname" "point_viewcontrol"
144///     "origin" "0 0 64"
145///     "angles" "0 90 0"
146///     "targetname" "camera1"
147///     "spawnflags" "0"
148///     "fov" "90"
149/// }
150/// ```
151impl<'src> InternalParser<'src> for Camera<'src> {
152    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
153    where
154        I: TokenSource<'src>,
155    {
156        impl_block_properties_parser! {
157            property_list: CameraProperty = {
158                p_id                       = key_value_numeric("id")                      => CameraProperty::Id,
159                p_classname                = key_value("classname")                       => CameraProperty::Classname,
160                p_origin                   = key_value_point3d("origin")                  => CameraProperty::Origin,
161                p_angles                   = key_value_point3d("angles")                  => CameraProperty::Angles,
162                p_targetname               = key_value("targetname")                      => CameraProperty::Targetname,
163                p_spawnflags               = key_value_numeric("spawnflags")              => CameraProperty::SpawnFlags,
164                p_wait                     = key_value_numeric("wait")                    => CameraProperty::Wait,
165                p_acceleration             = key_value_numeric("acceleration")            => CameraProperty::Acceleration,
166                p_deceleration             = key_value_numeric("deceleration")            => CameraProperty::Deceleration,
167                p_speed                    = key_value_numeric("speed")                   => CameraProperty::Speed,
168                p_fov                      = key_value_numeric("fov")                     => CameraProperty::Fov,
169                p_fov_rate                 = key_value_numeric("fov_rate")                => CameraProperty::FovRate,
170                p_use_screen_aspect_ratio  = key_value_boolean("use_screen_aspect_ratio") => CameraProperty::UseScreenAspectRatio,
171                p_interp_time              = key_value_numeric("interp_time")             => CameraProperty::InterpTime,
172            }
173        }
174
175        open_block("camera")
176            .ignore_then(property_list.repeated().collect::<Vec<CameraProperty>>())
177            .then_ignore(close_block())
178            .map(|properties: Vec<CameraProperty>| {
179                let mut camera = Camera::default();
180                for prop in properties {
181                    match prop {
182                        CameraProperty::Id(val) => camera.id = val,
183                        CameraProperty::Classname(val) => camera.classname = val,
184                        CameraProperty::Origin(val) => camera.origin = val,
185                        CameraProperty::Angles(val) => camera.angles = val,
186                        CameraProperty::Targetname(val) => camera.targetname = val,
187                        CameraProperty::SpawnFlags(val) => camera.spawnflags = Some(val),
188                        CameraProperty::Wait(val) => camera.wait = Some(val),
189                        CameraProperty::Acceleration(val) => camera.acceleration = Some(val),
190                        CameraProperty::Deceleration(val) => camera.deceleration = Some(val),
191                        CameraProperty::Speed(val) => camera.speed = Some(val),
192                        CameraProperty::Fov(val) => camera.fov = Some(val),
193                        CameraProperty::FovRate(val) => camera.fov_rate = Some(val),
194                        CameraProperty::UseScreenAspectRatio(val) => {
195                            camera.use_screen_aspect_ratio = Some(val)
196                        }
197                        CameraProperty::InterpTime(val) => camera.interp_time = Some(val),
198                    }
199                }
200                camera
201            })
202            .boxed()
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::util::lex;
210
211    #[test]
212    fn test_cameras_empty() {
213        let input = r#"
214        cameras
215        {
216            "activecamera" "-1"
217        }
218        "#;
219
220        let stream = lex(input);
221        let result = Cameras::parse(stream);
222        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
223
224        let cameras = result.unwrap();
225        assert_eq!(cameras.activecamera, -1);
226        assert_eq!(cameras.cameras.len(), 0);
227    }
228
229    #[test]
230    fn test_cameras_with_active_camera() {
231        let input = r#"
232        cameras
233        {
234            "activecamera" "0"
235        }
236        "#;
237
238        let stream = lex(input);
239        let result = Cameras::parse(stream);
240        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
241
242        let cameras = result.unwrap();
243        assert_eq!(cameras.activecamera, 0);
244        assert_eq!(cameras.cameras.len(), 0);
245    }
246
247    #[test]
248    fn test_cameras_missing_activecamera() {
249        let input = r#"
250        cameras
251        {
252        }
253        "#;
254
255        let stream = lex(input);
256        let result = Cameras::parse(stream);
257        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
258
259        let cameras = result.unwrap();
260        assert_eq!(cameras.activecamera, -1); // Default value
261        assert_eq!(cameras.cameras.len(), 0);
262    }
263
264    #[test]
265    fn test_cameras_invalid_block_name() {
266        let input = r#"
267        wrongname
268        {
269            "activecamera" "-1"
270        }
271        "#;
272
273        let stream = lex(input);
274        let result = Cameras::parse(stream);
275        assert!(result.is_err(), "Parser should fail on invalid block name");
276    }
277
278    #[test]
279    fn test_cameras_missing_closing_brace() {
280        let input = r#"
281        cameras
282        {
283            "activecamera" "-1"
284        "#;
285
286        let stream = lex(input);
287        let result = Cameras::parse(stream);
288        assert!(
289            result.is_err(),
290            "Parser should fail on missing closing brace"
291        );
292    }
293
294    #[test]
295    fn test_cameras_invalid_activecamera_type() {
296        let input = r#"
297        cameras
298        {
299            "activecamera" "not_a_number"
300        }
301        "#;
302
303        let stream = lex(input);
304        let result = Cameras::parse(stream);
305        assert!(
306            result.is_err(),
307            "Parser should fail on invalid activecamera type"
308        );
309    }
310
311    #[test]
312    fn test_camera_complete_valid() {
313        let input = r#"
314        camera
315        {
316            "id" "42"
317            "classname" "point_viewcontrol"
318            "origin" "100 200 64"
319            "angles" "0 90 0"
320            "targetname" "camera_main"
321            "spawnflags" "8"
322            "fov" "75"
323            "speed" "100"
324            "acceleration" "500"
325            "deceleration" "500"
326        }
327        "#;
328
329        let stream = lex(input);
330        let result = Camera::parse(stream);
331        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
332
333        let camera = result.unwrap();
334        assert_eq!(camera.id, 42);
335        assert_eq!(camera.classname, "point_viewcontrol");
336        assert_eq!(camera.origin.x, 100.0);
337        assert_eq!(camera.origin.y, 200.0);
338        assert_eq!(camera.origin.z, 64.0);
339        assert_eq!(camera.angles.x, 0.0);
340        assert_eq!(camera.angles.y, 90.0);
341        assert_eq!(camera.angles.z, 0.0);
342        assert_eq!(camera.targetname, "camera_main");
343        assert_eq!(camera.spawnflags, Some(8));
344        assert_eq!(camera.fov, Some(75.0));
345        assert_eq!(camera.speed, Some(100.0));
346        assert_eq!(camera.acceleration, Some(500.0));
347        assert_eq!(camera.deceleration, Some(500.0));
348    }
349
350    #[test]
351    fn test_camera_properties_out_of_order() {
352        let input = r#"
353        camera
354        {
355            "targetname" "test_camera"
356            "angles" "45 180 0"
357            "id" "10"
358            "fov" "60"
359            "origin" "0 0 128"
360            "classname" "point_viewcontrol"
361        }
362        "#;
363
364        let stream = lex(input);
365        let result = Camera::parse(stream);
366        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
367
368        let camera = result.unwrap();
369        assert_eq!(camera.id, 10);
370        assert_eq!(camera.classname, "point_viewcontrol");
371        assert_eq!(camera.targetname, "test_camera");
372        assert_eq!(camera.fov, Some(60.0));
373    }
374
375    #[test]
376    fn test_camera_minimal_properties() {
377        let input = r#"
378        camera
379        {
380            "id" "1"
381            "classname" "point_viewcontrol"
382            "origin" "0 0 0"
383            "angles" "0 0 0"
384            "targetname" "cam"
385        }
386        "#;
387
388        let stream = lex(input);
389        let result = Camera::parse(stream);
390        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
391
392        let camera = result.unwrap();
393        assert_eq!(camera.id, 1);
394        assert_eq!(camera.classname, "point_viewcontrol");
395        assert_eq!(camera.targetname, "cam");
396        assert_eq!(camera.spawnflags, None);
397        assert_eq!(camera.fov, None);
398    }
399
400    #[test]
401    fn test_camera_empty_block() {
402        let input = r#"
403        camera
404        {
405        }
406        "#;
407
408        let stream = lex(input);
409        let result = Camera::parse(stream);
410        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
411
412        let camera = result.unwrap();
413        let default = Camera::default();
414        assert_eq!(camera.id, default.id);
415    }
416
417    #[test]
418    fn test_camera_invalid_origin() {
419        let input = r#"
420        camera
421        {
422            "id" "1"
423            "origin" "not valid"
424        }
425        "#;
426
427        let stream = lex(input);
428        let result = Camera::parse(stream);
429        assert!(result.is_err(), "Parser should fail on invalid origin");
430    }
431
432    #[test]
433    fn test_camera_invalid_angles() {
434        let input = r#"
435        camera
436        {
437            "id" "1"
438            "angles" "0 90"
439        }
440        "#;
441
442        let stream = lex(input);
443        let result = Camera::parse(stream);
444        assert!(
445            result.is_err(),
446            "Parser should fail on invalid angles (too few values)"
447        );
448    }
449
450    #[test]
451    fn test_cameras_with_camera_blocks() {
452        let input = r#"
453        cameras
454        {
455            "activecamera" "0"
456            camera
457            {
458                "id" "1"
459                "classname" "point_viewcontrol"
460                "origin" "0 0 64"
461                "angles" "0 0 0"
462                "targetname" "camera1"
463            }
464            camera
465            {
466                "id" "2"
467                "classname" "point_viewcontrol"
468                "origin" "100 100 64"
469                "angles" "0 90 0"
470                "targetname" "camera2"
471            }
472        }
473        "#;
474
475        let stream = lex(input);
476        let result = Cameras::parse(stream);
477        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
478
479        let cameras = result.unwrap();
480        assert_eq!(cameras.activecamera, 0);
481        assert_eq!(cameras.cameras.len(), 2);
482        assert_eq!(cameras.cameras[0].id, 1);
483        assert_eq!(cameras.cameras[0].targetname, "camera1");
484        assert_eq!(cameras.cameras[1].id, 2);
485        assert_eq!(cameras.cameras[1].targetname, "camera2");
486    }
487}