1use cjseq::{
28 GeometryType as CjGeometryType, MaterialReference as CjMaterialReference,
29 MaterialValues as CjMaterialValues, Ring, SemanticSurfaceType, Semantics, SemanticsSurface,
30 SemanticsValues, Shell, Surface, TextureReference as CjTextureReference,
31 TextureValues as CjTextureValues, TexturedRing, TexturedShell, TexturedSurface,
32};
33
34use crate::error::Error;
35use crate::fb::{
36 Column, GeometryType, MaterialMapping, SemanticObject, SemanticSurfaceType as FbSurfaceType,
37 TextureMapping,
38};
39use std::collections::HashMap;
40
41use super::deserializer::decode_attributes;
42
43const NULL: u32 = u32::MAX;
45
46fn index(v: u32) -> Option<usize> {
47 if v == NULL {
48 None
49 } else {
50 Some(v as usize)
51 }
52}
53
54struct BoundaryCursor<'a> {
57 shells: &'a [u32],
58 surfaces: &'a [u32],
59 strings: &'a [u32],
60 indices: &'a [u32],
61 shell_cursor: usize,
62 surface_cursor: usize,
63 string_cursor: usize,
64 index_cursor: usize,
65}
66
67impl<'a> BoundaryCursor<'a> {
68 fn new(shells: &'a [u32], surfaces: &'a [u32], strings: &'a [u32], indices: &'a [u32]) -> Self {
69 BoundaryCursor {
70 shells,
71 surfaces,
72 strings,
73 indices,
74 shell_cursor: 0,
75 surface_cursor: 0,
76 string_cursor: 0,
77 index_cursor: 0,
78 }
79 }
80
81 fn take_ring(&mut self) -> Ring {
82 let size = self.strings.get(self.string_cursor).copied().unwrap_or(0) as usize;
83 self.string_cursor += 1;
84 let end = (self.index_cursor + size).min(self.indices.len());
85 let ring = self.indices[self.index_cursor..end]
86 .iter()
87 .map(|&i| i as usize)
88 .collect();
89 self.index_cursor = end;
90 ring
91 }
92
93 fn take_surface(&mut self) -> Surface {
94 let rings = self.surfaces.get(self.surface_cursor).copied().unwrap_or(0);
95 self.surface_cursor += 1;
96 (0..rings).map(|_| self.take_ring()).collect()
97 }
98
99 fn take_shell(&mut self) -> Shell {
100 let surfaces = self.shells.get(self.shell_cursor).copied().unwrap_or(0);
101 self.shell_cursor += 1;
102 (0..surfaces).map(|_| self.take_surface()).collect()
103 }
104}
105
106pub(crate) fn decode_points(indices: &[u32]) -> Ring {
108 indices.iter().map(|&i| i as usize).collect()
109}
110
111pub(crate) fn decode_rings(strings: &[u32], indices: &[u32]) -> Vec<Ring> {
113 let mut cursor = BoundaryCursor::new(&[], &[], strings, indices);
114 (0..strings.len()).map(|_| cursor.take_ring()).collect()
115}
116
117pub(crate) fn decode_surfaces(surfaces: &[u32], strings: &[u32], indices: &[u32]) -> Vec<Surface> {
119 let mut cursor = BoundaryCursor::new(&[], surfaces, strings, indices);
120 (0..surfaces.len()).map(|_| cursor.take_surface()).collect()
121}
122
123pub(crate) fn decode_shells(
125 shells: &[u32],
126 surfaces: &[u32],
127 strings: &[u32],
128 indices: &[u32],
129) -> Vec<Shell> {
130 let mut cursor = BoundaryCursor::new(shells, surfaces, strings, indices);
131 (0..shells.len()).map(|_| cursor.take_shell()).collect()
132}
133
134pub(crate) fn decode_solids(
136 solids: &[u32],
137 shells: &[u32],
138 surfaces: &[u32],
139 strings: &[u32],
140 indices: &[u32],
141) -> Vec<Vec<Shell>> {
142 let mut cursor = BoundaryCursor::new(shells, surfaces, strings, indices);
143 solids
144 .iter()
145 .map(|&n| (0..n).map(|_| cursor.take_shell()).collect())
146 .collect()
147}
148
149pub(crate) fn decode_semantics_surfaces(
152 semantics_objects: &[SemanticObject],
153 semantic_attr_schema: Option<flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<Column<'_>>>>,
154) -> Vec<SemanticsSurface> {
155 let surfaces = semantics_objects.iter().map(|s| {
156 let thetype = to_cj_surface_type(s.type_(), s.extension_type());
157
158 let children = s
159 .children()
160 .map(|c| c.iter().map(|i| i as usize).collect::<Vec<_>>());
161
162 let attributes = semantic_attr_schema
163 .as_ref()
164 .and_then(|schema| s.attributes().map(|a| decode_attributes(schema, a)));
165
166 let other = attributes
169 .and_then(|v| match v {
170 serde_json::Value::Object(map) => Some(map.into_iter().collect()),
171 _ => None,
172 })
173 .unwrap_or_default();
174
175 SemanticsSurface {
176 thetype,
177 parent: s.parent().map(|p| p as usize),
178 children,
179 other,
180 }
181 });
182 surfaces.collect()
183}
184
185pub(crate) fn to_cj_surface_type(
200 surface_type: FbSurfaceType,
201 extension_type: Option<&str>,
202) -> SemanticSurfaceType {
203 match surface_type {
204 FbSurfaceType::RoofSurface => SemanticSurfaceType::RoofSurface,
205 FbSurfaceType::GroundSurface => SemanticSurfaceType::GroundSurface,
206 FbSurfaceType::WallSurface => SemanticSurfaceType::WallSurface,
207 FbSurfaceType::ClosureSurface => SemanticSurfaceType::ClosureSurface,
208 FbSurfaceType::OuterCeilingSurface => SemanticSurfaceType::OuterCeilingSurface,
209 FbSurfaceType::OuterFloorSurface => SemanticSurfaceType::OuterFloorSurface,
210 FbSurfaceType::Window => SemanticSurfaceType::Window,
211 FbSurfaceType::Door => SemanticSurfaceType::Door,
212 FbSurfaceType::InteriorWallSurface => SemanticSurfaceType::InteriorWallSurface,
213 FbSurfaceType::CeilingSurface => SemanticSurfaceType::CeilingSurface,
214 FbSurfaceType::FloorSurface => SemanticSurfaceType::FloorSurface,
215 FbSurfaceType::WaterSurface => SemanticSurfaceType::WaterSurface,
216 FbSurfaceType::WaterGroundSurface => SemanticSurfaceType::WaterGroundSurface,
217 FbSurfaceType::WaterClosureSurface => SemanticSurfaceType::WaterClosureSurface,
218 FbSurfaceType::TrafficArea => SemanticSurfaceType::TrafficArea,
219 FbSurfaceType::AuxiliaryTrafficArea => SemanticSurfaceType::AuxiliaryTrafficArea,
220 FbSurfaceType::TransportationMarking => SemanticSurfaceType::TransportationMarking,
221 FbSurfaceType::TransportationHole => SemanticSurfaceType::TransportationHole,
222 _ => {
224 SemanticSurfaceType::Extension(extension_type.unwrap_or("+GenericSurface").to_string())
225 }
226 }
227}
228
229pub(crate) fn decode_semantics(
238 solids: &[u32],
239 shells: &[u32],
240 geometry_type: GeometryType,
241 semantics_objects: Vec<SemanticObject>,
242 semantics_values: Option<Vec<u32>>,
243 semantic_attr_schema: Option<flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<Column<'_>>>>,
244) -> Semantics {
245 let surfaces = decode_semantics_surfaces(&semantics_objects, semantic_attr_schema);
246
247 let Some(semantics_values) = semantics_values else {
250 return Semantics {
251 surfaces,
252 values: None,
253 other: HashMap::new(),
254 };
255 };
256
257 let mut cursor = 0usize;
258 let mut take_shell = |n: usize, values: &[u32]| -> Vec<Option<usize>> {
259 let end = (cursor + n).min(values.len());
260 let out = values[cursor..end].iter().map(|&v| index(v)).collect();
261 cursor = end;
262 out
263 };
264
265 let values = match geometry_type {
266 GeometryType::MultiPoint
268 | GeometryType::MultiLineString
269 | GeometryType::MultiSurface
270 | GeometryType::CompositeSurface => {
271 SemanticsValues::Surfaces(semantics_values.iter().map(|&v| index(v)).collect())
272 }
273 GeometryType::Solid => SemanticsValues::Shells(
275 shells
276 .iter()
277 .map(|&n| Some(take_shell(n as usize, &semantics_values)))
278 .collect(),
279 ),
280 GeometryType::MultiSolid | GeometryType::CompositeSolid => {
282 let mut shell_cursor = 0usize;
283 SemanticsValues::Solids(
284 solids
285 .iter()
286 .map(|&shell_count| {
287 Some(
288 (0..shell_count)
289 .map(|_| {
290 let n = shells.get(shell_cursor).copied().unwrap_or(0) as usize;
291 shell_cursor += 1;
292 Some(take_shell(n, &semantics_values))
293 })
294 .collect(),
295 )
296 })
297 .collect(),
298 )
299 }
300 _ => SemanticsValues::Surfaces(semantics_values.iter().map(|&v| index(v)).collect()),
302 };
303
304 Semantics {
305 surfaces,
306 values: Some(values),
307 other: HashMap::new(),
308 }
309}
310
311impl GeometryType {
329 pub fn to_str(self) -> Result<&'static str, Error> {
330 Ok(match self {
331 Self::MultiPoint => "MultiPoint",
332 Self::MultiLineString => "MultiLineString",
333 Self::MultiSurface => "MultiSurface",
334 Self::CompositeSurface => "CompositeSurface",
335 Self::Solid => "Solid",
336 Self::MultiSolid => "MultiSolid",
337 Self::CompositeSolid => "CompositeSolid",
338 Self::GeometryInstance => "GeometryInstance",
339 other => return Err(Error::UnknownEnumTag("GeometryType", format!("{other:?}"))),
340 })
341 }
342
343 pub fn to_cj(self) -> Result<CjGeometryType, Error> {
344 Ok(match self {
345 Self::MultiPoint => CjGeometryType::MultiPoint,
346 Self::MultiLineString => CjGeometryType::MultiLineString,
347 Self::MultiSurface => CjGeometryType::MultiSurface,
348 Self::CompositeSurface => CjGeometryType::CompositeSurface,
349 Self::Solid => CjGeometryType::Solid,
350 Self::MultiSolid => CjGeometryType::MultiSolid,
351 Self::CompositeSolid => CjGeometryType::CompositeSolid,
352 Self::GeometryInstance => CjGeometryType::GeometryInstance,
353 other => return Err(Error::UnknownEnumTag("GeometryType", format!("{other:?}"))),
354 })
355 }
356}
357
358pub(crate) fn decode_materials(
368 geometry_type: GeometryType,
369 material_mappings: &[MaterialMapping],
370) -> Option<HashMap<String, CjMaterialReference>> {
371 if material_mappings.is_empty() {
372 return None;
373 }
374
375 let mut materials = HashMap::new();
376
377 for mapping in material_mappings {
378 let theme = mapping.theme().unwrap_or("theme").to_string();
379
380 if let Some(value) = mapping.value() {
382 materials.insert(
383 theme,
384 CjMaterialReference {
385 value: Some(value as usize),
386 values: None,
387 other: HashMap::new(),
388 },
389 );
390 continue;
391 }
392
393 let solids = mapping
394 .solids()
395 .map(|s| s.iter().collect::<Vec<_>>())
396 .unwrap_or_default();
397 let shells = mapping
398 .shells()
399 .map(|s| s.iter().collect::<Vec<_>>())
400 .unwrap_or_default();
401 let Some(vertices) = mapping.vertices().map(|v| v.iter().collect::<Vec<_>>()) else {
404 materials.insert(
405 theme,
406 CjMaterialReference {
407 value: None,
408 values: Some(None),
409 other: HashMap::new(),
410 },
411 );
412 continue;
413 };
414
415 let mut vertex_cursor = 0usize;
416 let mut take_shell = |n: usize| -> Vec<Option<usize>> {
417 let end = (vertex_cursor + n).min(vertices.len());
418 let out = vertices[vertex_cursor..end]
419 .iter()
420 .map(|&v| index(v))
421 .collect();
422 vertex_cursor = end;
423 out
424 };
425
426 let values = match geometry_type {
427 GeometryType::MultiSurface | GeometryType::CompositeSurface => {
429 CjMaterialValues::Surfaces(vertices.iter().map(|&v| index(v)).collect())
430 }
431 GeometryType::Solid => CjMaterialValues::Shells(
433 shells
434 .iter()
435 .map(|&n| {
436 if n == NULL {
437 None
438 } else {
439 Some(take_shell(n as usize))
440 }
441 })
442 .collect(),
443 ),
444 GeometryType::MultiSolid | GeometryType::CompositeSolid => {
446 let mut shell_cursor = 0usize;
447 CjMaterialValues::Solids(
448 solids
449 .iter()
450 .map(|&shell_count| {
451 if shell_count == NULL {
452 return None;
453 }
454 Some(
455 (0..shell_count)
456 .map(|_| {
457 let n = shells.get(shell_cursor).copied().unwrap_or(0);
458 shell_cursor += 1;
459 if n == NULL {
460 None
461 } else {
462 Some(take_shell(n as usize))
463 }
464 })
465 .collect(),
466 )
467 })
468 .collect(),
469 )
470 }
471 _ => CjMaterialValues::Surfaces(vertices.iter().map(|&v| index(v)).collect()),
475 };
476
477 materials.insert(
478 theme,
479 CjMaterialReference {
480 value: None,
481 values: Some(Some(values)),
482 other: HashMap::new(),
483 },
484 );
485 }
486
487 Some(materials)
488}
489
490pub(crate) fn decode_textures(
497 geometry_type: GeometryType,
498 texture_mappings: &[TextureMapping],
499) -> Option<HashMap<String, CjTextureReference>> {
500 if texture_mappings.is_empty() {
501 return None;
502 }
503
504 let mut textures = HashMap::new();
505
506 for mapping in texture_mappings {
507 let theme = mapping.theme().unwrap_or("theme").to_string();
508
509 let Some(vertices) = mapping.vertices().map(|v| v.iter().collect::<Vec<_>>()) else {
512 textures.insert(
513 theme,
514 CjTextureReference {
515 values: None,
516 other: HashMap::new(),
517 },
518 );
519 continue;
520 };
521 let solids = mapping
522 .solids()
523 .map(|s| s.iter().collect::<Vec<_>>())
524 .unwrap_or_default();
525 let shells = mapping
526 .shells()
527 .map(|s| s.iter().collect::<Vec<_>>())
528 .unwrap_or_default();
529 let surfaces = mapping
530 .surfaces()
531 .map(|s| s.iter().collect::<Vec<_>>())
532 .unwrap_or_default();
533 let strings = mapping
534 .strings()
535 .map(|s| s.iter().collect::<Vec<_>>())
536 .unwrap_or_default();
537
538 let mut cursor = TextureCursor {
539 surfaces: &surfaces,
540 shells: &shells,
541 strings: &strings,
542 vertices: &vertices,
543 shell_cursor: 0,
544 surface_cursor: 0,
545 string_cursor: 0,
546 vertex_cursor: 0,
547 };
548
549 let values = match geometry_type {
550 GeometryType::MultiSurface | GeometryType::CompositeSurface => {
552 CjTextureValues::Surface(
553 (0..surfaces.len()).map(|_| cursor.take_surface()).collect(),
554 )
555 }
556 GeometryType::Solid => {
558 CjTextureValues::Shell((0..shells.len()).map(|_| cursor.take_shell()).collect())
559 }
560 GeometryType::MultiSolid | GeometryType::CompositeSolid => CjTextureValues::Solid(
562 solids
563 .iter()
564 .map(|&n| (0..n).map(|_| cursor.take_shell()).collect())
565 .collect(),
566 ),
567 _ => CjTextureValues::Surface(
570 (0..surfaces.len().max(1))
571 .map(|_| cursor.take_surface())
572 .collect(),
573 ),
574 };
575
576 textures.insert(
577 theme,
578 CjTextureReference {
579 values: Some(values),
580 other: HashMap::new(),
581 },
582 );
583 }
584
585 Some(textures)
586}
587
588struct TextureCursor<'a> {
592 surfaces: &'a [u32],
593 shells: &'a [u32],
594 strings: &'a [u32],
595 vertices: &'a [u32],
596 shell_cursor: usize,
597 surface_cursor: usize,
598 string_cursor: usize,
599 vertex_cursor: usize,
600}
601
602impl TextureCursor<'_> {
603 fn take_ring(&mut self) -> TexturedRing {
604 let size = self.strings.get(self.string_cursor).copied().unwrap_or(0) as usize;
605 self.string_cursor += 1;
606 let end = (self.vertex_cursor + size).min(self.vertices.len());
607 let ring = self.vertices[self.vertex_cursor..end]
608 .iter()
609 .map(|&v| index(v))
610 .collect();
611 self.vertex_cursor = end;
612 ring
613 }
614
615 fn take_surface(&mut self) -> TexturedSurface {
616 let rings = self.surfaces.get(self.surface_cursor).copied().unwrap_or(0);
617 self.surface_cursor += 1;
618 (0..rings).map(|_| self.take_ring()).collect()
619 }
620
621 fn take_shell(&mut self) -> TexturedShell {
622 let surfaces = self.shells.get(self.shell_cursor).copied().unwrap_or(0);
623 self.shell_cursor += 1;
624 (0..surfaces).map(|_| self.take_surface()).collect()
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631 use crate::fb::geometry_generated::{
632 MaterialMappingArgs, TextureMapping as FbTextureMapping, TextureMappingArgs,
633 };
634 use anyhow::Result;
635 use flatbuffers::FlatBufferBuilder;
636 use pretty_assertions::assert_eq;
637 use serde_json::json;
638
639 fn decode_material_values(
643 geometry_type: GeometryType,
644 solids: &[u32],
645 shells: &[u32],
646 vertices: &[u32],
647 ) -> serde_json::Value {
648 let mut fbb = FlatBufferBuilder::new();
649 let theme = fbb.create_string("t");
650 let solids_v = (!solids.is_empty()).then(|| fbb.create_vector(solids));
651 let shells_v = (!shells.is_empty()).then(|| fbb.create_vector(shells));
652 let vertices_v = fbb.create_vector(vertices);
653 let mapping = MaterialMapping::create(
654 &mut fbb,
655 &MaterialMappingArgs {
656 theme: Some(theme),
657 solids: solids_v,
658 shells: shells_v,
659 vertices: Some(vertices_v),
660 value: None,
661 },
662 );
663 fbb.finish(mapping, None);
664 let buf = fbb.finished_data().to_vec();
665 let mapping = flatbuffers::root::<MaterialMapping>(&buf).expect("valid mapping");
666 let decoded = decode_materials(geometry_type, &[mapping]).expect("one theme");
667 serde_json::to_value(&decoded["t"].values).expect("values serialize")
668 }
669
670 fn decode_texture_values(
671 geometry_type: GeometryType,
672 solids: &[u32],
673 shells: &[u32],
674 surfaces: &[u32],
675 strings: &[u32],
676 vertices: &[u32],
677 ) -> serde_json::Value {
678 let mut fbb = FlatBufferBuilder::new();
679 let theme = fbb.create_string("t");
680 let solids_v = fbb.create_vector(solids);
681 let shells_v = fbb.create_vector(shells);
682 let surfaces_v = fbb.create_vector(surfaces);
683 let strings_v = fbb.create_vector(strings);
684 let vertices_v = fbb.create_vector(vertices);
685 let mapping = FbTextureMapping::create(
686 &mut fbb,
687 &TextureMappingArgs {
688 theme: Some(theme),
689 solids: Some(solids_v),
690 shells: Some(shells_v),
691 surfaces: Some(surfaces_v),
692 strings: Some(strings_v),
693 vertices: Some(vertices_v),
694 },
695 );
696 fbb.finish(mapping, None);
697 let buf = fbb.finished_data().to_vec();
698 let mapping = flatbuffers::root::<FbTextureMapping>(&buf).expect("valid mapping");
699 let decoded = decode_textures(geometry_type, &[mapping]).expect("one theme");
700 serde_json::to_value(&decoded["t"].values).expect("values serialize")
701 }
702
703 #[test]
704 fn test_decode_boundaries() -> Result<()> {
705 assert_eq!(decode_points(&[2, 44, 0, 7]), vec![2, 44, 0, 7]);
707
708 assert_eq!(
710 serde_json::to_value(decode_rings(&[3, 3], &[2, 3, 5, 77, 55, 212]))?,
711 json!([[2, 3, 5], [77, 55, 212]])
712 );
713
714 assert_eq!(
716 serde_json::to_value(decode_surfaces(
717 &[1, 1, 1],
718 &[4, 4, 4],
719 &[0, 3, 2, 1, 4, 5, 6, 7, 0, 1, 5, 4]
720 ))?,
721 json!([[[0, 3, 2, 1]], [[4, 5, 6, 7]], [[0, 1, 5, 4]]])
722 );
723
724 let indices = [
726 0, 3, 2, 1, 22, 1, 2, 3, 4, 4, 5, 6, 7, 0, 1, 5, 4, 1, 2, 6, 5, 240, 243, 124, 244,
727 246, 724, 34, 414, 45, 111, 246, 5,
728 ];
729 assert_eq!(
730 serde_json::to_value(decode_shells(
731 &[4, 4],
732 &[2, 1, 1, 1, 1, 1, 1, 1],
733 &[5, 4, 4, 4, 4, 3, 3, 3, 3],
734 &indices
735 ))?,
736 json!([
737 [
738 [[0, 3, 2, 1, 22], [1, 2, 3, 4]],
739 [[4, 5, 6, 7]],
740 [[0, 1, 5, 4]],
741 [[1, 2, 6, 5]]
742 ],
743 [
744 [[240, 243, 124]],
745 [[244, 246, 724]],
746 [[34, 414, 45]],
747 [[111, 246, 5]]
748 ]
749 ])
750 );
751
752 let indices = [
754 0, 3, 2, 1, 22, 4, 5, 6, 7, 0, 1, 5, 4, 1, 2, 6, 5, 240, 243, 124, 244, 246, 724, 34,
755 414, 45, 111, 246, 5, 666, 667, 668, 74, 75, 76, 880, 881, 885, 111, 122, 226,
756 ];
757 assert_eq!(
758 serde_json::to_value(decode_solids(
759 &[2, 1],
760 &[4, 4, 4],
761 &[1; 12],
762 &[5, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3],
763 &indices
764 ))?,
765 json!([
766 [
767 [
768 [[0, 3, 2, 1, 22]],
769 [[4, 5, 6, 7]],
770 [[0, 1, 5, 4]],
771 [[1, 2, 6, 5]]
772 ],
773 [
774 [[240, 243, 124]],
775 [[244, 246, 724]],
776 [[34, 414, 45]],
777 [[111, 246, 5]]
778 ]
779 ],
780 [[
781 [[666, 667, 668]],
782 [[74, 75, 76]],
783 [[880, 881, 885]],
784 [[111, 122, 226]]
785 ]]
786 ])
787 );
788
789 Ok(())
790 }
791
792 #[test]
796 fn identical_material_arrays_decode_to_different_depths_per_type() {
797 let (solids, shells, vertices) = (&[1u32][..], &[2u32][..], &[0u32, 1][..]);
798
799 assert_eq!(
800 decode_material_values(GeometryType::Solid, solids, shells, vertices),
801 json!([[0, 1]]),
802 "a Solid's material values are one array per shell"
803 );
804 assert_eq!(
805 decode_material_values(GeometryType::MultiSolid, solids, shells, vertices),
806 json!([[[0, 1]]]),
807 "a MultiSolid's are one array per shell, per solid"
808 );
809 assert_eq!(
810 decode_material_values(GeometryType::CompositeSolid, solids, shells, vertices),
811 json!([[[0, 1]]]),
812 "a CompositeSolid decodes exactly as a MultiSolid does"
813 );
814 }
815
816 #[test]
817 fn test_decode_materials() -> Result<()> {
818 let mut fbb = FlatBufferBuilder::new();
820 let theme = fbb.create_string("theme1");
821 let mapping = MaterialMapping::create(
822 &mut fbb,
823 &MaterialMappingArgs {
824 theme: Some(theme),
825 value: Some(5),
826 ..Default::default()
827 },
828 );
829 fbb.finish(mapping, None);
830 let buf = fbb.finished_data().to_vec();
831 let mapping = flatbuffers::root::<MaterialMapping>(&buf)?;
832 let materials = decode_materials(GeometryType::Solid, &[mapping]).expect("one theme");
833 assert_eq!(materials["theme1"].value, Some(5));
834 assert!(materials["theme1"].values.is_none());
835
836 assert_eq!(
838 decode_material_values(GeometryType::MultiSurface, &[], &[], &[0, 1, NULL, 2]),
839 json!([0, 1, null, 2])
840 );
841 assert_eq!(
842 decode_material_values(GeometryType::CompositeSurface, &[], &[], &[0, 1, NULL, 2]),
843 json!([0, 1, null, 2])
844 );
845
846 assert_eq!(
848 decode_material_values(GeometryType::Solid, &[2], &[3, 3], &[0, 1, NULL, 2, 3, 4]),
849 json!([[0, 1, null], [2, 3, 4]])
850 );
851
852 assert_eq!(
854 decode_material_values(
855 GeometryType::CompositeSolid,
856 &[2, 1],
857 &[3, 3, 3],
858 &[0, 1, NULL, 2, NULL, NULL, 3, 4, NULL]
859 ),
860 json!([[[0, 1, null], [2, null, null]], [[3, 4, null]]])
861 );
862
863 Ok(())
864 }
865
866 #[test]
870 fn a_null_material_shell_or_solid_decodes_as_null() {
871 assert_eq!(
872 decode_material_values(GeometryType::Solid, &[2], &[2, NULL], &[0, 1]),
873 json!([[0, 1], null])
874 );
875 assert_eq!(
876 decode_material_values(GeometryType::CompositeSolid, &[1, NULL], &[2], &[0, 1]),
877 json!([[[0, 1]], null])
878 );
879 }
880
881 #[test]
882 fn test_decode_textures() -> Result<()> {
883 assert_eq!(
885 decode_texture_values(
886 GeometryType::MultiSurface,
887 &[],
888 &[3],
889 &[1, 1, 1],
890 &[4, 4, 4],
891 &[0, 10, 20, 30, 1, 11, 21, NULL, 2, 12, NULL, 32]
892 ),
893 json!([[[0, 10, 20, 30]], [[1, 11, 21, null]], [[2, 12, null, 32]]])
894 );
895
896 assert_eq!(
898 decode_texture_values(
899 GeometryType::Solid,
900 &[2],
901 &[3, 2],
902 &[1, 1, 1, 1, 1],
903 &[4, 4, 4, 4, 4],
904 &[0, 10, 20, 30, 1, 11, 21, NULL, 2, 12, NULL, 32, 3, 13, 23, 33, 4, 14, 24, NULL]
905 ),
906 json!([
907 [[[0, 10, 20, 30]], [[1, 11, 21, null]], [[2, 12, null, 32]]],
908 [[[3, 13, 23, 33]], [[4, 14, 24, null]]]
909 ])
910 );
911
912 assert_eq!(
914 decode_texture_values(
915 GeometryType::CompositeSolid,
916 &[2, 1],
917 &[2, 2, 2],
918 &[1; 6],
919 &[3; 6],
920 &[0, 10, 20, 1, 11, NULL, 2, 12, 22, 3, NULL, 23, 4, 14, 24, 5, 15, 25]
921 ),
922 json!([
923 [
924 [[[0, 10, 20]], [[1, 11, null]]],
925 [[[2, 12, 22]], [[3, null, 23]]]
926 ],
927 [[[[4, 14, 24]], [[5, 15, 25]]]]
928 ])
929 );
930
931 Ok(())
932 }
933
934 #[test]
937 fn identical_texture_arrays_decode_to_different_depths_per_type() {
938 let args = (
939 &[1u32][..],
940 &[1u32][..],
941 &[1u32][..],
942 &[3u32][..],
943 &[0u32, 10, 20][..],
944 );
945 assert_eq!(
946 decode_texture_values(GeometryType::Solid, args.0, args.1, args.2, args.3, args.4),
947 json!([[[[0, 10, 20]]]])
948 );
949 assert_eq!(
950 decode_texture_values(
951 GeometryType::MultiSolid,
952 args.0,
953 args.1,
954 args.2,
955 args.3,
956 args.4
957 ),
958 json!([[[[[0, 10, 20]]]]])
959 );
960 }
961
962 #[test]
974 fn an_unknown_geometry_tag_is_an_error_and_never_a_solid() {
975 for (tag, name) in [
977 (GeometryType::MultiPoint, "MultiPoint"),
978 (GeometryType::MultiLineString, "MultiLineString"),
979 (GeometryType::MultiSurface, "MultiSurface"),
980 (GeometryType::CompositeSurface, "CompositeSurface"),
981 (GeometryType::Solid, "Solid"),
982 (GeometryType::MultiSolid, "MultiSolid"),
983 (GeometryType::CompositeSolid, "CompositeSolid"),
984 (GeometryType::GeometryInstance, "GeometryInstance"),
985 ] {
986 assert_eq!(tag.to_str().unwrap(), name);
987 assert!(tag.to_cj().is_ok());
988 }
989
990 let unknown = GeometryType(GeometryType::ENUM_MAX + 1);
992 assert!(
993 matches!(
994 unknown.to_str(),
995 Err(Error::UnknownEnumTag("GeometryType", _))
996 ),
997 "an unknown geometry tag must be reported, not spelled `Solid`"
998 );
999 assert!(matches!(
1000 unknown.to_cj(),
1001 Err(Error::UnknownEnumTag("GeometryType", _))
1002 ));
1003 }
1004
1005 #[test]
1012 fn an_unnameable_semantic_surface_tag_becomes_a_plus_prefixed_extension() {
1013 assert_eq!(
1015 to_cj_surface_type(FbSurfaceType::ExtraSemanticSurface, Some("+ThermalSurface")),
1016 SemanticSurfaceType::Extension("+ThermalSurface".to_string())
1017 );
1018
1019 for tag in [
1020 FbSurfaceType::ExtraSemanticSurface,
1021 FbSurfaceType(FbSurfaceType::ENUM_MAX + 1),
1022 ] {
1023 let SemanticSurfaceType::Extension(name) = to_cj_surface_type(tag, None) else {
1024 panic!("an unnameable surface tag must become an Extension");
1025 };
1026 assert_eq!(name, "+GenericSurface");
1027 assert!(
1028 name.starts_with('+'),
1029 "{name} must be a valid Extension name"
1030 );
1031 assert_ne!(name, "ExtraSemanticSurface");
1032 }
1033 }
1034}