Skip to main content

mnk_vmf/types/
displacement.rs

1use chumsky::{IterParser, Parser as ChumskyParser};
2
3use crate::{
4    impl_block_properties_parser,
5    parser::{
6        any_quoted_string, close_block, key_value_boolean, key_value_numeric, open_block,
7        quoted_string, InternalParser, TokenError, TokenSource,
8    },
9    types::point::{parse_point_from_numbers_str, Point3D},
10    Parser,
11};
12
13/// Represents a displacement vertex
14#[derive(Debug, Clone, Default)]
15pub struct DispVertex {
16    pub position: Point3D,
17    pub normal: Point3D,
18    pub distance: f32,
19    pub alpha: f32,
20}
21
22/// Represents a displacement triangle
23#[derive(Debug, Clone, Default)]
24pub struct DispTri {
25    pub indices: [u32; 3],
26}
27
28/// Represents displacement information for terrain
29#[derive(Debug, Default, Clone)]
30pub struct DispInfo {
31    pub power: u32,              // Power of 2 determining grid size (2^power + 1)
32    pub start_position: Point3D, // Starting position of the displacement
33    pub elevation: f32,          // Base height offset
34    pub subdiv: bool,            // Whether to use subdivision
35
36    // Normals and distances
37    pub normals: Vec<Point3D>,
38    pub distances: Vec<f32>,
39
40    // Offsets (x,y,z) for each vertex
41    pub offsets: Vec<Point3D>,
42
43    // Offset normals
44    pub offset_normals: Vec<Point3D>,
45
46    // Alpha values (transparency/blending)
47    pub alphas: Vec<f32>,
48
49    // Triangle tags for collision
50    pub triangle_tags: Vec<u32>,
51
52    // Allowed vertex positions
53    pub allowed_verts: Vec<i32>,
54    pub flags: u32,
55}
56
57/// Internal [`DispInfo`] Properties to be used in a parser impl
58#[derive(Debug, Clone)]
59enum DispInfoProperty {
60    Power(u32),
61    StartPosition(Point3D),
62    Elevation(f32),
63    Subdiv(bool),
64    Flags(u32),
65    NormalsBlock(Vec<Point3D>),
66    DistancesBlock(Vec<f32>),
67    OffsetsBlock(Vec<Point3D>),
68    OffsetNormalsBlock(Vec<Point3D>),
69    AlphasBlock(Vec<f32>),
70    TriangleTagsBlock(Vec<u32>),
71    AllowedVertsBlock(Vec<i32>),
72}
73
74/// Helper to parse a row of displacement data (key-value pair where key is "rowN")
75fn parse_row_data<'src, I, T, F>(
76    block_name: &'static str,
77    parser_fn: F,
78) -> impl ChumskyParser<'src, I, Vec<T>, TokenError<'src>>
79where
80    I: TokenSource<'src>,
81    F: Fn(&'src str) -> Result<Vec<T>, String> + Clone + 'src,
82    T: 'src,
83{
84    use chumsky::error::Rich;
85
86    let row_parser =
87        any_quoted_string()
88            .then(any_quoted_string())
89            .try_map(move |(_key, value_str), span| {
90                // Key should be like "row0", "row1", etc.
91                parser_fn(value_str).map_err(|err_msg| {
92                    Rich::custom(span, format!("Invalid {} data: {}", block_name, err_msg))
93                })
94            });
95
96    open_block(block_name)
97        .ignore_then(row_parser.repeated().collect::<Vec<Vec<T>>>())
98        .then_ignore(close_block())
99        .map(|rows: Vec<Vec<T>>| rows.into_iter().flatten().collect())
100}
101
102/// Parse a row of Point3D normals from a string like "x1 y1 z1 x2 y2 z2 ..."
103fn parse_normals_row<'src>(value_str: &'src str) -> Result<Vec<Point3D>, String> {
104    let mut normals = Vec::new();
105    let mut parts = value_str.split_whitespace();
106
107    loop {
108        match (parts.next(), parts.next(), parts.next()) {
109            (Some(x_str), Some(y_str), Some(z_str)) => {
110                let x = x_str
111                    .parse::<f32>()
112                    .map_err(|e| format!("invalid x '{}': {}", x_str, e))?;
113                let y = y_str
114                    .parse::<f32>()
115                    .map_err(|e| format!("invalid y '{}': {}", y_str, e))?;
116                let z = z_str
117                    .parse::<f32>()
118                    .map_err(|e| format!("invalid z '{}': {}", z_str, e))?;
119                normals.push(Point3D { x, y, z });
120            }
121            (None, None, None) => break,
122            _ => return Err("expected multiple of 3 numbers".into()),
123        }
124    }
125
126    Ok(normals)
127}
128
129/// Parse a row of f32 distances from a string like "1.0 2.5 3.0 ..."
130fn parse_distances_row(value_str: &str) -> Result<Vec<f32>, String> {
131    let parts: Vec<&str> = value_str.split_whitespace().collect();
132    parts
133        .iter()
134        .map(|s| {
135            s.parse::<f32>()
136                .map_err(|e| format!("invalid float '{}': {}", s, e))
137        })
138        .collect()
139}
140
141/// Parse a row of u32 values from a string like "0 1 2 3 ..."
142fn parse_u32_row(value_str: &str) -> Result<Vec<u32>, String> {
143    let parts: Vec<&str> = value_str.split_whitespace().collect();
144    parts
145        .iter()
146        .map(|s| {
147            s.parse::<u32>()
148                .map_err(|e| format!("invalid integer '{}': {}", s, e))
149        })
150        .collect()
151}
152
153/// Parse a row of i32 values from a string like "0 1 -1 2 ..."
154/// Used for allowed_verts which can contain -1 to mean "all vertices allowed"
155fn parse_i32_row(value_str: &str) -> Result<Vec<i32>, String> {
156    let parts: Vec<&str> = value_str.split_whitespace().collect();
157    parts
158        .iter()
159        .map(|s| {
160            s.parse::<i32>()
161                .map_err(|e| format!("invalid integer '{}': {}", s, e))
162        })
163        .collect()
164}
165
166/// Parse startposition which has format "[x y z]"
167fn parse_startposition(value_str: &str) -> Result<Point3D, String> {
168    let trimmed = value_str.trim();
169
170    // Remove brackets
171    if !trimmed.starts_with('[') || !trimmed.ends_with(']') {
172        return Err(format!(
173            "startposition must be in format [x y z], got: {}",
174            value_str
175        ));
176    }
177
178    let inner = &trimmed[1..trimmed.len() - 1];
179    parse_point_from_numbers_str(inner)
180}
181
182/// Parses a key-value pair where the value is a Point3D with square brackets
183fn key_value_startposition<'src, I>() -> impl ChumskyParser<'src, I, Point3D, TokenError<'src>>
184where
185    I: TokenSource<'src>,
186{
187    use chumsky::error::Rich;
188    quoted_string("startposition")
189        .ignore_then(any_quoted_string())
190        .try_map(move |value_str, span| {
191            parse_startposition(value_str).map_err(|err_msg| {
192                Rich::custom(span, format!("Invalid startposition: {}", err_msg))
193            })
194        })
195}
196
197/// Public parser trait implementation that allows [`DispInfo`] to use ::parse(input) call.
198impl Parser<'_> for DispInfo {}
199
200/// A [`InternalParser`] implementation for [`DispInfo`].
201///
202/// usage: `let dispinfo = DispInfo::parser().parse(input);`.
203///
204/// The format that is being parsed here is:
205/// ```ignore
206/// dispinfo
207/// {
208///     "power" "3"
209///     "startposition" "[0 0 0]"
210///     "elevation" "0"
211///     "subdiv" "0"
212///     normals
213///     {
214///         "row0" "0 0 1 0 0 1 0 0 1"
215///         "row1" "0 0 1 0 0 1 0 0 1"
216///     }
217///     distances
218///     {
219///         "row0" "0 0 0"
220///         "row1" "0 0 0"
221///     }
222///     offsets
223///     {
224///         "row0" "0 0 0 0 0 0 0 0 0"
225///     }
226///     offset_normals
227///     {
228///         "row0" "0 0 0 0 0 0 0 0 0"
229///     }
230///     alphas
231///     {
232///         "row0" "0 0 0"
233///     }
234///     triangle_tags
235///     {
236///         "row0" "0 0 0"
237///     }
238///     allowed_verts
239///     {
240///         "10" "0 1 2 3 4 5 6 7 8 9"
241///     }
242/// }
243/// ```
244impl<'src> InternalParser<'src> for DispInfo {
245    fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
246    where
247        I: TokenSource<'src>,
248    {
249        let normals_parser =
250            parse_row_data("normals", parse_normals_row).map(DispInfoProperty::NormalsBlock);
251        let distances_parser =
252            parse_row_data("distances", parse_distances_row).map(DispInfoProperty::DistancesBlock);
253        let offsets_parser =
254            parse_row_data("offsets", parse_normals_row).map(DispInfoProperty::OffsetsBlock);
255        let offset_normals_parser = parse_row_data("offset_normals", parse_normals_row)
256            .map(DispInfoProperty::OffsetNormalsBlock);
257        let alphas_parser =
258            parse_row_data("alphas", parse_distances_row).map(DispInfoProperty::AlphasBlock);
259        let triangle_tags_parser =
260            parse_row_data("triangle_tags", parse_u32_row).map(DispInfoProperty::TriangleTagsBlock);
261        let allowed_verts_parser =
262            parse_row_data("allowed_verts", parse_i32_row).map(DispInfoProperty::AllowedVertsBlock);
263
264        impl_block_properties_parser! {
265            property_list: DispInfoProperty = {
266                p_power            = key_value_numeric("power")          => DispInfoProperty::Power,
267                p_startposition    = key_value_startposition()           => DispInfoProperty::StartPosition,
268                p_elevation        = key_value_numeric("elevation")      => DispInfoProperty::Elevation,
269                p_subdiv           = key_value_boolean("subdiv")         => DispInfoProperty::Subdiv,
270                p_flags            = key_value_numeric("flags")          => DispInfoProperty::Flags,
271            }
272        }
273
274        let any_property = property_list
275            .or(normals_parser)
276            .or(distances_parser)
277            .or(offsets_parser)
278            .or(offset_normals_parser)
279            .or(alphas_parser)
280            .or(triangle_tags_parser)
281            .or(allowed_verts_parser);
282
283        open_block("dispinfo")
284            .ignore_then(any_property.repeated().collect::<Vec<DispInfoProperty>>())
285            .then_ignore(close_block())
286            .map(|properties: Vec<DispInfoProperty>| {
287                let mut dispinfo = DispInfo::default();
288                for prop in properties {
289                    match prop {
290                        DispInfoProperty::Power(val) => dispinfo.power = val,
291                        DispInfoProperty::StartPosition(val) => dispinfo.start_position = val,
292                        DispInfoProperty::Elevation(val) => dispinfo.elevation = val,
293                        DispInfoProperty::Subdiv(val) => dispinfo.subdiv = val,
294                        DispInfoProperty::Flags(val) => dispinfo.flags = val,
295                        DispInfoProperty::NormalsBlock(val) => dispinfo.normals = val,
296                        DispInfoProperty::DistancesBlock(val) => dispinfo.distances = val,
297                        DispInfoProperty::OffsetsBlock(val) => dispinfo.offsets = val,
298                        DispInfoProperty::OffsetNormalsBlock(val) => dispinfo.offset_normals = val,
299                        DispInfoProperty::AlphasBlock(val) => dispinfo.alphas = val,
300                        DispInfoProperty::TriangleTagsBlock(val) => dispinfo.triangle_tags = val,
301                        DispInfoProperty::AllowedVertsBlock(val) => dispinfo.allowed_verts = val,
302                    }
303                }
304                dispinfo
305            })
306            .boxed()
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use crate::util::lex;
314
315    #[test]
316    fn test_dispinfo_minimal() {
317        let input = r#"
318        dispinfo
319        {
320            "power" "2"
321            "startposition" "[0 0 0]"
322            "elevation" "0"
323            "subdiv" "0"
324        }
325        "#;
326
327        let stream = lex(input);
328        let result = DispInfo::parse(stream);
329        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
330
331        let dispinfo = result.unwrap();
332        assert_eq!(dispinfo.power, 2);
333        assert_eq!(dispinfo.elevation, 0.0);
334        assert_eq!(dispinfo.subdiv, false);
335        assert_eq!(dispinfo.start_position.x, 0.0);
336        assert_eq!(dispinfo.start_position.y, 0.0);
337        assert_eq!(dispinfo.start_position.z, 0.0);
338    }
339
340    #[test]
341    fn test_dispinfo_with_normals() {
342        let input = r#"
343        dispinfo
344        {
345            "power" "3"
346            "startposition" "[100 200 0]"
347            "elevation" "5"
348            "subdiv" "1"
349            normals
350            {
351                "row0" "0 0 1 0 0 1 0 0 1"
352                "row1" "0 0 1 0 0 1 0 0 1"
353            }
354        }
355        "#;
356
357        let stream = lex(input);
358        let result = DispInfo::parse(stream);
359        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
360
361        let dispinfo = result.unwrap();
362        assert_eq!(dispinfo.power, 3);
363        assert_eq!(dispinfo.elevation, 5.0);
364        assert_eq!(dispinfo.subdiv, true);
365        assert_eq!(dispinfo.start_position.x, 100.0);
366        assert_eq!(dispinfo.start_position.y, 200.0);
367        assert_eq!(dispinfo.start_position.z, 0.0);
368        assert_eq!(dispinfo.normals.len(), 6); // 2 rows * 3 normals each
369        assert_eq!(dispinfo.normals[0].x, 0.0);
370        assert_eq!(dispinfo.normals[0].y, 0.0);
371        assert_eq!(dispinfo.normals[0].z, 1.0);
372    }
373
374    #[test]
375    fn test_dispinfo_with_distances() {
376        let input = r#"
377        dispinfo
378        {
379            "power" "2"
380            "startposition" "[0 0 0]"
381            "elevation" "0"
382            "subdiv" "0"
383            distances
384            {
385                "row0" "1.0 2.5 3.0"
386                "row1" "4.0 5.5 6.0"
387            }
388        }
389        "#;
390
391        let stream = lex(input);
392        let result = DispInfo::parse(stream);
393        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
394
395        let dispinfo = result.unwrap();
396        assert_eq!(dispinfo.distances.len(), 6);
397        assert_eq!(dispinfo.distances[0], 1.0);
398        assert_eq!(dispinfo.distances[1], 2.5);
399        assert_eq!(dispinfo.distances[2], 3.0);
400        assert_eq!(dispinfo.distances[3], 4.0);
401    }
402
403    #[test]
404    fn test_dispinfo_with_offsets() {
405        let input = r#"
406        dispinfo
407        {
408            "power" "2"
409            "startposition" "[0 0 0]"
410            "elevation" "0"
411            "subdiv" "0"
412            offsets
413            {
414                "row0" "0 0 5 0 0 10 0 0 15"
415            }
416        }
417        "#;
418
419        let stream = lex(input);
420        let result = DispInfo::parse(stream);
421        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
422
423        let dispinfo = result.unwrap();
424        assert_eq!(dispinfo.offsets.len(), 3);
425        assert_eq!(dispinfo.offsets[0].z, 5.0);
426        assert_eq!(dispinfo.offsets[1].z, 10.0);
427        assert_eq!(dispinfo.offsets[2].z, 15.0);
428    }
429
430    #[test]
431    fn test_dispinfo_with_alphas() {
432        let input = r#"
433        dispinfo
434        {
435            "power" "2"
436            "startposition" "[0 0 0]"
437            "elevation" "0"
438            "subdiv" "0"
439            alphas
440            {
441                "row0" "0 128 255"
442            }
443        }
444        "#;
445
446        let stream = lex(input);
447        let result = DispInfo::parse(stream);
448        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
449
450        let dispinfo = result.unwrap();
451        assert_eq!(dispinfo.alphas.len(), 3);
452        assert_eq!(dispinfo.alphas[0], 0.0);
453        assert_eq!(dispinfo.alphas[1], 128.0);
454        assert_eq!(dispinfo.alphas[2], 255.0);
455    }
456
457    #[test]
458    fn test_dispinfo_with_triangle_tags() {
459        let input = r#"
460        dispinfo
461        {
462            "power" "2"
463            "startposition" "[0 0 0]"
464            "elevation" "0"
465            "subdiv" "0"
466            triangle_tags
467            {
468                "row0" "0 1 2 3 4"
469            }
470        }
471        "#;
472
473        let stream = lex(input);
474        let result = DispInfo::parse(stream);
475        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
476
477        let dispinfo = result.unwrap();
478        assert_eq!(dispinfo.triangle_tags.len(), 5);
479        assert_eq!(dispinfo.triangle_tags[0], 0);
480        assert_eq!(dispinfo.triangle_tags[4], 4);
481    }
482
483    #[test]
484    fn test_dispinfo_with_allowed_verts() {
485        let input = r#"
486        dispinfo
487        {
488            "power" "2"
489            "startposition" "[0 0 0]"
490            "elevation" "0"
491            "subdiv" "0"
492            allowed_verts
493            {
494                "10" "0 1 2 3 4 5 6 7 8 9"
495            }
496        }
497        "#;
498
499        let stream = lex(input);
500        let result = DispInfo::parse(stream);
501        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
502
503        let dispinfo = result.unwrap();
504        assert_eq!(dispinfo.allowed_verts.len(), 10);
505        assert_eq!(dispinfo.allowed_verts[0], 0);
506        assert_eq!(dispinfo.allowed_verts[9], 9);
507    }
508
509    #[test]
510    fn test_dispinfo_complete() {
511        let input = r#"
512        dispinfo
513        {
514            "power" "3"
515            "startposition" "[128 256 64]"
516            "elevation" "10"
517            "subdiv" "1"
518            "flags" "0"
519            normals
520            {
521                "row0" "0 0 1 0 0 1"
522            }
523            distances
524            {
525                "row0" "0 0"
526            }
527            offsets
528            {
529                "row0" "0 0 0 0 0 0"
530            }
531            offset_normals
532            {
533                "row0" "0 0 0 0 0 0"
534            }
535            alphas
536            {
537                "row0" "0 0"
538            }
539            triangle_tags
540            {
541                "row0" "0 0"
542            }
543            allowed_verts
544            {
545                "10" "0 1 2 3 4 5 6 7 8 9"
546            }
547        }
548        "#;
549
550        let stream = lex(input);
551        let result = DispInfo::parse(stream);
552        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
553
554        let dispinfo = result.unwrap();
555        assert_eq!(dispinfo.power, 3);
556        assert_eq!(dispinfo.start_position.x, 128.0);
557        assert_eq!(dispinfo.elevation, 10.0);
558        assert_eq!(dispinfo.subdiv, true);
559        assert_eq!(dispinfo.flags, 0);
560        assert_eq!(dispinfo.normals.len(), 2);
561        assert_eq!(dispinfo.distances.len(), 2);
562        assert_eq!(dispinfo.offsets.len(), 2);
563        assert_eq!(dispinfo.offset_normals.len(), 2);
564        assert_eq!(dispinfo.alphas.len(), 2);
565        assert_eq!(dispinfo.triangle_tags.len(), 2);
566        assert_eq!(dispinfo.allowed_verts.len(), 10);
567    }
568
569    #[test]
570    fn test_dispinfo_properties_out_of_order() {
571        let input = r#"
572        dispinfo
573        {
574            "subdiv" "1"
575            "elevation" "5"
576            normals
577            {
578                "row0" "0 0 1"
579            }
580            "power" "2"
581            "startposition" "[0 0 0]"
582            distances
583            {
584                "row0" "0"
585            }
586        }
587        "#;
588
589        let stream = lex(input);
590        let result = DispInfo::parse(stream);
591        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
592
593        let dispinfo = result.unwrap();
594        assert_eq!(dispinfo.power, 2);
595        assert_eq!(dispinfo.subdiv, true);
596        assert_eq!(dispinfo.elevation, 5.0);
597        assert_eq!(dispinfo.normals.len(), 1);
598        assert_eq!(dispinfo.distances.len(), 1);
599    }
600
601    #[test]
602    fn test_dispinfo_empty() {
603        let input = r#"
604        dispinfo
605        {
606        }
607        "#;
608
609        let stream = lex(input);
610        let result = DispInfo::parse(stream);
611        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
612
613        let dispinfo = result.unwrap();
614        assert_eq!(dispinfo.power, 0);
615        assert_eq!(dispinfo.normals.len(), 0);
616    }
617
618    #[test]
619    fn test_dispinfo_invalid_normals() {
620        let input = r#"
621        dispinfo
622        {
623            "power" "2"
624            normals
625            {
626                "row0" "0 0 invalid"
627            }
628        }
629        "#;
630
631        let stream = lex(input);
632        let result = DispInfo::parse(stream);
633        assert!(result.is_err(), "Parser should fail on invalid normals");
634    }
635
636    #[test]
637    fn test_dispinfo_invalid_distances() {
638        let input = r#"
639        dispinfo
640        {
641            "power" "2"
642            distances
643            {
644                "row0" "1.0 not_a_number"
645            }
646        }
647        "#;
648
649        let stream = lex(input);
650        let result = DispInfo::parse(stream);
651        assert!(result.is_err(), "Parser should fail on invalid distances");
652    }
653
654    #[test]
655    fn test_dispinfo_invalid_block_name() {
656        let input = r#"
657        wrongname
658        {
659            "power" "2"
660        }
661        "#;
662
663        let stream = lex(input);
664        let result = DispInfo::parse(stream);
665        assert!(result.is_err(), "Parser should fail on invalid block name");
666    }
667
668    #[test]
669    fn test_dispinfo_missing_closing_brace() {
670        let input = r#"
671        dispinfo
672        {
673            "power" "2"
674        "#;
675
676        let stream = lex(input);
677        let result = DispInfo::parse(stream);
678        assert!(
679            result.is_err(),
680            "Parser should fail on missing closing brace"
681        );
682    }
683
684    #[test]
685    fn test_dispinfo_startposition_without_brackets() {
686        let input = r#"
687        dispinfo
688        {
689            "power" "2"
690            "startposition" "0 0 0"
691            "elevation" "0"
692            "subdiv" "0"
693        }
694        "#;
695
696        let stream = lex(input);
697        let result = DispInfo::parse(stream);
698        assert!(
699            result.is_err(),
700            "Parser should fail on startposition without brackets"
701        );
702    }
703
704    #[test]
705    fn test_dispinfo_startposition_with_brackets() {
706        let input = r#"
707        dispinfo
708        {
709            "power" "2"
710            "startposition" "[100 200 300]"
711            "elevation" "5"
712            "subdiv" "1"
713        }
714        "#;
715
716        let stream = lex(input);
717        let result = DispInfo::parse(stream);
718        assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
719
720        let dispinfo = result.unwrap();
721        assert_eq!(dispinfo.start_position.x, 100.0);
722        assert_eq!(dispinfo.start_position.y, 200.0);
723        assert_eq!(dispinfo.start_position.z, 300.0);
724    }
725}