use crate::core::{Point, PointCloud, PointXYZ};
use crate::error::{CloudError, Result};
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::Path;
pub fn load_ply<P: AsRef<Path>>(path: P) -> Result<PointCloud<PointXYZ>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
parse_ply(reader)
}
pub fn save_ply<P: Point, Q: AsRef<Path>>(cloud: &PointCloud<P>, path: Q) -> Result<()> {
let file = File::create(path)?;
let writer = BufWriter::new(file);
write_ply(cloud, writer)
}
fn parse_ply<R: BufRead>(reader: R) -> Result<PointCloud<PointXYZ>> {
let mut lines = reader.lines();
let mut points = Vec::new();
let mut vertex_count = 0usize;
let mut in_header = true;
while let Some(line) = lines.next() {
let line = line?;
let line = line.trim();
if line.starts_with("ply") {
continue;
} else if line.starts_with("format") {
if !line.contains("ascii") {
return Err(CloudError::format_error(
"Only ASCII PLY format is supported",
));
}
} else if line.starts_with("element vertex") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
vertex_count = parts[2]
.parse()
.map_err(|_| CloudError::format_error("Invalid vertex count"))?;
}
} else if line.starts_with("property") {
continue;
} else if line.starts_with("end_header") {
in_header = false;
break;
}
}
if in_header {
return Err(CloudError::format_error("No end_header found"));
}
points.reserve(vertex_count);
for line in lines.take(vertex_count) {
let line = line?;
let line = line.trim();
if line.is_empty() {
continue;
}
let coords: Vec<&str> = line.split_whitespace().collect();
if coords.len() >= 3 {
let x: f32 = coords[0]
.parse()
.map_err(|_| CloudError::format_error("Invalid x coordinate"))?;
let y: f32 = coords[1]
.parse()
.map_err(|_| CloudError::format_error("Invalid y coordinate"))?;
let z: f32 = coords[2]
.parse()
.map_err(|_| CloudError::format_error("Invalid z coordinate"))?;
points.push(PointXYZ::new(x, y, z));
}
}
Ok(PointCloud::from_points(points))
}
fn write_ply<P: Point, W: Write>(cloud: &PointCloud<P>, mut writer: W) -> Result<()> {
writeln!(writer, "ply")?;
writeln!(writer, "format ascii 1.0")?;
writeln!(writer, "element vertex {}", cloud.len())?;
writeln!(writer, "property float x")?;
writeln!(writer, "property float y")?;
writeln!(writer, "property float z")?;
writeln!(writer, "end_header")?;
for point in cloud.iter() {
let pos = point.position();
writeln!(writer, "{} {} {}", pos[0], pos[1], pos[2])?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_ply_parsing() {
let ply_data = r#"ply
format ascii 1.0
element vertex 3
property float x
property float y
property float z
end_header
0.0 0.0 0.0
1.0 1.0 1.0
2.0 2.0 2.0
"#;
let cursor = Cursor::new(ply_data);
let cloud = parse_ply(cursor).unwrap();
assert_eq!(cloud.len(), 3);
assert_eq!(cloud.get(0).unwrap().position(), [0.0, 0.0, 0.0]);
assert_eq!(cloud.get(1).unwrap().position(), [1.0, 1.0, 1.0]);
assert_eq!(cloud.get(2).unwrap().position(), [2.0, 2.0, 2.0]);
}
#[test]
fn test_ply_writing() {
let points = vec![PointXYZ::new(0.0, 0.0, 0.0), PointXYZ::new(1.0, 1.0, 1.0)];
let cloud = PointCloud::from_points(points);
let mut buffer = Vec::new();
write_ply(&cloud, &mut buffer).unwrap();
let output = String::from_utf8(buffer).unwrap();
assert!(output.contains("element vertex 2"));
assert!(output.contains("0 0 0"));
assert!(output.contains("1 1 1"));
}
}