1use cjseq::{
10 Geometry as CjGeometry, MaterialReference as CjMaterialReference,
11 MaterialValues as CjMaterialValues, Ring, Semantics as CjSemantics,
12 SemanticsSurface as CjSemanticsSurface, SemanticsValues as CjSemanticsValues, Shell, Surface,
13 TextureReference as CjTextureReference, TextureValues as CjTextureValues, TexturedShell,
14 TexturedSurface,
15};
16use std::collections::HashMap;
17
18const NULL: u32 = u32::MAX;
22
23#[derive(Debug, Clone, Default)]
24pub(crate) struct GMBoundaries {
25 pub(crate) solids: Vec<u32>, pub(crate) shells: Vec<u32>, pub(crate) surfaces: Vec<u32>, pub(crate) strings: Vec<u32>, pub(crate) indices: Vec<u32>, }
31
32#[derive(Debug, Clone, Default)]
33pub struct MaterialValues {
34 pub(crate) theme: String,
35 pub(crate) solids: Vec<u32>,
38 pub(crate) shells: Vec<u32>,
41 pub(crate) vertices: Vec<u32>,
43}
44
45#[derive(Debug, Clone, Default)]
46pub struct MaterialValue {
47 pub(crate) theme: String,
48 pub(crate) value: u32,
49}
50
51#[derive(Debug, Clone)]
52pub enum MaterialMapping {
53 Value(MaterialValue),
54 Values(MaterialValues),
55 NullValues(String),
59}
60
61#[derive(Debug, Clone, Default)]
62pub(crate) struct TextureMapping {
63 pub(crate) theme: String,
64 pub(crate) has_values: bool,
68 pub(crate) solids: Vec<u32>, pub(crate) shells: Vec<u32>, pub(crate) surfaces: Vec<u32>, pub(crate) strings: Vec<u32>, pub(crate) vertices: Vec<u32>, }
74
75#[derive(Debug, Clone, Default)]
76pub(crate) struct GMSemantics {
77 pub(crate) surfaces: Vec<CjSemanticsSurface>, pub(crate) values: Option<Vec<u32>>,
86}
87
88#[derive(Debug, Clone, Default)]
89#[doc(hidden)]
90pub(crate) struct EncodedGeometry {
91 pub(crate) boundaries: GMBoundaries,
92 pub(crate) semantics: Option<GMSemantics>,
93 pub(crate) textures: Option<Vec<TextureMapping>>,
94 pub(crate) materials: Option<Vec<MaterialMapping>>,
95}
96
97pub(crate) fn encode(geometry: &CjGeometry) -> EncodedGeometry {
103 let boundaries = encode_boundaries(geometry);
104
105 let common = geometry.common();
106 let semantics = common
107 .and_then(|c| c.semantics.as_ref())
108 .map(|s| encode_semantics(s, &boundaries));
109 let textures = common.and_then(|c| c.texture.as_ref()).map(encode_texture);
110 let materials = common
111 .and_then(|c| c.material.as_ref())
112 .map(encode_material);
113
114 EncodedGeometry {
115 boundaries,
116 semantics,
117 materials,
118 textures,
119 }
120}
121
122fn push_ring(ring: &Ring, b: &mut GMBoundaries) {
127 b.strings.push(ring.len() as u32);
128 b.indices.extend(ring.iter().map(|&i| i as u32));
129}
130
131fn push_surface(surface: &Surface, b: &mut GMBoundaries) {
132 for ring in surface {
133 push_ring(ring, b);
134 }
135 b.surfaces.push(surface.len() as u32);
136}
137
138fn push_shell(shell: &Shell, b: &mut GMBoundaries) {
139 for surface in shell {
140 push_surface(surface, b);
141 }
142 b.shells.push(shell.len() as u32);
143}
144
145fn push_solid(solid: &[Shell], b: &mut GMBoundaries) {
146 for shell in solid {
147 push_shell(shell, b);
148 }
149 b.solids.push(solid.len() as u32);
150}
151
152pub(crate) fn encode_boundaries(geometry: &CjGeometry) -> GMBoundaries {
161 let mut b = GMBoundaries::default();
162 match geometry {
163 CjGeometry::MultiPoint { boundaries, .. } => push_ring(boundaries, &mut b),
164 CjGeometry::MultiLineString { boundaries, .. } => {
165 for ring in boundaries {
166 push_ring(ring, &mut b);
167 }
168 b.surfaces.push(boundaries.len() as u32);
169 }
170 CjGeometry::MultiSurface { boundaries, .. }
171 | CjGeometry::CompositeSurface { boundaries, .. } => {
172 for surface in boundaries {
173 push_surface(surface, &mut b);
174 }
175 b.shells.push(boundaries.len() as u32);
176 }
177 CjGeometry::Solid { boundaries, .. } => push_solid(boundaries, &mut b),
178 CjGeometry::MultiSolid { boundaries, .. }
179 | CjGeometry::CompositeSolid { boundaries, .. } => {
180 for solid in boundaries {
181 push_solid(solid, &mut b);
182 }
183 }
184 CjGeometry::GeometryInstance { .. } => {}
187 }
188 b
189}
190
191fn material_index(i: Option<usize>) -> u32 {
196 i.map_or(NULL, |v| v as u32)
197}
198
199pub(crate) fn encode_material(
205 materials: &HashMap<String, CjMaterialReference>,
206) -> Vec<MaterialMapping> {
207 let mut material_mappings = Vec::new();
208 let mut themes: Vec<&String> = materials.keys().collect();
211 themes.sort_unstable();
212 for (theme, material) in themes
213 .into_iter()
214 .filter_map(|t| materials.get(t).map(|m| (t, m)))
215 {
216 if let Some(value) = material.value {
217 material_mappings.push(MaterialMapping::Value(MaterialValue {
218 theme: theme.clone(),
219 value: value as u32,
220 }));
221 continue;
222 }
223
224 let values = match material.values.as_ref() {
226 Some(Some(values)) => values,
227 Some(None) => {
228 material_mappings.push(MaterialMapping::NullValues(theme.clone()));
229 continue;
230 }
231 None => continue,
234 };
235
236 let mut mv = MaterialValues {
237 theme: theme.clone(),
238 ..Default::default()
239 };
240
241 match values {
242 CjMaterialValues::Surfaces(surfaces) => {
244 mv.vertices
245 .extend(surfaces.iter().copied().map(material_index));
246 }
247 CjMaterialValues::Shells(shells) => {
249 mv.solids.push(shells.len() as u32);
250 for shell in shells {
251 push_material_shell(shell.as_deref(), &mut mv);
252 }
253 }
254 CjMaterialValues::Solids(solids) => {
256 for solid in solids {
257 match solid {
258 Some(shells) => {
259 mv.solids.push(shells.len() as u32);
260 for shell in shells {
261 push_material_shell(shell.as_deref(), &mut mv);
262 }
263 }
264 None => mv.solids.push(NULL),
265 }
266 }
267 }
268 }
269
270 material_mappings.push(MaterialMapping::Values(mv));
271 }
272 material_mappings
273}
274
275fn push_material_shell(shell: Option<&[Option<usize>]>, mv: &mut MaterialValues) {
276 match shell {
277 Some(indices) => {
278 mv.shells.push(indices.len() as u32);
279 mv.vertices
280 .extend(indices.iter().copied().map(material_index));
281 }
282 None => mv.shells.push(NULL),
283 }
284}
285
286pub(crate) fn encode_texture(
291 texture_map: &HashMap<String, CjTextureReference>,
292) -> Vec<TextureMapping> {
293 let mut texture_mappings = Vec::new();
294
295 let mut themes: Vec<&String> = texture_map.keys().collect();
297 themes.sort_unstable();
298 for (theme, texture) in themes
299 .into_iter()
300 .filter_map(|t| texture_map.get(t).map(|x| (t, x)))
301 {
302 let mut mapping = TextureMapping {
303 theme: theme.clone(),
304 ..Default::default()
305 };
306
307 if let Some(values) = texture.values.as_ref() {
310 mapping.has_values = true;
311 encode_texture_values(values, &mut mapping);
312 }
313
314 texture_mappings.push(mapping);
315 }
316
317 texture_mappings
318}
319
320fn push_textured_surface(surface: &TexturedSurface, m: &mut TextureMapping) {
321 for ring in surface {
322 m.strings.push(ring.len() as u32);
323 m.vertices.extend(ring.iter().copied().map(material_index));
324 }
325 m.surfaces.push(surface.len() as u32);
326}
327
328fn push_textured_shell(shell: &TexturedShell, m: &mut TextureMapping) {
329 for surface in shell {
330 push_textured_surface(surface, m);
331 }
332 m.shells.push(shell.len() as u32);
333}
334
335fn encode_texture_values(values: &CjTextureValues, m: &mut TextureMapping) {
339 match values {
340 CjTextureValues::Surface(surfaces) => {
341 for surface in surfaces {
342 push_textured_surface(surface, m);
343 }
344 m.shells.push(surfaces.len() as u32);
345 }
346 CjTextureValues::Shell(shells) => {
347 for shell in shells {
348 push_textured_shell(shell, m);
349 }
350 m.solids.push(shells.len() as u32);
351 }
352 CjTextureValues::Solid(solids) => {
353 for solid in solids {
354 for shell in solid {
355 push_textured_shell(shell, m);
356 }
357 m.solids.push(solid.len() as u32);
358 }
359 }
360 }
361}
362
363fn semantics_index(i: Option<usize>) -> u32 {
368 i.map_or(NULL, |v| v as u32)
369}
370
371fn encode_semantics_values(
380 values: &CjSemanticsValues,
381 boundaries: &GMBoundaries,
382 flattened: &mut Vec<u32>,
383) {
384 match values {
385 CjSemanticsValues::Surfaces(surfaces) => {
386 flattened.extend(surfaces.iter().copied().map(semantics_index));
387 }
388 CjSemanticsValues::Shells(shells) => {
389 let mut shell_cursor = 0;
390 for shell in shells {
391 push_semantics_shell(shell.as_deref(), boundaries, &mut shell_cursor, flattened);
392 }
393 }
394 CjSemanticsValues::Solids(solids) => {
395 let mut shell_cursor = 0;
396 for (i, solid) in solids.iter().enumerate() {
397 let shell_count = boundaries.solids.get(i).copied().unwrap_or(0) as usize;
398 match solid {
399 Some(shells) => {
400 for shell in shells {
401 push_semantics_shell(
402 shell.as_deref(),
403 boundaries,
404 &mut shell_cursor,
405 flattened,
406 );
407 }
408 }
409 None => {
410 for _ in 0..shell_count {
411 push_semantics_shell(None, boundaries, &mut shell_cursor, flattened);
412 }
413 }
414 }
415 }
416 }
417 }
418}
419
420fn push_semantics_shell(
421 shell: Option<&[Option<usize>]>,
422 boundaries: &GMBoundaries,
423 shell_cursor: &mut usize,
424 flattened: &mut Vec<u32>,
425) {
426 let surface_count = boundaries.shells.get(*shell_cursor).copied().unwrap_or(0) as usize;
427 *shell_cursor += 1;
428 match shell {
429 Some(indices) => flattened.extend(indices.iter().copied().map(semantics_index)),
430 None => flattened.extend(std::iter::repeat_n(NULL, surface_count)),
431 }
432}
433
434pub(crate) fn encode_semantics(semantics: &CjSemantics, boundaries: &GMBoundaries) -> GMSemantics {
437 let values = semantics.values.as_ref().map(|v| {
438 let mut values = Vec::new();
439 encode_semantics_values(v, boundaries, &mut values);
440 values
441 });
442
443 GMSemantics {
444 surfaces: semantics.surfaces.to_vec(),
445 values,
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use anyhow::Result;
453 use cjseq::SemanticSurfaceType;
454 use pretty_assertions::assert_eq;
455 use serde_json::json;
456
457 fn theme_of(m: &MaterialMapping) -> &str {
458 match m {
459 MaterialMapping::Value(v) => &v.theme,
460 MaterialMapping::Values(v) => &v.theme,
461 MaterialMapping::NullValues(theme) => theme,
462 }
463 }
464
465 fn geom(v: serde_json::Value) -> CjGeometry {
466 serde_json::from_value(v).expect("test geometry must parse")
467 }
468
469 #[test]
470 fn test_encode_boundaries() -> Result<()> {
471 let encoded = encode(&geom(json!({
473 "type": "MultiPoint", "lod": "1", "boundaries": [2, 44, 0, 7]
474 })));
475 assert_eq!(vec![2, 44, 0, 7], encoded.boundaries.indices);
476 assert_eq!(vec![4], encoded.boundaries.strings);
477 assert!(encoded.boundaries.surfaces.is_empty());
478 assert!(encoded.boundaries.shells.is_empty());
479 assert!(encoded.boundaries.solids.is_empty());
480
481 let encoded = encode(&geom(json!({
483 "type": "MultiLineString", "lod": "1", "boundaries": [[2, 3, 5], [77, 55, 212]]
484 })));
485 assert_eq!(vec![2, 3, 5, 77, 55, 212], encoded.boundaries.indices);
486 assert_eq!(vec![3, 3], encoded.boundaries.strings);
487 assert_eq!(vec![2], encoded.boundaries.surfaces);
488 assert!(encoded.boundaries.shells.is_empty());
489 assert!(encoded.boundaries.solids.is_empty());
490
491 let encoded = encode(&geom(json!({
493 "type": "MultiSurface", "lod": "1",
494 "boundaries": [[[0, 3, 2, 1]], [[4, 5, 6, 7]], [[0, 1, 5, 4]]]
495 })));
496 assert_eq!(
497 vec![0, 3, 2, 1, 4, 5, 6, 7, 0, 1, 5, 4],
498 encoded.boundaries.indices
499 );
500 assert_eq!(vec![4, 4, 4], encoded.boundaries.strings);
501 assert_eq!(vec![1, 1, 1], encoded.boundaries.surfaces);
502 assert_eq!(vec![3], encoded.boundaries.shells);
503 assert!(encoded.boundaries.solids.is_empty());
504
505 let encoded = encode(&geom(json!({
507 "type": "Solid", "lod": "1",
508 "boundaries": [
509 [
510 [[0, 3, 2, 1, 22], [1, 2, 3, 4]],
511 [[4, 5, 6, 7]],
512 [[0, 1, 5, 4]],
513 [[1, 2, 6, 5]]
514 ],
515 [
516 [[240, 243, 124]],
517 [[244, 246, 724]],
518 [[34, 414, 45]],
519 [[111, 246, 5]]
520 ]
521 ]
522 })));
523 assert_eq!(
524 vec![
525 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,
526 246, 724, 34, 414, 45, 111, 246, 5
527 ],
528 encoded.boundaries.indices
529 );
530 assert_eq!(vec![5, 4, 4, 4, 4, 3, 3, 3, 3], encoded.boundaries.strings);
531 assert_eq!(vec![2, 1, 1, 1, 1, 1, 1, 1], encoded.boundaries.surfaces);
532 assert_eq!(vec![4, 4], encoded.boundaries.shells);
533 assert_eq!(vec![2], encoded.boundaries.solids);
534
535 let encoded = encode(&geom(json!({
537 "type": "CompositeSolid", "lod": "1",
538 "boundaries": [
539 [
540 [
541 [[0, 3, 2, 1, 22]],
542 [[4, 5, 6, 7]],
543 [[0, 1, 5, 4]],
544 [[1, 2, 6, 5]]
545 ],
546 [
547 [[240, 243, 124]],
548 [[244, 246, 724]],
549 [[34, 414, 45]],
550 [[111, 246, 5]]
551 ]
552 ],
553 [[
554 [[666, 667, 668]],
555 [[74, 75, 76]],
556 [[880, 881, 885]],
557 [[111, 122, 226]]
558 ]]
559 ]
560 })));
561 assert_eq!(
562 vec![
563 0, 3, 2, 1, 22, 4, 5, 6, 7, 0, 1, 5, 4, 1, 2, 6, 5, 240, 243, 124, 244, 246, 724,
564 34, 414, 45, 111, 246, 5, 666, 667, 668, 74, 75, 76, 880, 881, 885, 111, 122, 226
565 ],
566 encoded.boundaries.indices
567 );
568 assert_eq!(
569 encoded.boundaries.strings,
570 vec![5, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3]
571 );
572 assert_eq!(
573 encoded.boundaries.surfaces,
574 vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
575 );
576 assert_eq!(encoded.boundaries.shells, vec![4, 4, 4]);
577 assert_eq!(encoded.boundaries.solids, vec![2, 1]);
578
579 Ok(())
580 }
581
582 #[test]
587 fn types_of_equal_depth_flatten_identically() {
588 let surface_boundaries = json!([[[0, 1, 2]], [[3, 4, 5]]]);
589 let ms = encode(&geom(
590 json!({"type": "MultiSurface", "boundaries": surface_boundaries}),
591 ));
592 let cs = encode(&geom(
593 json!({"type": "CompositeSurface", "boundaries": surface_boundaries}),
594 ));
595 assert_eq!(ms.boundaries.shells, cs.boundaries.shells);
596 assert_eq!(ms.boundaries.surfaces, cs.boundaries.surfaces);
597 assert_eq!(ms.boundaries.indices, cs.boundaries.indices);
598
599 let solid_boundaries = json!([[[[[0, 1, 2]]]], [[[[3, 4, 5]]]]]);
600 let msol = encode(&geom(
601 json!({"type": "MultiSolid", "boundaries": solid_boundaries}),
602 ));
603 let csol = encode(&geom(
604 json!({"type": "CompositeSolid", "boundaries": solid_boundaries}),
605 ));
606 assert_eq!(msol.boundaries.solids, csol.boundaries.solids);
607 assert_eq!(msol.boundaries.shells, csol.boundaries.shells);
608 }
609
610 #[test]
611 fn test_encode_semantics() -> Result<()> {
612 let multi_surface = geom(json!({
614 "type": "MultiSurface",
615 "lod": "2",
616 "boundaries": [
617 [[0, 3, 2, 1]],
618 [[4, 5, 6, 7]],
619 [[0, 1, 5, 4]],
620 [[0, 2, 3, 8]],
621 [[10, 12, 23, 48]]
622 ],
623 "semantics": {
624 "surfaces": [
625 {"type": "WallSurface", "slope": 33.4, "children": [2]},
626 {"type": "RoofSurface", "slope": 66.6},
627 {"type": "OuterCeilingSurface", "parent": 0, "colour": "blue"}
628 ],
629 "values": [0, 0, null, 1, 2]
630 }
631 }));
632 let encoded = encode(&multi_surface);
633 let encoded_semantics = encoded.semantics.expect("semantics must be encoded");
634
635 let expected_semantics_surfaces = vec![
636 CjSemanticsSurface {
637 thetype: SemanticSurfaceType::WallSurface,
638 parent: None,
639 children: Some(vec![2]),
640 other: HashMap::from([("slope".to_string(), json!(33.4))]),
641 },
642 CjSemanticsSurface {
643 thetype: SemanticSurfaceType::RoofSurface,
644 parent: None,
645 children: None,
646 other: HashMap::from([("slope".to_string(), json!(66.6))]),
647 },
648 CjSemanticsSurface {
649 thetype: SemanticSurfaceType::OuterCeilingSurface,
650 parent: Some(0),
651 children: None,
652 other: HashMap::from([("colour".to_string(), json!("blue"))]),
653 },
654 ];
655
656 assert_eq!(expected_semantics_surfaces, encoded_semantics.surfaces);
657 assert_eq!(Some(vec![0, 0, NULL, 1, 2]), encoded_semantics.values);
658
659 let composite_solid = geom(json!({
661 "type": "CompositeSolid",
662 "lod": "2.2",
663 "boundaries": [
664 [[
665 [[0, 3, 2, 1, 22]],
666 [[4, 5, 6, 7]],
667 [[0, 1, 5, 4]],
668 [[1, 2, 6, 5]]
669 ]],
670 [[
671 [[666, 667, 668]],
672 [[74, 75, 76]],
673 [[880, 881, 885]]
674 ]]
675 ],
676 "semantics": {
677 "surfaces": [{"type": "RoofSurface"}, {"type": "WallSurface"}],
678 "values": [[[0, 1, 1, null]], [[null, null, null]]]
679 }
680 }));
681 let encoded = encode(&composite_solid);
682 let encoded_semantics = encoded.semantics.expect("semantics must be encoded");
683
684 let expected_semantics_surfaces = vec![
685 CjSemanticsSurface {
686 thetype: SemanticSurfaceType::RoofSurface,
687 parent: None,
688 children: None,
689 other: HashMap::new(),
690 },
691 CjSemanticsSurface {
692 thetype: SemanticSurfaceType::WallSurface,
693 parent: None,
694 children: None,
695 other: HashMap::new(),
696 },
697 ];
698
699 assert_eq!(expected_semantics_surfaces, encoded_semantics.surfaces);
700 assert_eq!(
701 Some(vec![0, 1, 1, NULL, NULL, NULL, NULL]),
702 encoded_semantics.values
703 );
704 Ok(())
705 }
706
707 #[test]
711 fn a_null_semantics_shell_expands_to_one_null_per_surface() {
712 let solid = geom(json!({
713 "type": "Solid",
714 "boundaries": [
715 [[[0, 1, 2]], [[3, 4, 5]]],
716 [[[6, 7, 8]]]
717 ],
718 "semantics": {
719 "surfaces": [{"type": "RoofSurface"}],
720 "values": [[0, 0], null]
721 }
722 }));
723 let encoded = encode(&solid);
724 let semantics = encoded.semantics.expect("semantics must be encoded");
725 assert_eq!(semantics.values, Some(vec![0, 0, NULL]));
727 }
728
729 #[test]
730 fn test_encode_material() -> Result<()> {
731 let materials = HashMap::from([(
733 "theme1".to_string(),
734 serde_json::from_value::<CjMaterialReference>(json!({"value": 5}))?,
735 )]);
736
737 let encoded = encode_material(&materials);
738 assert_eq!(encoded.len(), 1);
739 match &encoded[0] {
740 MaterialMapping::Value(value) => {
741 assert_eq!(value.theme, "theme1");
742 assert_eq!(value.value, 5);
743 }
744 _ => panic!("Expected MaterialMapping::Value"),
745 }
746
747 let materials = HashMap::from([(
749 "theme2".to_string(),
750 serde_json::from_value::<CjMaterialReference>(json!({"values": [0, 1, null, 2]}))?,
751 )]);
752
753 let encoded = encode_material(&materials);
754 assert_eq!(encoded.len(), 1);
755 match &encoded[0] {
756 MaterialMapping::Values(values) => {
757 assert_eq!(values.theme, "theme2");
758 assert_eq!(values.vertices, vec![0, 1, NULL, 2]);
759 assert!(values.shells.is_empty());
760 assert!(values.solids.is_empty());
761 }
762 _ => panic!("Expected MaterialMapping::Values"),
763 }
764
765 let materials = HashMap::from([(
767 "theme3".to_string(),
768 serde_json::from_value::<CjMaterialReference>(
769 json!({"values": [[0, 1, null], [2, 3, 4]]}),
770 )?,
771 )]);
772
773 let encoded = encode_material(&materials);
774 assert_eq!(encoded.len(), 1);
775 match &encoded[0] {
776 MaterialMapping::Values(values) => {
777 assert_eq!(values.theme, "theme3");
778 assert_eq!(values.solids, vec![2]); assert_eq!(values.shells, vec![3, 3]); assert_eq!(values.vertices, vec![0, 1, NULL, 2, 3, 4]);
781 }
782 _ => panic!("Expected MaterialMapping::Values"),
783 }
784
785 let materials = HashMap::from([
787 (
788 "theme4".to_string(),
789 serde_json::from_value::<CjMaterialReference>(json!({"value": 7}))?,
790 ),
791 (
792 "theme5".to_string(),
793 serde_json::from_value::<CjMaterialReference>(json!({"values": [8, 9]}))?,
794 ),
795 ]);
796
797 let encoded = encode_material(&materials);
798 assert_eq!(encoded.len(), 2);
799
800 let theme4_mapping = encoded
802 .iter()
803 .find(|m| theme_of(m) == "theme4")
804 .expect("Should have theme4 mapping");
805
806 let theme5_mapping = encoded
807 .iter()
808 .find(|m| theme_of(m) == "theme5")
809 .expect("Should have theme5 mapping");
810
811 match theme4_mapping {
812 MaterialMapping::Value(value) => {
813 assert_eq!(value.theme, "theme4");
814 assert_eq!(value.value, 7);
815 }
816 _ => panic!("Expected MaterialMapping::Value for theme4"),
817 }
818
819 match theme5_mapping {
820 MaterialMapping::Values(values) => {
821 assert_eq!(values.theme, "theme5");
822 assert_eq!(values.vertices, vec![8, 9]);
823 assert!(values.shells.is_empty());
824 assert!(values.solids.is_empty());
825 }
826 _ => panic!("Expected MaterialMapping::Values for theme5"),
827 }
828
829 let materials = HashMap::from([(
831 "theme6".to_string(),
832 serde_json::from_value::<CjMaterialReference>(json!({
833 "values": [[[0, 1, null], [2, null, null]], [[3, 4, null]]]
834 }))?,
835 )]);
836
837 let encoded = encode_material(&materials);
838 assert_eq!(encoded.len(), 1);
839 match &encoded[0] {
840 MaterialMapping::Values(values) => {
841 assert_eq!(values.theme, "theme6");
842 assert_eq!(values.solids, vec![2, 1]); assert_eq!(values.shells, vec![3, 3, 3]); assert_eq!(values.vertices, vec![0, 1, NULL, 2, NULL, NULL, 3, 4, NULL]);
845 }
846 _ => panic!("Expected MaterialMapping::Values"),
847 }
848
849 Ok(())
850 }
851
852 #[test]
856 fn a_null_material_shell_or_solid_is_recorded_as_a_null_count() -> Result<()> {
857 let materials = HashMap::from([(
858 "t".to_string(),
859 serde_json::from_value::<CjMaterialReference>(json!({"values": [[0, 1], null]}))?,
860 )]);
861 match &encode_material(&materials)[0] {
862 MaterialMapping::Values(v) => {
863 assert_eq!(v.solids, vec![2]);
864 assert_eq!(v.shells, vec![2, NULL]);
865 assert_eq!(v.vertices, vec![0, 1]);
866 }
867 _ => panic!("expected Values"),
868 }
869
870 let materials = HashMap::from([(
871 "t".to_string(),
872 serde_json::from_value::<CjMaterialReference>(json!({"values": [[[0, 1]], null]}))?,
873 )]);
874 match &encode_material(&materials)[0] {
875 MaterialMapping::Values(v) => {
876 assert_eq!(v.solids, vec![1, NULL]);
877 assert_eq!(v.shells, vec![2]);
878 assert_eq!(v.vertices, vec![0, 1]);
879 }
880 _ => panic!("expected Values"),
881 }
882 Ok(())
883 }
884
885 #[test]
886 fn test_encode_texture() -> Result<()> {
887 let theme = "test-theme".to_string();
888 let texture = |v: serde_json::Value| -> HashMap<String, CjTextureReference> {
889 HashMap::from([(
890 theme.clone(),
891 serde_json::from_value::<CjTextureReference>(json!({"values": v}))
892 .expect("texture values must parse"),
893 )])
894 };
895
896 let encoded = encode_texture(&texture(json!([
898 [[0, 10, 20, 30]],
899 [[1, 11, 21, null]],
900 [[2, 12, null, 32]]
901 ])));
902 assert_eq!(encoded.len(), 1);
903 assert_eq!(encoded[0].theme, theme);
904 assert_eq!(
905 encoded[0].vertices,
906 vec![0, 10, 20, 30, 1, 11, 21, NULL, 2, 12, NULL, 32]
907 );
908 assert_eq!(encoded[0].strings, vec![4, 4, 4]);
909 assert_eq!(encoded[0].surfaces, vec![1, 1, 1]);
910 assert_eq!(encoded[0].shells, vec![3]);
911 assert!(encoded[0].solids.is_empty());
912
913 let encoded = encode_texture(&texture(json!([
915 [[[0, 10, 20, 30]], [[1, 11, 21, null]], [[2, 12, null, 32]]],
916 [[[3, 13, 23, 33]], [[4, 14, 24, null]]]
917 ])));
918 assert_eq!(encoded.len(), 1);
919 assert_eq!(
920 encoded[0].vertices,
921 vec![0, 10, 20, 30, 1, 11, 21, NULL, 2, 12, NULL, 32, 3, 13, 23, 33, 4, 14, 24, NULL]
922 );
923 assert_eq!(encoded[0].strings, vec![4, 4, 4, 4, 4]);
924 assert_eq!(encoded[0].surfaces, vec![1, 1, 1, 1, 1]);
925 assert_eq!(encoded[0].shells, vec![3, 2]);
926 assert_eq!(encoded[0].solids, vec![2]);
927
928 let encoded = encode_texture(&texture(json!([
930 [
931 [[[0, 10, 20]], [[1, 11, null]]],
932 [[[2, 12, 22]], [[3, null, 23]]]
933 ],
934 [[[[4, 14, 24]], [[5, 15, 25]]]]
935 ])));
936 assert_eq!(encoded.len(), 1);
937 assert_eq!(
938 encoded[0].vertices,
939 vec![0, 10, 20, 1, 11, NULL, 2, 12, 22, 3, NULL, 23, 4, 14, 24, 5, 15, 25]
940 );
941 assert_eq!(encoded[0].strings, vec![3, 3, 3, 3, 3, 3]);
942 assert_eq!(encoded[0].surfaces, vec![1, 1, 1, 1, 1, 1]);
943 assert_eq!(encoded[0].shells, vec![2, 2, 2]);
944 assert_eq!(encoded[0].solids, vec![2, 1]);
945
946 let textures = HashMap::from([
948 (
949 "winter".to_string(),
950 serde_json::from_value::<CjTextureReference>(json!({"values": [[[0, 10, 20]]]}))?,
951 ),
952 (
953 "summer".to_string(),
954 serde_json::from_value::<CjTextureReference>(json!({"values": [[[1, 11, null]]]}))?,
955 ),
956 ]);
957
958 let encoded = encode_texture(&textures);
959 assert_eq!(encoded.len(), 2);
960
961 let winter_mapping = encoded
962 .iter()
963 .find(|m| m.theme == "winter")
964 .expect("Should have winter mapping");
965 let summer_mapping = encoded
966 .iter()
967 .find(|m| m.theme == "summer")
968 .expect("Should have summer mapping");
969
970 assert_eq!(winter_mapping.vertices, vec![0, 10, 20]);
971 assert_eq!(winter_mapping.strings, vec![3]);
972
973 assert_eq!(summer_mapping.vertices, vec![1, 11, NULL]);
974 assert_eq!(summer_mapping.strings, vec![3]);
975
976 Ok(())
977 }
978}