Skip to main content

concinnity_core/components/
model.rs

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