concinnity_core/components/
instanced_prop.rs1use crate::ecs::MaterialHandle;
4use crate::ecs::MeshHandle;
5use crate::ecs::TextureHandle;
6use crate::ecs::asset_id::AssetId;
7use crate::ecs::de_opt_material_handle;
8use crate::ecs::de_opt_mesh_handle;
9use crate::ecs::de_opt_texture_handle;
10use alloc::vec::Vec;
11
12#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
14#[serde(default)]
15pub struct InstanceTransform {
16 pub position: [f32; 3],
18 pub rotation_deg: [f32; 3],
20 pub scale: [f32; 3],
22}
23
24impl Default for InstanceTransform {
25 fn default() -> Self {
26 Self {
27 position: [0.0, 0.0, 0.0],
28 rotation_deg: [0.0, 0.0, 0.0],
29 scale: [1.0, 1.0, 1.0],
30 }
31 }
32}
33
34#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
43#[serde(default)]
44pub struct InstancedProp {
45 #[serde(skip)]
47 pub asset_id: AssetId,
48 #[serde(deserialize_with = "de_opt_mesh_handle")]
51 pub mesh: Option<MeshHandle>,
52 #[serde(deserialize_with = "de_opt_material_handle")]
54 pub material: Option<MaterialHandle>,
55 #[serde(deserialize_with = "de_opt_texture_handle")]
57 pub texture: Option<TextureHandle>,
58 pub instances: Vec<InstanceTransform>,
60 pub cull_distance: f32,
62}
63
64impl Default for InstancedProp {
65 fn default() -> Self {
66 Self {
67 asset_id: AssetId::default(),
68 mesh: None,
69 material: None,
70 texture: None,
71 instances: Vec::new(),
72 cull_distance: 0.0,
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn a_blank_instance_is_an_untransformed_copy() {
83 let t = InstanceTransform::default();
84 assert_eq!(t.position, [0.0, 0.0, 0.0]);
85 assert_eq!(t.rotation_deg, [0.0, 0.0, 0.0]);
86 assert_eq!(t.scale, [1.0, 1.0, 1.0]);
88 }
89
90 #[test]
91 fn a_blank_prop_draws_nothing_and_never_culls() {
92 let p = InstancedProp::default();
93 assert!(p.instances.is_empty());
94 assert!(p.mesh.is_none());
95 assert!(p.material.is_none());
96 assert!(p.texture.is_none());
97 assert_eq!(p.cull_distance, 0.0);
99 }
100
101 #[test]
102 fn an_authored_instance_list_parses_and_round_trips_through_postcard() {
103 crate::test_support::install_resolvers();
104 let p: InstancedProp = serde_json::from_str(
105 r#"{"mesh":"tree_mesh","material":"bark","texture":"bark_tex","cull_distance":120,
106 "instances":[{"position":[1,0,2]},{"position":[3,0,4],"scale":[2,2,2]}]}"#,
107 )
108 .unwrap();
109 assert_eq!(p.mesh, Some(MeshHandle(9)));
110 assert_eq!(p.material, Some(MaterialHandle(4)));
111 assert_eq!(p.texture, Some(TextureHandle(8)));
112 assert_eq!(p.instances.len(), 2);
113 assert_eq!(p.instances[0].scale, [1.0, 1.0, 1.0]);
115 assert_eq!(p.instances[1].scale, [2.0, 2.0, 2.0]);
116
117 let bytes = postcard::to_allocvec(&p).unwrap();
118 let back: InstancedProp = postcard::from_bytes(&bytes).unwrap();
119 assert_eq!(back.instances.len(), 2);
120 assert_eq!(back.instances[1].position, [3.0, 0.0, 4.0]);
121 assert_eq!(back.cull_distance, 120.0);
122 assert_eq!(back.asset_id, AssetId::default());
123 }
124}