use cjseq::{
Geometry as CjGeometry, MaterialReference as CjMaterialReference,
MaterialValues as CjMaterialValues, Ring, Semantics as CjSemantics,
SemanticsSurface as CjSemanticsSurface, SemanticsValues as CjSemanticsValues, Shell, Surface,
TextureReference as CjTextureReference, TextureValues as CjTextureValues, TexturedShell,
TexturedSurface,
};
use std::collections::HashMap;
const NULL: u32 = u32::MAX;
#[derive(Debug, Clone, Default)]
pub(crate) struct GMBoundaries {
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>, }
#[derive(Debug, Clone, Default)]
pub struct MaterialValues {
pub(crate) theme: String,
pub(crate) solids: Vec<u32>,
pub(crate) shells: Vec<u32>,
pub(crate) vertices: Vec<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct MaterialValue {
pub(crate) theme: String,
pub(crate) value: u32,
}
#[derive(Debug, Clone)]
pub enum MaterialMapping {
Value(MaterialValue),
Values(MaterialValues),
NullValues(String),
}
#[derive(Debug, Clone, Default)]
pub(crate) struct TextureMapping {
pub(crate) theme: String,
pub(crate) has_values: bool,
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>, }
#[derive(Debug, Clone, Default)]
pub(crate) struct GMSemantics {
pub(crate) surfaces: Vec<CjSemanticsSurface>, pub(crate) values: Option<Vec<u32>>,
}
#[derive(Debug, Clone, Default)]
#[doc(hidden)]
pub(crate) struct EncodedGeometry {
pub(crate) boundaries: GMBoundaries,
pub(crate) semantics: Option<GMSemantics>,
pub(crate) textures: Option<Vec<TextureMapping>>,
pub(crate) materials: Option<Vec<MaterialMapping>>,
}
pub(crate) fn encode(geometry: &CjGeometry) -> EncodedGeometry {
let boundaries = encode_boundaries(geometry);
let common = geometry.common();
let semantics = common
.and_then(|c| c.semantics.as_ref())
.map(|s| encode_semantics(s, &boundaries));
let textures = common.and_then(|c| c.texture.as_ref()).map(encode_texture);
let materials = common
.and_then(|c| c.material.as_ref())
.map(encode_material);
EncodedGeometry {
boundaries,
semantics,
materials,
textures,
}
}
fn push_ring(ring: &Ring, b: &mut GMBoundaries) {
b.strings.push(ring.len() as u32);
b.indices.extend(ring.iter().map(|&i| i as u32));
}
fn push_surface(surface: &Surface, b: &mut GMBoundaries) {
for ring in surface {
push_ring(ring, b);
}
b.surfaces.push(surface.len() as u32);
}
fn push_shell(shell: &Shell, b: &mut GMBoundaries) {
for surface in shell {
push_surface(surface, b);
}
b.shells.push(shell.len() as u32);
}
fn push_solid(solid: &[Shell], b: &mut GMBoundaries) {
for shell in solid {
push_shell(shell, b);
}
b.solids.push(solid.len() as u32);
}
pub(crate) fn encode_boundaries(geometry: &CjGeometry) -> GMBoundaries {
let mut b = GMBoundaries::default();
match geometry {
CjGeometry::MultiPoint { boundaries, .. } => push_ring(boundaries, &mut b),
CjGeometry::MultiLineString { boundaries, .. } => {
for ring in boundaries {
push_ring(ring, &mut b);
}
b.surfaces.push(boundaries.len() as u32);
}
CjGeometry::MultiSurface { boundaries, .. }
| CjGeometry::CompositeSurface { boundaries, .. } => {
for surface in boundaries {
push_surface(surface, &mut b);
}
b.shells.push(boundaries.len() as u32);
}
CjGeometry::Solid { boundaries, .. } => push_solid(boundaries, &mut b),
CjGeometry::MultiSolid { boundaries, .. }
| CjGeometry::CompositeSolid { boundaries, .. } => {
for solid in boundaries {
push_solid(solid, &mut b);
}
}
CjGeometry::GeometryInstance { .. } => {}
}
b
}
fn material_index(i: Option<usize>) -> u32 {
i.map_or(NULL, |v| v as u32)
}
pub(crate) fn encode_material(
materials: &HashMap<String, CjMaterialReference>,
) -> Vec<MaterialMapping> {
let mut material_mappings = Vec::new();
let mut themes: Vec<&String> = materials.keys().collect();
themes.sort_unstable();
for (theme, material) in themes
.into_iter()
.filter_map(|t| materials.get(t).map(|m| (t, m)))
{
if let Some(value) = material.value {
material_mappings.push(MaterialMapping::Value(MaterialValue {
theme: theme.clone(),
value: value as u32,
}));
continue;
}
let values = match material.values.as_ref() {
Some(Some(values)) => values,
Some(None) => {
material_mappings.push(MaterialMapping::NullValues(theme.clone()));
continue;
}
None => continue,
};
let mut mv = MaterialValues {
theme: theme.clone(),
..Default::default()
};
match values {
CjMaterialValues::Surfaces(surfaces) => {
mv.vertices
.extend(surfaces.iter().copied().map(material_index));
}
CjMaterialValues::Shells(shells) => {
mv.solids.push(shells.len() as u32);
for shell in shells {
push_material_shell(shell.as_deref(), &mut mv);
}
}
CjMaterialValues::Solids(solids) => {
for solid in solids {
match solid {
Some(shells) => {
mv.solids.push(shells.len() as u32);
for shell in shells {
push_material_shell(shell.as_deref(), &mut mv);
}
}
None => mv.solids.push(NULL),
}
}
}
}
material_mappings.push(MaterialMapping::Values(mv));
}
material_mappings
}
fn push_material_shell(shell: Option<&[Option<usize>]>, mv: &mut MaterialValues) {
match shell {
Some(indices) => {
mv.shells.push(indices.len() as u32);
mv.vertices
.extend(indices.iter().copied().map(material_index));
}
None => mv.shells.push(NULL),
}
}
pub(crate) fn encode_texture(
texture_map: &HashMap<String, CjTextureReference>,
) -> Vec<TextureMapping> {
let mut texture_mappings = Vec::new();
let mut themes: Vec<&String> = texture_map.keys().collect();
themes.sort_unstable();
for (theme, texture) in themes
.into_iter()
.filter_map(|t| texture_map.get(t).map(|x| (t, x)))
{
let mut mapping = TextureMapping {
theme: theme.clone(),
..Default::default()
};
if let Some(values) = texture.values.as_ref() {
mapping.has_values = true;
encode_texture_values(values, &mut mapping);
}
texture_mappings.push(mapping);
}
texture_mappings
}
fn push_textured_surface(surface: &TexturedSurface, m: &mut TextureMapping) {
for ring in surface {
m.strings.push(ring.len() as u32);
m.vertices.extend(ring.iter().copied().map(material_index));
}
m.surfaces.push(surface.len() as u32);
}
fn push_textured_shell(shell: &TexturedShell, m: &mut TextureMapping) {
for surface in shell {
push_textured_surface(surface, m);
}
m.shells.push(shell.len() as u32);
}
fn encode_texture_values(values: &CjTextureValues, m: &mut TextureMapping) {
match values {
CjTextureValues::Surface(surfaces) => {
for surface in surfaces {
push_textured_surface(surface, m);
}
m.shells.push(surfaces.len() as u32);
}
CjTextureValues::Shell(shells) => {
for shell in shells {
push_textured_shell(shell, m);
}
m.solids.push(shells.len() as u32);
}
CjTextureValues::Solid(solids) => {
for solid in solids {
for shell in solid {
push_textured_shell(shell, m);
}
m.solids.push(solid.len() as u32);
}
}
}
}
fn semantics_index(i: Option<usize>) -> u32 {
i.map_or(NULL, |v| v as u32)
}
fn encode_semantics_values(
values: &CjSemanticsValues,
boundaries: &GMBoundaries,
flattened: &mut Vec<u32>,
) {
match values {
CjSemanticsValues::Surfaces(surfaces) => {
flattened.extend(surfaces.iter().copied().map(semantics_index));
}
CjSemanticsValues::Shells(shells) => {
let mut shell_cursor = 0;
for shell in shells {
push_semantics_shell(shell.as_deref(), boundaries, &mut shell_cursor, flattened);
}
}
CjSemanticsValues::Solids(solids) => {
let mut shell_cursor = 0;
for (i, solid) in solids.iter().enumerate() {
let shell_count = boundaries.solids.get(i).copied().unwrap_or(0) as usize;
match solid {
Some(shells) => {
for shell in shells {
push_semantics_shell(
shell.as_deref(),
boundaries,
&mut shell_cursor,
flattened,
);
}
}
None => {
for _ in 0..shell_count {
push_semantics_shell(None, boundaries, &mut shell_cursor, flattened);
}
}
}
}
}
}
}
fn push_semantics_shell(
shell: Option<&[Option<usize>]>,
boundaries: &GMBoundaries,
shell_cursor: &mut usize,
flattened: &mut Vec<u32>,
) {
let surface_count = boundaries.shells.get(*shell_cursor).copied().unwrap_or(0) as usize;
*shell_cursor += 1;
match shell {
Some(indices) => flattened.extend(indices.iter().copied().map(semantics_index)),
None => flattened.extend(std::iter::repeat_n(NULL, surface_count)),
}
}
pub(crate) fn encode_semantics(semantics: &CjSemantics, boundaries: &GMBoundaries) -> GMSemantics {
let values = semantics.values.as_ref().map(|v| {
let mut values = Vec::new();
encode_semantics_values(v, boundaries, &mut values);
values
});
GMSemantics {
surfaces: semantics.surfaces.to_vec(),
values,
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use cjseq::SemanticSurfaceType;
use pretty_assertions::assert_eq;
use serde_json::json;
fn theme_of(m: &MaterialMapping) -> &str {
match m {
MaterialMapping::Value(v) => &v.theme,
MaterialMapping::Values(v) => &v.theme,
MaterialMapping::NullValues(theme) => theme,
}
}
fn geom(v: serde_json::Value) -> CjGeometry {
serde_json::from_value(v).expect("test geometry must parse")
}
#[test]
fn test_encode_boundaries() -> Result<()> {
let encoded = encode(&geom(json!({
"type": "MultiPoint", "lod": "1", "boundaries": [2, 44, 0, 7]
})));
assert_eq!(vec![2, 44, 0, 7], encoded.boundaries.indices);
assert_eq!(vec![4], encoded.boundaries.strings);
assert!(encoded.boundaries.surfaces.is_empty());
assert!(encoded.boundaries.shells.is_empty());
assert!(encoded.boundaries.solids.is_empty());
let encoded = encode(&geom(json!({
"type": "MultiLineString", "lod": "1", "boundaries": [[2, 3, 5], [77, 55, 212]]
})));
assert_eq!(vec![2, 3, 5, 77, 55, 212], encoded.boundaries.indices);
assert_eq!(vec![3, 3], encoded.boundaries.strings);
assert_eq!(vec![2], encoded.boundaries.surfaces);
assert!(encoded.boundaries.shells.is_empty());
assert!(encoded.boundaries.solids.is_empty());
let encoded = encode(&geom(json!({
"type": "MultiSurface", "lod": "1",
"boundaries": [[[0, 3, 2, 1]], [[4, 5, 6, 7]], [[0, 1, 5, 4]]]
})));
assert_eq!(
vec![0, 3, 2, 1, 4, 5, 6, 7, 0, 1, 5, 4],
encoded.boundaries.indices
);
assert_eq!(vec![4, 4, 4], encoded.boundaries.strings);
assert_eq!(vec![1, 1, 1], encoded.boundaries.surfaces);
assert_eq!(vec![3], encoded.boundaries.shells);
assert!(encoded.boundaries.solids.is_empty());
let encoded = encode(&geom(json!({
"type": "Solid", "lod": "1",
"boundaries": [
[
[[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, 246, 724]],
[[34, 414, 45]],
[[111, 246, 5]]
]
]
})));
assert_eq!(
vec![
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,
246, 724, 34, 414, 45, 111, 246, 5
],
encoded.boundaries.indices
);
assert_eq!(vec![5, 4, 4, 4, 4, 3, 3, 3, 3], encoded.boundaries.strings);
assert_eq!(vec![2, 1, 1, 1, 1, 1, 1, 1], encoded.boundaries.surfaces);
assert_eq!(vec![4, 4], encoded.boundaries.shells);
assert_eq!(vec![2], encoded.boundaries.solids);
let encoded = encode(&geom(json!({
"type": "CompositeSolid", "lod": "1",
"boundaries": [
[
[
[[0, 3, 2, 1, 22]],
[[4, 5, 6, 7]],
[[0, 1, 5, 4]],
[[1, 2, 6, 5]]
],
[
[[240, 243, 124]],
[[244, 246, 724]],
[[34, 414, 45]],
[[111, 246, 5]]
]
],
[[
[[666, 667, 668]],
[[74, 75, 76]],
[[880, 881, 885]],
[[111, 122, 226]]
]]
]
})));
assert_eq!(
vec![
0, 3, 2, 1, 22, 4, 5, 6, 7, 0, 1, 5, 4, 1, 2, 6, 5, 240, 243, 124, 244, 246, 724,
34, 414, 45, 111, 246, 5, 666, 667, 668, 74, 75, 76, 880, 881, 885, 111, 122, 226
],
encoded.boundaries.indices
);
assert_eq!(
encoded.boundaries.strings,
vec![5, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3]
);
assert_eq!(
encoded.boundaries.surfaces,
vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
);
assert_eq!(encoded.boundaries.shells, vec![4, 4, 4]);
assert_eq!(encoded.boundaries.solids, vec![2, 1]);
Ok(())
}
#[test]
fn types_of_equal_depth_flatten_identically() {
let surface_boundaries = json!([[[0, 1, 2]], [[3, 4, 5]]]);
let ms = encode(&geom(
json!({"type": "MultiSurface", "boundaries": surface_boundaries}),
));
let cs = encode(&geom(
json!({"type": "CompositeSurface", "boundaries": surface_boundaries}),
));
assert_eq!(ms.boundaries.shells, cs.boundaries.shells);
assert_eq!(ms.boundaries.surfaces, cs.boundaries.surfaces);
assert_eq!(ms.boundaries.indices, cs.boundaries.indices);
let solid_boundaries = json!([[[[[0, 1, 2]]]], [[[[3, 4, 5]]]]]);
let msol = encode(&geom(
json!({"type": "MultiSolid", "boundaries": solid_boundaries}),
));
let csol = encode(&geom(
json!({"type": "CompositeSolid", "boundaries": solid_boundaries}),
));
assert_eq!(msol.boundaries.solids, csol.boundaries.solids);
assert_eq!(msol.boundaries.shells, csol.boundaries.shells);
}
#[test]
fn test_encode_semantics() -> Result<()> {
let multi_surface = geom(json!({
"type": "MultiSurface",
"lod": "2",
"boundaries": [
[[0, 3, 2, 1]],
[[4, 5, 6, 7]],
[[0, 1, 5, 4]],
[[0, 2, 3, 8]],
[[10, 12, 23, 48]]
],
"semantics": {
"surfaces": [
{"type": "WallSurface", "slope": 33.4, "children": [2]},
{"type": "RoofSurface", "slope": 66.6},
{"type": "OuterCeilingSurface", "parent": 0, "colour": "blue"}
],
"values": [0, 0, null, 1, 2]
}
}));
let encoded = encode(&multi_surface);
let encoded_semantics = encoded.semantics.expect("semantics must be encoded");
let expected_semantics_surfaces = vec![
CjSemanticsSurface {
thetype: SemanticSurfaceType::WallSurface,
parent: None,
children: Some(vec![2]),
other: HashMap::from([("slope".to_string(), json!(33.4))]),
},
CjSemanticsSurface {
thetype: SemanticSurfaceType::RoofSurface,
parent: None,
children: None,
other: HashMap::from([("slope".to_string(), json!(66.6))]),
},
CjSemanticsSurface {
thetype: SemanticSurfaceType::OuterCeilingSurface,
parent: Some(0),
children: None,
other: HashMap::from([("colour".to_string(), json!("blue"))]),
},
];
assert_eq!(expected_semantics_surfaces, encoded_semantics.surfaces);
assert_eq!(Some(vec![0, 0, NULL, 1, 2]), encoded_semantics.values);
let composite_solid = geom(json!({
"type": "CompositeSolid",
"lod": "2.2",
"boundaries": [
[[
[[0, 3, 2, 1, 22]],
[[4, 5, 6, 7]],
[[0, 1, 5, 4]],
[[1, 2, 6, 5]]
]],
[[
[[666, 667, 668]],
[[74, 75, 76]],
[[880, 881, 885]]
]]
],
"semantics": {
"surfaces": [{"type": "RoofSurface"}, {"type": "WallSurface"}],
"values": [[[0, 1, 1, null]], [[null, null, null]]]
}
}));
let encoded = encode(&composite_solid);
let encoded_semantics = encoded.semantics.expect("semantics must be encoded");
let expected_semantics_surfaces = vec![
CjSemanticsSurface {
thetype: SemanticSurfaceType::RoofSurface,
parent: None,
children: None,
other: HashMap::new(),
},
CjSemanticsSurface {
thetype: SemanticSurfaceType::WallSurface,
parent: None,
children: None,
other: HashMap::new(),
},
];
assert_eq!(expected_semantics_surfaces, encoded_semantics.surfaces);
assert_eq!(
Some(vec![0, 1, 1, NULL, NULL, NULL, NULL]),
encoded_semantics.values
);
Ok(())
}
#[test]
fn a_null_semantics_shell_expands_to_one_null_per_surface() {
let solid = geom(json!({
"type": "Solid",
"boundaries": [
[[[0, 1, 2]], [[3, 4, 5]]],
[[[6, 7, 8]]]
],
"semantics": {
"surfaces": [{"type": "RoofSurface"}],
"values": [[0, 0], null]
}
}));
let encoded = encode(&solid);
let semantics = encoded.semantics.expect("semantics must be encoded");
assert_eq!(semantics.values, Some(vec![0, 0, NULL]));
}
#[test]
fn test_encode_material() -> Result<()> {
let materials = HashMap::from([(
"theme1".to_string(),
serde_json::from_value::<CjMaterialReference>(json!({"value": 5}))?,
)]);
let encoded = encode_material(&materials);
assert_eq!(encoded.len(), 1);
match &encoded[0] {
MaterialMapping::Value(value) => {
assert_eq!(value.theme, "theme1");
assert_eq!(value.value, 5);
}
_ => panic!("Expected MaterialMapping::Value"),
}
let materials = HashMap::from([(
"theme2".to_string(),
serde_json::from_value::<CjMaterialReference>(json!({"values": [0, 1, null, 2]}))?,
)]);
let encoded = encode_material(&materials);
assert_eq!(encoded.len(), 1);
match &encoded[0] {
MaterialMapping::Values(values) => {
assert_eq!(values.theme, "theme2");
assert_eq!(values.vertices, vec![0, 1, NULL, 2]);
assert!(values.shells.is_empty());
assert!(values.solids.is_empty());
}
_ => panic!("Expected MaterialMapping::Values"),
}
let materials = HashMap::from([(
"theme3".to_string(),
serde_json::from_value::<CjMaterialReference>(
json!({"values": [[0, 1, null], [2, 3, 4]]}),
)?,
)]);
let encoded = encode_material(&materials);
assert_eq!(encoded.len(), 1);
match &encoded[0] {
MaterialMapping::Values(values) => {
assert_eq!(values.theme, "theme3");
assert_eq!(values.solids, vec![2]); assert_eq!(values.shells, vec![3, 3]); assert_eq!(values.vertices, vec![0, 1, NULL, 2, 3, 4]);
}
_ => panic!("Expected MaterialMapping::Values"),
}
let materials = HashMap::from([
(
"theme4".to_string(),
serde_json::from_value::<CjMaterialReference>(json!({"value": 7}))?,
),
(
"theme5".to_string(),
serde_json::from_value::<CjMaterialReference>(json!({"values": [8, 9]}))?,
),
]);
let encoded = encode_material(&materials);
assert_eq!(encoded.len(), 2);
let theme4_mapping = encoded
.iter()
.find(|m| theme_of(m) == "theme4")
.expect("Should have theme4 mapping");
let theme5_mapping = encoded
.iter()
.find(|m| theme_of(m) == "theme5")
.expect("Should have theme5 mapping");
match theme4_mapping {
MaterialMapping::Value(value) => {
assert_eq!(value.theme, "theme4");
assert_eq!(value.value, 7);
}
_ => panic!("Expected MaterialMapping::Value for theme4"),
}
match theme5_mapping {
MaterialMapping::Values(values) => {
assert_eq!(values.theme, "theme5");
assert_eq!(values.vertices, vec![8, 9]);
assert!(values.shells.is_empty());
assert!(values.solids.is_empty());
}
_ => panic!("Expected MaterialMapping::Values for theme5"),
}
let materials = HashMap::from([(
"theme6".to_string(),
serde_json::from_value::<CjMaterialReference>(json!({
"values": [[[0, 1, null], [2, null, null]], [[3, 4, null]]]
}))?,
)]);
let encoded = encode_material(&materials);
assert_eq!(encoded.len(), 1);
match &encoded[0] {
MaterialMapping::Values(values) => {
assert_eq!(values.theme, "theme6");
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]);
}
_ => panic!("Expected MaterialMapping::Values"),
}
Ok(())
}
#[test]
fn a_null_material_shell_or_solid_is_recorded_as_a_null_count() -> Result<()> {
let materials = HashMap::from([(
"t".to_string(),
serde_json::from_value::<CjMaterialReference>(json!({"values": [[0, 1], null]}))?,
)]);
match &encode_material(&materials)[0] {
MaterialMapping::Values(v) => {
assert_eq!(v.solids, vec![2]);
assert_eq!(v.shells, vec![2, NULL]);
assert_eq!(v.vertices, vec![0, 1]);
}
_ => panic!("expected Values"),
}
let materials = HashMap::from([(
"t".to_string(),
serde_json::from_value::<CjMaterialReference>(json!({"values": [[[0, 1]], null]}))?,
)]);
match &encode_material(&materials)[0] {
MaterialMapping::Values(v) => {
assert_eq!(v.solids, vec![1, NULL]);
assert_eq!(v.shells, vec![2]);
assert_eq!(v.vertices, vec![0, 1]);
}
_ => panic!("expected Values"),
}
Ok(())
}
#[test]
fn test_encode_texture() -> Result<()> {
let theme = "test-theme".to_string();
let texture = |v: serde_json::Value| -> HashMap<String, CjTextureReference> {
HashMap::from([(
theme.clone(),
serde_json::from_value::<CjTextureReference>(json!({"values": v}))
.expect("texture values must parse"),
)])
};
let encoded = encode_texture(&texture(json!([
[[0, 10, 20, 30]],
[[1, 11, 21, null]],
[[2, 12, null, 32]]
])));
assert_eq!(encoded.len(), 1);
assert_eq!(encoded[0].theme, theme);
assert_eq!(
encoded[0].vertices,
vec![0, 10, 20, 30, 1, 11, 21, NULL, 2, 12, NULL, 32]
);
assert_eq!(encoded[0].strings, vec![4, 4, 4]);
assert_eq!(encoded[0].surfaces, vec![1, 1, 1]);
assert_eq!(encoded[0].shells, vec![3]);
assert!(encoded[0].solids.is_empty());
let encoded = encode_texture(&texture(json!([
[[[0, 10, 20, 30]], [[1, 11, 21, null]], [[2, 12, null, 32]]],
[[[3, 13, 23, 33]], [[4, 14, 24, null]]]
])));
assert_eq!(encoded.len(), 1);
assert_eq!(
encoded[0].vertices,
vec![0, 10, 20, 30, 1, 11, 21, NULL, 2, 12, NULL, 32, 3, 13, 23, 33, 4, 14, 24, NULL]
);
assert_eq!(encoded[0].strings, vec![4, 4, 4, 4, 4]);
assert_eq!(encoded[0].surfaces, vec![1, 1, 1, 1, 1]);
assert_eq!(encoded[0].shells, vec![3, 2]);
assert_eq!(encoded[0].solids, vec![2]);
let encoded = encode_texture(&texture(json!([
[
[[[0, 10, 20]], [[1, 11, null]]],
[[[2, 12, 22]], [[3, null, 23]]]
],
[[[[4, 14, 24]], [[5, 15, 25]]]]
])));
assert_eq!(encoded.len(), 1);
assert_eq!(
encoded[0].vertices,
vec![0, 10, 20, 1, 11, NULL, 2, 12, 22, 3, NULL, 23, 4, 14, 24, 5, 15, 25]
);
assert_eq!(encoded[0].strings, vec![3, 3, 3, 3, 3, 3]);
assert_eq!(encoded[0].surfaces, vec![1, 1, 1, 1, 1, 1]);
assert_eq!(encoded[0].shells, vec![2, 2, 2]);
assert_eq!(encoded[0].solids, vec![2, 1]);
let textures = HashMap::from([
(
"winter".to_string(),
serde_json::from_value::<CjTextureReference>(json!({"values": [[[0, 10, 20]]]}))?,
),
(
"summer".to_string(),
serde_json::from_value::<CjTextureReference>(json!({"values": [[[1, 11, null]]]}))?,
),
]);
let encoded = encode_texture(&textures);
assert_eq!(encoded.len(), 2);
let winter_mapping = encoded
.iter()
.find(|m| m.theme == "winter")
.expect("Should have winter mapping");
let summer_mapping = encoded
.iter()
.find(|m| m.theme == "summer")
.expect("Should have summer mapping");
assert_eq!(winter_mapping.vertices, vec![0, 10, 20]);
assert_eq!(winter_mapping.strings, vec![3]);
assert_eq!(summer_mapping.vertices, vec![1, 11, NULL]);
assert_eq!(summer_mapping.strings, vec![3]);
Ok(())
}
}