Skip to main content

threecrate_io/
ply.rs

1//! Robust PLY format support
2//!
3//! This module provides comprehensive PLY (Polygon File Format) reading and writing
4//! capabilities including:
5//! - ASCII and binary (little/big endian) format support
6//! - Vertex, face, normal, color, and generic property parsing
7//! - Metadata and comment preservation
8//! - Streaming support for large files
9//! - Structured error handling
10
11#[cfg(feature = "io-mmap")]
12use crate::mmap::MmapReader;
13use crate::{MeshReader, MeshWriter, PointCloudReader, PointCloudWriter};
14use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
15use std::collections::HashMap;
16use std::fs::File;
17use std::io::{BufRead, BufReader, BufWriter, Read};
18use std::path::Path;
19use threecrate_core::{Error, Point3f, PointCloud, Result, TriangleMesh, Vector3f};
20
21/// PLY file format variants
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum PlyFormat {
24    Ascii,
25    BinaryLittleEndian,
26    BinaryBigEndian,
27}
28
29/// PLY property data types
30#[derive(Debug, Clone, PartialEq)]
31pub enum PlyPropertyType {
32    Char,
33    UChar,
34    Short,
35    UShort,
36    Int,
37    UInt,
38    Float,
39    Double,
40    List(Box<PlyPropertyType>, Box<PlyPropertyType>), // count type, item type
41}
42
43/// PLY property definition
44#[derive(Debug, Clone)]
45pub struct PlyProperty {
46    pub name: String,
47    pub property_type: PlyPropertyType,
48}
49
50/// PLY element definition
51#[derive(Debug, Clone)]
52pub struct PlyElement {
53    pub name: String,
54    pub count: usize,
55    pub properties: Vec<PlyProperty>,
56}
57
58/// PLY property value
59#[derive(Debug, Clone)]
60pub enum PlyValue {
61    Char(i8),
62    UChar(u8),
63    Short(i16),
64    UShort(u16),
65    Int(i32),
66    UInt(u32),
67    Float(f32),
68    Double(f64),
69    List(Vec<PlyValue>),
70}
71
72/// PLY header information
73#[derive(Debug, Clone)]
74pub struct PlyHeader {
75    pub format: PlyFormat,
76    pub version: String,
77    pub elements: Vec<PlyElement>,
78    pub comments: Vec<String>,
79    pub obj_info: Vec<String>,
80}
81
82/// Complete PLY file data
83#[derive(Debug)]
84pub struct PlyData {
85    pub header: PlyHeader,
86    pub elements: HashMap<String, Vec<HashMap<String, PlyValue>>>,
87}
88
89/// Enhanced PLY reader with comprehensive format support
90pub struct RobustPlyReader;
91
92/// PLY writer configuration options
93#[derive(Debug, Clone)]
94pub struct PlyWriteOptions {
95    /// Output format (ASCII or binary)
96    pub format: PlyFormat,
97    /// Comments to include in the header
98    pub comments: Vec<String>,
99    /// Object info to include in the header
100    pub obj_info: Vec<String>,
101    /// Custom properties to include for vertices
102    pub custom_vertex_properties: Vec<(String, Vec<PlyValue>)>,
103    /// Custom properties to include for faces
104    pub custom_face_properties: Vec<(String, Vec<PlyValue>)>,
105    /// Whether to include normals if available
106    pub include_normals: bool,
107    /// Whether to include colors if available
108    pub include_colors: bool,
109    /// Custom property ordering for vertices
110    pub vertex_property_order: Option<Vec<String>>,
111}
112
113impl Default for PlyWriteOptions {
114    fn default() -> Self {
115        Self {
116            format: PlyFormat::Ascii,
117            comments: Vec::new(),
118            obj_info: Vec::new(),
119            custom_vertex_properties: Vec::new(),
120            custom_face_properties: Vec::new(),
121            include_normals: true,
122            include_colors: false,
123            vertex_property_order: None,
124        }
125    }
126}
127
128impl PlyWriteOptions {
129    /// Create new options with ASCII format
130    pub fn ascii() -> Self {
131        Self {
132            format: PlyFormat::Ascii,
133            ..Default::default()
134        }
135    }
136
137    /// Create new options with binary little endian format
138    pub fn binary_little_endian() -> Self {
139        Self {
140            format: PlyFormat::BinaryLittleEndian,
141            ..Default::default()
142        }
143    }
144
145    /// Create new options with binary big endian format
146    pub fn binary_big_endian() -> Self {
147        Self {
148            format: PlyFormat::BinaryBigEndian,
149            ..Default::default()
150        }
151    }
152
153    /// Add a comment to the header
154    pub fn with_comment<S: Into<String>>(mut self, comment: S) -> Self {
155        self.comments.push(comment.into());
156        self
157    }
158
159    /// Add object info to the header
160    pub fn with_obj_info<S: Into<String>>(mut self, info: S) -> Self {
161        self.obj_info.push(info.into());
162        self
163    }
164
165    /// Include normals in output
166    pub fn with_normals(mut self, include: bool) -> Self {
167        self.include_normals = include;
168        self
169    }
170
171    /// Include colors in output
172    pub fn with_colors(mut self, include: bool) -> Self {
173        self.include_colors = include;
174        self
175    }
176
177    /// Set custom vertex property ordering
178    pub fn with_vertex_property_order(mut self, order: Vec<String>) -> Self {
179        self.vertex_property_order = Some(order);
180        self
181    }
182
183    /// Add custom vertex property
184    pub fn with_custom_vertex_property<S: Into<String>>(
185        mut self,
186        name: S,
187        values: Vec<PlyValue>,
188    ) -> Self {
189        self.custom_vertex_properties.push((name.into(), values));
190        self
191    }
192}
193
194/// Enhanced PLY writer with comprehensive format support
195pub struct RobustPlyWriter;
196
197impl RobustPlyWriter {
198    /// Write point cloud to PLY file with options
199    pub fn write_point_cloud<P: AsRef<Path>>(
200        cloud: &PointCloud<Point3f>,
201        path: P,
202        options: &PlyWriteOptions,
203    ) -> Result<()> {
204        let file = File::create(path)?;
205        let mut writer = BufWriter::new(file);
206        Self::write_point_cloud_to_writer(cloud, &mut writer, options)
207    }
208
209    /// Write point cloud to writer with options
210    pub fn write_point_cloud_to_writer<W: std::io::Write>(
211        cloud: &PointCloud<Point3f>,
212        writer: &mut W,
213        options: &PlyWriteOptions,
214    ) -> Result<()> {
215        // Build PLY data structure
216        let mut ply_data = PlyData {
217            header: PlyHeader {
218                format: options.format,
219                version: "1.0".to_string(),
220                elements: Vec::new(),
221                comments: options.comments.clone(),
222                obj_info: options.obj_info.clone(),
223            },
224            elements: HashMap::new(),
225        };
226
227        // Build vertex element definition
228        let mut vertex_properties = Vec::new();
229        let mut vertex_data = Vec::new();
230
231        // Always include x, y, z
232        vertex_properties.push(PlyProperty {
233            name: "x".to_string(),
234            property_type: PlyPropertyType::Float,
235        });
236        vertex_properties.push(PlyProperty {
237            name: "y".to_string(),
238            property_type: PlyPropertyType::Float,
239        });
240        vertex_properties.push(PlyProperty {
241            name: "z".to_string(),
242            property_type: PlyPropertyType::Float,
243        });
244
245        // Add custom properties if specified
246        for (prop_name, _) in &options.custom_vertex_properties {
247            // Determine property type from first value
248            if let Some(first_values) = options
249                .custom_vertex_properties
250                .iter()
251                .find(|(name, _)| name == prop_name)
252                .map(|(_, values)| values)
253            {
254                if let Some(first_value) = first_values.first() {
255                    let prop_type = Self::value_to_property_type(first_value);
256                    vertex_properties.push(PlyProperty {
257                        name: prop_name.clone(),
258                        property_type: prop_type,
259                    });
260                }
261            }
262        }
263
264        // Reorder properties if custom order is specified
265        if let Some(order) = &options.vertex_property_order {
266            vertex_properties.sort_by_key(|prop| {
267                order
268                    .iter()
269                    .position(|name| name == &prop.name)
270                    .unwrap_or(order.len())
271            });
272        }
273
274        // Build vertex data
275        for (i, point) in cloud.iter().enumerate() {
276            let mut vertex_instance = HashMap::new();
277            vertex_instance.insert("x".to_string(), PlyValue::Float(point.x));
278            vertex_instance.insert("y".to_string(), PlyValue::Float(point.y));
279            vertex_instance.insert("z".to_string(), PlyValue::Float(point.z));
280
281            // Add custom properties
282            for (prop_name, values) in &options.custom_vertex_properties {
283                if i < values.len() {
284                    vertex_instance.insert(prop_name.clone(), values[i].clone());
285                }
286            }
287
288            vertex_data.push(vertex_instance);
289        }
290
291        // Add vertex element
292        ply_data.header.elements.push(PlyElement {
293            name: "vertex".to_string(),
294            count: cloud.len(),
295            properties: vertex_properties,
296        });
297        ply_data.elements.insert("vertex".to_string(), vertex_data);
298
299        // Write PLY data
300        Self::write_ply_data(writer, &ply_data)
301    }
302
303    /// Write triangle mesh to PLY file with options
304    pub fn write_mesh<P: AsRef<Path>>(
305        mesh: &TriangleMesh,
306        path: P,
307        options: &PlyWriteOptions,
308    ) -> Result<()> {
309        let file = File::create(path)?;
310        let mut writer = BufWriter::new(file);
311        Self::write_mesh_to_writer(mesh, &mut writer, options)
312    }
313
314    /// Write triangle mesh to writer with options
315    pub fn write_mesh_to_writer<W: std::io::Write>(
316        mesh: &TriangleMesh,
317        writer: &mut W,
318        options: &PlyWriteOptions,
319    ) -> Result<()> {
320        // Build PLY data structure
321        let mut ply_data = PlyData {
322            header: PlyHeader {
323                format: options.format,
324                version: "1.0".to_string(),
325                elements: Vec::new(),
326                comments: options.comments.clone(),
327                obj_info: options.obj_info.clone(),
328            },
329            elements: HashMap::new(),
330        };
331
332        // Build vertex element
333        let mut vertex_properties = Vec::new();
334        let mut vertex_data = Vec::new();
335
336        // Always include x, y, z
337        vertex_properties.push(PlyProperty {
338            name: "x".to_string(),
339            property_type: PlyPropertyType::Float,
340        });
341        vertex_properties.push(PlyProperty {
342            name: "y".to_string(),
343            property_type: PlyPropertyType::Float,
344        });
345        vertex_properties.push(PlyProperty {
346            name: "z".to_string(),
347            property_type: PlyPropertyType::Float,
348        });
349
350        // Add normals if requested and available
351        if options.include_normals && mesh.normals.is_some() {
352            vertex_properties.push(PlyProperty {
353                name: "nx".to_string(),
354                property_type: PlyPropertyType::Float,
355            });
356            vertex_properties.push(PlyProperty {
357                name: "ny".to_string(),
358                property_type: PlyPropertyType::Float,
359            });
360            vertex_properties.push(PlyProperty {
361                name: "nz".to_string(),
362                property_type: PlyPropertyType::Float,
363            });
364        }
365
366        // Add custom vertex properties
367        for (prop_name, _) in &options.custom_vertex_properties {
368            if let Some(first_values) = options
369                .custom_vertex_properties
370                .iter()
371                .find(|(name, _)| name == prop_name)
372                .map(|(_, values)| values)
373            {
374                if let Some(first_value) = first_values.first() {
375                    let prop_type = Self::value_to_property_type(first_value);
376                    vertex_properties.push(PlyProperty {
377                        name: prop_name.clone(),
378                        property_type: prop_type,
379                    });
380                }
381            }
382        }
383
384        // Reorder properties if specified
385        if let Some(order) = &options.vertex_property_order {
386            vertex_properties.sort_by_key(|prop| {
387                order
388                    .iter()
389                    .position(|name| name == &prop.name)
390                    .unwrap_or(order.len())
391            });
392        }
393
394        // Build vertex data
395        for (i, vertex) in mesh.vertices.iter().enumerate() {
396            let mut vertex_instance = HashMap::new();
397            vertex_instance.insert("x".to_string(), PlyValue::Float(vertex.x));
398            vertex_instance.insert("y".to_string(), PlyValue::Float(vertex.y));
399            vertex_instance.insert("z".to_string(), PlyValue::Float(vertex.z));
400
401            // Add normals if available and requested
402            if options.include_normals {
403                if let Some(normals) = &mesh.normals {
404                    if i < normals.len() {
405                        vertex_instance.insert("nx".to_string(), PlyValue::Float(normals[i].x));
406                        vertex_instance.insert("ny".to_string(), PlyValue::Float(normals[i].y));
407                        vertex_instance.insert("nz".to_string(), PlyValue::Float(normals[i].z));
408                    }
409                }
410            }
411
412            // Add custom properties
413            for (prop_name, values) in &options.custom_vertex_properties {
414                if i < values.len() {
415                    vertex_instance.insert(prop_name.clone(), values[i].clone());
416                }
417            }
418
419            vertex_data.push(vertex_instance);
420        }
421
422        // Add vertex element
423        ply_data.header.elements.push(PlyElement {
424            name: "vertex".to_string(),
425            count: mesh.vertices.len(),
426            properties: vertex_properties,
427        });
428        ply_data.elements.insert("vertex".to_string(), vertex_data);
429
430        // Build face element
431        if !mesh.faces.is_empty() {
432            let face_properties = vec![PlyProperty {
433                name: "vertex_indices".to_string(),
434                property_type: PlyPropertyType::List(
435                    Box::new(PlyPropertyType::UChar),
436                    Box::new(PlyPropertyType::Int),
437                ),
438            }];
439
440            let mut face_data = Vec::new();
441            for face in &mesh.faces {
442                let mut face_instance = HashMap::new();
443                let indices = vec![
444                    PlyValue::Int(face[0] as i32),
445                    PlyValue::Int(face[1] as i32),
446                    PlyValue::Int(face[2] as i32),
447                ];
448                face_instance.insert("vertex_indices".to_string(), PlyValue::List(indices));
449                face_data.push(face_instance);
450            }
451
452            ply_data.header.elements.push(PlyElement {
453                name: "face".to_string(),
454                count: mesh.faces.len(),
455                properties: face_properties,
456            });
457            ply_data.elements.insert("face".to_string(), face_data);
458        }
459
460        // Write PLY data
461        Self::write_ply_data(writer, &ply_data)
462    }
463
464    /// Write PLY data to writer
465    fn write_ply_data<W: std::io::Write>(writer: &mut W, ply_data: &PlyData) -> Result<()> {
466        // Write header
467        Self::write_header(writer, &ply_data.header)?;
468
469        // Write element data
470        match ply_data.header.format {
471            PlyFormat::Ascii => Self::write_ascii_data(writer, ply_data)?,
472            PlyFormat::BinaryLittleEndian => {
473                Self::write_binary_data::<LittleEndian, _>(writer, ply_data)?
474            }
475            PlyFormat::BinaryBigEndian => {
476                Self::write_binary_data::<BigEndian, _>(writer, ply_data)?
477            }
478        }
479
480        Ok(())
481    }
482
483    /// Write PLY header
484    fn write_header<W: std::io::Write>(writer: &mut W, header: &PlyHeader) -> Result<()> {
485        writeln!(writer, "ply")?;
486
487        let format_str = match header.format {
488            PlyFormat::Ascii => "ascii",
489            PlyFormat::BinaryLittleEndian => "binary_little_endian",
490            PlyFormat::BinaryBigEndian => "binary_big_endian",
491        };
492        writeln!(writer, "format {} {}", format_str, header.version)?;
493
494        // Write comments
495        for comment in &header.comments {
496            writeln!(writer, "comment {}", comment)?;
497        }
498
499        // Write obj_info
500        for info in &header.obj_info {
501            writeln!(writer, "obj_info {}", info)?;
502        }
503
504        // Write elements and properties
505        for element in &header.elements {
506            writeln!(writer, "element {} {}", element.name, element.count)?;
507            for property in &element.properties {
508                Self::write_property_definition(writer, property)?;
509            }
510        }
511
512        writeln!(writer, "end_header")?;
513        Ok(())
514    }
515
516    /// Write property definition
517    fn write_property_definition<W: std::io::Write>(
518        writer: &mut W,
519        property: &PlyProperty,
520    ) -> Result<()> {
521        match &property.property_type {
522            PlyPropertyType::List(count_type, item_type) => {
523                let count_str = Self::property_type_to_string(count_type);
524                let item_str = Self::property_type_to_string(item_type);
525                writeln!(
526                    writer,
527                    "property list {} {} {}",
528                    count_str, item_str, property.name
529                )?;
530            }
531            _ => {
532                let type_str = Self::property_type_to_string(&property.property_type);
533                writeln!(writer, "property {} {}", type_str, property.name)?;
534            }
535        }
536        Ok(())
537    }
538
539    /// Convert property type to string
540    fn property_type_to_string(prop_type: &PlyPropertyType) -> &'static str {
541        match prop_type {
542            PlyPropertyType::Char => "char",
543            PlyPropertyType::UChar => "uchar",
544            PlyPropertyType::Short => "short",
545            PlyPropertyType::UShort => "ushort",
546            PlyPropertyType::Int => "int",
547            PlyPropertyType::UInt => "uint",
548            PlyPropertyType::Float => "float",
549            PlyPropertyType::Double => "double",
550            PlyPropertyType::List(_, _) => "list", // Should not be called directly for lists
551        }
552    }
553
554    /// Write ASCII format data
555    fn write_ascii_data<W: std::io::Write>(writer: &mut W, ply_data: &PlyData) -> Result<()> {
556        for element_def in &ply_data.header.elements {
557            if let Some(element_data) = ply_data.elements.get(&element_def.name) {
558                for instance in element_data {
559                    let mut values = Vec::new();
560
561                    for property in &element_def.properties {
562                        if let Some(value) = instance.get(&property.name) {
563                            Self::format_ascii_value(value, &mut values)?;
564                        }
565                    }
566
567                    writeln!(writer, "{}", values.join(" "))?;
568                }
569            }
570        }
571        Ok(())
572    }
573
574    /// Format a value for ASCII output
575    fn format_ascii_value(value: &PlyValue, output: &mut Vec<String>) -> Result<()> {
576        match value {
577            PlyValue::Char(v) => output.push(v.to_string()),
578            PlyValue::UChar(v) => output.push(v.to_string()),
579            PlyValue::Short(v) => output.push(v.to_string()),
580            PlyValue::UShort(v) => output.push(v.to_string()),
581            PlyValue::Int(v) => output.push(v.to_string()),
582            PlyValue::UInt(v) => output.push(v.to_string()),
583            PlyValue::Float(v) => output.push(v.to_string()),
584            PlyValue::Double(v) => output.push(v.to_string()),
585            PlyValue::List(values) => {
586                output.push(values.len().to_string());
587                for item in values {
588                    Self::format_ascii_value(item, output)?;
589                }
590            }
591        }
592        Ok(())
593    }
594
595    /// Write binary format data
596    fn write_binary_data<E: byteorder::ByteOrder, W: std::io::Write>(
597        writer: &mut W,
598        ply_data: &PlyData,
599    ) -> Result<()> {
600        for element_def in &ply_data.header.elements {
601            if let Some(element_data) = ply_data.elements.get(&element_def.name) {
602                for instance in element_data {
603                    for property in &element_def.properties {
604                        if let Some(value) = instance.get(&property.name) {
605                            Self::write_binary_value::<E, _>(writer, value)?;
606                        }
607                    }
608                }
609            }
610        }
611        Ok(())
612    }
613
614    /// Write a binary value
615    fn write_binary_value<E: byteorder::ByteOrder, W: std::io::Write>(
616        writer: &mut W,
617        value: &PlyValue,
618    ) -> Result<()> {
619        use byteorder::WriteBytesExt;
620
621        match value {
622            PlyValue::Char(v) => writer.write_i8(*v)?,
623            PlyValue::UChar(v) => writer.write_u8(*v)?,
624            PlyValue::Short(v) => writer.write_i16::<E>(*v)?,
625            PlyValue::UShort(v) => writer.write_u16::<E>(*v)?,
626            PlyValue::Int(v) => writer.write_i32::<E>(*v)?,
627            PlyValue::UInt(v) => writer.write_u32::<E>(*v)?,
628            PlyValue::Float(v) => writer.write_f32::<E>(*v)?,
629            PlyValue::Double(v) => writer.write_f64::<E>(*v)?,
630            PlyValue::List(values) => {
631                // Write count as uchar (assuming list count type is uchar)
632                writer.write_u8(values.len() as u8)?;
633                for item in values {
634                    Self::write_binary_value::<E, _>(writer, item)?;
635                }
636            }
637        }
638        Ok(())
639    }
640
641    /// Determine property type from PLY value
642    fn value_to_property_type(value: &PlyValue) -> PlyPropertyType {
643        match value {
644            PlyValue::Char(_) => PlyPropertyType::Char,
645            PlyValue::UChar(_) => PlyPropertyType::UChar,
646            PlyValue::Short(_) => PlyPropertyType::Short,
647            PlyValue::UShort(_) => PlyPropertyType::UShort,
648            PlyValue::Int(_) => PlyPropertyType::Int,
649            PlyValue::UInt(_) => PlyPropertyType::UInt,
650            PlyValue::Float(_) => PlyPropertyType::Float,
651            PlyValue::Double(_) => PlyPropertyType::Double,
652            PlyValue::List(values) => {
653                let item_type = if let Some(first_item) = values.first() {
654                    Self::value_to_property_type(first_item)
655                } else {
656                    PlyPropertyType::Int
657                };
658                PlyPropertyType::List(Box::new(PlyPropertyType::UChar), Box::new(item_type))
659            }
660        }
661    }
662}
663
664impl RobustPlyReader {
665    /// Read a complete PLY file with all metadata and elements
666    pub fn read_ply_data<R: BufRead>(reader: &mut R) -> Result<PlyData> {
667        let header = Self::read_header(reader)?;
668        let elements = Self::read_elements(reader, &header)?;
669
670        Ok(PlyData { header, elements })
671    }
672
673    /// Read PLY file from path
674    pub fn read_ply_file<P: AsRef<Path>>(path: P) -> Result<PlyData> {
675        let path = path.as_ref();
676
677        // Try memory-mapped reading for binary files if feature is enabled
678        #[cfg(feature = "io-mmap")]
679        {
680            if let Some(ply_data) = Self::try_read_ply_mmap(path)? {
681                return Ok(ply_data);
682            }
683        }
684
685        // Fall back to standard buffered reading
686        let file = File::open(path)?;
687        let mut reader = BufReader::new(file);
688        Self::read_ply_data(&mut reader)
689    }
690
691    /// Try to read PLY file using memory mapping (binary files only)
692    #[cfg(feature = "io-mmap")]
693    fn try_read_ply_mmap<P: AsRef<Path>>(path: P) -> Result<Option<PlyData>> {
694        let path = path.as_ref();
695
696        // Check if we should use memory mapping
697        if !crate::mmap::should_use_mmap(path) {
698            return Ok(None);
699        }
700
701        // First, read the header using standard I/O to determine format
702        let file = File::open(path)?;
703        let mut reader = BufReader::new(file);
704        let header = Self::read_header(&mut reader)?;
705
706        // Only use mmap for binary formats
707        match header.format {
708            PlyFormat::BinaryLittleEndian | PlyFormat::BinaryBigEndian => {
709                // Calculate header size by reading until "end_header"
710                let file = File::open(path)?;
711                let mut reader = BufReader::new(file);
712                let mut header_size = 0;
713                let mut line = String::new();
714
715                loop {
716                    let line_start = header_size;
717                    line.clear();
718                    let bytes_read = reader.read_line(&mut line)?;
719                    if bytes_read == 0 {
720                        return Err(Error::InvalidData(
721                            "Unexpected end of file in header".to_string(),
722                        ));
723                    }
724                    header_size += bytes_read;
725
726                    if line.trim() == "end_header" {
727                        break;
728                    }
729                }
730
731                // Now use memory mapping for the data section
732                if let Some(mut mmap_reader) = MmapReader::new(path)? {
733                    // Skip to the data section
734                    mmap_reader.seek(header_size)?;
735
736                    // Read elements using memory mapping
737                    let elements = Self::read_elements_mmap(&mut mmap_reader, &header)?;
738
739                    return Ok(Some(PlyData { header, elements }));
740                }
741            }
742            PlyFormat::Ascii => {
743                // ASCII format - use standard buffered I/O
744                return Ok(None);
745            }
746        }
747
748        Ok(None)
749    }
750
751    /// Read elements using memory mapping
752    #[cfg(feature = "io-mmap")]
753    fn read_elements_mmap(
754        reader: &mut MmapReader,
755        header: &PlyHeader,
756    ) -> Result<HashMap<String, Vec<HashMap<String, PlyValue>>>> {
757        let mut elements = HashMap::new();
758
759        for element_def in &header.elements {
760            let mut element_data = Vec::with_capacity(crate::capacity_hint(element_def.count));
761
762            for _ in 0..element_def.count {
763                let mut instance = HashMap::new();
764
765                match header.format {
766                    PlyFormat::BinaryLittleEndian => {
767                        for property in &element_def.properties {
768                            let value = Self::read_binary_property_value_mmap_le(
769                                reader,
770                                &property.property_type,
771                            )?;
772                            instance.insert(property.name.clone(), value);
773                        }
774                    }
775                    PlyFormat::BinaryBigEndian => {
776                        for property in &element_def.properties {
777                            let value = Self::read_binary_property_value_mmap_be(
778                                reader,
779                                &property.property_type,
780                            )?;
781                            instance.insert(property.name.clone(), value);
782                        }
783                    }
784                    PlyFormat::Ascii => {
785                        return Err(Error::InvalidData(
786                            "ASCII format should not use mmap reader".to_string(),
787                        ));
788                    }
789                }
790
791                element_data.push(instance);
792            }
793
794            elements.insert(element_def.name.clone(), element_data);
795        }
796
797        Ok(elements)
798    }
799
800    /// Read binary property value using memory mapping (little endian)
801    #[cfg(feature = "io-mmap")]
802    fn read_binary_property_value_mmap_le(
803        reader: &mut MmapReader,
804        property_type: &PlyPropertyType,
805    ) -> Result<PlyValue> {
806        match property_type {
807            PlyPropertyType::Char => Ok(PlyValue::Char(reader.read_u8()? as i8)),
808            PlyPropertyType::UChar => Ok(PlyValue::UChar(reader.read_u8()?)),
809            PlyPropertyType::Short => Ok(PlyValue::Short(reader.read_u16_le()? as i16)),
810            PlyPropertyType::UShort => Ok(PlyValue::UShort(reader.read_u16_le()?)),
811            PlyPropertyType::Int => Ok(PlyValue::Int(reader.read_u32_le()? as i32)),
812            PlyPropertyType::UInt => Ok(PlyValue::UInt(reader.read_u32_le()?)),
813            PlyPropertyType::Float => Ok(PlyValue::Float(reader.read_f32_le()?)),
814            PlyPropertyType::Double => Ok(PlyValue::Double(reader.read_f64_le()?)),
815            PlyPropertyType::List(count_type, item_type) => {
816                let count_value = Self::read_binary_property_value_mmap_le(reader, count_type)?;
817                let count = count_value.as_usize()?;
818
819                let mut list = Vec::with_capacity(crate::capacity_hint(count));
820                for _ in 0..count {
821                    let item = Self::read_binary_property_value_mmap_le(reader, item_type)?;
822                    list.push(item);
823                }
824
825                Ok(PlyValue::List(list))
826            }
827        }
828    }
829
830    /// Read binary property value using memory mapping (big endian)
831    #[cfg(feature = "io-mmap")]
832    fn read_binary_property_value_mmap_be(
833        reader: &mut MmapReader,
834        property_type: &PlyPropertyType,
835    ) -> Result<PlyValue> {
836        match property_type {
837            PlyPropertyType::Char => Ok(PlyValue::Char(reader.read_u8()? as i8)),
838            PlyPropertyType::UChar => Ok(PlyValue::UChar(reader.read_u8()?)),
839            PlyPropertyType::Short => Ok(PlyValue::Short(reader.read_u16_be()? as i16)),
840            PlyPropertyType::UShort => Ok(PlyValue::UShort(reader.read_u16_be()?)),
841            PlyPropertyType::Int => Ok(PlyValue::Int(reader.read_u32_be()? as i32)),
842            PlyPropertyType::UInt => Ok(PlyValue::UInt(reader.read_u32_be()?)),
843            PlyPropertyType::Float => Ok(PlyValue::Float(reader.read_f32_be()?)),
844            PlyPropertyType::Double => Ok(PlyValue::Double(reader.read_f64_be()?)),
845            PlyPropertyType::List(count_type, item_type) => {
846                let count_value = Self::read_binary_property_value_mmap_be(reader, count_type)?;
847                let count = count_value.as_usize()?;
848
849                let mut list = Vec::with_capacity(crate::capacity_hint(count));
850                for _ in 0..count {
851                    let item = Self::read_binary_property_value_mmap_be(reader, item_type)?;
852                    list.push(item);
853                }
854
855                Ok(PlyValue::List(list))
856            }
857        }
858    }
859
860    /// Read and parse PLY header
861    fn read_header<R: BufRead>(reader: &mut R) -> Result<PlyHeader> {
862        let mut format = None;
863        let mut version = "1.0".to_string();
864        let mut elements = Vec::new();
865        let mut comments = Vec::new();
866        let mut obj_info = Vec::new();
867
868        let mut line = String::new();
869
870        // Read magic number
871        reader.read_line(&mut line)?;
872        if line.trim() != "ply" {
873            return Err(Error::InvalidData(
874                "Not a PLY file - missing magic number".to_string(),
875            ));
876        }
877
878        // Parse header lines
879        loop {
880            line.clear();
881            if reader.read_line(&mut line)? == 0 {
882                return Err(Error::InvalidData(
883                    "Unexpected end of file in header".to_string(),
884                ));
885            }
886
887            let line = line.trim();
888            if line == "end_header" {
889                break;
890            }
891
892            let parts: Vec<&str> = line.split_whitespace().collect();
893            if parts.is_empty() {
894                continue;
895            }
896
897            match parts[0] {
898                "format" => {
899                    if parts.len() < 3 {
900                        return Err(Error::InvalidData("Invalid format line".to_string()));
901                    }
902                    format = Some(match parts[1] {
903                        "ascii" => PlyFormat::Ascii,
904                        "binary_little_endian" => PlyFormat::BinaryLittleEndian,
905                        "binary_big_endian" => PlyFormat::BinaryBigEndian,
906                        _ => {
907                            return Err(Error::InvalidData(format!("Unknown format: {}", parts[1])))
908                        }
909                    });
910                    version = parts[2].to_string();
911                }
912                "comment" => {
913                    if parts.len() > 1 {
914                        comments.push(parts[1..].join(" "));
915                    }
916                }
917                "obj_info" => {
918                    if parts.len() > 1 {
919                        obj_info.push(parts[1..].join(" "));
920                    }
921                }
922                "element" => {
923                    if parts.len() < 3 {
924                        return Err(Error::InvalidData("Invalid element line".to_string()));
925                    }
926                    let name = parts[1].to_string();
927                    let count: usize = parts[2]
928                        .parse()
929                        .map_err(|_| Error::InvalidData("Invalid element count".to_string()))?;
930                    elements.push(PlyElement {
931                        name,
932                        count,
933                        properties: Vec::new(),
934                    });
935                }
936                "property" => {
937                    if elements.is_empty() {
938                        return Err(Error::InvalidData("Property without element".to_string()));
939                    }
940                    let property = Self::parse_property(&parts[1..])?;
941                    elements.last_mut().unwrap().properties.push(property);
942                }
943                _ => {
944                    // Ignore unknown header lines
945                }
946            }
947        }
948
949        let format =
950            format.ok_or_else(|| Error::InvalidData("Missing format specification".to_string()))?;
951
952        Ok(PlyHeader {
953            format,
954            version,
955            elements,
956            comments,
957            obj_info,
958        })
959    }
960
961    /// Parse a property definition
962    fn parse_property(parts: &[&str]) -> Result<PlyProperty> {
963        if parts.is_empty() {
964            return Err(Error::InvalidData("Empty property definition".to_string()));
965        }
966
967        let property_type = if parts[0] == "list" {
968            if parts.len() < 4 {
969                return Err(Error::InvalidData(
970                    "Invalid list property definition".to_string(),
971                ));
972            }
973            let count_type = Self::parse_scalar_type(parts[1])?;
974            let item_type = Self::parse_scalar_type(parts[2])?;
975            PlyPropertyType::List(Box::new(count_type), Box::new(item_type))
976        } else {
977            if parts.len() < 2 {
978                return Err(Error::InvalidData(
979                    "Invalid property definition".to_string(),
980                ));
981            }
982            Self::parse_scalar_type(parts[0])?
983        };
984
985        let name = parts.last().unwrap().to_string();
986
987        Ok(PlyProperty {
988            name,
989            property_type,
990        })
991    }
992
993    /// Parse a scalar type
994    fn parse_scalar_type(type_str: &str) -> Result<PlyPropertyType> {
995        match type_str {
996            "char" | "int8" => Ok(PlyPropertyType::Char),
997            "uchar" | "uint8" => Ok(PlyPropertyType::UChar),
998            "short" | "int16" => Ok(PlyPropertyType::Short),
999            "ushort" | "uint16" => Ok(PlyPropertyType::UShort),
1000            "int" | "int32" => Ok(PlyPropertyType::Int),
1001            "uint" | "uint32" => Ok(PlyPropertyType::UInt),
1002            "float" | "float32" => Ok(PlyPropertyType::Float),
1003            "double" | "float64" => Ok(PlyPropertyType::Double),
1004            _ => Err(Error::InvalidData(format!(
1005                "Unknown property type: {}",
1006                type_str
1007            ))),
1008        }
1009    }
1010
1011    /// Read all elements according to header specification
1012    fn read_elements<R: BufRead>(
1013        reader: &mut R,
1014        header: &PlyHeader,
1015    ) -> Result<HashMap<String, Vec<HashMap<String, PlyValue>>>> {
1016        let mut elements = HashMap::new();
1017
1018        for element_def in &header.elements {
1019            let mut element_data = Vec::with_capacity(crate::capacity_hint(element_def.count));
1020
1021            for _ in 0..element_def.count {
1022                element_data.push(Self::read_record(reader, header.format, element_def)?);
1023            }
1024
1025            elements.insert(element_def.name.clone(), element_data);
1026        }
1027
1028        Ok(elements)
1029    }
1030
1031    /// Read one record of `element`.
1032    fn read_record<R: BufRead>(
1033        reader: &mut R,
1034        format: PlyFormat,
1035        element: &PlyElement,
1036    ) -> Result<HashMap<String, PlyValue>> {
1037        let mut instance = HashMap::new();
1038        match format {
1039            PlyFormat::Ascii => {
1040                let mut line = String::new();
1041                reader.read_line(&mut line)?;
1042                let values = line.trim().split_whitespace().collect::<Vec<_>>();
1043                let mut value_idx = 0;
1044
1045                for property in &element.properties {
1046                    let value = Self::read_ascii_property_value(
1047                        &values,
1048                        &mut value_idx,
1049                        &property.property_type,
1050                    )?;
1051                    instance.insert(property.name.clone(), value);
1052                }
1053            }
1054            PlyFormat::BinaryLittleEndian => {
1055                for property in &element.properties {
1056                    let value = Self::read_binary_property_value::<LittleEndian, _>(
1057                        reader,
1058                        &property.property_type,
1059                    )?;
1060                    instance.insert(property.name.clone(), value);
1061                }
1062            }
1063            PlyFormat::BinaryBigEndian => {
1064                for property in &element.properties {
1065                    let value = Self::read_binary_property_value::<BigEndian, _>(
1066                        reader,
1067                        &property.property_type,
1068                    )?;
1069                    instance.insert(property.name.clone(), value);
1070                }
1071            }
1072        }
1073        Ok(instance)
1074    }
1075
1076    /// Read ASCII property value
1077    fn read_ascii_property_value(
1078        values: &[&str],
1079        value_idx: &mut usize,
1080        property_type: &PlyPropertyType,
1081    ) -> Result<PlyValue> {
1082        if *value_idx >= values.len() {
1083            return Err(Error::InvalidData("Not enough values in line".to_string()));
1084        }
1085
1086        match property_type {
1087            PlyPropertyType::Char => {
1088                let val = values[*value_idx]
1089                    .parse::<i8>()
1090                    .map_err(|_| Error::InvalidData("Invalid char value".to_string()))?;
1091                *value_idx += 1;
1092                Ok(PlyValue::Char(val))
1093            }
1094            PlyPropertyType::UChar => {
1095                let val = values[*value_idx]
1096                    .parse::<u8>()
1097                    .map_err(|_| Error::InvalidData("Invalid uchar value".to_string()))?;
1098                *value_idx += 1;
1099                Ok(PlyValue::UChar(val))
1100            }
1101            PlyPropertyType::Short => {
1102                let val = values[*value_idx]
1103                    .parse::<i16>()
1104                    .map_err(|_| Error::InvalidData("Invalid short value".to_string()))?;
1105                *value_idx += 1;
1106                Ok(PlyValue::Short(val))
1107            }
1108            PlyPropertyType::UShort => {
1109                let val = values[*value_idx]
1110                    .parse::<u16>()
1111                    .map_err(|_| Error::InvalidData("Invalid ushort value".to_string()))?;
1112                *value_idx += 1;
1113                Ok(PlyValue::UShort(val))
1114            }
1115            PlyPropertyType::Int => {
1116                let val = values[*value_idx]
1117                    .parse::<i32>()
1118                    .map_err(|_| Error::InvalidData("Invalid int value".to_string()))?;
1119                *value_idx += 1;
1120                Ok(PlyValue::Int(val))
1121            }
1122            PlyPropertyType::UInt => {
1123                let val = values[*value_idx]
1124                    .parse::<u32>()
1125                    .map_err(|_| Error::InvalidData("Invalid uint value".to_string()))?;
1126                *value_idx += 1;
1127                Ok(PlyValue::UInt(val))
1128            }
1129            PlyPropertyType::Float => {
1130                let val = values[*value_idx]
1131                    .parse::<f32>()
1132                    .map_err(|_| Error::InvalidData("Invalid float value".to_string()))?;
1133                *value_idx += 1;
1134                Ok(PlyValue::Float(val))
1135            }
1136            PlyPropertyType::Double => {
1137                let val = values[*value_idx]
1138                    .parse::<f64>()
1139                    .map_err(|_| Error::InvalidData("Invalid double value".to_string()))?;
1140                *value_idx += 1;
1141                Ok(PlyValue::Double(val))
1142            }
1143            PlyPropertyType::List(count_type, item_type) => {
1144                let count_value = Self::read_ascii_property_value(values, value_idx, count_type)?;
1145                let count = count_value.as_usize()?;
1146
1147                let mut list = Vec::with_capacity(crate::capacity_hint(count));
1148                for _ in 0..count {
1149                    let item = Self::read_ascii_property_value(values, value_idx, item_type)?;
1150                    list.push(item);
1151                }
1152
1153                Ok(PlyValue::List(list))
1154            }
1155        }
1156    }
1157
1158    /// Read binary property value
1159    fn read_binary_property_value<E: byteorder::ByteOrder, R: Read>(
1160        reader: &mut R,
1161        property_type: &PlyPropertyType,
1162    ) -> Result<PlyValue> {
1163        match property_type {
1164            PlyPropertyType::Char => Ok(PlyValue::Char(reader.read_i8()?)),
1165            PlyPropertyType::UChar => Ok(PlyValue::UChar(reader.read_u8()?)),
1166            PlyPropertyType::Short => Ok(PlyValue::Short(reader.read_i16::<E>()?)),
1167            PlyPropertyType::UShort => Ok(PlyValue::UShort(reader.read_u16::<E>()?)),
1168            PlyPropertyType::Int => Ok(PlyValue::Int(reader.read_i32::<E>()?)),
1169            PlyPropertyType::UInt => Ok(PlyValue::UInt(reader.read_u32::<E>()?)),
1170            PlyPropertyType::Float => Ok(PlyValue::Float(reader.read_f32::<E>()?)),
1171            PlyPropertyType::Double => Ok(PlyValue::Double(reader.read_f64::<E>()?)),
1172            PlyPropertyType::List(count_type, item_type) => {
1173                let count_value = Self::read_binary_property_value::<E, _>(reader, count_type)?;
1174                let count = count_value.as_usize()?;
1175
1176                let mut list = Vec::with_capacity(crate::capacity_hint(count));
1177                for _ in 0..count {
1178                    let item = Self::read_binary_property_value::<E, _>(reader, item_type)?;
1179                    list.push(item);
1180                }
1181
1182                Ok(PlyValue::List(list))
1183            }
1184        }
1185    }
1186}
1187
1188impl PlyValue {
1189    /// Convert PLY value to f32
1190    pub fn as_f32(&self) -> Result<f32> {
1191        match self {
1192            PlyValue::Char(v) => Ok(*v as f32),
1193            PlyValue::UChar(v) => Ok(*v as f32),
1194            PlyValue::Short(v) => Ok(*v as f32),
1195            PlyValue::UShort(v) => Ok(*v as f32),
1196            PlyValue::Int(v) => Ok(*v as f32),
1197            PlyValue::UInt(v) => Ok(*v as f32),
1198            PlyValue::Float(v) => Ok(*v),
1199            PlyValue::Double(v) => Ok(*v as f32),
1200            _ => Err(Error::InvalidData("Cannot convert list to f32".to_string())),
1201        }
1202    }
1203
1204    /// Convert PLY value to usize
1205    pub fn as_usize(&self) -> Result<usize> {
1206        match self {
1207            PlyValue::UChar(v) => Ok(*v as usize),
1208            PlyValue::UShort(v) => Ok(*v as usize),
1209            PlyValue::UInt(v) => Ok(*v as usize),
1210            PlyValue::Int(v) if *v >= 0 => Ok(*v as usize),
1211            _ => Err(Error::InvalidData(
1212                "Cannot convert value to usize".to_string(),
1213            )),
1214        }
1215    }
1216
1217    /// Convert PLY value to Vec<usize> (for face indices)
1218    pub fn as_usize_list(&self) -> Result<Vec<usize>> {
1219        match self {
1220            PlyValue::List(values) => values.iter().map(|v| v.as_usize()).collect(),
1221            _ => Err(Error::InvalidData("Value is not a list".to_string())),
1222        }
1223    }
1224}
1225
1226// Legacy PLY reader/writer using ply-rs for backward compatibility
1227pub struct PlyReader;
1228pub struct PlyWriter;
1229
1230// Implement the new unified traits
1231/// Positions straight from the file when the vertex element comes first and
1232/// has only scalar properties (the usual layout). `None` means the general
1233/// reader is needed.
1234fn read_positions_fast(path: &Path) -> Result<Option<Vec<Point3f>>> {
1235    use crate::records::{read_ascii_xyz, read_binary_xyz, BinaryXyz, Scalar};
1236    let mut reader = BufReader::new(File::open(path)?);
1237    let header = RobustPlyReader::read_header(&mut reader)?;
1238    let Some(vertex) = header.elements.first().filter(|e| e.name == "vertex") else {
1239        return Ok(None);
1240    };
1241    let mut offset = 0;
1242    let mut found = [None; 3];
1243    for (column, property) in vertex.properties.iter().enumerate() {
1244        let scalar = match property.property_type {
1245            PlyPropertyType::Char => Scalar::I8,
1246            PlyPropertyType::UChar => Scalar::U8,
1247            PlyPropertyType::Short => Scalar::I16,
1248            PlyPropertyType::UShort => Scalar::U16,
1249            PlyPropertyType::Int => Scalar::I32,
1250            PlyPropertyType::UInt => Scalar::U32,
1251            PlyPropertyType::Float => Scalar::F32,
1252            PlyPropertyType::Double => Scalar::F64,
1253            PlyPropertyType::List(..) => return Ok(None),
1254        };
1255        if let Some(axis) = ["x", "y", "z"].iter().position(|n| *n == property.name) {
1256            found[axis] = Some((column, offset, scalar));
1257        }
1258        offset += scalar.size();
1259    }
1260    let [Some(x), Some(y), Some(z)] = found else {
1261        return Err(Error::InvalidData(
1262            "PLY vertex element is missing x, y or z".to_string(),
1263        ));
1264    };
1265    let points = match header.format {
1266        PlyFormat::Ascii => read_ascii_xyz(&mut reader, [x.0, y.0, z.0], vertex.count)?,
1267        PlyFormat::BinaryLittleEndian | PlyFormat::BinaryBigEndian => {
1268            let layout = BinaryXyz {
1269                record_size: offset,
1270                xyz: [(x.1, x.2), (y.1, y.2), (z.1, z.2)],
1271                big_endian: header.format == PlyFormat::BinaryBigEndian,
1272            };
1273            read_binary_xyz(&mut reader, &layout, vertex.count)?
1274        }
1275    };
1276    Ok(Some(points))
1277}
1278
1279impl crate::registry::PointCloudReader for PlyReader {
1280    fn read_point_cloud(&self, path: &Path) -> Result<PointCloud<Point3f>> {
1281        if let Some(points) = read_positions_fast(path)? {
1282            return Ok(PointCloud::from_points(points));
1283        }
1284        // Use the robust reader for better format support
1285        let ply_data = RobustPlyReader::read_ply_file(path)?;
1286
1287        let mut points = Vec::new();
1288
1289        if let Some(vertex_elements) = ply_data.elements.get("vertex") {
1290            for vertex in vertex_elements {
1291                let x = vertex
1292                    .get("x")
1293                    .ok_or_else(|| Error::InvalidData("Missing x coordinate".to_string()))?
1294                    .as_f32()?;
1295                let y = vertex
1296                    .get("y")
1297                    .ok_or_else(|| Error::InvalidData("Missing y coordinate".to_string()))?
1298                    .as_f32()?;
1299                let z = vertex
1300                    .get("z")
1301                    .ok_or_else(|| Error::InvalidData("Missing z coordinate".to_string()))?
1302                    .as_f32()?;
1303
1304                points.push(Point3f::new(x, y, z));
1305            }
1306        }
1307
1308        Ok(PointCloud::from_points(points))
1309    }
1310
1311    fn can_read(&self, path: &Path) -> bool {
1312        // Check if file starts with "ply"
1313        if let Ok(mut file) = File::open(path) {
1314            let mut header = [0u8; 4];
1315            if let Ok(_) = file.read(&mut header) {
1316                return header.starts_with(b"ply");
1317            }
1318        }
1319        false
1320    }
1321
1322    fn format_name(&self) -> &'static str {
1323        "ply"
1324    }
1325}
1326
1327impl crate::registry::MeshReader for PlyReader {
1328    fn read_mesh(&self, path: &Path) -> Result<TriangleMesh> {
1329        let ply_data = RobustPlyReader::read_ply_file(path)?;
1330
1331        // Extract vertices
1332        let mut vertices = Vec::new();
1333        if let Some(vertex_elements) = ply_data.elements.get("vertex") {
1334            for vertex in vertex_elements {
1335                let x = vertex
1336                    .get("x")
1337                    .ok_or_else(|| Error::InvalidData("Missing x coordinate".to_string()))?
1338                    .as_f32()?;
1339                let y = vertex
1340                    .get("y")
1341                    .ok_or_else(|| Error::InvalidData("Missing y coordinate".to_string()))?
1342                    .as_f32()?;
1343                let z = vertex
1344                    .get("z")
1345                    .ok_or_else(|| Error::InvalidData("Missing z coordinate".to_string()))?
1346                    .as_f32()?;
1347
1348                vertices.push(Point3f::new(x, y, z));
1349            }
1350        }
1351
1352        // Extract faces
1353        let mut faces = Vec::new();
1354        if let Some(face_elements) = ply_data.elements.get("face") {
1355            for face in face_elements {
1356                // Look for vertex_indices or vertex_index property
1357                let indices = if let Some(vertex_indices) = face.get("vertex_indices") {
1358                    vertex_indices.as_usize_list()?
1359                } else if let Some(vertex_index) = face.get("vertex_index") {
1360                    vertex_index.as_usize_list()?
1361                } else {
1362                    return Err(Error::InvalidData(
1363                        "Face missing vertex indices".to_string(),
1364                    ));
1365                };
1366
1367                // Convert to triangles (assuming triangular faces or taking first 3 vertices)
1368                if indices.len() >= 3 {
1369                    faces.push([indices[0], indices[1], indices[2]]);
1370                }
1371                // For quads and other polygons, we could triangulate here
1372                if indices.len() == 4 {
1373                    faces.push([indices[0], indices[2], indices[3]]);
1374                }
1375            }
1376        }
1377
1378        // Extract normals if available
1379        let normals = if let Some(vertex_elements) = ply_data.elements.get("vertex") {
1380            let mut normals = Vec::new();
1381            let mut has_normals = true;
1382
1383            for vertex in vertex_elements {
1384                if let (Some(nx), Some(ny), Some(nz)) =
1385                    (vertex.get("nx"), vertex.get("ny"), vertex.get("nz"))
1386                {
1387                    normals.push(Vector3f::new(nx.as_f32()?, ny.as_f32()?, nz.as_f32()?));
1388                } else {
1389                    has_normals = false;
1390                    break;
1391                }
1392            }
1393
1394            if has_normals {
1395                Some(normals)
1396            } else {
1397                None
1398            }
1399        } else {
1400            None
1401        };
1402
1403        let mut mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
1404        if let Some(normals) = normals {
1405            mesh.set_normals(normals);
1406        }
1407
1408        Ok(mesh)
1409    }
1410
1411    fn can_read(&self, path: &Path) -> bool {
1412        // Check if file starts with "ply"
1413        if let Ok(mut file) = File::open(path) {
1414            let mut header = [0u8; 4];
1415            if let Ok(_) = file.read(&mut header) {
1416                return header.starts_with(b"ply");
1417            }
1418        }
1419        false
1420    }
1421
1422    fn format_name(&self) -> &'static str {
1423        "ply"
1424    }
1425}
1426
1427impl crate::registry::PointCloudWriter for PlyWriter {
1428    fn write_point_cloud(&self, cloud: &PointCloud<Point3f>, path: &Path) -> Result<()> {
1429        // Use the legacy ply-rs for writing for now
1430        use ply_rs::{
1431            ply::{
1432                Addable, DefaultElement, ElementDef, Ply, Property, PropertyDef, PropertyType,
1433                ScalarType,
1434            },
1435            writer::Writer,
1436        };
1437
1438        let file = File::create(path)?;
1439        let mut writer = BufWriter::new(file);
1440
1441        // Create PLY structure
1442        let mut ply = Ply::<DefaultElement>::new();
1443
1444        // Define vertex element
1445        let mut vertex_element = ElementDef::new("vertex".to_string());
1446        vertex_element.count = cloud.len();
1447        vertex_element.properties.add(PropertyDef::new(
1448            "x".to_string(),
1449            PropertyType::Scalar(ScalarType::Float),
1450        ));
1451        vertex_element.properties.add(PropertyDef::new(
1452            "y".to_string(),
1453            PropertyType::Scalar(ScalarType::Float),
1454        ));
1455        vertex_element.properties.add(PropertyDef::new(
1456            "z".to_string(),
1457            PropertyType::Scalar(ScalarType::Float),
1458        ));
1459
1460        ply.header.elements.add(vertex_element);
1461
1462        // Add vertex data
1463        let mut vertices = Vec::new();
1464        for point in &cloud.points {
1465            let mut vertex = DefaultElement::new();
1466            vertex.insert("x".to_string(), Property::Float(point.x));
1467            vertex.insert("y".to_string(), Property::Float(point.y));
1468            vertex.insert("z".to_string(), Property::Float(point.z));
1469            vertices.push(vertex);
1470        }
1471        ply.payload.insert("vertex".to_string(), vertices);
1472
1473        // Write PLY file
1474        let writer_instance = Writer::new();
1475        writer_instance.write_ply(&mut writer, &mut ply)?;
1476
1477        Ok(())
1478    }
1479
1480    fn format_name(&self) -> &'static str {
1481        "ply"
1482    }
1483}
1484
1485impl crate::registry::MeshWriter for PlyWriter {
1486    fn write_mesh(&self, mesh: &TriangleMesh, path: &Path) -> Result<()> {
1487        // Use the legacy ply-rs for writing for now
1488        use ply_rs::{
1489            ply::{
1490                Addable, DefaultElement, ElementDef, Ply, Property, PropertyDef, PropertyType,
1491                ScalarType,
1492            },
1493            writer::Writer,
1494        };
1495
1496        let file = File::create(path)?;
1497        let mut writer = BufWriter::new(file);
1498
1499        // Create PLY structure
1500        let mut ply = Ply::<DefaultElement>::new();
1501
1502        // Define vertex element
1503        let mut vertex_element = ElementDef::new("vertex".to_string());
1504        vertex_element.count = mesh.vertices.len();
1505        vertex_element.properties.add(PropertyDef::new(
1506            "x".to_string(),
1507            PropertyType::Scalar(ScalarType::Float),
1508        ));
1509        vertex_element.properties.add(PropertyDef::new(
1510            "y".to_string(),
1511            PropertyType::Scalar(ScalarType::Float),
1512        ));
1513        vertex_element.properties.add(PropertyDef::new(
1514            "z".to_string(),
1515            PropertyType::Scalar(ScalarType::Float),
1516        ));
1517
1518        // Add normal properties if available
1519        if mesh.normals.is_some() {
1520            vertex_element.properties.add(PropertyDef::new(
1521                "nx".to_string(),
1522                PropertyType::Scalar(ScalarType::Float),
1523            ));
1524            vertex_element.properties.add(PropertyDef::new(
1525                "ny".to_string(),
1526                PropertyType::Scalar(ScalarType::Float),
1527            ));
1528            vertex_element.properties.add(PropertyDef::new(
1529                "nz".to_string(),
1530                PropertyType::Scalar(ScalarType::Float),
1531            ));
1532        }
1533
1534        ply.header.elements.add(vertex_element);
1535
1536        // Add vertex data
1537        let mut vertices = Vec::new();
1538        for (i, point) in mesh.vertices.iter().enumerate() {
1539            let mut vertex = DefaultElement::new();
1540            vertex.insert("x".to_string(), Property::Float(point.x));
1541            vertex.insert("y".to_string(), Property::Float(point.y));
1542            vertex.insert("z".to_string(), Property::Float(point.z));
1543
1544            // Add normals if available
1545            if let Some(ref normals) = mesh.normals {
1546                if i < normals.len() {
1547                    vertex.insert("nx".to_string(), Property::Float(normals[i].x));
1548                    vertex.insert("ny".to_string(), Property::Float(normals[i].y));
1549                    vertex.insert("nz".to_string(), Property::Float(normals[i].z));
1550                }
1551            }
1552
1553            vertices.push(vertex);
1554        }
1555        ply.payload.insert("vertex".to_string(), vertices);
1556
1557        // Define face element if we have faces
1558        if !mesh.faces.is_empty() {
1559            let mut face_element = ElementDef::new("face".to_string());
1560            face_element.count = mesh.faces.len();
1561            face_element.properties.add(PropertyDef::new(
1562                "vertex_indices".to_string(),
1563                PropertyType::List(ScalarType::UChar, ScalarType::Int),
1564            ));
1565
1566            ply.header.elements.add(face_element);
1567
1568            // Add face data
1569            let mut faces = Vec::new();
1570            for face in &mesh.faces {
1571                let mut face_data = DefaultElement::new();
1572                let indices = vec![face[0] as i32, face[1] as i32, face[2] as i32];
1573                face_data.insert("vertex_indices".to_string(), Property::ListInt(indices));
1574                faces.push(face_data);
1575            }
1576            ply.payload.insert("face".to_string(), faces);
1577        }
1578
1579        // Write PLY file
1580        let writer_instance = Writer::new();
1581        writer_instance.write_ply(&mut writer, &mut ply)?;
1582
1583        Ok(())
1584    }
1585
1586    fn format_name(&self) -> &'static str {
1587        "ply"
1588    }
1589}
1590
1591// Keep the legacy trait implementations for backward compatibility
1592impl PointCloudReader for PlyReader {
1593    fn read_point_cloud<P: AsRef<Path>>(path: P) -> Result<PointCloud<Point3f>> {
1594        let reader = PlyReader;
1595        crate::registry::PointCloudReader::read_point_cloud(&reader, path.as_ref())
1596    }
1597}
1598
1599impl MeshReader for PlyReader {
1600    fn read_mesh<P: AsRef<Path>>(path: P) -> Result<TriangleMesh> {
1601        let reader = PlyReader;
1602        crate::registry::MeshReader::read_mesh(&reader, path.as_ref())
1603    }
1604}
1605
1606impl PointCloudWriter for PlyWriter {
1607    fn write_point_cloud<P: AsRef<Path>>(cloud: &PointCloud<Point3f>, path: P) -> Result<()> {
1608        let writer = PlyWriter;
1609        crate::registry::PointCloudWriter::write_point_cloud(&writer, cloud, path.as_ref())
1610    }
1611}
1612
1613impl MeshWriter for PlyWriter {
1614    fn write_mesh<P: AsRef<Path>>(mesh: &TriangleMesh, path: P) -> Result<()> {
1615        let writer = PlyWriter;
1616        crate::registry::MeshWriter::write_mesh(&writer, mesh, path.as_ref())
1617    }
1618}
1619
1620/// Open a PLY file and skip to the first record of `element`, using the same
1621/// record decoder as the full reader so any property layout works.
1622fn open_at_element<P: AsRef<Path>>(
1623    path: P,
1624    element: &str,
1625) -> Result<(BufReader<File>, PlyHeader, usize)> {
1626    let mut reader = BufReader::new(File::open(path)?);
1627    let header = RobustPlyReader::read_header(&mut reader)?;
1628    let index = header
1629        .elements
1630        .iter()
1631        .position(|e| e.name == element)
1632        .ok_or_else(|| Error::InvalidData(format!("No {element} element found in PLY file")))?;
1633    for skipped in &header.elements[..index] {
1634        for _ in 0..skipped.count {
1635            RobustPlyReader::read_record(&mut reader, header.format, skipped)?;
1636        }
1637    }
1638    Ok((reader, header, index))
1639}
1640
1641/// Streaming PLY reader for point clouds
1642pub struct PlyStreamingReader {
1643    reader: BufReader<File>,
1644    format: PlyFormat,
1645    vertex: PlyElement,
1646    current_index: usize,
1647}
1648
1649impl PlyStreamingReader {
1650    /// Create a new streaming PLY reader. `_chunk_size` is kept for
1651    /// compatibility; reads are already buffered.
1652    pub fn new<P: AsRef<Path>>(path: P, _chunk_size: usize) -> Result<Self> {
1653        let (reader, header, index) = open_at_element(path, "vertex")?;
1654        Ok(Self {
1655            reader,
1656            format: header.format,
1657            vertex: header.elements[index].clone(),
1658            current_index: 0,
1659        })
1660    }
1661}
1662
1663impl Iterator for PlyStreamingReader {
1664    type Item = Result<Point3f>;
1665
1666    fn next(&mut self) -> Option<Self::Item> {
1667        if self.current_index >= self.vertex.count {
1668            return None;
1669        }
1670        self.current_index += 1;
1671        let point = RobustPlyReader::read_record(&mut self.reader, self.format, &self.vertex)
1672            .and_then(|record| {
1673                let coord = |name: &str| {
1674                    record
1675                        .get(name)
1676                        .ok_or_else(|| Error::InvalidData(format!("Missing {name} coordinate")))?
1677                        .as_f32()
1678                };
1679                Ok(Point3f::new(coord("x")?, coord("y")?, coord("z")?))
1680            });
1681        if point.is_err() {
1682            self.current_index = self.vertex.count; // stop after the first error
1683        }
1684        Some(point)
1685    }
1686}
1687
1688/// Streaming PLY reader for mesh faces
1689pub struct PlyMeshStreamingReader {
1690    reader: BufReader<File>,
1691    format: PlyFormat,
1692    face: PlyElement,
1693    vertex_count: usize,
1694    current_index: usize,
1695}
1696
1697impl PlyMeshStreamingReader {
1698    /// Create a new streaming PLY mesh reader. `_chunk_size` is kept for
1699    /// compatibility; reads are already buffered.
1700    pub fn new<P: AsRef<Path>>(path: P, _chunk_size: usize) -> Result<Self> {
1701        let (reader, header, index) = open_at_element(path, "face")?;
1702        let vertex_count = header
1703            .elements
1704            .iter()
1705            .find(|e| e.name == "vertex")
1706            .ok_or_else(|| Error::InvalidData("No vertex element found in PLY file".to_string()))?
1707            .count;
1708        Ok(Self {
1709            reader,
1710            format: header.format,
1711            face: header.elements[index].clone(),
1712            vertex_count,
1713            current_index: 0,
1714        })
1715    }
1716}
1717
1718impl Iterator for PlyMeshStreamingReader {
1719    type Item = Result<[usize; 3]>;
1720
1721    fn next(&mut self) -> Option<Self::Item> {
1722        if self.current_index >= self.face.count {
1723            return None;
1724        }
1725        self.current_index += 1;
1726        let face = RobustPlyReader::read_record(&mut self.reader, self.format, &self.face)
1727            .and_then(|record| {
1728                let indices = record
1729                    .get("vertex_indices")
1730                    .or_else(|| record.get("vertex_index"))
1731                    .ok_or_else(|| Error::InvalidData("Face has no vertex indices".to_string()))?
1732                    .as_usize_list()?;
1733                let [a, b, c] = indices[..] else {
1734                    return Err(Error::InvalidData(
1735                        "Only triangular faces supported".to_string(),
1736                    ));
1737                };
1738                if a.max(b).max(c) >= self.vertex_count {
1739                    return Err(Error::InvalidData("Face index out of range".to_string()));
1740                }
1741                Ok([a, b, c])
1742            });
1743        if face.is_err() {
1744            self.current_index = self.face.count; // stop after the first error
1745        }
1746        Some(face)
1747    }
1748}
1749
1750#[cfg(test)]
1751mod streaming_tests {
1752    use super::*;
1753
1754    fn write_temp(name: &str, bytes: &[u8]) -> std::path::PathBuf {
1755        let path =
1756            std::env::temp_dir().join(format!("threecrate_{name}_{}.ply", std::process::id()));
1757        std::fs::write(&path, bytes).unwrap();
1758        path
1759    }
1760
1761    #[test]
1762    fn streams_binary_ply_with_extra_properties() {
1763        // Normals, colours and a double: the old reader assumed 12 bytes per vertex.
1764        let mut bytes = b"ply\nformat binary_little_endian 1.0\nelement vertex 2\n\
1765property double x\nproperty double y\nproperty double z\n\
1766property float nx\nproperty float ny\nproperty float nz\n\
1767property uchar red\nproperty uchar green\nproperty uchar blue\n\
1768element face 1\nproperty list uchar int vertex_indices\nend_header\n"
1769            .to_vec();
1770        for (p, n) in [([1.0f64, 2.0, 3.0], 0.0f32), ([4.0, 5.0, 6.0], 1.0)] {
1771            p.iter().for_each(|v| bytes.extend(v.to_le_bytes()));
1772            [n, n, n].iter().for_each(|v| bytes.extend(v.to_le_bytes()));
1773            bytes.extend([10u8, 20, 30]);
1774        }
1775        bytes.push(3);
1776        [0i32, 1, 1]
1777            .iter()
1778            .for_each(|v| bytes.extend(v.to_le_bytes()));
1779        let path = write_temp("stream_bin", &bytes);
1780
1781        let points: Vec<Point3f> = PlyStreamingReader::new(&path, 1)
1782            .unwrap()
1783            .collect::<Result<_>>()
1784            .unwrap();
1785        assert_eq!(
1786            points,
1787            vec![Point3f::new(1.0, 2.0, 3.0), Point3f::new(4.0, 5.0, 6.0)]
1788        );
1789
1790        let faces: Vec<[usize; 3]> = PlyMeshStreamingReader::new(&path, 1)
1791            .unwrap()
1792            .collect::<Result<_>>()
1793            .unwrap();
1794        assert_eq!(faces, vec![[0, 1, 1]]);
1795        std::fs::remove_file(path).ok();
1796    }
1797
1798    #[test]
1799    fn streams_ascii_with_reordered_properties_and_rejects_bad_faces() {
1800        let text = "ply\nformat ascii 1.0\nelement vertex 2\nproperty float z\nproperty float x\n\
1801property float y\nelement face 1\nproperty list uchar int vertex_indices\nend_header\n\
18023 1 2\n6 4 5\n3 0 1 7\n";
1803        let path = write_temp("stream_ascii", text.as_bytes());
1804
1805        let points: Vec<Point3f> = PlyStreamingReader::new(&path, 8)
1806            .unwrap()
1807            .collect::<Result<_>>()
1808            .unwrap();
1809        assert_eq!(
1810            points,
1811            vec![Point3f::new(1.0, 2.0, 3.0), Point3f::new(4.0, 5.0, 6.0)]
1812        );
1813
1814        let faces: Vec<Result<[usize; 3]>> =
1815            PlyMeshStreamingReader::new(&path, 8).unwrap().collect();
1816        assert_eq!(faces.len(), 1);
1817        assert!(faces[0].is_err(), "index 7 is out of range");
1818        std::fs::remove_file(path).ok();
1819    }
1820}