1use crate::mesh_attributes::{ExtendedTriangleMesh, MeshAttributeOptions, Tangent};
8use crate::{obj, ply};
9use threecrate_core::{TriangleMesh, Point3f, Vector3f, Result, Error};
10use std::path::Path;
11use std::collections::HashMap;
12
13#[derive(Debug, Clone)]
15pub struct SerializationOptions {
16 pub attributes: MeshAttributeOptions,
18 pub preserve_custom_properties: bool,
20 pub validate_before_write: bool,
22 pub attach_metadata: bool,
24 pub custom_properties: HashMap<String, Vec<f32>>,
26}
27
28impl Default for SerializationOptions {
29 fn default() -> Self {
30 Self {
31 attributes: MeshAttributeOptions::default(),
32 preserve_custom_properties: true,
33 validate_before_write: true,
34 attach_metadata: true,
35 custom_properties: HashMap::new(),
36 }
37 }
38}
39
40impl SerializationOptions {
41 pub fn preserve_all() -> Self {
43 Self {
44 attributes: MeshAttributeOptions::recompute_all(),
45 preserve_custom_properties: true,
46 validate_before_write: true,
47 attach_metadata: true,
48 custom_properties: HashMap::new(),
49 }
50 }
51
52 pub fn fast() -> Self {
54 Self {
55 attributes: MeshAttributeOptions::validate_only(),
56 preserve_custom_properties: false,
57 validate_before_write: false,
58 attach_metadata: false,
59 custom_properties: HashMap::new(),
60 }
61 }
62
63 pub fn with_custom_property<S: Into<String>>(mut self, name: S, values: Vec<f32>) -> Self {
65 self.custom_properties.insert(name.into(), values);
66 self
67 }
68}
69
70pub struct AttributePreservingReader;
72
73pub struct AttributePreservingWriter;
75
76impl AttributePreservingReader {
77 pub fn read_extended_mesh<P: AsRef<Path>>(
79 path: P,
80 options: &SerializationOptions
81 ) -> Result<ExtendedTriangleMesh> {
82 let path = path.as_ref();
83 let extension = path.extension()
84 .and_then(|s| s.to_str())
85 .ok_or_else(|| Error::UnsupportedFormat("No file extension found".to_string()))?;
86
87 match extension.to_lowercase().as_str() {
88 "obj" => Self::read_obj_extended(path, options),
89 "ply" => Self::read_ply_extended(path, options),
90 _ => {
91 let mesh = crate::read_mesh(path)?;
93 let mut extended = ExtendedTriangleMesh::from_mesh(mesh);
94 extended.metadata.source_format = Some(extension.to_string());
95 extended.process_attributes(&options.attributes)?;
96 Ok(extended)
97 }
98 }
99 }
100
101 fn read_obj_extended<P: AsRef<Path>>(
103 path: P,
104 options: &SerializationOptions
105 ) -> Result<ExtendedTriangleMesh> {
106 let obj_data = obj::RobustObjReader::read_obj_file(path)?;
107 let mut extended = Self::obj_data_to_extended_mesh(&obj_data)?;
108
109 extended.metadata.source_format = Some("obj".to_string());
110 extended.metadata.uvs_loaded = !obj_data.texture_coords.is_empty();
111
112 if options.attributes.validate_attributes {
113 extended.validate_attributes()?;
114 }
115
116 extended.process_attributes(&options.attributes)?;
118
119 Ok(extended)
120 }
121
122 fn read_ply_extended<P: AsRef<Path>>(
124 path: P,
125 options: &SerializationOptions
126 ) -> Result<ExtendedTriangleMesh> {
127 let ply_data = ply::RobustPlyReader::read_ply_file(path)?;
128 let mut extended = Self::ply_data_to_extended_mesh(&ply_data)?;
129
130 extended.metadata.source_format = Some("ply".to_string());
131
132 if options.attributes.validate_attributes {
133 extended.validate_attributes()?;
134 }
135
136 extended.process_attributes(&options.attributes)?;
138
139 Ok(extended)
140 }
141
142 fn obj_data_to_extended_mesh(obj_data: &obj::ObjData) -> Result<ExtendedTriangleMesh> {
144 let base_mesh = obj::RobustObjReader::obj_data_to_mesh(obj_data)?;
146 let mut extended = ExtendedTriangleMesh::from_mesh(base_mesh);
147
148 if !obj_data.texture_coords.is_empty() {
150 let mut uvs = vec![[0.0, 0.0]; extended.vertex_count()];
151 let mut uv_extracted = false;
152
153 for group in &obj_data.groups {
155 for face in &group.faces {
156 for face_vertex in &face.vertices {
157 if let Some(tex_idx) = face_vertex.texture {
158 if tex_idx < obj_data.texture_coords.len() {
159 uvs[face_vertex.vertex] = obj_data.texture_coords[tex_idx];
160 uv_extracted = true;
161 }
162 }
163 }
164 }
165 }
166
167 if uv_extracted {
168 extended.set_uvs(uvs);
169 }
170 }
171
172 Ok(extended)
173 }
174
175 fn ply_data_to_extended_mesh(ply_data: &ply::PlyData) -> Result<ExtendedTriangleMesh> {
177 let mut vertices = Vec::new();
179 let mut normals = Vec::new();
180 let mut uvs = Vec::new();
181 let mut tangents = Vec::new();
182 let mut has_normals = false;
183 let mut has_uvs = false;
184 let mut has_tangents = false;
185
186 if let Some(vertex_elements) = ply_data.elements.get("vertex") {
187 for vertex in vertex_elements {
188 let x = vertex.get("x")
190 .ok_or_else(|| Error::InvalidData("Missing x coordinate".to_string()))?
191 .as_f32()?;
192 let y = vertex.get("y")
193 .ok_or_else(|| Error::InvalidData("Missing y coordinate".to_string()))?
194 .as_f32()?;
195 let z = vertex.get("z")
196 .ok_or_else(|| Error::InvalidData("Missing z coordinate".to_string()))?
197 .as_f32()?;
198
199 vertices.push(Point3f::new(x, y, z));
200
201 if let (Some(nx), Some(ny), Some(nz)) = (
203 vertex.get("nx"),
204 vertex.get("ny"),
205 vertex.get("nz")
206 ) {
207 normals.push(Vector3f::new(
208 nx.as_f32()?,
209 ny.as_f32()?,
210 nz.as_f32()?,
211 ));
212 has_normals = true;
213 }
214
215 if let Some(u) = vertex.get("u").or_else(|| vertex.get("texture_u")) {
217 if let Some(v) = vertex.get("v").or_else(|| vertex.get("texture_v")) {
218 uvs.push([u.as_f32()?, v.as_f32()?]);
219 has_uvs = true;
220 }
221 } else if let Some(s) = vertex.get("s") {
222 if let Some(t) = vertex.get("t") {
223 uvs.push([s.as_f32()?, t.as_f32()?]);
224 has_uvs = true;
225 }
226 }
227
228 if let (Some(tx), Some(ty), Some(tz)) = (
230 vertex.get("tx").or_else(|| vertex.get("tangent_x")),
231 vertex.get("ty").or_else(|| vertex.get("tangent_y")),
232 vertex.get("tz").or_else(|| vertex.get("tangent_z"))
233 ) {
234 let handedness = vertex.get("th")
235 .or_else(|| vertex.get("tangent_handedness"))
236 .map(|h| h.as_f32().unwrap_or(1.0))
237 .unwrap_or(1.0);
238
239 tangents.push(Tangent::new(
240 Vector3f::new(tx.as_f32()?, ty.as_f32()?, tz.as_f32()?),
241 handedness
242 ));
243 has_tangents = true;
244 }
245 }
246 }
247
248 let mut faces = Vec::new();
250 if let Some(face_elements) = ply_data.elements.get("face") {
251 for face in face_elements {
252 let indices = if let Some(vertex_indices) = face.get("vertex_indices") {
253 vertex_indices.as_usize_list()?
254 } else if let Some(vertex_index) = face.get("vertex_index") {
255 vertex_index.as_usize_list()?
256 } else {
257 return Err(Error::InvalidData("Face missing vertex indices".to_string()));
258 };
259
260 if indices.len() >= 3 {
262 faces.push([indices[0], indices[1], indices[2]]);
263 }
264 if indices.len() == 4 {
265 faces.push([indices[0], indices[2], indices[3]]);
266 }
267 }
268 }
269
270 let mut base_mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
272 if has_normals && normals.len() == base_mesh.vertex_count() {
273 base_mesh.set_normals(normals);
274 }
275
276 let mut extended = ExtendedTriangleMesh::from_mesh(base_mesh);
278
279 if has_uvs && uvs.len() == extended.vertex_count() {
280 extended.set_uvs(uvs);
281 }
282
283 if has_tangents && tangents.len() == extended.vertex_count() {
284 extended.set_tangents(tangents);
285 }
286
287 Ok(extended)
288 }
289}
290
291impl AttributePreservingWriter {
292 pub fn write_extended_mesh<P: AsRef<Path>>(
294 mesh: &ExtendedTriangleMesh,
295 path: P,
296 options: &SerializationOptions,
297 ) -> Result<()> {
298 let path = path.as_ref();
299 let extension = path.extension()
300 .and_then(|s| s.to_str())
301 .ok_or_else(|| Error::UnsupportedFormat("No file extension found".to_string()))?;
302
303 if options.validate_before_write {
305 let warnings = crate::mesh_attributes::utils::validate_round_trip(mesh)?;
306 if !warnings.is_empty() {
307 eprintln!("Mesh validation warnings: {:#?}", warnings);
308 }
309 }
310
311 match extension.to_lowercase().as_str() {
312 "obj" => Self::write_obj_extended(mesh, path, options),
313 "ply" => Self::write_ply_extended(mesh, path, options),
314 _ => {
315 crate::write_mesh(&mesh.mesh, path)
317 }
318 }
319 }
320
321 fn write_obj_extended<P: AsRef<Path>>(
323 mesh: &ExtendedTriangleMesh,
324 path: P,
325 options: &SerializationOptions,
326 ) -> Result<()> {
327 let obj_data = Self::extended_mesh_to_obj_data(mesh, options)?;
328 let obj_options = obj::ObjWriteOptions::new()
329 .with_normals(mesh.mesh.normals.is_some())
330 .with_texcoords(mesh.uvs.is_some())
331 .with_comment("Generated by ThreeCrate with attribute preservation");
332
333 obj::RobustObjWriter::write_obj_file(&obj_data, path, &obj_options)
334 }
335
336 fn write_ply_extended<P: AsRef<Path>>(
338 mesh: &ExtendedTriangleMesh,
339 path: P,
340 options: &SerializationOptions,
341 ) -> Result<()> {
342 let mut ply_options = ply::PlyWriteOptions::ascii()
343 .with_normals(mesh.mesh.normals.is_some())
344 .with_comment("Generated by ThreeCrate with attribute preservation");
345
346 if let Some(ref uvs) = mesh.uvs {
348 let u_values: Vec<ply::PlyValue> = uvs.iter().map(|uv| ply::PlyValue::Float(uv[0])).collect();
349 let v_values: Vec<ply::PlyValue> = uvs.iter().map(|uv| ply::PlyValue::Float(uv[1])).collect();
350 ply_options = ply_options
351 .with_custom_vertex_property("u", u_values)
352 .with_custom_vertex_property("v", v_values);
353 }
354
355 if let Some(ref tangents) = mesh.tangents {
357 let tx_values: Vec<ply::PlyValue> = tangents.iter().map(|t| ply::PlyValue::Float(t.vector.x)).collect();
358 let ty_values: Vec<ply::PlyValue> = tangents.iter().map(|t| ply::PlyValue::Float(t.vector.y)).collect();
359 let tz_values: Vec<ply::PlyValue> = tangents.iter().map(|t| ply::PlyValue::Float(t.vector.z)).collect();
360 let th_values: Vec<ply::PlyValue> = tangents.iter().map(|t| ply::PlyValue::Float(t.handedness)).collect();
361
362 ply_options = ply_options
363 .with_custom_vertex_property("tx", tx_values)
364 .with_custom_vertex_property("ty", ty_values)
365 .with_custom_vertex_property("tz", tz_values)
366 .with_custom_vertex_property("th", th_values);
367 }
368
369 for (name, values) in &options.custom_properties {
371 let ply_values: Vec<ply::PlyValue> = values.iter().map(|&v| ply::PlyValue::Float(v)).collect();
372 ply_options = ply_options.with_custom_vertex_property(name, ply_values);
373 }
374
375 ply::RobustPlyWriter::write_mesh(&mesh.mesh, path, &ply_options)
376 }
377
378 fn extended_mesh_to_obj_data(
380 mesh: &ExtendedTriangleMesh,
381 _options: &SerializationOptions,
382 ) -> Result<obj::ObjData> {
383 let mut obj_data = obj::ObjData {
384 vertices: mesh.mesh.vertices.clone(),
385 texture_coords: Vec::new(),
386 normals: mesh.mesh.normals.clone().unwrap_or_default(),
387 groups: Vec::new(),
388 materials: HashMap::new(),
389 mtl_files: Vec::new(),
390 };
391
392 if let Some(ref uvs) = mesh.uvs {
394 obj_data.texture_coords = uvs.iter().map(|&uv| uv).collect();
395 }
396
397 let mut faces = Vec::new();
399 for &face_indices in &mesh.mesh.faces {
400 let mut face_vertices = Vec::new();
401 for &vertex_idx in &face_indices {
402 let face_vertex = obj::FaceVertex {
403 vertex: vertex_idx,
404 texture: if mesh.uvs.is_some() { Some(vertex_idx) } else { None },
405 normal: if mesh.mesh.normals.is_some() { Some(vertex_idx) } else { None },
406 };
407 face_vertices.push(face_vertex);
408 }
409
410 faces.push(obj::Face {
411 vertices: face_vertices,
412 material: None,
413 });
414 }
415
416 obj_data.groups.push(obj::Group {
418 name: "default".to_string(),
419 faces,
420 });
421
422 Ok(obj_data)
423 }
424}
425
426pub mod utils {
428 use super::*;
429
430 pub fn read_mesh_with_attributes<P: AsRef<Path>>(
432 path: P,
433 options: Option<SerializationOptions>,
434 ) -> Result<ExtendedTriangleMesh> {
435 let options = options.unwrap_or_default();
436 AttributePreservingReader::read_extended_mesh(path, &options)
437 }
438
439 pub fn write_mesh_with_attributes<P: AsRef<Path>>(
441 mesh: &ExtendedTriangleMesh,
442 path: P,
443 options: Option<SerializationOptions>,
444 ) -> Result<()> {
445 let options = options.unwrap_or_default();
446 AttributePreservingWriter::write_extended_mesh(mesh, path, &options)
447 }
448
449 pub fn test_round_trip<P: AsRef<Path>>(
451 input_path: P,
452 output_path: P,
453 options: Option<SerializationOptions>,
454 ) -> Result<(ExtendedTriangleMesh, Vec<String>)> {
455 let options = options.unwrap_or(SerializationOptions::preserve_all());
456
457 let mesh = read_mesh_with_attributes(input_path, Some(options.clone()))?;
459
460 write_mesh_with_attributes(&mesh, output_path, Some(options))?;
462
463 let warnings = crate::mesh_attributes::utils::validate_round_trip(&mesh)?;
465
466 Ok((mesh, warnings))
467 }
468
469 pub fn prepare_mesh_for_format(
471 mesh: &mut ExtendedTriangleMesh,
472 format: &str,
473 ) -> Result<()> {
474 crate::mesh_attributes::utils::prepare_for_serialization(mesh, format)
475 }
476
477 pub fn compare_meshes(
479 original: &ExtendedTriangleMesh,
480 loaded: &ExtendedTriangleMesh,
481 ) -> Result<Vec<String>> {
482 let mut differences = Vec::new();
483
484 if original.vertex_count() != loaded.vertex_count() {
485 differences.push(format!(
486 "Vertex count mismatch: {} vs {}",
487 original.vertex_count(),
488 loaded.vertex_count()
489 ));
490 }
491
492 if original.face_count() != loaded.face_count() {
493 differences.push(format!(
494 "Face count mismatch: {} vs {}",
495 original.face_count(),
496 loaded.face_count()
497 ));
498 }
499
500 if original.vertex_count() == loaded.vertex_count() {
502 for (i, (v1, v2)) in original.mesh.vertices.iter()
503 .zip(loaded.mesh.vertices.iter())
504 .enumerate()
505 {
506 let diff = (v1.x - v2.x).abs() + (v1.y - v2.y).abs() + (v1.z - v2.z).abs();
507 if diff > 1e-5 {
508 differences.push(format!("Vertex {} differs by {}", i, diff));
509 break; }
511 }
512 }
513
514 match (&original.mesh.normals, &loaded.mesh.normals) {
516 (Some(n1), Some(n2)) => {
517 if n1.len() != n2.len() {
518 differences.push("Normal count mismatch".to_string());
519 }
520 }
521 (Some(_), None) => differences.push("Normals lost during round-trip".to_string()),
522 (None, Some(_)) => differences.push("Normals added during round-trip".to_string()),
523 (None, None) => {} }
525
526 match (&original.uvs, &loaded.uvs) {
528 (Some(uv1), Some(uv2)) => {
529 if uv1.len() != uv2.len() {
530 differences.push("UV count mismatch".to_string());
531 }
532 }
533 (Some(_), None) => differences.push("UVs lost during round-trip".to_string()),
534 (None, Some(_)) => differences.push("UVs added during round-trip".to_string()),
535 (None, None) => {} }
537
538 match (&original.tangents, &loaded.tangents) {
540 (Some(t1), Some(t2)) => {
541 if t1.len() != t2.len() {
542 differences.push("Tangent count mismatch".to_string());
543 }
544 }
545 (Some(_), None) => differences.push("Tangents lost during round-trip".to_string()),
546 (None, Some(_)) => differences.push("Tangents added during round-trip".to_string()),
547 (None, None) => {} }
549
550 Ok(differences)
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use std::fs;
558
559 fn create_test_extended_mesh() -> ExtendedTriangleMesh {
560 let vertices = vec![
561 Point3f::new(0.0, 0.0, 0.0),
562 Point3f::new(1.0, 0.0, 0.0),
563 Point3f::new(0.5, 1.0, 0.0),
564 ];
565 let faces = vec![[0, 1, 2]];
566 let base_mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
567
568 let mut extended = ExtendedTriangleMesh::from_mesh(base_mesh);
569
570 extended.compute_normals(true, true).unwrap();
572 extended.generate_default_uvs().unwrap();
573 extended.compute_tangents(true).unwrap();
574
575 extended
576 }
577
578 #[test]
579 fn test_serialization_options() {
580 let options = SerializationOptions::preserve_all();
581 assert!(options.attributes.recompute_normals);
582 assert!(options.attributes.recompute_tangents);
583 assert!(options.validate_before_write);
584
585 let fast_options = SerializationOptions::fast();
586 assert!(!fast_options.validate_before_write);
587 assert!(!fast_options.preserve_custom_properties);
588 }
589
590 #[test]
591 fn test_obj_round_trip() {
592 let mesh = create_test_extended_mesh();
593 let temp_file = "test_obj_round_trip.obj";
594
595 let options = SerializationOptions::preserve_all();
596
597 AttributePreservingWriter::write_extended_mesh(&mesh, temp_file, &options).unwrap();
599
600 let loaded_mesh = AttributePreservingReader::read_extended_mesh(temp_file, &options).unwrap();
602
603 let differences = utils::compare_meshes(&mesh, &loaded_mesh).unwrap();
605 assert!(differences.is_empty(), "Round-trip differences: {:#?}", differences);
606
607 let _ = fs::remove_file(temp_file);
609 }
610
611 #[test]
612 fn test_ply_round_trip() {
613 let mesh = create_test_extended_mesh();
614 let temp_file = "test_ply_round_trip.ply";
615
616 let options = SerializationOptions::preserve_all();
617
618 AttributePreservingWriter::write_extended_mesh(&mesh, temp_file, &options).unwrap();
620
621 let loaded_mesh = AttributePreservingReader::read_extended_mesh(temp_file, &options).unwrap();
623
624 let differences = utils::compare_meshes(&mesh, &loaded_mesh).unwrap();
626 println!("PLY round-trip differences: {:#?}", differences);
628
629 let _ = fs::remove_file(temp_file);
631 }
632
633 #[test]
634 fn test_attribute_validation() {
635 let mesh = create_test_extended_mesh();
636 let warnings = crate::mesh_attributes::utils::validate_round_trip(&mesh).unwrap();
637
638 assert!(warnings.len() <= 1, "Too many warnings: {:#?}", warnings);
640 }
641
642 #[test]
643 fn test_mesh_comparison() {
644 let mesh1 = create_test_extended_mesh();
645 let mesh2 = create_test_extended_mesh();
646
647 let differences = utils::compare_meshes(&mesh1, &mesh2).unwrap();
648 assert!(differences.is_empty(), "Identical meshes should have no differences");
649 }
650}