1use chumsky::IterParser;
2use chumsky::Parser as ChumskyParser;
3
4use crate::impl_block_properties_parser;
5use crate::parser::{
6 close_block, key_value, key_value_numeric, open_block, skip_unknown_block, InternalParser,
7 TokenError, TokenSource,
8};
9use crate::types::point::key_value_plane;
10use crate::types::textureaxis::key_value_texture_axis;
11use crate::Parser;
12
13use super::point::Point3D;
14use super::textureaxis::TextureAxis;
15use super::DispInfo;
16
17#[derive(Debug, Default, Clone)]
19pub struct Side<'src> {
20 pub id: u32,
21 pub plane: (Point3D, Point3D, Point3D),
22 pub material: &'src str,
23 pub uaxis: TextureAxis,
24 pub vaxis: TextureAxis,
25 pub rotation: f32,
26 pub lightmapscale: u32,
27 pub smoothing_groups: u32,
28 pub dispinfo: Option<DispInfo>, }
30
31enum SideProperty<'src> {
33 Id(u32),
34 Plane((Point3D, Point3D, Point3D)),
35 Material(&'src str),
36 UAxis(TextureAxis),
37 VAxis(TextureAxis),
38 Rotation(f32),
39 LightmapScale(u32),
40 SmoothingGroups(u32),
41 DispInfo(DispInfo),
42}
43
44impl<'src> Parser<'src> for Side<'src> {}
46
47impl<'src> InternalParser<'src> for Side<'src> {
65 fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
66 where
67 I: TokenSource<'src>,
68 {
69 impl_block_properties_parser! {
70 property_list: SideProperty = {
71 p_id = key_value_numeric("id") => SideProperty::Id,
72 p_plane = key_value_plane("plane") => SideProperty::Plane,
73 p_material = key_value("material") => SideProperty::Material,
74 p_uaxis = key_value_texture_axis("uaxis") => SideProperty::UAxis,
75 p_vaxis = key_value_texture_axis("vaxis") => SideProperty::VAxis,
76 p_rotation = key_value_numeric("rotation") => SideProperty::Rotation,
77 p_lightmap_scale = key_value_numeric("lightmapscale") => SideProperty::LightmapScale,
78 p_smoothing_groups = key_value_numeric("smoothing_groups") => SideProperty::SmoothingGroups,
79 }
80 }
81
82 let dispinfo_parser = DispInfo::parser().map(SideProperty::DispInfo);
83 let any_property_or_block = property_list
84 .or(dispinfo_parser)
85 .map(Some)
86 .or(skip_unknown_block().map(|_| None));
87
88 open_block("side")
89 .ignore_then(
90 any_property_or_block
91 .repeated()
92 .collect::<Vec<Option<SideProperty>>>(),
93 )
94 .then_ignore(close_block())
95 .map(|properties: Vec<Option<SideProperty>>| {
96 let mut side = Side::default();
97 for prop_opt in properties {
98 if let Some(prop) = prop_opt {
99 match prop {
100 SideProperty::Id(val) => side.id = val,
101 SideProperty::Plane(val) => side.plane = val,
102 SideProperty::Material(val) => side.material = val,
103 SideProperty::UAxis(val) => side.uaxis = val,
104 SideProperty::VAxis(val) => side.vaxis = val,
105 SideProperty::Rotation(val) => side.rotation = val,
106 SideProperty::LightmapScale(val) => side.lightmapscale = val,
107 SideProperty::SmoothingGroups(val) => side.smoothing_groups = val,
108 SideProperty::DispInfo(val) => side.dispinfo = Some(val),
109 }
110 }
111 }
112 side
113 })
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use crate::util::lex;
120
121 use super::*;
122 use chumsky::Parser as ChumskyParser;
123
124 #[test]
125 fn test_parse_side_complete_valid_order() {
126 let input = r#"
127 side
128 {
129 "id" "1"
130 "plane" "(-320 -320 0) (-320 320 0) (320 320 0)"
131 "material" "DEV/DEV_MEASUREGENERIC01B"
132 "uaxis" "[1 0 0 0] 0.25"
133 "vaxis" "[0 -1 0 0] 0.25"
134 "rotation" "0"
135 "lightmapscale" "16"
136 "smoothing_groups" "0"
137 }
138 "#;
139 let stream = lex(input);
140 let result = Side::parser().parse(stream).into_result();
141
142 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
143 let side = result.unwrap();
144
145 let expected_plane = (
146 Point3D {
147 x: -320.0,
148 y: -320.0,
149 z: 0.0,
150 },
151 Point3D {
152 x: -320.0,
153 y: 320.0,
154 z: 0.0,
155 },
156 Point3D {
157 x: 320.0,
158 y: 320.0,
159 z: 0.0,
160 },
161 );
162 let expected_uaxis = TextureAxis {
163 x: 1.0,
164 y: 0.0,
165 z: 0.0,
166 shift: 0.0,
167 scale: 0.25,
168 };
169 let expected_vaxis = TextureAxis {
170 x: 0.0,
171 y: -1.0,
172 z: 0.0,
173 shift: 0.0,
174 scale: 0.25,
175 };
176
177 assert_eq!(side.id, 1);
178 assert_eq!(side.plane, expected_plane);
179 assert_eq!(side.material, "DEV/DEV_MEASUREGENERIC01B");
180 assert_eq!(side.uaxis, expected_uaxis);
181 assert_eq!(side.vaxis, expected_vaxis);
182 assert_eq!(side.rotation, 0.0);
183 assert_eq!(side.lightmapscale, 16);
184 assert_eq!(side.smoothing_groups, 0);
185 }
186
187 #[test]
188 fn test_parse_side_properties_out_of_order() {
189 let input = r#"
190 side
191 {
192 "material" "BRICK/BRICKWALL001A"
193 "id" "42"
194 "uaxis" "[0 1 0 10] 0.125"
195 "smoothing_groups" "1"
196 "plane" "(0 0 0) (100 0 0) (100 100 0)"
197 "lightmapscale" "32"
198 "vaxis" "[1 0 0 20] 0.125"
199 "rotation" "90"
200 }
201 "#;
202 let stream = lex(input);
203 let result = Side::parser().parse(stream).into_result();
204
205 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
206 let side = result.unwrap();
207
208 let expected_plane = (
209 Point3D {
210 x: 0.0,
211 y: 0.0,
212 z: 0.0,
213 },
214 Point3D {
215 x: 100.0,
216 y: 0.0,
217 z: 0.0,
218 },
219 Point3D {
220 x: 100.0,
221 y: 100.0,
222 z: 0.0,
223 },
224 );
225 let expected_uaxis = TextureAxis {
226 x: 0.0,
227 y: 1.0,
228 z: 0.0,
229 shift: 10.0,
230 scale: 0.125,
231 };
232 let expected_vaxis = TextureAxis {
233 x: 1.0,
234 y: 0.0,
235 z: 0.0,
236 shift: 20.0,
237 scale: 0.125,
238 };
239
240 assert_eq!(side.id, 42);
241 assert_eq!(side.plane, expected_plane);
242 assert_eq!(side.material, "BRICK/BRICKWALL001A");
243 assert_eq!(side.uaxis, expected_uaxis);
244 assert_eq!(side.vaxis, expected_vaxis);
245 assert_eq!(side.rotation, 90.0);
246 assert_eq!(side.lightmapscale, 32);
247 assert_eq!(side.smoothing_groups, 1);
248 }
249
250 #[test]
251 fn test_parse_side_missing_optional_properties() {
252 let input = r#"
253 side
254 {
255 "id" "3"
256 "plane" "(1 1 1) (2 2 2) (3 3 3)"
257 "material" "CONCRETE/CONCRETEFLOOR001"
258 "uaxis" "[1 0 0 0] 1"
259 "vaxis" "[0 1 0 0] 1"
260 }
261 "#;
262 let stream = lex(input);
263 let result = Side::parser().parse(stream).into_result();
264
265 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
266 let side = result.unwrap();
267 let default_side_for_missing_fields = Side::default(); let expected_plane = (
270 Point3D {
271 x: 1.0,
272 y: 1.0,
273 z: 1.0,
274 },
275 Point3D {
276 x: 2.0,
277 y: 2.0,
278 z: 2.0,
279 },
280 Point3D {
281 x: 3.0,
282 y: 3.0,
283 z: 3.0,
284 },
285 );
286 let expected_uaxis = TextureAxis {
287 x: 1.0,
288 y: 0.0,
289 z: 0.0,
290 shift: 0.0,
291 scale: 1.0,
292 };
293 let expected_vaxis = TextureAxis {
294 x: 0.0,
295 y: 1.0,
296 z: 0.0,
297 shift: 0.0,
298 scale: 1.0,
299 };
300
301 assert_eq!(side.id, 3);
302 assert_eq!(side.plane, expected_plane);
303 assert_eq!(side.material, "CONCRETE/CONCRETEFLOOR001");
304 assert_eq!(side.uaxis, expected_uaxis);
305 assert_eq!(side.vaxis, expected_vaxis);
306 assert_eq!(side.rotation, default_side_for_missing_fields.rotation);
307 assert_eq!(
308 side.lightmapscale,
309 default_side_for_missing_fields.lightmapscale
310 );
311 assert_eq!(
312 side.smoothing_groups,
313 default_side_for_missing_fields.smoothing_groups
314 );
315 }
316
317 #[test]
318 fn test_parse_side_empty_block() {
319 let input = r#"
320 side
321 {
322 }
323 "#;
324 let stream = lex(input);
325 let result = Side::parser().parse(stream).into_result();
326
327 assert!(result.is_ok(), "Parsing failed: {:?}", result.err());
328 let side = result.unwrap();
329 let expected_side = Side::default(); assert_eq!(side.id, expected_side.id);
332 assert_eq!(side.plane, expected_side.plane);
333 assert_eq!(side.material, expected_side.material);
334 assert_eq!(side.uaxis, expected_side.uaxis);
335 assert_eq!(side.vaxis, expected_side.vaxis);
336 assert_eq!(side.rotation, expected_side.rotation);
337 assert_eq!(side.lightmapscale, expected_side.lightmapscale);
338 assert_eq!(side.smoothing_groups, expected_side.smoothing_groups);
339 }
340
341 #[test]
342 fn test_parse_side_malformed_id() {
343 let input = r#"
344 side
345 {
346 "id" "not_a_number"
347 "plane" "(-320 -320 0) (-320 320 0) (320 320 0)"
348 "material" "DEV/DEV_MEASUREGENERIC01B"
349 "uaxis" "[1 0 0 0] 0.25"
350 "vaxis" "[0 -1 0 0] 0.25"
351 "rotation" "0"
352 "lightmapscale" "16"
353 "smoothing_groups" "0"
354 }
355 "#;
356 let stream = lex(input);
357 let result = Side::parser().parse(stream).into_result();
358 assert!(
359 result.is_err(),
360 "Parsing should have failed for malformed id"
361 );
362 }
363
364 #[test]
365 fn test_parse_side_malformed_plane() {
366 let input = r#"
367 side
368 {
369 "id" "1"
370 "plane" "this_is_not_a_plane"
371 "material" "DEV/DEV_MEASUREGENERIC01B"
372 "uaxis" "[1 0 0 0] 0.25"
373 "vaxis" "[0 -1 0 0] 0.25"
374 "rotation" "0"
375 "lightmapscale" "16"
376 "smoothing_groups" "0"
377 }
378 "#;
379 let stream = lex(input);
380 let result = Side::parser().parse(stream).into_result();
381 assert!(
382 result.is_err(),
383 "Parsing should have failed for malformed plane"
384 );
385 }
386
387 #[test]
388 fn test_parse_side_malformed_uaxis() {
389 let input = r#"
390 side
391 {
392 "id" "1"
393 "plane" "(-320 -320 0) (-320 320 0) (320 320 0)"
394 "material" "DEV/DEV_MEASUREGENERIC01B"
395 "uaxis" "not_a_uaxis"
396 "vaxis" "[0 -1 0 0] 0.25"
397 "rotation" "0"
398 "lightmapscale" "16"
399 "smoothing_groups" "0"
400 }
401 "#;
402 let stream = lex(input);
403 let result = Side::parser().parse(stream).into_result();
404 assert!(
405 result.is_err(),
406 "Parsing should have failed for malformed uaxis"
407 );
408 }
409
410 #[test]
411 fn test_parse_side_missing_closing_brace_for_block() {
412 let input = r#"
413 side
414 {
415 "id" "1"
416 "plane" "(-320 -320 0) (-320 320 0) (320 320 0)"
417 "material" "DEV/DEV_MEASUREGENERIC01B"
418 "uaxis" "[1 0 0 0] 0.25"
419 "vaxis" "[0 -1 0 0] 0.25"
420 "rotation" "0"
421 "lightmapscale" "16"
422 "smoothing_groups" "0"
423 "#;
424 let stream = lex(input);
425 let result = Side::parser().parse(stream).into_result();
426 assert!(
427 result.is_err(),
428 "Parsing should have failed for missing closing brace"
429 );
430 }
431
432 #[test]
433 fn test_parse_side_unknown_property() {
434 let input = r#"
435 side
436 {
437 "id" "1"
438 "unknown_property" "some_value"
439 "material" "DEV/DEV_MEASUREGENERIC01B"
440 }
441 "#;
442 let stream = lex(input);
443 let result = Side::parser().parse(stream).into_result();
444
445 assert!(
446 result.is_err(),
447 "Parsing should fail on unknown property if not explicitly skipped"
448 );
449 }
450}