1#![allow(dead_code)]
5
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9use anyhow::Result;
10use oxihuman_mesh::MeshBuffers;
11
12pub struct CsvExportReport {
18 pub vertices_path: PathBuf,
19 pub faces_path: PathBuf,
20 pub normals_path: PathBuf,
21 pub uvs_path: PathBuf,
22 pub vertex_count: usize,
23 pub face_count: usize,
24}
25
26pub fn vertices_to_csv_string(mesh: &MeshBuffers) -> String {
32 let mut out = String::from("index,x,y,z\n");
33 for (i, p) in mesh.positions.iter().enumerate() {
34 out.push_str(&format!("{},{},{},{}\n", i, p[0], p[1], p[2]));
35 }
36 out
37}
38
39pub fn faces_to_csv_string(mesh: &MeshBuffers) -> String {
41 let mut out = String::from("face_index,v0,v1,v2\n");
42 for (fi, tri) in mesh.indices.chunks(3).enumerate() {
43 if tri.len() == 3 {
44 out.push_str(&format!("{},{},{},{}\n", fi, tri[0], tri[1], tri[2]));
45 }
46 }
47 out
48}
49
50pub fn export_vertices_csv(mesh: &MeshBuffers, path: &Path) -> Result<()> {
56 std::fs::write(path, vertices_to_csv_string(mesh))?;
57 Ok(())
58}
59
60pub fn export_faces_csv(mesh: &MeshBuffers, path: &Path) -> Result<()> {
62 std::fs::write(path, faces_to_csv_string(mesh))?;
63 Ok(())
64}
65
66pub fn export_normals_csv(mesh: &MeshBuffers, path: &Path) -> Result<()> {
68 let mut out = String::from("index,nx,ny,nz\n");
69 for (i, n) in mesh.normals.iter().enumerate() {
70 out.push_str(&format!("{},{},{},{}\n", i, n[0], n[1], n[2]));
71 }
72 std::fs::write(path, out)?;
73 Ok(())
74}
75
76pub fn export_uvs_csv(mesh: &MeshBuffers, path: &Path) -> Result<()> {
78 let mut out = String::from("index,u,v\n");
79 for (i, uv) in mesh.uvs.iter().enumerate() {
80 out.push_str(&format!("{},{},{}\n", i, uv[0], uv[1]));
81 }
82 std::fs::write(path, out)?;
83 Ok(())
84}
85
86pub fn export_stats_csv(mesh: &MeshBuffers, path: &Path) -> Result<()> {
89 let has_normals = !mesh.normals.is_empty();
90 let has_uvs = !mesh.uvs.is_empty();
91 let out = format!(
92 "vertex_count,face_count,has_normals,has_uvs\n{},{},{},{}\n",
93 mesh.vertex_count(),
94 mesh.face_count(),
95 has_normals,
96 has_uvs,
97 );
98 std::fs::write(path, out)?;
99 Ok(())
100}
101
102pub fn export_map_csv(data: &HashMap<String, f32>, path: &Path) -> Result<()> {
104 let mut out = String::from("key,value\n");
105 let mut keys: Vec<&String> = data.keys().collect();
106 keys.sort();
107 for k in keys {
108 out.push_str(&format!("{},{}\n", k, data[k]));
109 }
110 std::fs::write(path, out)?;
111 Ok(())
112}
113
114pub fn export_mesh_csv(mesh: &MeshBuffers, dir: &Path) -> Result<CsvExportReport> {
116 std::fs::create_dir_all(dir)?;
117
118 let vertices_path = dir.join("vertices.csv");
119 let faces_path = dir.join("faces.csv");
120 let normals_path = dir.join("normals.csv");
121 let uvs_path = dir.join("uvs.csv");
122
123 export_vertices_csv(mesh, &vertices_path)?;
124 export_faces_csv(mesh, &faces_path)?;
125 export_normals_csv(mesh, &normals_path)?;
126 export_uvs_csv(mesh, &uvs_path)?;
127
128 Ok(CsvExportReport {
129 vertices_path,
130 faces_path,
131 normals_path,
132 uvs_path,
133 vertex_count: mesh.vertex_count(),
134 face_count: mesh.face_count(),
135 })
136}
137
138#[cfg(test)]
143mod tests {
144 use super::*;
145 use oxihuman_mesh::MeshBuffers;
146 use oxihuman_morph::engine::MeshBuffers as MB;
147
148 fn two_tri_mesh() -> MeshBuffers {
150 MeshBuffers::from_morph(MB {
151 positions: vec![
152 [0.0, 0.0, 0.0],
153 [1.0, 0.0, 0.0],
154 [1.0, 1.0, 0.0],
155 [0.0, 1.0, 0.0],
156 ],
157 normals: vec![[0.0, 0.0, 1.0]; 4],
158 uvs: vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
159 indices: vec![0, 1, 2, 0, 2, 3],
160 has_suit: false,
161 })
162 }
163
164 fn empty_mesh() -> MeshBuffers {
166 MeshBuffers::from_morph(MB {
167 positions: vec![],
168 normals: vec![],
169 uvs: vec![],
170 indices: vec![],
171 has_suit: false,
172 })
173 }
174
175 #[test]
176 fn test_vertices_to_csv_string() {
177 let mesh = two_tri_mesh();
178 let csv = vertices_to_csv_string(&mesh);
179 assert!(csv.starts_with("index,x,y,z\n"));
180 assert!(csv.contains("0,0,0,0"));
181 assert!(csv.contains("1,1,0,0"));
182 assert!(csv.contains("2,1,1,0"));
183 assert!(csv.contains("3,0,1,0"));
184 let lines: Vec<&str> = csv.trim_end().lines().collect();
185 assert_eq!(lines.len(), 5);
187 }
188
189 #[test]
190 fn test_faces_to_csv_string() {
191 let mesh = two_tri_mesh();
192 let csv = faces_to_csv_string(&mesh);
193 assert!(csv.starts_with("face_index,v0,v1,v2\n"));
194 assert!(csv.contains("0,0,1,2"));
195 assert!(csv.contains("1,0,2,3"));
196 let lines: Vec<&str> = csv.trim_end().lines().collect();
197 assert_eq!(lines.len(), 3);
199 }
200
201 #[test]
202 fn test_export_vertices_csv() {
203 let mesh = two_tri_mesh();
204 let path = Path::new("/tmp/test_oxihuman_vertices.csv");
205 export_vertices_csv(&mesh, path).expect("should succeed");
206 let content = std::fs::read_to_string(path).expect("should succeed");
207 assert!(content.starts_with("index,x,y,z\n"));
208 let lines: Vec<&str> = content.trim_end().lines().collect();
209 assert_eq!(lines.len(), 5);
210 }
211
212 #[test]
213 fn test_export_faces_csv() {
214 let mesh = two_tri_mesh();
215 let path = Path::new("/tmp/test_oxihuman_faces.csv");
216 export_faces_csv(&mesh, path).expect("should succeed");
217 let content = std::fs::read_to_string(path).expect("should succeed");
218 assert!(content.starts_with("face_index,v0,v1,v2\n"));
219 let lines: Vec<&str> = content.trim_end().lines().collect();
220 assert_eq!(lines.len(), 3);
221 }
222
223 #[test]
224 fn test_export_normals_csv() {
225 let mesh = two_tri_mesh();
226 let path = Path::new("/tmp/test_oxihuman_normals.csv");
227 export_normals_csv(&mesh, path).expect("should succeed");
228 let content = std::fs::read_to_string(path).expect("should succeed");
229 assert!(content.starts_with("index,nx,ny,nz\n"));
230 let lines: Vec<&str> = content.trim_end().lines().collect();
232 assert_eq!(lines.len(), 5);
233 assert!(content.contains("0,0,0,1"));
234 }
235
236 #[test]
237 fn test_export_uvs_csv() {
238 let mesh = two_tri_mesh();
239 let path = Path::new("/tmp/test_oxihuman_uvs.csv");
240 export_uvs_csv(&mesh, path).expect("should succeed");
241 let content = std::fs::read_to_string(path).expect("should succeed");
242 assert!(content.starts_with("index,u,v\n"));
243 let lines: Vec<&str> = content.trim_end().lines().collect();
244 assert_eq!(lines.len(), 5);
246 assert!(content.contains("0,0,0"));
247 }
248
249 #[test]
250 fn test_export_stats_csv() {
251 let mesh = two_tri_mesh();
252 let path = Path::new("/tmp/test_oxihuman_stats.csv");
253 export_stats_csv(&mesh, path).expect("should succeed");
254 let content = std::fs::read_to_string(path).expect("should succeed");
255 assert!(content.starts_with("vertex_count,face_count,has_normals,has_uvs\n"));
256 assert!(content.contains("4,2,true,true"));
257 }
258
259 #[test]
260 fn test_export_map_csv() {
261 let mut data = HashMap::new();
262 data.insert("height".to_string(), 1.75_f32);
263 data.insert("weight".to_string(), 70.0_f32);
264 let path = Path::new("/tmp/test_oxihuman_map.csv");
265 export_map_csv(&data, path).expect("should succeed");
266 let content = std::fs::read_to_string(path).expect("should succeed");
267 assert!(content.starts_with("key,value\n"));
268 assert!(content.contains("height,"));
269 assert!(content.contains("weight,"));
270 }
271
272 #[test]
273 fn test_export_mesh_csv() {
274 let mesh = two_tri_mesh();
275 let dir = Path::new("/tmp/test_oxihuman_mesh_csv");
276 let report = export_mesh_csv(&mesh, dir).expect("should succeed");
277 assert_eq!(report.vertex_count, 4);
278 assert_eq!(report.face_count, 2);
279 assert!(report.vertices_path.exists());
280 assert!(report.faces_path.exists());
281 assert!(report.normals_path.exists());
282 assert!(report.uvs_path.exists());
283 }
284
285 #[test]
286 fn test_csv_header_format() {
287 let mesh = two_tri_mesh();
288 let v_csv = vertices_to_csv_string(&mesh);
289 let f_csv = faces_to_csv_string(&mesh);
290 assert_eq!(v_csv.lines().next().expect("should succeed"), "index,x,y,z");
291 assert_eq!(f_csv.lines().next().expect("should succeed"), "face_index,v0,v1,v2");
292 }
293
294 #[test]
295 fn test_csv_empty_mesh() {
296 let mesh = empty_mesh();
297 let v_csv = vertices_to_csv_string(&mesh);
298 let f_csv = faces_to_csv_string(&mesh);
299 assert_eq!(v_csv.trim_end().lines().count(), 1);
301 assert_eq!(f_csv.trim_end().lines().count(), 1);
302
303 let stats_path = Path::new("/tmp/test_oxihuman_empty_stats.csv");
304 export_stats_csv(&mesh, stats_path).expect("should succeed");
305 let content = std::fs::read_to_string(stats_path).expect("should succeed");
306 assert!(content.contains("0,0,false,false"));
307 }
308
309 #[test]
310 fn test_export_map_csv_sorted() {
311 let mut data = HashMap::new();
312 data.insert("zebra".to_string(), 3.0_f32);
313 data.insert("apple".to_string(), 1.0_f32);
314 data.insert("mango".to_string(), 2.0_f32);
315 let path = Path::new("/tmp/test_oxihuman_map_sorted.csv");
316 export_map_csv(&data, path).expect("should succeed");
317 let content = std::fs::read_to_string(path).expect("should succeed");
318 let lines: Vec<&str> = content.lines().collect();
319 assert_eq!(lines.len(), 4);
321 assert!(lines[1].starts_with("apple,"));
322 assert!(lines[2].starts_with("mango,"));
323 assert!(lines[3].starts_with("zebra,"));
324 }
325}