Skip to main content

concinnity_asset/
model.rs

1// Multi-mesh model schema.
2
3use crate::{AssetId, MaterialHandle, MeshHandle, de_opt_material_handle, de_opt_mesh_handle};
4use alloc::vec::Vec;
5
6/// One geometric part of a Model, referencing a mesh and its surface material.
7#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
8pub struct SubMeshRef {
9    /// A [Mesh](#mesh) or [ProceduralMesh](#proceduralmesh) asset.
10    #[serde(default, deserialize_with = "de_opt_mesh_handle")]
11    pub mesh: Option<MeshHandle>,
12    /// A [Material](#material) asset.  `None` uses the default material.
13    #[serde(default, deserialize_with = "de_opt_material_handle")]
14    pub material: Option<MaterialHandle>,
15}
16
17/// An ordered list of sub-meshes, each with its own material.
18///
19/// Use via the `model` field on a [Prop](#prop) instead of `mesh`. Each
20/// sub-mesh is drawn with its own material, all sharing the prop's transform.
21///
22/// Each `mesh` must name a [Mesh](#mesh) or [ProceduralMesh](#proceduralmesh)
23/// asset present in the scene. `material` may be empty to use the default
24/// material.
25#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
26#[serde(default)]
27pub struct Model {
28    /// Asset identity; injected via `inject_name`. Not part of `args`.
29    #[serde(skip)]
30    pub asset_id: AssetId,
31    /// Ordered list of sub-meshes that make up this model.
32    pub meshes: Vec<SubMeshRef>,
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn a_blank_model_has_no_sub_meshes() {
41        let m = Model::default();
42        assert!(m.meshes.is_empty());
43        assert_eq!(m.asset_id, AssetId::default());
44    }
45
46    #[test]
47    fn a_sub_mesh_may_declare_geometry_without_a_material() {
48        crate::test_support::install_resolvers();
49        let s: SubMeshRef = serde_json::from_str(r#"{"mesh":"body"}"#).unwrap();
50        assert_eq!(s.mesh, Some(MeshHandle(4)));
51        // No material means the sub-mesh inherits whatever the prop supplies.
52        assert_eq!(s.material, None);
53        let s: SubMeshRef = serde_json::from_str("{}").unwrap();
54        assert_eq!(s.mesh, None);
55        assert_eq!(s.material, None);
56    }
57
58    #[test]
59    fn an_imported_model_keeps_its_sub_mesh_order_through_postcard() {
60        crate::test_support::install_resolvers();
61        let m: Model = serde_json::from_str(
62            r#"{"meshes":[{"mesh":"body","material":"skin"},
63                         {"mesh":"trim","material":"metal"}]}"#,
64        )
65        .unwrap();
66        assert_eq!(m.meshes.len(), 2);
67
68        let bytes = postcard::to_allocvec(&m).unwrap();
69        let back: Model = postcard::from_bytes(&bytes).unwrap();
70        assert_eq!(back.meshes[0].mesh, Some(MeshHandle(4)));
71        assert_eq!(back.meshes[0].material, Some(MaterialHandle(4)));
72        assert_eq!(back.meshes[1].mesh, Some(MeshHandle(4)));
73        assert_eq!(back.meshes[1].material, Some(MaterialHandle(5)));
74        assert_eq!(back.asset_id, AssetId::default());
75    }
76}