1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, PartialEq)]
10pub enum AssetType {
11 Mesh,
12 Texture,
13 Material,
14 Animation,
15 Skeleton,
16 Scene,
17 Other(String),
18}
19
20impl AssetType {
21 #[allow(dead_code)]
22 pub fn as_str(&self) -> &str {
23 match self {
24 AssetType::Mesh => "mesh",
25 AssetType::Texture => "texture",
26 AssetType::Material => "material",
27 AssetType::Animation => "animation",
28 AssetType::Skeleton => "skeleton",
29 AssetType::Scene => "scene",
30 AssetType::Other(s) => s.as_str(),
31 }
32 }
33}
34
35#[allow(dead_code)]
37#[derive(Debug, Clone)]
38pub struct AssetStats {
39 pub vertex_count: u32,
40 pub face_count: u32,
41 pub file_size_bytes: u64,
42 pub lod_levels: u8,
43}
44
45impl AssetStats {
46 #[allow(dead_code)]
47 pub fn zero() -> Self {
48 Self {
49 vertex_count: 0,
50 face_count: 0,
51 file_size_bytes: 0,
52 lod_levels: 0,
53 }
54 }
55}
56
57#[allow(dead_code)]
59#[derive(Debug, Clone)]
60pub struct AssetEntry {
61 pub id: u32,
62 pub name: String,
63 pub asset_type: AssetType,
64 pub path: String,
65 pub stats: AssetStats,
66}
67
68#[allow(dead_code)]
70pub struct AssetInventory {
71 pub project: String,
72 pub entries: Vec<AssetEntry>,
73 next_id: u32,
74}
75
76impl AssetInventory {
77 #[allow(dead_code)]
78 pub fn new(project: &str) -> Self {
79 Self {
80 project: project.to_string(),
81 entries: Vec::new(),
82 next_id: 0,
83 }
84 }
85}
86
87#[allow(dead_code)]
89pub fn add_asset(
90 inventory: &mut AssetInventory,
91 name: &str,
92 asset_type: AssetType,
93 path: &str,
94 stats: AssetStats,
95) -> u32 {
96 let id = inventory.next_id;
97 inventory.next_id += 1;
98 inventory.entries.push(AssetEntry {
99 id,
100 name: name.to_string(),
101 asset_type,
102 path: path.to_string(),
103 stats,
104 });
105 id
106}
107
108#[allow(dead_code)]
110pub fn export_inventory_csv(inventory: &AssetInventory) -> String {
111 let mut out = String::from("id,name,type,path,vertices,faces,size_bytes,lod_levels\n");
112 for e in &inventory.entries {
113 out.push_str(&format!(
114 "{},{},{},{},{},{},{},{}\n",
115 e.id,
116 e.name,
117 e.asset_type.as_str(),
118 e.path,
119 e.stats.vertex_count,
120 e.stats.face_count,
121 e.stats.file_size_bytes,
122 e.stats.lod_levels
123 ));
124 }
125 out
126}
127
128#[allow(dead_code)]
130pub fn export_inventory_json(inventory: &AssetInventory) -> String {
131 let mut out = format!("{{\"project\":\"{}\",\"assets\":[", inventory.project);
132 for (i, e) in inventory.entries.iter().enumerate() {
133 if i > 0 {
134 out.push(',');
135 }
136 out.push_str(&format!(
137 "{{\"id\":{},\"name\":\"{}\",\"type\":\"{}\",\"path\":\"{}\",\
138 \"vertices\":{},\"faces\":{},\"size_bytes\":{},\"lod_levels\":{}}}",
139 e.id,
140 e.name,
141 e.asset_type.as_str(),
142 e.path,
143 e.stats.vertex_count,
144 e.stats.face_count,
145 e.stats.file_size_bytes,
146 e.stats.lod_levels
147 ));
148 }
149 out.push_str("]}");
150 out
151}
152
153#[allow(dead_code)]
155pub fn asset_count(inventory: &AssetInventory) -> usize {
156 inventory.entries.len()
157}
158
159#[allow(dead_code)]
161pub fn count_by_type(inventory: &AssetInventory, asset_type: &AssetType) -> usize {
162 inventory
163 .entries
164 .iter()
165 .filter(|e| &e.asset_type == asset_type)
166 .count()
167}
168
169#[allow(dead_code)]
171pub fn total_file_size(inventory: &AssetInventory) -> u64 {
172 inventory
173 .entries
174 .iter()
175 .map(|e| e.stats.file_size_bytes)
176 .sum()
177}
178
179#[allow(dead_code)]
181pub fn total_vertex_count(inventory: &AssetInventory) -> u64 {
182 inventory
183 .entries
184 .iter()
185 .map(|e| e.stats.vertex_count as u64)
186 .sum()
187}
188
189#[allow(dead_code)]
191pub fn find_asset_by_name<'a>(inventory: &'a AssetInventory, name: &str) -> Option<&'a AssetEntry> {
192 inventory.entries.iter().find(|e| e.name == name)
193}
194
195#[allow(dead_code)]
197pub fn find_asset_by_id(inventory: &AssetInventory, id: u32) -> Option<&AssetEntry> {
198 inventory.entries.iter().find(|e| e.id == id)
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 fn sample_inventory() -> AssetInventory {
206 let mut inv = AssetInventory::new("oxihuman-project");
207 add_asset(
208 &mut inv,
209 "head_mesh",
210 AssetType::Mesh,
211 "assets/head.glb",
212 AssetStats {
213 vertex_count: 5000,
214 face_count: 9800,
215 file_size_bytes: 204800,
216 lod_levels: 3,
217 },
218 );
219 add_asset(
220 &mut inv,
221 "body_mesh",
222 AssetType::Mesh,
223 "assets/body.glb",
224 AssetStats {
225 vertex_count: 12000,
226 face_count: 23000,
227 file_size_bytes: 512000,
228 lod_levels: 4,
229 },
230 );
231 add_asset(
232 &mut inv,
233 "skin_texture",
234 AssetType::Texture,
235 "assets/skin.png",
236 AssetStats {
237 vertex_count: 0,
238 face_count: 0,
239 file_size_bytes: 1048576,
240 lod_levels: 0,
241 },
242 );
243 inv
244 }
245
246 #[test]
247 fn asset_count_correct() {
248 let inv = sample_inventory();
249 assert_eq!(asset_count(&inv), 3);
250 }
251
252 #[test]
253 fn count_by_type_mesh() {
254 let inv = sample_inventory();
255 assert_eq!(count_by_type(&inv, &AssetType::Mesh), 2);
256 }
257
258 #[test]
259 fn count_by_type_texture() {
260 let inv = sample_inventory();
261 assert_eq!(count_by_type(&inv, &AssetType::Texture), 1);
262 }
263
264 #[test]
265 fn total_file_size_correct() {
266 let inv = sample_inventory();
267 assert_eq!(total_file_size(&inv), 204800 + 512000 + 1048576);
268 }
269
270 #[test]
271 fn total_vertex_count_correct() {
272 let inv = sample_inventory();
273 assert_eq!(total_vertex_count(&inv), 17000);
274 }
275
276 #[test]
277 fn csv_header_present() {
278 let inv = sample_inventory();
279 let csv = export_inventory_csv(&inv);
280 assert!(csv.starts_with("id,name,type"));
281 }
282
283 #[test]
284 fn json_contains_project() {
285 let inv = sample_inventory();
286 let json = export_inventory_json(&inv);
287 assert!(json.contains("oxihuman-project"));
288 }
289
290 #[test]
291 fn find_asset_by_name_some() {
292 let inv = sample_inventory();
293 assert!(find_asset_by_name(&inv, "head_mesh").is_some());
294 }
295
296 #[test]
297 fn find_asset_by_name_none() {
298 let inv = sample_inventory();
299 assert!(find_asset_by_name(&inv, "nonexistent").is_none());
300 }
301
302 #[test]
303 fn find_asset_by_id_correct() {
304 let inv = sample_inventory();
305 let e = find_asset_by_id(&inv, 1);
306 assert!(e.is_some_and(|a| a.name == "body_mesh"));
307 }
308}