Skip to main content

draco_io/
ply_reader.rs

1//! PLY format reader for meshes and point clouds.
2//!
3//! Reads positions, triangle/polygon faces, normals, colors, and per-vertex
4//! texture coordinates from ASCII and binary PLY files. Polygon faces are
5//! triangulated with a fan.
6
7use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
8use std::fs;
9use std::io::{self, Cursor, Write};
10use std::path::Path;
11
12use draco_core::draco_types::DataType;
13use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
14use draco_core::mesh::Mesh;
15
16pub use crate::ply_format::PlyFormat;
17use crate::traits::{PointCloudReader, ReadFromBytes, Reader};
18
19#[derive(Debug)]
20struct ParsedPlyColorData {
21    num_components: u8,
22    values: Vec<[u8; 4]>,
23}
24
25#[derive(Debug)]
26struct ParsedPlyData {
27    positions: ParsedPlyPositionData,
28    faces: Vec<[u32; 3]>,
29    normals: Option<Vec<[f32; 3]>>,
30    colors: Option<ParsedPlyColorData>,
31    texcoords: Option<Vec<[f32; 2]>>,
32}
33
34#[derive(Debug)]
35enum ParsedPlyPositionData {
36    Float32(Vec<[f32; 3]>),
37    Int32(Vec<[i32; 3]>),
38}
39
40impl ParsedPlyPositionData {
41    fn len(&self) -> usize {
42        match self {
43            ParsedPlyPositionData::Float32(values) => values.len(),
44            ParsedPlyPositionData::Int32(values) => values.len(),
45        }
46    }
47
48    fn to_f32_positions(&self) -> Vec<[f32; 3]> {
49        match self {
50            ParsedPlyPositionData::Float32(values) => values.clone(),
51            ParsedPlyPositionData::Int32(values) => values
52                .iter()
53                .map(|value| [value[0] as f32, value[1] as f32, value[2] as f32])
54                .collect(),
55        }
56    }
57}
58
59#[derive(Debug, Clone)]
60enum PlyPropertyKind {
61    Scalar(DataType),
62    List {
63        count_type: DataType,
64        item_type: DataType,
65    },
66}
67
68#[derive(Debug, Clone)]
69struct PlyPropertyDef {
70    name: String,
71    kind: PlyPropertyKind,
72}
73
74impl PlyPropertyDef {
75    fn scalar_type(&self) -> Option<DataType> {
76        match self.kind {
77            PlyPropertyKind::Scalar(data_type) => Some(data_type),
78            PlyPropertyKind::List { .. } => None,
79        }
80    }
81}
82
83#[derive(Debug, Clone)]
84struct PlyHeader {
85    format: PlyFormat,
86    vertex_count: usize,
87    face_count: usize,
88    elements: Vec<PlyElementDef>,
89    vertex_properties: Vec<PlyPropertyDef>,
90    face_properties: Vec<PlyPropertyDef>,
91}
92
93#[derive(Debug, Clone)]
94struct PlyElementDef {
95    name: String,
96    count: usize,
97    properties: Vec<PlyPropertyDef>,
98}
99
100#[derive(Debug, Clone, Copy)]
101struct PlyReadSchema {
102    position_data_type: DataType,
103    has_normals: bool,
104    color_components: u8,
105    texcoord_pair: Option<TexcoordPropertyPair>,
106}
107
108#[derive(Debug, Clone, Copy)]
109struct TexcoordPropertyPair {
110    u: &'static str,
111    v: &'static str,
112}
113
114fn parse_ply_scalar_type(token: &str) -> Option<DataType> {
115    match token {
116        "char" | "int8" => Some(DataType::Int8),
117        "uchar" | "uint8" => Some(DataType::Uint8),
118        "short" | "int16" => Some(DataType::Int16),
119        "ushort" | "uint16" => Some(DataType::Uint16),
120        "int" | "int32" => Some(DataType::Int32),
121        "uint" | "uint32" => Some(DataType::Uint32),
122        "float" | "float32" => Some(DataType::Float32),
123        "double" | "float64" => Some(DataType::Float64),
124        _ => None,
125    }
126}
127
128/// PLY format reader.
129///
130/// Reads vertex positions from ASCII and little-endian binary PLY files.
131#[derive(Debug)]
132pub struct PlyReader {
133    source: PlyReaderSource,
134}
135
136#[derive(Debug, Clone)]
137enum PlyReaderSource {
138    Path(std::path::PathBuf),
139    Bytes(Vec<u8>),
140}
141
142impl PlyReader {
143    /// Open a PLY file for reading.
144    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
145        let path = path.as_ref().to_path_buf();
146        if !path.exists() {
147            return Err(io::Error::new(
148                io::ErrorKind::NotFound,
149                format!("File not found: {}", path.display()),
150            ));
151        }
152        Ok(Self {
153            source: PlyReaderSource::Path(path),
154        })
155    }
156
157    /// Create a PLY reader from in-memory bytes.
158    pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
159        Self {
160            source: PlyReaderSource::Bytes(bytes.into()),
161        }
162    }
163
164    /// Read a mesh directly from in-memory bytes.
165    pub fn read_from_bytes(bytes: &[u8]) -> io::Result<Mesh> {
166        let mut reader = Self::from_bytes(bytes.to_vec());
167        reader.read_mesh()
168    }
169
170    /// Read all positions from the PLY file.
171    pub fn read_positions(&mut self) -> io::Result<Vec<[f32; 3]>> {
172        Ok(read_ply_source(&self.source)?.positions.to_f32_positions())
173    }
174
175    /// Read a mesh with positions (and faces if present).
176    pub fn read_mesh(&mut self) -> io::Result<Mesh> {
177        let parsed = read_ply_source(&self.source)?;
178        let mut mesh = Mesh::new();
179
180        if parsed.positions.len() == 0 {
181            return Ok(mesh);
182        }
183
184        mesh.set_num_points(parsed.positions.len());
185        mesh.set_num_faces(parsed.faces.len());
186
187        // Create position attribute
188        match &parsed.positions {
189            ParsedPlyPositionData::Float32(values) => {
190                mesh.add_attribute(make_f32x3_attribute(
191                    GeometryAttributeType::Position,
192                    values,
193                ));
194            }
195            ParsedPlyPositionData::Int32(values) => {
196                mesh.add_attribute(make_i32x3_attribute(
197                    GeometryAttributeType::Position,
198                    values,
199                ));
200            }
201        }
202
203        if let Some(normals) = parsed.normals.as_ref() {
204            mesh.add_attribute(make_f32x3_attribute(GeometryAttributeType::Normal, normals));
205        }
206
207        if let Some(colors) = parsed.colors.as_ref() {
208            mesh.add_attribute(make_u8_attribute(
209                GeometryAttributeType::Color,
210                colors.num_components,
211                true,
212                &colors.values,
213            ));
214        }
215
216        if let Some(texcoords) = parsed.texcoords.as_ref() {
217            mesh.add_attribute(make_f32x2_attribute(
218                GeometryAttributeType::TexCoord,
219                texcoords,
220            ));
221        }
222
223        for (i, face) in parsed.faces.iter().enumerate() {
224            mesh.set_face(
225                draco_core::geometry_indices::FaceIndex(i as u32),
226                [
227                    draco_core::geometry_indices::PointIndex(face[0]),
228                    draco_core::geometry_indices::PointIndex(face[1]),
229                    draco_core::geometry_indices::PointIndex(face[2]),
230                ],
231            );
232        }
233
234        if mesh.num_faces() > 0 {
235            // Match C++ Draco behavior: deduplicate point IDs in face-traversal order.
236            // This ensures binary compatibility when encoding.
237            mesh.deduplicate_point_ids();
238        }
239
240        Ok(mesh)
241    }
242}
243
244impl Reader for PlyReader {
245    fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
246        PlyReader::open(path)
247    }
248
249    fn read_meshes(&mut self) -> io::Result<Vec<Mesh>> {
250        let m = self.read_mesh()?;
251        Ok(vec![m])
252    }
253}
254
255impl ReadFromBytes for PlyReader {
256    fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
257        Ok(Self::from_bytes(bytes.to_vec()))
258    }
259}
260
261impl PointCloudReader for PlyReader {
262    fn read_points(&mut self) -> io::Result<Vec<[f32; 3]>> {
263        self.read_positions()
264    }
265}
266
267// ============================================================================
268// Convenience Functions (for backward compatibility)
269// ============================================================================
270
271/// Parse point positions from an ASCII or binary little-endian PLY file.
272/// Returns a vec of [x, y, z] positions.
273pub fn read_ply_positions<P: AsRef<Path>>(path: P) -> io::Result<Vec<[f32; 3]>> {
274    Ok(read_ply(path)?.positions.to_f32_positions())
275}
276
277fn make_f32x3_attribute(
278    attribute_type: GeometryAttributeType,
279    values: &[[f32; 3]],
280) -> PointAttribute {
281    let mut attribute = PointAttribute::new();
282    attribute.init(attribute_type, 3, DataType::Float32, false, values.len());
283
284    let buffer = attribute.buffer_mut();
285    for (i, value) in values.iter().enumerate() {
286        let bytes: Vec<u8> = value
287            .iter()
288            .flat_map(|component| component.to_le_bytes())
289            .collect();
290        buffer.write(i * 12, &bytes);
291    }
292
293    attribute
294}
295
296fn make_f32x2_attribute(
297    attribute_type: GeometryAttributeType,
298    values: &[[f32; 2]],
299) -> PointAttribute {
300    let mut attribute = PointAttribute::new();
301    attribute.init(attribute_type, 2, DataType::Float32, false, values.len());
302
303    let buffer = attribute.buffer_mut();
304    for (i, value) in values.iter().enumerate() {
305        let bytes: Vec<u8> = value
306            .iter()
307            .flat_map(|component| component.to_le_bytes())
308            .collect();
309        buffer.write(i * 8, &bytes);
310    }
311
312    attribute
313}
314
315fn make_i32x3_attribute(
316    attribute_type: GeometryAttributeType,
317    values: &[[i32; 3]],
318) -> PointAttribute {
319    let mut attribute = PointAttribute::new();
320    attribute.init(attribute_type, 3, DataType::Int32, false, values.len());
321
322    let buffer = attribute.buffer_mut();
323    for (i, value) in values.iter().enumerate() {
324        let bytes: Vec<u8> = value
325            .iter()
326            .flat_map(|component| component.to_le_bytes())
327            .collect();
328        buffer.write(i * 12, &bytes);
329    }
330
331    attribute
332}
333
334fn make_u8_attribute(
335    attribute_type: GeometryAttributeType,
336    num_components: u8,
337    normalized: bool,
338    values: &[[u8; 4]],
339) -> PointAttribute {
340    let mut attribute = PointAttribute::new();
341    attribute.init(
342        attribute_type,
343        num_components,
344        DataType::Uint8,
345        normalized,
346        values.len(),
347    );
348
349    let buffer = attribute.buffer_mut();
350    for (i, value) in values.iter().enumerate() {
351        let end = num_components as usize;
352        buffer.write(i * end, &value[..end]);
353    }
354
355    attribute
356}
357
358fn invalid_ply(message: impl Into<String>) -> io::Error {
359    io::Error::new(io::ErrorKind::InvalidData, message.into())
360}
361
362fn parse_ply_property(parts: &[&str]) -> io::Result<PlyPropertyDef> {
363    if parts.len() < 3 {
364        return Err(invalid_ply("Malformed property declaration"));
365    }
366
367    if parts[1] == "list" {
368        if parts.len() < 5 {
369            return Err(invalid_ply("Malformed list property declaration"));
370        }
371        let count_type = parse_ply_scalar_type(parts[2])
372            .ok_or_else(|| invalid_ply(format!("Unsupported PLY scalar type: {}", parts[2])))?;
373        let item_type = parse_ply_scalar_type(parts[3])
374            .ok_or_else(|| invalid_ply(format!("Unsupported PLY scalar type: {}", parts[3])))?;
375        Ok(PlyPropertyDef {
376            name: parts[4].to_string(),
377            kind: PlyPropertyKind::List {
378                count_type,
379                item_type,
380            },
381        })
382    } else {
383        let data_type = parse_ply_scalar_type(parts[1])
384            .ok_or_else(|| invalid_ply(format!("Unsupported PLY scalar type: {}", parts[1])))?;
385        Ok(PlyPropertyDef {
386            name: parts[2].to_string(),
387            kind: PlyPropertyKind::Scalar(data_type),
388        })
389    }
390}
391
392fn parse_ply_header(bytes: &[u8]) -> io::Result<(PlyHeader, usize)> {
393    if bytes.is_empty() {
394        return Err(invalid_ply("Empty PLY file"));
395    }
396
397    let mut body_offset = None;
398    let mut offset = 0usize;
399    while offset < bytes.len() {
400        let line_end = bytes[offset..]
401            .iter()
402            .position(|byte| *byte == b'\n')
403            .map(|idx| offset + idx);
404        match line_end {
405            Some(end) => {
406                let line_bytes = if end > offset && bytes[end - 1] == b'\r' {
407                    &bytes[offset..end - 1]
408                } else {
409                    &bytes[offset..end]
410                };
411                let line = std::str::from_utf8(line_bytes)
412                    .map_err(|_| invalid_ply("PLY header must be valid UTF-8/ASCII"))?;
413                offset = end + 1;
414                if line.trim() == "end_header" {
415                    body_offset = Some(offset);
416                    break;
417                }
418            }
419            None => {
420                let line = std::str::from_utf8(&bytes[offset..])
421                    .map_err(|_| invalid_ply("PLY header must be valid UTF-8/ASCII"))?;
422                if line.trim() == "end_header" {
423                    body_offset = Some(bytes.len());
424                    break;
425                }
426                break;
427            }
428        }
429    }
430
431    let body_offset = body_offset.ok_or_else(|| invalid_ply("No end_header found"))?;
432    let header_text = std::str::from_utf8(&bytes[..body_offset])
433        .map_err(|_| invalid_ply("PLY header must be valid UTF-8/ASCII"))?;
434
435    let mut lines = header_text.lines();
436    let first_line = lines.next().ok_or_else(|| invalid_ply("Empty PLY file"))?;
437    if first_line.trim() != "ply" {
438        return Err(invalid_ply("Missing PLY header"));
439    }
440
441    let mut format = None;
442    let mut vertex_count = 0usize;
443    let mut face_count = 0usize;
444    let mut elements: Vec<PlyElementDef> = Vec::new();
445
446    for line in lines {
447        let trimmed = line.trim();
448        if trimmed.is_empty() || trimmed == "end_header" {
449            continue;
450        }
451
452        let parts: Vec<&str> = trimmed.split_whitespace().collect();
453        if parts.is_empty() {
454            continue;
455        }
456
457        match parts[0] {
458            "comment" | "obj_info" => {}
459            "format" => {
460                if parts.len() < 2 {
461                    return Err(invalid_ply("Malformed format declaration"));
462                }
463                format = Some(match parts[1] {
464                    "ascii" => PlyFormat::Ascii,
465                    "binary_little_endian" => PlyFormat::BinaryLittleEndian,
466                    "binary_big_endian" => PlyFormat::BinaryBigEndian,
467                    other => {
468                        return Err(invalid_ply(format!("Unsupported PLY format: {other}")));
469                    }
470                });
471            }
472            "element" => {
473                if parts.len() < 3 {
474                    return Err(invalid_ply("Malformed element declaration"));
475                }
476                let count = parts[2]
477                    .parse()
478                    .map_err(|_| invalid_ply("Invalid element count"))?;
479                elements.push(PlyElementDef {
480                    name: parts[1].to_string(),
481                    count,
482                    properties: Vec::new(),
483                });
484                match parts[1] {
485                    "vertex" => {
486                        vertex_count = count;
487                    }
488                    "face" => {
489                        face_count = count;
490                    }
491                    _ => {}
492                }
493            }
494            "property" => {
495                let property = parse_ply_property(&parts)?;
496                let Some(element) = elements.last_mut() else {
497                    return Err(invalid_ply("Property declared before element"));
498                };
499                element.properties.push(property);
500            }
501            _ => {}
502        }
503    }
504
505    let mut vertex_properties = Vec::new();
506    let mut face_properties = Vec::new();
507    for element in &elements {
508        match element.name.as_str() {
509            "vertex" => vertex_properties = element.properties.clone(),
510            "face" => face_properties = element.properties.clone(),
511            _ => {}
512        }
513    }
514
515    Ok((
516        PlyHeader {
517            format: format.ok_or_else(|| invalid_ply("Missing PLY format declaration"))?,
518            vertex_count,
519            face_count,
520            elements,
521            vertex_properties,
522            face_properties,
523        },
524        body_offset,
525    ))
526}
527
528fn skip_ascii_element_lines<'a>(lines: &mut std::str::Lines<'a>, count: usize) {
529    for _ in 0..count {
530        let _ = lines.next();
531    }
532}
533
534fn ascii_scalar_token_count(data_type: DataType) -> usize {
535    if data_type == DataType::Invalid {
536        0
537    } else {
538        1
539    }
540}
541
542fn split_ascii_vertex_lines<'a>(
543    header: &PlyHeader,
544    body_text: &'a str,
545) -> io::Result<(Vec<&'a str>, Vec<&'a str>)> {
546    let mut lines = body_text.lines();
547    let mut vertex_lines = Vec::new();
548    let mut face_lines = Vec::new();
549
550    for element in &header.elements {
551        match element.name.as_str() {
552            "vertex" => {
553                for _ in 0..element.count {
554                    if let Some(line) = lines.next() {
555                        vertex_lines.push(line);
556                    }
557                }
558            }
559            "face" => {
560                for _ in 0..element.count {
561                    if let Some(line) = lines.next() {
562                        face_lines.push(line);
563                    }
564                }
565            }
566            _ => skip_ascii_element_lines(&mut lines, element.count),
567        }
568    }
569
570    Ok((vertex_lines, face_lines))
571}
572
573fn position_data_type_for_scalar(data_type: DataType) -> DataType {
574    match data_type {
575        DataType::Int32 => DataType::Int32,
576        _ => DataType::Float32,
577    }
578}
579
580fn scalar_property_type(header: &PlyHeader, name: &str) -> Option<DataType> {
581    header.vertex_properties.iter().find_map(|property| {
582        (property.name == name)
583            .then(|| property.scalar_type())
584            .flatten()
585    })
586}
587
588fn detect_texcoord_pair(header: &PlyHeader) -> io::Result<Option<TexcoordPropertyPair>> {
589    const PAIRS: [TexcoordPropertyPair; 3] = [
590        TexcoordPropertyPair {
591            u: "texture_u",
592            v: "texture_v",
593        },
594        TexcoordPropertyPair { u: "u", v: "v" },
595        TexcoordPropertyPair { u: "s", v: "t" },
596    ];
597
598    for pair in PAIRS {
599        let u_type = scalar_property_type(header, pair.u);
600        let v_type = scalar_property_type(header, pair.v);
601        if u_type.is_some() || v_type.is_some() {
602            if u_type == Some(DataType::Float32) && v_type == Some(DataType::Float32) {
603                return Ok(Some(pair));
604            }
605            return Err(invalid_ply(format!(
606                "Texture coordinate properties {} and {} must both be float",
607                pair.u, pair.v
608            )));
609        }
610    }
611
612    Ok(None)
613}
614
615fn build_read_schema(header: &PlyHeader) -> io::Result<PlyReadSchema> {
616    let mut has_x = false;
617    let mut has_y = false;
618    let mut has_z = false;
619    let mut position_data_type = DataType::Float32;
620    let mut prop_nx_type = None;
621    let mut prop_ny_type = None;
622    let mut prop_nz_type = None;
623    let mut prop_r_type = None;
624    let mut prop_g_type = None;
625    let mut prop_b_type = None;
626    let mut prop_a_type = None;
627
628    for property in &header.vertex_properties {
629        let Some(data_type) = property.scalar_type() else {
630            continue;
631        };
632
633        match property.name.as_str() {
634            "x" => {
635                has_x = true;
636                position_data_type = position_data_type_for_scalar(data_type);
637            }
638            "y" => {
639                has_y = true;
640                position_data_type = position_data_type_for_scalar(data_type);
641            }
642            "z" => {
643                has_z = true;
644                position_data_type = position_data_type_for_scalar(data_type);
645            }
646            "nx" => prop_nx_type = Some(data_type),
647            "ny" => prop_ny_type = Some(data_type),
648            "nz" => prop_nz_type = Some(data_type),
649            "red" => prop_r_type = Some(data_type),
650            "green" => prop_g_type = Some(data_type),
651            "blue" => prop_b_type = Some(data_type),
652            "alpha" => prop_a_type = Some(data_type),
653            _ => {}
654        }
655    }
656
657    if !has_x {
658        return Err(invalid_ply("No x property"));
659    }
660    if !has_y {
661        return Err(invalid_ply("No y property"));
662    }
663    if !has_z {
664        return Err(invalid_ply("No z property"));
665    }
666
667    let has_normals = prop_nx_type == Some(DataType::Float32)
668        && prop_ny_type == Some(DataType::Float32)
669        && prop_nz_type == Some(DataType::Float32);
670
671    let color_types = [prop_r_type, prop_g_type, prop_b_type, prop_a_type];
672    let color_components = color_types.iter().flatten().count() as u8;
673    if color_components > 0 {
674        for color_type in color_types.into_iter().flatten() {
675            if color_type != DataType::Uint8 {
676                return Err(invalid_ply("Color properties must be uint8"));
677            }
678        }
679    }
680
681    Ok(PlyReadSchema {
682        position_data_type,
683        has_normals,
684        color_components,
685        texcoord_pair: detect_texcoord_pair(header)?,
686    })
687}
688
689fn triangulate_vertex_indices(indices: &[u32], faces: &mut Vec<[u32; 3]>) {
690    if indices.len() < 3 {
691        return;
692    }
693
694    for j in 1..indices.len() - 1 {
695        faces.push([indices[0], indices[j], indices[j + 1]]);
696    }
697}
698
699fn parse_ascii_face_line(
700    header: &PlyHeader,
701    line: &str,
702    faces: &mut Vec<[u32; 3]>,
703) -> io::Result<()> {
704    let parts: Vec<&str> = line.split_whitespace().collect();
705    if parts.is_empty() {
706        return Ok(());
707    }
708
709    if header.face_properties.is_empty() {
710        let indices: Vec<u32> = parts
711            .iter()
712            .map(|part| {
713                part.parse::<u32>()
714                    .map_err(|_| invalid_ply("Bad face index value"))
715            })
716            .collect::<io::Result<Vec<u32>>>()?;
717
718        if indices.is_empty() {
719            return Ok(());
720        }
721
722        let polygon_size = indices[0] as usize;
723        if polygon_size < 3 || indices.len() < polygon_size + 1 {
724            return Ok(());
725        }
726
727        triangulate_vertex_indices(&indices[1..polygon_size + 1], faces);
728        return Ok(());
729    }
730
731    let mut cursor = 0usize;
732    let mut polygon_indices: Option<Vec<u32>> = None;
733
734    for property in &header.face_properties {
735        match property.kind {
736            PlyPropertyKind::Scalar(_) => {
737                if cursor >= parts.len() {
738                    return Ok(());
739                }
740                cursor += 1;
741            }
742            PlyPropertyKind::List { .. } => {
743                if cursor >= parts.len() {
744                    return Ok(());
745                }
746                let count: usize = parts[cursor]
747                    .parse()
748                    .map_err(|_| invalid_ply("Bad face list size"))?;
749                cursor += 1;
750                if parts.len() < cursor + count {
751                    return Ok(());
752                }
753
754                let values = parts[cursor..cursor + count]
755                    .iter()
756                    .map(|part| {
757                        part.parse::<u32>()
758                            .map_err(|_| invalid_ply("Bad face index value"))
759                    })
760                    .collect::<io::Result<Vec<u32>>>()?;
761                cursor += count;
762
763                if property.name == "vertex_indices" || polygon_indices.is_none() {
764                    polygon_indices = Some(values);
765                }
766            }
767        }
768    }
769
770    if let Some(indices) = polygon_indices {
771        triangulate_vertex_indices(&indices, faces);
772    }
773
774    Ok(())
775}
776
777fn parse_ascii_f32(token: &str, label: &str) -> io::Result<f32> {
778    token
779        .parse()
780        .map_err(|_| invalid_ply(format!("Bad {label} value")))
781}
782
783fn parse_ascii_i32(token: &str, label: &str) -> io::Result<i32> {
784    token
785        .parse()
786        .map_err(|_| invalid_ply(format!("Bad {label} value")))
787}
788
789fn parse_ascii_u8(token: &str) -> io::Result<u8> {
790    token
791        .parse()
792        .map_err(|_| invalid_ply("Bad color component value"))
793}
794
795fn read_ply_ascii_body(header: &PlyHeader, body: &[u8]) -> io::Result<ParsedPlyData> {
796    let schema = build_read_schema(header)?;
797    let body_text = std::str::from_utf8(body)
798        .map_err(|_| invalid_ply("ASCII PLY payload must be valid UTF-8/ASCII"))?;
799    let (vertex_lines, face_lines) = split_ascii_vertex_lines(header, body_text)?;
800
801    let mut float_positions = matches!(schema.position_data_type, DataType::Float32)
802        .then(|| Vec::with_capacity(header.vertex_count));
803    let mut int_positions = matches!(schema.position_data_type, DataType::Int32)
804        .then(|| Vec::with_capacity(header.vertex_count));
805    let mut normals = schema
806        .has_normals
807        .then(|| Vec::with_capacity(header.vertex_count));
808    let mut colors = (schema.color_components > 0).then(|| ParsedPlyColorData {
809        num_components: schema.color_components,
810        values: Vec::with_capacity(header.vertex_count),
811    });
812    let mut texcoords = schema
813        .texcoord_pair
814        .is_some()
815        .then(|| Vec::with_capacity(header.vertex_count));
816
817    for line in vertex_lines {
818        let trimmed = line.trim();
819        if trimmed.is_empty() {
820            continue;
821        }
822
823        let parts: Vec<&str> = trimmed.split_whitespace().collect();
824        let mut float_position = [0.0f32; 3];
825        let mut int_position = [0i32; 3];
826        let mut normal = [0.0f32; 3];
827        let mut color = [0u8; 4];
828        let mut texcoord = [0.0f32; 2];
829        let mut color_component = 0usize;
830        let mut cursor = 0usize;
831
832        for property in &header.vertex_properties {
833            let Some(data_type) = property.scalar_type() else {
834                if cursor >= parts.len() {
835                    break;
836                }
837                let count: usize = parts[cursor]
838                    .parse()
839                    .map_err(|_| invalid_ply("Bad vertex list size"))?;
840                cursor = cursor
841                    .checked_add(1 + count)
842                    .ok_or_else(|| invalid_ply("ASCII PLY line is too large"))?;
843                continue;
844            };
845            if cursor >= parts.len() {
846                break;
847            }
848            let token = parts[cursor];
849            cursor += ascii_scalar_token_count(data_type);
850
851            match property.name.as_str() {
852                "x" => match schema.position_data_type {
853                    DataType::Int32 => int_position[0] = parse_ascii_i32(token, "x")?,
854                    _ => float_position[0] = parse_ascii_f32(token, "x")?,
855                },
856                "y" => match schema.position_data_type {
857                    DataType::Int32 => int_position[1] = parse_ascii_i32(token, "y")?,
858                    _ => float_position[1] = parse_ascii_f32(token, "y")?,
859                },
860                "z" => match schema.position_data_type {
861                    DataType::Int32 => int_position[2] = parse_ascii_i32(token, "z")?,
862                    _ => float_position[2] = parse_ascii_f32(token, "z")?,
863                },
864                "nx" if schema.has_normals => normal[0] = parse_ascii_f32(token, "nx")?,
865                "ny" if schema.has_normals => normal[1] = parse_ascii_f32(token, "ny")?,
866                "nz" if schema.has_normals => normal[2] = parse_ascii_f32(token, "nz")?,
867                "red" | "green" | "blue" | "alpha" if schema.color_components > 0 => {
868                    color[color_component] = parse_ascii_u8(token)?;
869                    color_component += 1;
870                }
871                name if schema.texcoord_pair.is_some_and(|pair| name == pair.u) => {
872                    texcoord[0] = parse_ascii_f32(token, name)?;
873                }
874                name if schema.texcoord_pair.is_some_and(|pair| name == pair.v) => {
875                    texcoord[1] = parse_ascii_f32(token, name)?;
876                }
877                _ => {}
878            }
879        }
880
881        match schema.position_data_type {
882            DataType::Int32 => int_positions.as_mut().unwrap().push(int_position),
883            _ => float_positions.as_mut().unwrap().push(float_position),
884        }
885
886        if let Some(normals) = normals.as_mut() {
887            normals.push(normal);
888        }
889
890        if let Some(colors) = colors.as_mut() {
891            colors.values.push(color);
892        }
893
894        if let Some(texcoords) = texcoords.as_mut() {
895            texcoords.push(texcoord);
896        }
897    }
898
899    let mut faces = Vec::with_capacity(header.face_count);
900    for line in face_lines {
901        let trimmed = line.trim();
902        if trimmed.is_empty() {
903            continue;
904        }
905        parse_ascii_face_line(header, trimmed, &mut faces)?;
906    }
907
908    Ok(ParsedPlyData {
909        positions: match schema.position_data_type {
910            DataType::Int32 => ParsedPlyPositionData::Int32(int_positions.unwrap_or_default()),
911            _ => ParsedPlyPositionData::Float32(float_positions.unwrap_or_default()),
912        },
913        faces,
914        normals,
915        colors,
916        texcoords,
917    })
918}
919
920fn ensure_remaining(cursor: &Cursor<&[u8]>, bytes_needed: usize) -> io::Result<()> {
921    let position = cursor.position() as usize;
922    let end = position
923        .checked_add(bytes_needed)
924        .ok_or_else(|| invalid_ply("PLY payload is too large"))?;
925    if end > cursor.get_ref().len() {
926        return Err(io::Error::new(
927            io::ErrorKind::UnexpectedEof,
928            "Unexpected end of binary PLY payload",
929        ));
930    }
931    Ok(())
932}
933
934fn skip_binary_scalar(cursor: &mut Cursor<&[u8]>, data_type: DataType) -> io::Result<()> {
935    ensure_remaining(cursor, data_type.byte_length())?;
936    cursor.set_position(cursor.position() + data_type.byte_length() as u64);
937    Ok(())
938}
939
940#[derive(Debug, Clone, Copy)]
941enum BinaryEndian {
942    Little,
943    Big,
944}
945
946fn read_binary_scalar_as_f32(
947    cursor: &mut Cursor<&[u8]>,
948    data_type: DataType,
949    endian: BinaryEndian,
950) -> io::Result<f32> {
951    ensure_remaining(cursor, data_type.byte_length())?;
952    match data_type {
953        DataType::Int8 => cursor.read_i8().map(|value| value as f32),
954        DataType::Uint8 => cursor.read_u8().map(|value| value as f32),
955        DataType::Int16 => match endian {
956            BinaryEndian::Little => cursor.read_i16::<LittleEndian>().map(|value| value as f32),
957            BinaryEndian::Big => cursor.read_i16::<BigEndian>().map(|value| value as f32),
958        },
959        DataType::Uint16 => match endian {
960            BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as f32),
961            BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as f32),
962        },
963        DataType::Int32 => match endian {
964            BinaryEndian::Little => cursor.read_i32::<LittleEndian>().map(|value| value as f32),
965            BinaryEndian::Big => cursor.read_i32::<BigEndian>().map(|value| value as f32),
966        },
967        DataType::Uint32 => match endian {
968            BinaryEndian::Little => cursor.read_u32::<LittleEndian>().map(|value| value as f32),
969            BinaryEndian::Big => cursor.read_u32::<BigEndian>().map(|value| value as f32),
970        },
971        DataType::Int64 => match endian {
972            BinaryEndian::Little => cursor.read_i64::<LittleEndian>().map(|value| value as f32),
973            BinaryEndian::Big => cursor.read_i64::<BigEndian>().map(|value| value as f32),
974        },
975        DataType::Uint64 => match endian {
976            BinaryEndian::Little => cursor.read_u64::<LittleEndian>().map(|value| value as f32),
977            BinaryEndian::Big => cursor.read_u64::<BigEndian>().map(|value| value as f32),
978        },
979        DataType::Float32 => match endian {
980            BinaryEndian::Little => cursor.read_f32::<LittleEndian>(),
981            BinaryEndian::Big => cursor.read_f32::<BigEndian>(),
982        },
983        DataType::Float64 => match endian {
984            BinaryEndian::Little => cursor.read_f64::<LittleEndian>().map(|value| value as f32),
985            BinaryEndian::Big => cursor.read_f64::<BigEndian>().map(|value| value as f32),
986        },
987        _ => Err(invalid_ply("Unsupported binary scalar type")),
988    }
989}
990
991fn read_binary_scalar_as_i32(
992    cursor: &mut Cursor<&[u8]>,
993    data_type: DataType,
994    endian: BinaryEndian,
995) -> io::Result<i32> {
996    ensure_remaining(cursor, data_type.byte_length())?;
997    match data_type {
998        DataType::Int8 => cursor.read_i8().map(|value| value as i32),
999        DataType::Uint8 => cursor.read_u8().map(|value| value as i32),
1000        DataType::Int16 => match endian {
1001            BinaryEndian::Little => cursor.read_i16::<LittleEndian>().map(|value| value as i32),
1002            BinaryEndian::Big => cursor.read_i16::<BigEndian>().map(|value| value as i32),
1003        },
1004        DataType::Uint16 => match endian {
1005            BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as i32),
1006            BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as i32),
1007        },
1008        DataType::Int32 => match endian {
1009            BinaryEndian::Little => cursor.read_i32::<LittleEndian>(),
1010            BinaryEndian::Big => cursor.read_i32::<BigEndian>(),
1011        },
1012        DataType::Uint32 => {
1013            let value = match endian {
1014                BinaryEndian::Little => cursor.read_u32::<LittleEndian>()?,
1015                BinaryEndian::Big => cursor.read_u32::<BigEndian>()?,
1016            };
1017            i32::try_from(value).map_err(|_| invalid_ply("Binary PLY value does not fit in int32"))
1018        }
1019        _ => Err(invalid_ply("Unsupported binary int32 scalar type")),
1020    }
1021}
1022
1023fn read_binary_scalar_as_u8(cursor: &mut Cursor<&[u8]>, data_type: DataType) -> io::Result<u8> {
1024    ensure_remaining(cursor, data_type.byte_length())?;
1025    match data_type {
1026        DataType::Uint8 => cursor.read_u8(),
1027        DataType::Int8 => {
1028            let value = cursor.read_i8()?;
1029            u8::try_from(value).map_err(|_| invalid_ply("Negative color component value"))
1030        }
1031        _ => Err(invalid_ply("Color properties must be uint8")),
1032    }
1033}
1034
1035fn read_binary_scalar_as_u32(
1036    cursor: &mut Cursor<&[u8]>,
1037    data_type: DataType,
1038    endian: BinaryEndian,
1039) -> io::Result<u32> {
1040    ensure_remaining(cursor, data_type.byte_length())?;
1041    match data_type {
1042        DataType::Uint8 => cursor.read_u8().map(|value| value as u32),
1043        DataType::Int8 => {
1044            let value = cursor.read_i8()?;
1045            u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1046        }
1047        DataType::Uint16 => match endian {
1048            BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as u32),
1049            BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as u32),
1050        },
1051        DataType::Int16 => {
1052            let value = match endian {
1053                BinaryEndian::Little => cursor.read_i16::<LittleEndian>()?,
1054                BinaryEndian::Big => cursor.read_i16::<BigEndian>()?,
1055            };
1056            u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1057        }
1058        DataType::Uint32 => match endian {
1059            BinaryEndian::Little => cursor.read_u32::<LittleEndian>(),
1060            BinaryEndian::Big => cursor.read_u32::<BigEndian>(),
1061        },
1062        DataType::Int32 => {
1063            let value = match endian {
1064                BinaryEndian::Little => cursor.read_i32::<LittleEndian>()?,
1065                BinaryEndian::Big => cursor.read_i32::<BigEndian>()?,
1066            };
1067            u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1068        }
1069        _ => Err(invalid_ply("Unsupported face index scalar type")),
1070    }
1071}
1072
1073fn read_binary_scalar_as_usize(
1074    cursor: &mut Cursor<&[u8]>,
1075    data_type: DataType,
1076    endian: BinaryEndian,
1077) -> io::Result<usize> {
1078    let value = read_binary_scalar_as_u32(cursor, data_type, endian)?;
1079    usize::try_from(value).map_err(|_| invalid_ply("Binary list size is too large"))
1080}
1081
1082fn skip_binary_element(
1083    cursor: &mut Cursor<&[u8]>,
1084    element: &PlyElementDef,
1085    endian: BinaryEndian,
1086) -> io::Result<()> {
1087    for _ in 0..element.count {
1088        for property in &element.properties {
1089            match property.kind {
1090                PlyPropertyKind::Scalar(data_type) => skip_binary_scalar(cursor, data_type)?,
1091                PlyPropertyKind::List {
1092                    count_type,
1093                    item_type,
1094                } => {
1095                    let count = read_binary_scalar_as_usize(cursor, count_type, endian)?;
1096                    for _ in 0..count {
1097                        skip_binary_scalar(cursor, item_type)?;
1098                    }
1099                }
1100            }
1101        }
1102    }
1103    Ok(())
1104}
1105
1106fn read_ply_binary_body(
1107    header: &PlyHeader,
1108    body: &[u8],
1109    endian: BinaryEndian,
1110) -> io::Result<ParsedPlyData> {
1111    let schema = build_read_schema(header)?;
1112    let mut cursor = Cursor::new(body);
1113    let vertex_element_index = header
1114        .elements
1115        .iter()
1116        .position(|element| element.name == "vertex")
1117        .ok_or_else(|| invalid_ply("Missing vertex element"))?;
1118    for element in &header.elements[..vertex_element_index] {
1119        skip_binary_element(&mut cursor, element, endian)?;
1120    }
1121
1122    let mut float_positions = matches!(schema.position_data_type, DataType::Float32)
1123        .then(|| Vec::with_capacity(header.vertex_count));
1124    let mut int_positions = matches!(schema.position_data_type, DataType::Int32)
1125        .then(|| Vec::with_capacity(header.vertex_count));
1126    let mut normals = schema
1127        .has_normals
1128        .then(|| Vec::with_capacity(header.vertex_count));
1129    let mut colors = (schema.color_components > 0).then(|| ParsedPlyColorData {
1130        num_components: schema.color_components,
1131        values: Vec::with_capacity(header.vertex_count),
1132    });
1133    let mut texcoords = schema
1134        .texcoord_pair
1135        .is_some()
1136        .then(|| Vec::with_capacity(header.vertex_count));
1137
1138    for _ in 0..header.vertex_count {
1139        let mut float_position = [0.0f32; 3];
1140        let mut int_position = [0i32; 3];
1141        let mut normal = [0.0f32; 3];
1142        let mut color = [0u8; 4];
1143        let mut texcoord = [0.0f32; 2];
1144        let mut color_component = 0usize;
1145
1146        for property in &header.vertex_properties {
1147            match property.kind {
1148                PlyPropertyKind::Scalar(data_type) => match property.name.as_str() {
1149                    "x" => match schema.position_data_type {
1150                        DataType::Int32 => {
1151                            int_position[0] =
1152                                read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1153                        }
1154                        _ => {
1155                            float_position[0] =
1156                                read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1157                        }
1158                    },
1159                    "y" => match schema.position_data_type {
1160                        DataType::Int32 => {
1161                            int_position[1] =
1162                                read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1163                        }
1164                        _ => {
1165                            float_position[1] =
1166                                read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1167                        }
1168                    },
1169                    "z" => match schema.position_data_type {
1170                        DataType::Int32 => {
1171                            int_position[2] =
1172                                read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1173                        }
1174                        _ => {
1175                            float_position[2] =
1176                                read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1177                        }
1178                    },
1179                    "nx" if schema.has_normals => {
1180                        normal[0] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1181                    }
1182                    "ny" if schema.has_normals => {
1183                        normal[1] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1184                    }
1185                    "nz" if schema.has_normals => {
1186                        normal[2] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1187                    }
1188                    "red" | "green" | "blue" | "alpha" if schema.color_components > 0 => {
1189                        color[color_component] = read_binary_scalar_as_u8(&mut cursor, data_type)?;
1190                        color_component += 1;
1191                    }
1192                    name if schema.texcoord_pair.is_some_and(|pair| name == pair.u) => {
1193                        texcoord[0] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1194                    }
1195                    name if schema.texcoord_pair.is_some_and(|pair| name == pair.v) => {
1196                        texcoord[1] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1197                    }
1198                    _ => skip_binary_scalar(&mut cursor, data_type)?,
1199                },
1200                PlyPropertyKind::List {
1201                    count_type,
1202                    item_type,
1203                } => {
1204                    let count = read_binary_scalar_as_usize(&mut cursor, count_type, endian)?;
1205                    for _ in 0..count {
1206                        skip_binary_scalar(&mut cursor, item_type)?;
1207                    }
1208                }
1209            }
1210        }
1211
1212        match schema.position_data_type {
1213            DataType::Int32 => int_positions.as_mut().unwrap().push(int_position),
1214            _ => float_positions.as_mut().unwrap().push(float_position),
1215        }
1216
1217        if let Some(normals) = normals.as_mut() {
1218            normals.push(normal);
1219        }
1220
1221        if let Some(colors) = colors.as_mut() {
1222            colors.values.push(color);
1223        }
1224
1225        if let Some(texcoords) = texcoords.as_mut() {
1226            texcoords.push(texcoord);
1227        }
1228    }
1229
1230    let face_element_index = header
1231        .elements
1232        .iter()
1233        .position(|element| element.name == "face");
1234    if let Some(face_element_index) = face_element_index {
1235        if face_element_index < vertex_element_index {
1236            return Err(invalid_ply(
1237                "PLY face element before vertex element is not supported",
1238            ));
1239        }
1240        for element in &header.elements[vertex_element_index + 1..face_element_index] {
1241            skip_binary_element(&mut cursor, element, endian)?;
1242        }
1243    }
1244
1245    if header.face_count > 0 && header.face_properties.is_empty() {
1246        return Err(invalid_ply(
1247            "Binary PLY faces require a face property declaration",
1248        ));
1249    }
1250
1251    let mut faces = Vec::with_capacity(header.face_count);
1252    for _ in 0..header.face_count {
1253        let mut polygon_indices: Option<Vec<u32>> = None;
1254
1255        for property in &header.face_properties {
1256            match property.kind {
1257                PlyPropertyKind::Scalar(data_type) => skip_binary_scalar(&mut cursor, data_type)?,
1258                PlyPropertyKind::List {
1259                    count_type,
1260                    item_type,
1261                } => {
1262                    let count = read_binary_scalar_as_usize(&mut cursor, count_type, endian)?;
1263                    let mut values = Vec::with_capacity(count);
1264                    for _ in 0..count {
1265                        values.push(read_binary_scalar_as_u32(&mut cursor, item_type, endian)?);
1266                    }
1267
1268                    if property.name == "vertex_indices" || polygon_indices.is_none() {
1269                        polygon_indices = Some(values);
1270                    }
1271                }
1272            }
1273        }
1274
1275        if let Some(indices) = polygon_indices {
1276            triangulate_vertex_indices(&indices, &mut faces);
1277        }
1278    }
1279
1280    Ok(ParsedPlyData {
1281        positions: match schema.position_data_type {
1282            DataType::Int32 => ParsedPlyPositionData::Int32(int_positions.unwrap_or_default()),
1283            _ => ParsedPlyPositionData::Float32(float_positions.unwrap_or_default()),
1284        },
1285        faces,
1286        normals,
1287        colors,
1288        texcoords,
1289    })
1290}
1291
1292fn read_ply<P: AsRef<Path>>(path: P) -> io::Result<ParsedPlyData> {
1293    let bytes = fs::read(path)?;
1294    read_ply_bytes(&bytes)
1295}
1296
1297fn read_ply_source(source: &PlyReaderSource) -> io::Result<ParsedPlyData> {
1298    match source {
1299        PlyReaderSource::Path(path) => read_ply(path),
1300        PlyReaderSource::Bytes(bytes) => read_ply_bytes(bytes),
1301    }
1302}
1303
1304fn read_ply_bytes(bytes: &[u8]) -> io::Result<ParsedPlyData> {
1305    let (header, body_offset) = parse_ply_header(bytes)?;
1306
1307    match header.format {
1308        PlyFormat::Ascii => read_ply_ascii_body(&header, &bytes[body_offset..]),
1309        PlyFormat::BinaryLittleEndian => {
1310            read_ply_binary_body(&header, &bytes[body_offset..], BinaryEndian::Little)
1311        }
1312        PlyFormat::BinaryBigEndian => {
1313            read_ply_binary_body(&header, &bytes[body_offset..], BinaryEndian::Big)
1314        }
1315    }
1316}
1317
1318/// Write point positions to an ASCII PLY file.
1319pub fn write_ply_positions<P: AsRef<Path>>(path: P, points: &[[f32; 3]]) -> io::Result<()> {
1320    let mut file = fs::File::create(path)?;
1321
1322    writeln!(file, "ply")?;
1323    writeln!(file, "format ascii 1.0")?;
1324    writeln!(file, "element vertex {}", points.len())?;
1325    writeln!(file, "property float x")?;
1326    writeln!(file, "property float y")?;
1327    writeln!(file, "property float z")?;
1328    writeln!(file, "end_header")?;
1329
1330    for p in points {
1331        writeln!(file, "{:.6} {:.6} {:.6}", p[0], p[1], p[2])?;
1332    }
1333
1334    Ok(())
1335}
1336
1337#[cfg(test)]
1338mod tests {
1339    use super::*;
1340    use draco_core::geometry_attribute::GeometryAttributeType;
1341    use tempfile::NamedTempFile;
1342
1343    #[test]
1344    fn test_read_write_ply() {
1345        let expected = vec![
1346            [0.0, 0.0, 0.0],
1347            [1.0, 0.0, 0.0],
1348            [0.0, 1.0, 0.0],
1349            [0.0, 0.0, 1.0],
1350            [-1.0, -1.0, -1.0],
1351        ];
1352
1353        let file = NamedTempFile::new().unwrap();
1354        write_ply_positions(file.path(), &expected).unwrap();
1355
1356        let positions = read_ply_positions(file.path()).unwrap();
1357        assert_eq!(positions.len(), expected.len());
1358
1359        for (i, (a, b)) in positions.iter().zip(expected.iter()).enumerate() {
1360            let diff = (a[0] - b[0]).abs() + (a[1] - b[1]).abs() + (a[2] - b[2]).abs();
1361            assert!(
1362                diff < 1e-5,
1363                "Position mismatch at index {i}: {a:?} vs {b:?}"
1364            );
1365        }
1366    }
1367
1368    #[test]
1369    fn test_read_mesh_parses_and_triangulates_faces() {
1370        let file = NamedTempFile::new().unwrap();
1371        let ply = r#"ply
1372format ascii 1.0
1373element vertex 4
1374property float x
1375property float y
1376property float z
1377element face 2
1378property list uchar int vertex_indices
1379end_header
13800 0 0
13811 0 0
13821 1 0
13830 1 0
13843 0 1 2
13854 0 1 2 3
1386"#;
1387
1388        std::fs::write(file.path(), ply).unwrap();
1389
1390        let mut reader = PlyReader::open(file.path()).unwrap();
1391        let mesh = reader.read_mesh().unwrap();
1392
1393        assert_eq!(mesh.num_points(), 4);
1394        assert_eq!(mesh.num_faces(), 3);
1395        assert_eq!(
1396            mesh.face(draco_core::geometry_indices::FaceIndex(0)),
1397            [0u32.into(), 1u32.into(), 2u32.into()]
1398        );
1399        assert_eq!(
1400            mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1401            [0u32.into(), 1u32.into(), 2u32.into()]
1402        );
1403        assert_eq!(
1404            mesh.face(draco_core::geometry_indices::FaceIndex(2)),
1405            [0u32.into(), 2u32.into(), 3u32.into()]
1406        );
1407    }
1408
1409    #[test]
1410    fn test_read_mesh_parses_normals_and_colors() {
1411        let file = NamedTempFile::new().unwrap();
1412        let ply = r#"ply
1413format ascii 1.0
1414element vertex 2
1415property float x
1416property float y
1417property float z
1418property float nx
1419property float ny
1420property float nz
1421property uchar red
1422property uchar green
1423property uchar blue
1424property uchar alpha
1425end_header
14260 0 0 0 0 1 10 20 30 40
14271 0 0 0 1 0 50 60 70 80
1428"#;
1429
1430        std::fs::write(file.path(), ply).unwrap();
1431
1432        let mut reader = PlyReader::open(file.path()).unwrap();
1433        let mesh = reader.read_mesh().unwrap();
1434
1435        assert_eq!(mesh.num_points(), 2);
1436        assert_eq!(mesh.num_faces(), 0);
1437        assert_eq!(mesh.num_attributes(), 3);
1438
1439        let normal_att = mesh.named_attribute(GeometryAttributeType::Normal).unwrap();
1440        assert_eq!(normal_att.data_type(), DataType::Float32);
1441        assert_eq!(normal_att.num_components(), 3);
1442        assert!(!normal_att.normalized());
1443
1444        let normal_data = normal_att.buffer().data();
1445        let first_normal = [
1446            f32::from_le_bytes(normal_data[0..4].try_into().unwrap()),
1447            f32::from_le_bytes(normal_data[4..8].try_into().unwrap()),
1448            f32::from_le_bytes(normal_data[8..12].try_into().unwrap()),
1449        ];
1450        assert_eq!(first_normal, [0.0, 0.0, 1.0]);
1451
1452        let color_att = mesh.named_attribute(GeometryAttributeType::Color).unwrap();
1453        assert_eq!(color_att.data_type(), DataType::Uint8);
1454        assert_eq!(color_att.num_components(), 4);
1455        assert!(color_att.normalized());
1456        assert_eq!(color_att.buffer().data(), &[10, 20, 30, 40, 50, 60, 70, 80]);
1457    }
1458
1459    #[test]
1460    fn test_read_mesh_preserves_int32_positions() {
1461        let file = NamedTempFile::new().unwrap();
1462        let ply = r#"ply
1463format ascii 1.0
1464element vertex 2
1465property int x
1466property int y
1467property int z
1468end_header
14691 2 3
14704 5 6
1471"#;
1472
1473        std::fs::write(file.path(), ply).unwrap();
1474
1475        let mut reader = PlyReader::open(file.path()).unwrap();
1476        let mesh = reader.read_mesh().unwrap();
1477
1478        let position_att = mesh
1479            .named_attribute(GeometryAttributeType::Position)
1480            .unwrap();
1481        assert_eq!(position_att.data_type(), DataType::Int32);
1482        assert_eq!(position_att.num_components(), 3);
1483        assert!(!position_att.normalized());
1484
1485        let position_data = position_att.buffer().data();
1486        let first_position = [
1487            i32::from_le_bytes(position_data[0..4].try_into().unwrap()),
1488            i32::from_le_bytes(position_data[4..8].try_into().unwrap()),
1489            i32::from_le_bytes(position_data[8..12].try_into().unwrap()),
1490        ];
1491        assert_eq!(first_position, [1, 2, 3]);
1492    }
1493
1494    #[test]
1495    fn test_read_mesh_ignores_non_float_normals() {
1496        let file = NamedTempFile::new().unwrap();
1497        let ply = r#"ply
1498format ascii 1.0
1499element vertex 1
1500property float x
1501property float y
1502property float z
1503property int nx
1504property int ny
1505property int nz
1506end_header
15070 0 0 0 0 1
1508"#;
1509
1510        std::fs::write(file.path(), ply).unwrap();
1511
1512        let mut reader = PlyReader::open(file.path()).unwrap();
1513        let mesh = reader.read_mesh().unwrap();
1514
1515        assert_eq!(mesh.named_attribute_id(GeometryAttributeType::Normal), -1);
1516    }
1517
1518    #[test]
1519    fn test_read_mesh_rejects_non_uint8_colors() {
1520        let file = NamedTempFile::new().unwrap();
1521        let ply = r#"ply
1522format ascii 1.0
1523element vertex 1
1524property float x
1525property float y
1526property float z
1527property int red
1528property int green
1529property int blue
1530end_header
15310 0 0 1 2 3
1532"#;
1533
1534        std::fs::write(file.path(), ply).unwrap();
1535
1536        let mut reader = PlyReader::open(file.path()).unwrap();
1537        let error = reader.read_mesh().unwrap_err();
1538        assert_eq!(error.kind(), io::ErrorKind::InvalidData);
1539        assert!(error.to_string().contains("Color properties must be uint8"));
1540    }
1541
1542    #[test]
1543    fn test_read_binary_little_endian_mesh() {
1544        let file = NamedTempFile::new().unwrap();
1545        let mut ply = Vec::new();
1546        ply.extend_from_slice(
1547            br#"ply
1548format binary_little_endian 1.0
1549element vertex 4
1550property float x
1551property float y
1552property float z
1553element face 2
1554property list uchar int vertex_indices
1555end_header
1556"#,
1557        );
1558
1559        for vertex in [
1560            [0.0f32, 0.0, 0.0],
1561            [1.0, 0.0, 0.0],
1562            [1.0, 1.0, 0.0],
1563            [0.0, 1.0, 0.0],
1564        ] {
1565            for component in vertex {
1566                ply.extend_from_slice(&component.to_le_bytes());
1567            }
1568        }
1569
1570        ply.push(3);
1571        for index in [0i32, 1, 2] {
1572            ply.extend_from_slice(&index.to_le_bytes());
1573        }
1574
1575        ply.push(4);
1576        for index in [0i32, 1, 2, 3] {
1577            ply.extend_from_slice(&index.to_le_bytes());
1578        }
1579
1580        std::fs::write(file.path(), ply).unwrap();
1581
1582        let mut reader = PlyReader::open(file.path()).unwrap();
1583        let mesh = reader.read_mesh().unwrap();
1584
1585        assert_eq!(mesh.num_points(), 4);
1586        assert_eq!(mesh.num_faces(), 3);
1587        assert_eq!(
1588            mesh.face(draco_core::geometry_indices::FaceIndex(0)),
1589            [0u32.into(), 1u32.into(), 2u32.into()]
1590        );
1591        assert_eq!(
1592            mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1593            [0u32.into(), 1u32.into(), 2u32.into()]
1594        );
1595        assert_eq!(
1596            mesh.face(draco_core::geometry_indices::FaceIndex(2)),
1597            [0u32.into(), 2u32.into(), 3u32.into()]
1598        );
1599    }
1600
1601    #[test]
1602    fn test_read_binary_little_endian_attributes_and_int_positions() {
1603        let file = NamedTempFile::new().unwrap();
1604        let mut ply = Vec::new();
1605        ply.extend_from_slice(
1606            br#"ply
1607format binary_little_endian 1.0
1608element vertex 2
1609property int x
1610property int y
1611property int z
1612property float nx
1613property float ny
1614property float nz
1615property uchar red
1616property uchar green
1617property uchar blue
1618property uchar alpha
1619end_header
1620"#,
1621        );
1622
1623        for (position, normal, color) in [
1624            ([1i32, 2, 3], [0.0f32, 0.0, 1.0], [10u8, 20, 30, 40]),
1625            ([4i32, 5, 6], [0.0f32, 1.0, 0.0], [50u8, 60, 70, 80]),
1626        ] {
1627            for component in position {
1628                ply.extend_from_slice(&component.to_le_bytes());
1629            }
1630            for component in normal {
1631                ply.extend_from_slice(&component.to_le_bytes());
1632            }
1633            ply.extend_from_slice(&color);
1634        }
1635
1636        std::fs::write(file.path(), ply).unwrap();
1637
1638        let mut reader = PlyReader::open(file.path()).unwrap();
1639        let mesh = reader.read_mesh().unwrap();
1640
1641        let position_att = mesh
1642            .named_attribute(GeometryAttributeType::Position)
1643            .unwrap();
1644        assert_eq!(position_att.data_type(), DataType::Int32);
1645        assert_eq!(position_att.num_components(), 3);
1646
1647        let position_data = position_att.buffer().data();
1648        let first_position = [
1649            i32::from_le_bytes(position_data[0..4].try_into().unwrap()),
1650            i32::from_le_bytes(position_data[4..8].try_into().unwrap()),
1651            i32::from_le_bytes(position_data[8..12].try_into().unwrap()),
1652        ];
1653        assert_eq!(first_position, [1, 2, 3]);
1654
1655        let normal_att = mesh.named_attribute(GeometryAttributeType::Normal).unwrap();
1656        assert_eq!(normal_att.data_type(), DataType::Float32);
1657        assert_eq!(normal_att.num_components(), 3);
1658
1659        let normal_data = normal_att.buffer().data();
1660        let first_normal = [
1661            f32::from_le_bytes(normal_data[0..4].try_into().unwrap()),
1662            f32::from_le_bytes(normal_data[4..8].try_into().unwrap()),
1663            f32::from_le_bytes(normal_data[8..12].try_into().unwrap()),
1664        ];
1665        assert_eq!(first_normal, [0.0, 0.0, 1.0]);
1666
1667        let color_att = mesh.named_attribute(GeometryAttributeType::Color).unwrap();
1668        assert_eq!(color_att.data_type(), DataType::Uint8);
1669        assert_eq!(color_att.num_components(), 4);
1670        assert!(color_att.normalized());
1671        assert_eq!(color_att.buffer().data(), &[10, 20, 30, 40, 50, 60, 70, 80]);
1672    }
1673
1674    #[test]
1675    fn test_read_binary_big_endian_mesh() {
1676        let mut ply = Vec::new();
1677        ply.extend_from_slice(
1678            br#"ply
1679format binary_big_endian 1.0
1680element vertex 4
1681property float x
1682property float y
1683property float z
1684element face 1
1685property list uchar int vertex_indices
1686end_header
1687"#,
1688        );
1689
1690        for vertex in [
1691            [0.0f32, 0.0, 0.0],
1692            [1.0, 0.0, 0.0],
1693            [1.0, 1.0, 0.0],
1694            [0.0, 1.0, 0.0],
1695        ] {
1696            for component in vertex {
1697                ply.extend_from_slice(&component.to_be_bytes());
1698            }
1699        }
1700
1701        ply.push(4);
1702        for index in [0i32, 1, 2, 3] {
1703            ply.extend_from_slice(&index.to_be_bytes());
1704        }
1705
1706        let mesh = PlyReader::read_from_bytes(&ply).unwrap();
1707        assert_eq!(mesh.num_points(), 4);
1708        assert_eq!(mesh.num_faces(), 2);
1709        assert_eq!(
1710            mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1711            [0u32.into(), 2u32.into(), 3u32.into()]
1712        );
1713    }
1714}