concinnity_core/components/
mesh.rs1use crate::ecs::PayloadLocator;
4use crate::ecs::asset_id::AssetId;
5use alloc::string::String;
6use alloc::vec::Vec;
7
8#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
10pub struct VertexData {
11 pub pos: [f32; 3],
13 pub color: [f32; 3],
16 #[serde(default)]
18 pub uv: [f32; 2],
19}
20
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
37#[serde(default)]
38pub struct Mesh {
39 #[serde(skip)]
41 pub asset_id: AssetId,
42 pub source: String,
45 pub primitive_index: u32,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub chunk_index: Option<u32>,
54 pub vertices: Vec<VertexData>,
56 pub indices: Vec<u16>,
58 #[serde(default = "default_lod_levels")]
61 pub lod_levels: u32,
62 pub lod_distances: Vec<f32>,
67 #[serde(skip)]
69 pub locator: Option<PayloadLocator>,
70}
71
72fn default_lod_levels() -> u32 {
73 1
74}
75
76impl Default for Mesh {
77 fn default() -> Self {
78 Self {
79 asset_id: AssetId::default(),
80 source: String::new(),
81 primitive_index: 0,
82 chunk_index: None,
83 vertices: Vec::new(),
84 indices: Vec::new(),
85 lod_levels: 1,
86 lod_distances: Vec::new(),
87 locator: None,
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn a_vertex_without_uvs_samples_the_texture_origin() {
98 let v: VertexData = serde_json::from_str(r#"{"pos":[1,2,3],"color":[1,0,0]}"#).unwrap();
99 assert_eq!(v.pos, [1.0, 2.0, 3.0]);
100 assert_eq!(v.color, [1.0, 0.0, 0.0]);
101 assert_eq!(v.uv, [0.0, 0.0]);
102 assert!(serde_json::from_str::<VertexData>(r#"{"pos":[0,0,0]}"#).is_err());
105 }
106
107 #[test]
108 fn a_blank_mesh_has_one_lod_and_no_geometry() {
109 let m = Mesh::default();
110 assert!(m.vertices.is_empty());
111 assert!(m.indices.is_empty());
112 assert!(m.lod_distances.is_empty());
113 assert_eq!(m.lod_levels, 1);
115 assert_eq!(m.primitive_index, 0);
116 assert_eq!(m.chunk_index, None);
117 assert!(m.locator.is_none());
118 }
119
120 #[test]
121 fn an_omitted_lod_level_count_still_means_one() {
122 let m: Mesh = serde_json::from_str(r#"{"source":"board.obj"}"#).unwrap();
125 assert_eq!(m.lod_levels, 1);
126 assert_eq!(m.source, "board.obj");
127 }
128
129 #[test]
130 fn an_inline_mesh_round_trips_through_postcard() {
131 let m: Mesh = serde_json::from_str(
132 r#"{"source":"tile.obj","primitive_index":2,"chunk_index":7,"lod_levels":3,
133 "lod_distances":[10,40],
134 "vertices":[{"pos":[0,0,0],"color":[1,1,1],"uv":[0.5,0.5]}],
135 "indices":[0,0,0]}"#,
136 )
137 .unwrap();
138 assert_eq!(m.chunk_index, Some(7));
139
140 let bytes = postcard::to_allocvec(&m).unwrap();
141 let back: Mesh = postcard::from_bytes(&bytes).unwrap();
142 assert_eq!(back.primitive_index, 2);
143 assert_eq!(back.chunk_index, Some(7));
144 assert_eq!(back.lod_levels, 3);
145 assert_eq!(back.lod_distances, [10.0, 40.0]);
146 assert_eq!(back.vertices[0].uv, [0.5, 0.5]);
147 assert_eq!(back.indices, [0, 0, 0]);
148 assert_eq!(back.asset_id, AssetId::default());
149 }
150
151 #[test]
152 fn an_absent_chunk_index_is_omitted_from_the_serialized_args() {
153 let m: Mesh = serde_json::from_str(r#"{"source":"board.obj"}"#).unwrap();
154 let json = serde_json::to_string(&m).unwrap();
155 assert!(!json.contains("chunk_index"), "{json}");
156 }
157}