use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use crate::error::Result;
#[cfg(feature = "rayon")]
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
pub fn write_obj(
nodes: &[[f64; 3]],
cells: &[[usize; 3]],
filename: Option<&str>,
) -> Result<()> {
let file = File::create(filename.unwrap_or("mesh.obj"))?;
let mut writer = BufWriter::new(file);
writeln!(writer, "# OBJ file generated by Rust mesh library")?;
writeln!(writer, "# {} vertices, {} faces", nodes.len(), cells.len())?;
for node in nodes {
writeln!(writer, "v {} {} {}", node[0], node[1], node[2])?;
}
let process_cell = |cell: &[usize; 3]| -> String {
format!("f {} {} {}", cell[0] + 1, cell[1] + 1, cell[2] + 1)
};
#[cfg(feature = "rayon")]
let face_strings: Vec<String> = cells.par_iter().map(process_cell).collect();
#[cfg(not(feature = "rayon"))]
let face_strings: Vec<String> = cells.iter().map(process_cell).collect();
for face_str in face_strings {
writeln!(writer, "{}", face_str)?;
}
Ok(())
}
pub fn read_obj(filename: &str) -> Result<(Vec<[f64; 3]>, Vec<[usize; 3]>)> {
let file = File::open(filename)?;
let reader = BufReader::new(file);
let mut nodes = Vec::new();
let mut cells = Vec::new();
for line in reader.lines() {
let line = line?;
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
continue;
}
match parts[0] {
"v" => {
if parts.len() >= 4 {
let x = parts[1].parse::<f64>().unwrap_or(0.0);
let y = parts[2].parse::<f64>().unwrap_or(0.0);
let z = parts[3].parse::<f64>().unwrap_or(0.0);
nodes.push([x, y, z]);
}
}
"f" => {
if parts.len() >= 4 {
let mut face_indices = Vec::new();
for part in &parts[1..] {
let index_str = part.split('/').next().unwrap_or("");
if let Ok(index) = index_str.parse::<usize>() {
face_indices.push(index - 1);
}
}
if face_indices.len() == 3 {
cells.push([face_indices[0], face_indices[1], face_indices[2]]);
} else if face_indices.len() == 4 {
cells.push([face_indices[0], face_indices[1], face_indices[2]]);
cells.push([face_indices[0], face_indices[2], face_indices[3]]);
}
}
}
_ => {}
}
}
Ok((nodes, cells))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::{remove_file, read_to_string};
use std::io::Write;
fn create_temp_file(content: &str) -> String {
let filename = format!(
"temp_{}.obj",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
let mut file = File::create(&filename).unwrap();
writeln!(file, "{}", content).unwrap();
filename
}
#[test]
fn test_write_obj_simple() {
let nodes = vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
let cells = vec![[0, 1, 2]];
let filename = "test_write_simple.obj";
let result = write_obj(&nodes, &cells, Some(filename));
assert!(result.is_ok());
let content = read_to_string(filename).unwrap();
assert!(content.contains("v 1 2 3"));
assert!(content.contains("v 4 5 6"));
assert!(content.contains("v 7 8 9"));
assert!(content.contains("f 1 2 3"));
remove_file(filename).unwrap();
}
#[test]
fn test_read_obj_simple() {
let obj_content = "
# Test comment
v 1.0 0.0 0.0
v 0.0 1.0 0.0
v 0.0 0.0 1.0
f 1 2 3
";
let filename = create_temp_file(obj_content);
let (nodes, cells) = read_obj(&filename).unwrap();
assert_eq!(nodes.len(), 3);
assert_eq!(cells.len(), 1);
assert_eq!(nodes[0], [1.0, 0.0, 0.0]);
assert_eq!(cells[0], [0, 1, 2]);
remove_file(filename).unwrap();
}
#[test]
fn test_read_obj_complex_faces() {
let obj_content = "
v 1.0 0.0 0.0
v 0.0 1.0 0.0
v 0.0 0.0 1.0
vt 0.0 0.0
vn 0.0 1.0 0.0
f 1/1/1 2/1/1 3/1/1
";
let filename = create_temp_file(obj_content);
let (nodes, cells) = read_obj(&filename).unwrap();
assert_eq!(nodes.len(), 3);
assert_eq!(cells.len(), 1);
assert_eq!(cells[0], [0, 1, 2], "Should correctly parse v/vt/vn format");
remove_file(filename).unwrap();
}
#[test]
fn test_read_obj_quad_triangulation() {
let obj_content = "
v 0.0 0.0 0.0
v 1.0 0.0 0.0
v 1.0 1.0 0.0
v 0.0 1.0 0.0
# A single quad face
f 1 2 3 4
";
let filename = create_temp_file(obj_content);
let (_, cells) = read_obj(&filename).unwrap();
assert_eq!(
cells.len(),
2,
"A quad should be triangulated into two faces"
);
assert_eq!(cells[0], [0, 1, 2]);
assert_eq!(cells[1], [0, 2, 3]);
remove_file(filename).unwrap();
}
}