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#[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#[derive(Debug, Clone, Default)]
24pub struct DispTri {
25 pub indices: [u32; 3],
26}
27
28#[derive(Debug, Default, Clone)]
30pub struct DispInfo {
31 pub power: u32, pub start_position: Point3D, pub elevation: f32, pub subdiv: bool, pub normals: Vec<Point3D>,
38 pub distances: Vec<f32>,
39
40 pub offsets: Vec<Point3D>,
42
43 pub offset_normals: Vec<Point3D>,
45
46 pub alphas: Vec<f32>,
48
49 pub triangle_tags: Vec<u32>,
51
52 pub allowed_verts: Vec<i32>,
54 pub flags: u32,
55}
56
57#[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
74fn 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 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
102fn 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
129fn 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
141fn 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
153fn 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
166fn parse_startposition(value_str: &str) -> Result<Point3D, String> {
168 let trimmed = value_str.trim();
169
170 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
182fn 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
197impl Parser<'_> for DispInfo {}
199
200impl<'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); 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}