1use 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#[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 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 pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
159 Self {
160 source: PlyReaderSource::Bytes(bytes.into()),
161 }
162 }
163
164 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 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 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 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 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
267pub 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| matches!(*byte, b'\n' | b'\r'))
403 .map(|idx| offset + idx);
404 match line_end {
405 Some(end) => {
406 let line_bytes = &bytes[offset..end];
407 let line = std::str::from_utf8(line_bytes)
408 .map_err(|_| invalid_ply("PLY header must be valid UTF-8/ASCII"))?;
409 offset = end + 1;
410 if bytes[end] == b'\r' && bytes.get(offset) == Some(&b'\n') {
411 offset += 1;
412 }
413 if line.trim() == "end_header" {
414 body_offset = Some(offset);
415 break;
416 }
417 }
418 None => {
419 let line = std::str::from_utf8(&bytes[offset..])
420 .map_err(|_| invalid_ply("PLY header must be valid UTF-8/ASCII"))?;
421 if line.trim() == "end_header" {
422 body_offset = Some(bytes.len());
423 break;
424 }
425 break;
426 }
427 }
428 }
429
430 let body_offset = body_offset.ok_or_else(|| invalid_ply("No end_header found"))?;
431 let header_text = std::str::from_utf8(&bytes[..body_offset])
432 .map_err(|_| invalid_ply("PLY header must be valid UTF-8/ASCII"))?;
433
434 let mut lines = header_text.split(['\n', '\r']);
437 let first_line = lines.next().ok_or_else(|| invalid_ply("Empty PLY file"))?;
438 if first_line.trim() != "ply" {
439 return Err(invalid_ply("Missing PLY header"));
440 }
441
442 let mut format = None;
443 let mut vertex_count = 0usize;
444 let mut face_count = 0usize;
445 let mut elements: Vec<PlyElementDef> = Vec::new();
446
447 for line in lines {
448 let trimmed = line.trim();
449 if trimmed.is_empty() || trimmed == "end_header" {
450 continue;
451 }
452
453 let parts: Vec<&str> = trimmed.split_whitespace().collect();
454 if parts.is_empty() {
455 continue;
456 }
457
458 match parts[0] {
459 "comment" | "obj_info" => {}
460 "format" => {
461 if parts.len() < 2 {
462 return Err(invalid_ply("Malformed format declaration"));
463 }
464 format = Some(match parts[1] {
465 "ascii" => PlyFormat::Ascii,
466 "binary_little_endian" => PlyFormat::BinaryLittleEndian,
467 "binary_big_endian" => PlyFormat::BinaryBigEndian,
468 other => {
469 return Err(invalid_ply(format!("Unsupported PLY format: {other}")));
470 }
471 });
472 }
473 "element" => {
474 if parts.len() < 3 {
475 return Err(invalid_ply("Malformed element declaration"));
476 }
477 let count = parts[2]
478 .parse()
479 .map_err(|_| invalid_ply("Invalid element count"))?;
480 elements.push(PlyElementDef {
481 name: parts[1].to_string(),
482 count,
483 properties: Vec::new(),
484 });
485 match parts[1] {
486 "vertex" => {
487 vertex_count = count;
488 }
489 "face" => {
490 face_count = count;
491 }
492 _ => {}
493 }
494 }
495 "property" => {
496 let property = parse_ply_property(&parts)?;
497 let Some(element) = elements.last_mut() else {
498 return Err(invalid_ply("Property declared before element"));
499 };
500 element.properties.push(property);
501 }
502 _ => {}
503 }
504 }
505
506 let mut vertex_properties = Vec::new();
507 let mut face_properties = Vec::new();
508 for element in &elements {
509 match element.name.as_str() {
510 "vertex" => vertex_properties = element.properties.clone(),
511 "face" => face_properties = element.properties.clone(),
512 _ => {}
513 }
514 }
515
516 Ok((
517 PlyHeader {
518 format: format.ok_or_else(|| invalid_ply("Missing PLY format declaration"))?,
519 vertex_count,
520 face_count,
521 elements,
522 vertex_properties,
523 face_properties,
524 },
525 body_offset,
526 ))
527}
528
529fn skip_ascii_element_lines<'a>(lines: &mut std::str::Lines<'a>, count: usize) {
530 for _ in 0..count {
531 let _ = lines.next();
532 }
533}
534
535fn ascii_scalar_token_count(data_type: DataType) -> usize {
536 if data_type == DataType::Invalid {
537 0
538 } else {
539 1
540 }
541}
542
543fn split_ascii_vertex_lines<'a>(
544 header: &PlyHeader,
545 body_text: &'a str,
546) -> io::Result<(Vec<&'a str>, Vec<&'a str>)> {
547 let mut lines = body_text.lines();
548 let mut vertex_lines = Vec::new();
549 let mut face_lines = Vec::new();
550
551 for element in &header.elements {
552 match element.name.as_str() {
553 "vertex" => {
554 for _ in 0..element.count {
555 if let Some(line) = lines.next() {
556 vertex_lines.push(line);
557 }
558 }
559 }
560 "face" => {
561 for _ in 0..element.count {
562 if let Some(line) = lines.next() {
563 face_lines.push(line);
564 }
565 }
566 }
567 _ => skip_ascii_element_lines(&mut lines, element.count),
568 }
569 }
570
571 Ok((vertex_lines, face_lines))
572}
573
574fn position_data_type_for_scalar(data_type: DataType) -> DataType {
575 match data_type {
576 DataType::Int32 => DataType::Int32,
577 _ => DataType::Float32,
578 }
579}
580
581fn scalar_property_type(header: &PlyHeader, name: &str) -> Option<DataType> {
582 header.vertex_properties.iter().find_map(|property| {
583 (property.name == name)
584 .then(|| property.scalar_type())
585 .flatten()
586 })
587}
588
589fn detect_texcoord_pair(header: &PlyHeader) -> io::Result<Option<TexcoordPropertyPair>> {
590 const PAIRS: [TexcoordPropertyPair; 3] = [
591 TexcoordPropertyPair {
592 u: "texture_u",
593 v: "texture_v",
594 },
595 TexcoordPropertyPair { u: "u", v: "v" },
596 TexcoordPropertyPair { u: "s", v: "t" },
597 ];
598
599 for pair in PAIRS {
600 let u_type = scalar_property_type(header, pair.u);
601 let v_type = scalar_property_type(header, pair.v);
602 if u_type.is_some() || v_type.is_some() {
603 if u_type == Some(DataType::Float32) && v_type == Some(DataType::Float32) {
604 return Ok(Some(pair));
605 }
606 return Err(invalid_ply(format!(
607 "Texture coordinate properties {} and {} must both be float",
608 pair.u, pair.v
609 )));
610 }
611 }
612
613 Ok(None)
614}
615
616fn build_read_schema(header: &PlyHeader) -> io::Result<PlyReadSchema> {
617 let mut has_x = false;
618 let mut has_y = false;
619 let mut has_z = false;
620 let mut position_data_type = DataType::Float32;
621 let mut prop_nx_type = None;
622 let mut prop_ny_type = None;
623 let mut prop_nz_type = None;
624 let mut prop_r_type = None;
625 let mut prop_g_type = None;
626 let mut prop_b_type = None;
627 let mut prop_a_type = None;
628
629 for property in &header.vertex_properties {
630 let Some(data_type) = property.scalar_type() else {
631 continue;
632 };
633
634 match property.name.as_str() {
635 "x" => {
636 has_x = true;
637 position_data_type = position_data_type_for_scalar(data_type);
638 }
639 "y" => {
640 has_y = true;
641 position_data_type = position_data_type_for_scalar(data_type);
642 }
643 "z" => {
644 has_z = true;
645 position_data_type = position_data_type_for_scalar(data_type);
646 }
647 "nx" => prop_nx_type = Some(data_type),
648 "ny" => prop_ny_type = Some(data_type),
649 "nz" => prop_nz_type = Some(data_type),
650 "red" => prop_r_type = Some(data_type),
651 "green" => prop_g_type = Some(data_type),
652 "blue" => prop_b_type = Some(data_type),
653 "alpha" => prop_a_type = Some(data_type),
654 _ => {}
655 }
656 }
657
658 if !has_x {
659 return Err(invalid_ply("No x property"));
660 }
661 if !has_y {
662 return Err(invalid_ply("No y property"));
663 }
664 if !has_z {
665 return Err(invalid_ply("No z property"));
666 }
667
668 let has_normals = prop_nx_type == Some(DataType::Float32)
669 && prop_ny_type == Some(DataType::Float32)
670 && prop_nz_type == Some(DataType::Float32);
671
672 let color_types = [prop_r_type, prop_g_type, prop_b_type, prop_a_type];
673 let color_components = color_types.iter().flatten().count() as u8;
674 if color_components > 0 {
675 for color_type in color_types.into_iter().flatten() {
676 if color_type != DataType::Uint8 {
677 return Err(invalid_ply("Color properties must be uint8"));
678 }
679 }
680 }
681
682 Ok(PlyReadSchema {
683 position_data_type,
684 has_normals,
685 color_components,
686 texcoord_pair: detect_texcoord_pair(header)?,
687 })
688}
689
690fn triangulate_vertex_indices(indices: &[u32], faces: &mut Vec<[u32; 3]>) {
691 if indices.len() < 3 {
692 return;
693 }
694
695 for j in 1..indices.len() - 1 {
696 faces.push([indices[0], indices[j], indices[j + 1]]);
697 }
698}
699
700fn face_index_property(properties: &[PlyPropertyDef]) -> Option<usize> {
711 let lists = || {
712 properties
713 .iter()
714 .enumerate()
715 .filter(|(_, property)| matches!(property.kind, PlyPropertyKind::List { .. }))
716 };
717 lists()
718 .find(|(_, property)| property.name == "vertex_indices")
719 .or_else(|| lists().next())
720 .map(|(index, _)| index)
721}
722
723fn parse_ascii_face_line(
724 header: &PlyHeader,
725 line: &str,
726 faces: &mut Vec<[u32; 3]>,
727) -> io::Result<()> {
728 let parts: Vec<&str> = line.split_whitespace().collect();
729 if parts.is_empty() {
730 return Ok(());
731 }
732
733 if header.face_properties.is_empty() {
734 let indices: Vec<u32> = parts
735 .iter()
736 .map(|part| {
737 part.parse::<u32>()
738 .map_err(|_| invalid_ply("Bad face index value"))
739 })
740 .collect::<io::Result<Vec<u32>>>()?;
741
742 if indices.is_empty() {
743 return Ok(());
744 }
745
746 let polygon_size = indices[0] as usize;
747 if polygon_size < 3 || indices.len() < polygon_size + 1 {
748 return Ok(());
749 }
750
751 triangulate_vertex_indices(&indices[1..polygon_size + 1], faces);
752 return Ok(());
753 }
754
755 let index_property = face_index_property(&header.face_properties);
756 let mut cursor = 0usize;
757 let mut polygon_indices: Option<Vec<u32>> = None;
758
759 for (position, property) in header.face_properties.iter().enumerate() {
760 match property.kind {
761 PlyPropertyKind::Scalar(_) => {
762 if cursor >= parts.len() {
763 return Ok(());
764 }
765 cursor += 1;
766 }
767 PlyPropertyKind::List { .. } => {
768 if cursor >= parts.len() {
769 return Ok(());
770 }
771 let count: usize = parts[cursor]
772 .parse()
773 .map_err(|_| invalid_ply("Bad face list size"))?;
774 cursor += 1;
775 if parts.len() < cursor + count {
776 return Ok(());
777 }
778
779 if index_property == Some(position) {
780 polygon_indices = Some(
781 parts[cursor..cursor + count]
782 .iter()
783 .map(|part| {
784 part.parse::<u32>()
785 .map_err(|_| invalid_ply("Bad face index value"))
786 })
787 .collect::<io::Result<Vec<u32>>>()?,
788 );
789 }
790 cursor += count;
791 }
792 }
793 }
794
795 if let Some(indices) = polygon_indices {
796 triangulate_vertex_indices(&indices, faces);
797 }
798
799 Ok(())
800}
801
802fn parse_ascii_f32(token: &str, label: &str) -> io::Result<f32> {
803 token
804 .parse()
805 .map_err(|_| invalid_ply(format!("Bad {label} value")))
806}
807
808fn parse_ascii_i32(token: &str, label: &str) -> io::Result<i32> {
809 token
810 .parse()
811 .map_err(|_| invalid_ply(format!("Bad {label} value")))
812}
813
814fn parse_ascii_u8(token: &str) -> io::Result<u8> {
815 token
816 .parse()
817 .map_err(|_| invalid_ply("Bad color component value"))
818}
819
820fn read_ply_ascii_body(header: &PlyHeader, body: &[u8]) -> io::Result<ParsedPlyData> {
821 let schema = build_read_schema(header)?;
822 let body_text = std::str::from_utf8(body)
823 .map_err(|_| invalid_ply("ASCII PLY payload must be valid UTF-8/ASCII"))?;
824 let (vertex_lines, face_lines) = split_ascii_vertex_lines(header, body_text)?;
825
826 let mut float_positions = matches!(schema.position_data_type, DataType::Float32)
827 .then(|| Vec::with_capacity(header.vertex_count));
828 let mut int_positions = matches!(schema.position_data_type, DataType::Int32)
829 .then(|| Vec::with_capacity(header.vertex_count));
830 let mut normals = schema
831 .has_normals
832 .then(|| Vec::with_capacity(header.vertex_count));
833 let mut colors = (schema.color_components > 0).then(|| ParsedPlyColorData {
834 num_components: schema.color_components,
835 values: Vec::with_capacity(header.vertex_count),
836 });
837 let mut texcoords = schema
838 .texcoord_pair
839 .is_some()
840 .then(|| Vec::with_capacity(header.vertex_count));
841
842 for line in vertex_lines {
843 let trimmed = line.trim();
844 if trimmed.is_empty() {
845 continue;
846 }
847
848 let parts: Vec<&str> = trimmed.split_whitespace().collect();
849 let mut float_position = [0.0f32; 3];
850 let mut int_position = [0i32; 3];
851 let mut normal = [0.0f32; 3];
852 let mut color = [0u8; 4];
853 let mut texcoord = [0.0f32; 2];
854 let mut color_component = 0usize;
855 let mut cursor = 0usize;
856
857 for property in &header.vertex_properties {
858 let Some(data_type) = property.scalar_type() else {
859 if cursor >= parts.len() {
860 break;
861 }
862 let count: usize = parts[cursor]
863 .parse()
864 .map_err(|_| invalid_ply("Bad vertex list size"))?;
865 cursor = cursor
866 .checked_add(1 + count)
867 .ok_or_else(|| invalid_ply("ASCII PLY line is too large"))?;
868 continue;
869 };
870 if cursor >= parts.len() {
871 break;
872 }
873 let token = parts[cursor];
874 cursor += ascii_scalar_token_count(data_type);
875
876 match property.name.as_str() {
877 "x" => match schema.position_data_type {
878 DataType::Int32 => int_position[0] = parse_ascii_i32(token, "x")?,
879 _ => float_position[0] = parse_ascii_f32(token, "x")?,
880 },
881 "y" => match schema.position_data_type {
882 DataType::Int32 => int_position[1] = parse_ascii_i32(token, "y")?,
883 _ => float_position[1] = parse_ascii_f32(token, "y")?,
884 },
885 "z" => match schema.position_data_type {
886 DataType::Int32 => int_position[2] = parse_ascii_i32(token, "z")?,
887 _ => float_position[2] = parse_ascii_f32(token, "z")?,
888 },
889 "nx" if schema.has_normals => normal[0] = parse_ascii_f32(token, "nx")?,
890 "ny" if schema.has_normals => normal[1] = parse_ascii_f32(token, "ny")?,
891 "nz" if schema.has_normals => normal[2] = parse_ascii_f32(token, "nz")?,
892 "red" | "green" | "blue" | "alpha" if schema.color_components > 0 => {
893 color[color_component] = parse_ascii_u8(token)?;
894 color_component += 1;
895 }
896 name if schema.texcoord_pair.is_some_and(|pair| name == pair.u) => {
897 texcoord[0] = parse_ascii_f32(token, name)?;
898 }
899 name if schema.texcoord_pair.is_some_and(|pair| name == pair.v) => {
900 texcoord[1] = parse_ascii_f32(token, name)?;
901 }
902 _ => {}
903 }
904 }
905
906 match schema.position_data_type {
907 DataType::Int32 => int_positions.as_mut().unwrap().push(int_position),
908 _ => float_positions.as_mut().unwrap().push(float_position),
909 }
910
911 if let Some(normals) = normals.as_mut() {
912 normals.push(normal);
913 }
914
915 if let Some(colors) = colors.as_mut() {
916 colors.values.push(color);
917 }
918
919 if let Some(texcoords) = texcoords.as_mut() {
920 texcoords.push(texcoord);
921 }
922 }
923
924 let mut faces = Vec::with_capacity(header.face_count);
925 for line in face_lines {
926 let trimmed = line.trim();
927 if trimmed.is_empty() {
928 continue;
929 }
930 parse_ascii_face_line(header, trimmed, &mut faces)?;
931 }
932
933 Ok(ParsedPlyData {
934 positions: match schema.position_data_type {
935 DataType::Int32 => ParsedPlyPositionData::Int32(int_positions.unwrap_or_default()),
936 _ => ParsedPlyPositionData::Float32(float_positions.unwrap_or_default()),
937 },
938 faces,
939 normals,
940 colors,
941 texcoords,
942 })
943}
944
945fn ensure_remaining(cursor: &Cursor<&[u8]>, bytes_needed: usize) -> io::Result<()> {
946 let position = cursor.position() as usize;
947 let end = position
948 .checked_add(bytes_needed)
949 .ok_or_else(|| invalid_ply("PLY payload is too large"))?;
950 if end > cursor.get_ref().len() {
951 return Err(io::Error::new(
952 io::ErrorKind::UnexpectedEof,
953 "Unexpected end of binary PLY payload",
954 ));
955 }
956 Ok(())
957}
958
959fn skip_binary_scalar(cursor: &mut Cursor<&[u8]>, data_type: DataType) -> io::Result<()> {
960 ensure_remaining(cursor, data_type.byte_length())?;
961 cursor.set_position(cursor.position() + data_type.byte_length() as u64);
962 Ok(())
963}
964
965#[derive(Debug, Clone, Copy)]
966enum BinaryEndian {
967 Little,
968 Big,
969}
970
971fn read_binary_scalar_as_f32(
972 cursor: &mut Cursor<&[u8]>,
973 data_type: DataType,
974 endian: BinaryEndian,
975) -> io::Result<f32> {
976 ensure_remaining(cursor, data_type.byte_length())?;
977 match data_type {
978 DataType::Int8 => cursor.read_i8().map(|value| value as f32),
979 DataType::Uint8 => cursor.read_u8().map(|value| value as f32),
980 DataType::Int16 => match endian {
981 BinaryEndian::Little => cursor.read_i16::<LittleEndian>().map(|value| value as f32),
982 BinaryEndian::Big => cursor.read_i16::<BigEndian>().map(|value| value as f32),
983 },
984 DataType::Uint16 => match endian {
985 BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as f32),
986 BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as f32),
987 },
988 DataType::Int32 => match endian {
989 BinaryEndian::Little => cursor.read_i32::<LittleEndian>().map(|value| value as f32),
990 BinaryEndian::Big => cursor.read_i32::<BigEndian>().map(|value| value as f32),
991 },
992 DataType::Uint32 => match endian {
993 BinaryEndian::Little => cursor.read_u32::<LittleEndian>().map(|value| value as f32),
994 BinaryEndian::Big => cursor.read_u32::<BigEndian>().map(|value| value as f32),
995 },
996 DataType::Int64 => match endian {
997 BinaryEndian::Little => cursor.read_i64::<LittleEndian>().map(|value| value as f32),
998 BinaryEndian::Big => cursor.read_i64::<BigEndian>().map(|value| value as f32),
999 },
1000 DataType::Uint64 => match endian {
1001 BinaryEndian::Little => cursor.read_u64::<LittleEndian>().map(|value| value as f32),
1002 BinaryEndian::Big => cursor.read_u64::<BigEndian>().map(|value| value as f32),
1003 },
1004 DataType::Float32 => match endian {
1005 BinaryEndian::Little => cursor.read_f32::<LittleEndian>(),
1006 BinaryEndian::Big => cursor.read_f32::<BigEndian>(),
1007 },
1008 DataType::Float64 => match endian {
1009 BinaryEndian::Little => cursor.read_f64::<LittleEndian>().map(|value| value as f32),
1010 BinaryEndian::Big => cursor.read_f64::<BigEndian>().map(|value| value as f32),
1011 },
1012 _ => Err(invalid_ply("Unsupported binary scalar type")),
1013 }
1014}
1015
1016fn read_binary_scalar_as_i32(
1017 cursor: &mut Cursor<&[u8]>,
1018 data_type: DataType,
1019 endian: BinaryEndian,
1020) -> io::Result<i32> {
1021 ensure_remaining(cursor, data_type.byte_length())?;
1022 match data_type {
1023 DataType::Int8 => cursor.read_i8().map(|value| value as i32),
1024 DataType::Uint8 => cursor.read_u8().map(|value| value as i32),
1025 DataType::Int16 => match endian {
1026 BinaryEndian::Little => cursor.read_i16::<LittleEndian>().map(|value| value as i32),
1027 BinaryEndian::Big => cursor.read_i16::<BigEndian>().map(|value| value as i32),
1028 },
1029 DataType::Uint16 => match endian {
1030 BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as i32),
1031 BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as i32),
1032 },
1033 DataType::Int32 => match endian {
1034 BinaryEndian::Little => cursor.read_i32::<LittleEndian>(),
1035 BinaryEndian::Big => cursor.read_i32::<BigEndian>(),
1036 },
1037 DataType::Uint32 => {
1038 let value = match endian {
1039 BinaryEndian::Little => cursor.read_u32::<LittleEndian>()?,
1040 BinaryEndian::Big => cursor.read_u32::<BigEndian>()?,
1041 };
1042 i32::try_from(value).map_err(|_| invalid_ply("Binary PLY value does not fit in int32"))
1043 }
1044 _ => Err(invalid_ply("Unsupported binary int32 scalar type")),
1045 }
1046}
1047
1048fn read_binary_scalar_as_u8(cursor: &mut Cursor<&[u8]>, data_type: DataType) -> io::Result<u8> {
1049 ensure_remaining(cursor, data_type.byte_length())?;
1050 match data_type {
1051 DataType::Uint8 => cursor.read_u8(),
1052 DataType::Int8 => {
1053 let value = cursor.read_i8()?;
1054 u8::try_from(value).map_err(|_| invalid_ply("Negative color component value"))
1055 }
1056 _ => Err(invalid_ply("Color properties must be uint8")),
1057 }
1058}
1059
1060fn read_binary_scalar_as_u32(
1061 cursor: &mut Cursor<&[u8]>,
1062 data_type: DataType,
1063 endian: BinaryEndian,
1064) -> io::Result<u32> {
1065 ensure_remaining(cursor, data_type.byte_length())?;
1066 match data_type {
1067 DataType::Uint8 => cursor.read_u8().map(|value| value as u32),
1068 DataType::Int8 => {
1069 let value = cursor.read_i8()?;
1070 u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1071 }
1072 DataType::Uint16 => match endian {
1073 BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as u32),
1074 BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as u32),
1075 },
1076 DataType::Int16 => {
1077 let value = match endian {
1078 BinaryEndian::Little => cursor.read_i16::<LittleEndian>()?,
1079 BinaryEndian::Big => cursor.read_i16::<BigEndian>()?,
1080 };
1081 u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1082 }
1083 DataType::Uint32 => match endian {
1084 BinaryEndian::Little => cursor.read_u32::<LittleEndian>(),
1085 BinaryEndian::Big => cursor.read_u32::<BigEndian>(),
1086 },
1087 DataType::Int32 => {
1088 let value = match endian {
1089 BinaryEndian::Little => cursor.read_i32::<LittleEndian>()?,
1090 BinaryEndian::Big => cursor.read_i32::<BigEndian>()?,
1091 };
1092 u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1093 }
1094 _ => Err(invalid_ply("Unsupported face index scalar type")),
1095 }
1096}
1097
1098fn read_binary_scalar_as_usize(
1099 cursor: &mut Cursor<&[u8]>,
1100 data_type: DataType,
1101 endian: BinaryEndian,
1102) -> io::Result<usize> {
1103 let value = read_binary_scalar_as_u32(cursor, data_type, endian)?;
1104 usize::try_from(value).map_err(|_| invalid_ply("Binary list size is too large"))
1105}
1106
1107fn skip_binary_element(
1108 cursor: &mut Cursor<&[u8]>,
1109 element: &PlyElementDef,
1110 endian: BinaryEndian,
1111) -> io::Result<()> {
1112 for _ in 0..element.count {
1113 for property in &element.properties {
1114 match property.kind {
1115 PlyPropertyKind::Scalar(data_type) => skip_binary_scalar(cursor, data_type)?,
1116 PlyPropertyKind::List {
1117 count_type,
1118 item_type,
1119 } => {
1120 let count = read_binary_scalar_as_usize(cursor, count_type, endian)?;
1121 for _ in 0..count {
1122 skip_binary_scalar(cursor, item_type)?;
1123 }
1124 }
1125 }
1126 }
1127 }
1128 Ok(())
1129}
1130
1131fn read_ply_binary_body(
1132 header: &PlyHeader,
1133 body: &[u8],
1134 endian: BinaryEndian,
1135) -> io::Result<ParsedPlyData> {
1136 let schema = build_read_schema(header)?;
1137 let mut cursor = Cursor::new(body);
1138 let vertex_element_index = header
1139 .elements
1140 .iter()
1141 .position(|element| element.name == "vertex")
1142 .ok_or_else(|| invalid_ply("Missing vertex element"))?;
1143 for element in &header.elements[..vertex_element_index] {
1144 skip_binary_element(&mut cursor, element, endian)?;
1145 }
1146
1147 let mut float_positions = matches!(schema.position_data_type, DataType::Float32)
1148 .then(|| Vec::with_capacity(header.vertex_count));
1149 let mut int_positions = matches!(schema.position_data_type, DataType::Int32)
1150 .then(|| Vec::with_capacity(header.vertex_count));
1151 let mut normals = schema
1152 .has_normals
1153 .then(|| Vec::with_capacity(header.vertex_count));
1154 let mut colors = (schema.color_components > 0).then(|| ParsedPlyColorData {
1155 num_components: schema.color_components,
1156 values: Vec::with_capacity(header.vertex_count),
1157 });
1158 let mut texcoords = schema
1159 .texcoord_pair
1160 .is_some()
1161 .then(|| Vec::with_capacity(header.vertex_count));
1162
1163 for _ in 0..header.vertex_count {
1164 let mut float_position = [0.0f32; 3];
1165 let mut int_position = [0i32; 3];
1166 let mut normal = [0.0f32; 3];
1167 let mut color = [0u8; 4];
1168 let mut texcoord = [0.0f32; 2];
1169 let mut color_component = 0usize;
1170
1171 for property in &header.vertex_properties {
1172 match property.kind {
1173 PlyPropertyKind::Scalar(data_type) => match property.name.as_str() {
1174 "x" => match schema.position_data_type {
1175 DataType::Int32 => {
1176 int_position[0] =
1177 read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1178 }
1179 _ => {
1180 float_position[0] =
1181 read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1182 }
1183 },
1184 "y" => match schema.position_data_type {
1185 DataType::Int32 => {
1186 int_position[1] =
1187 read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1188 }
1189 _ => {
1190 float_position[1] =
1191 read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1192 }
1193 },
1194 "z" => match schema.position_data_type {
1195 DataType::Int32 => {
1196 int_position[2] =
1197 read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1198 }
1199 _ => {
1200 float_position[2] =
1201 read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1202 }
1203 },
1204 "nx" if schema.has_normals => {
1205 normal[0] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1206 }
1207 "ny" if schema.has_normals => {
1208 normal[1] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1209 }
1210 "nz" if schema.has_normals => {
1211 normal[2] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1212 }
1213 "red" | "green" | "blue" | "alpha" if schema.color_components > 0 => {
1214 color[color_component] = read_binary_scalar_as_u8(&mut cursor, data_type)?;
1215 color_component += 1;
1216 }
1217 name if schema.texcoord_pair.is_some_and(|pair| name == pair.u) => {
1218 texcoord[0] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1219 }
1220 name if schema.texcoord_pair.is_some_and(|pair| name == pair.v) => {
1221 texcoord[1] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1222 }
1223 _ => skip_binary_scalar(&mut cursor, data_type)?,
1224 },
1225 PlyPropertyKind::List {
1226 count_type,
1227 item_type,
1228 } => {
1229 let count = read_binary_scalar_as_usize(&mut cursor, count_type, endian)?;
1230 for _ in 0..count {
1231 skip_binary_scalar(&mut cursor, item_type)?;
1232 }
1233 }
1234 }
1235 }
1236
1237 match schema.position_data_type {
1238 DataType::Int32 => int_positions.as_mut().unwrap().push(int_position),
1239 _ => float_positions.as_mut().unwrap().push(float_position),
1240 }
1241
1242 if let Some(normals) = normals.as_mut() {
1243 normals.push(normal);
1244 }
1245
1246 if let Some(colors) = colors.as_mut() {
1247 colors.values.push(color);
1248 }
1249
1250 if let Some(texcoords) = texcoords.as_mut() {
1251 texcoords.push(texcoord);
1252 }
1253 }
1254
1255 let face_element_index = header
1256 .elements
1257 .iter()
1258 .position(|element| element.name == "face");
1259 if let Some(face_element_index) = face_element_index {
1260 if face_element_index < vertex_element_index {
1261 return Err(invalid_ply(
1262 "PLY face element before vertex element is not supported",
1263 ));
1264 }
1265 for element in &header.elements[vertex_element_index + 1..face_element_index] {
1266 skip_binary_element(&mut cursor, element, endian)?;
1267 }
1268 }
1269
1270 if header.face_count > 0 && header.face_properties.is_empty() {
1271 return Err(invalid_ply(
1272 "Binary PLY faces require a face property declaration",
1273 ));
1274 }
1275
1276 let index_property = face_index_property(&header.face_properties);
1277 let mut faces = Vec::with_capacity(header.face_count);
1278 for _ in 0..header.face_count {
1279 let mut polygon_indices: Option<Vec<u32>> = None;
1280
1281 for (position, property) in header.face_properties.iter().enumerate() {
1282 match property.kind {
1283 PlyPropertyKind::Scalar(data_type) => skip_binary_scalar(&mut cursor, data_type)?,
1284 PlyPropertyKind::List {
1285 count_type,
1286 item_type,
1287 } => {
1288 let count = read_binary_scalar_as_usize(&mut cursor, count_type, endian)?;
1289 if index_property == Some(position) {
1290 let mut values = Vec::with_capacity(count);
1291 for _ in 0..count {
1292 values.push(read_binary_scalar_as_u32(&mut cursor, item_type, endian)?);
1293 }
1294 polygon_indices = Some(values);
1295 } else {
1296 for _ in 0..count {
1297 skip_binary_scalar(&mut cursor, item_type)?;
1298 }
1299 }
1300 }
1301 }
1302 }
1303
1304 if let Some(indices) = polygon_indices {
1305 triangulate_vertex_indices(&indices, &mut faces);
1306 }
1307 }
1308
1309 Ok(ParsedPlyData {
1310 positions: match schema.position_data_type {
1311 DataType::Int32 => ParsedPlyPositionData::Int32(int_positions.unwrap_or_default()),
1312 _ => ParsedPlyPositionData::Float32(float_positions.unwrap_or_default()),
1313 },
1314 faces,
1315 normals,
1316 colors,
1317 texcoords,
1318 })
1319}
1320
1321fn read_ply<P: AsRef<Path>>(path: P) -> io::Result<ParsedPlyData> {
1322 let bytes = fs::read(path)?;
1323 read_ply_bytes(&bytes)
1324}
1325
1326fn read_ply_source(source: &PlyReaderSource) -> io::Result<ParsedPlyData> {
1327 match source {
1328 PlyReaderSource::Path(path) => read_ply(path),
1329 PlyReaderSource::Bytes(bytes) => read_ply_bytes(bytes),
1330 }
1331}
1332
1333fn read_ply_bytes(bytes: &[u8]) -> io::Result<ParsedPlyData> {
1334 let (header, body_offset) = parse_ply_header(bytes)?;
1335
1336 match header.format {
1337 PlyFormat::Ascii => read_ply_ascii_body(&header, &bytes[body_offset..]),
1338 PlyFormat::BinaryLittleEndian => {
1339 read_ply_binary_body(&header, &bytes[body_offset..], BinaryEndian::Little)
1340 }
1341 PlyFormat::BinaryBigEndian => {
1342 read_ply_binary_body(&header, &bytes[body_offset..], BinaryEndian::Big)
1343 }
1344 }
1345}
1346
1347pub fn write_ply_positions<P: AsRef<Path>>(path: P, points: &[[f32; 3]]) -> io::Result<()> {
1349 let mut file = fs::File::create(path)?;
1350
1351 writeln!(file, "ply")?;
1352 writeln!(file, "format ascii 1.0")?;
1353 writeln!(file, "element vertex {}", points.len())?;
1354 writeln!(file, "property float x")?;
1355 writeln!(file, "property float y")?;
1356 writeln!(file, "property float z")?;
1357 writeln!(file, "end_header")?;
1358
1359 for p in points {
1360 writeln!(file, "{:.6} {:.6} {:.6}", p[0], p[1], p[2])?;
1361 }
1362
1363 Ok(())
1364}
1365
1366#[cfg(test)]
1367mod tests {
1368 use super::*;
1369 use draco_core::geometry_attribute::GeometryAttributeType;
1370 use tempfile::NamedTempFile;
1371
1372 #[test]
1373 fn test_read_write_ply() {
1374 let expected = vec![
1375 [0.0, 0.0, 0.0],
1376 [1.0, 0.0, 0.0],
1377 [0.0, 1.0, 0.0],
1378 [0.0, 0.0, 1.0],
1379 [-1.0, -1.0, -1.0],
1380 ];
1381
1382 let file = NamedTempFile::new().unwrap();
1383 write_ply_positions(file.path(), &expected).unwrap();
1384
1385 let positions = read_ply_positions(file.path()).unwrap();
1386 assert_eq!(positions.len(), expected.len());
1387
1388 for (i, (a, b)) in positions.iter().zip(expected.iter()).enumerate() {
1389 let diff = (a[0] - b[0]).abs() + (a[1] - b[1]).abs() + (a[2] - b[2]).abs();
1390 assert!(
1391 diff < 1e-5,
1392 "Position mismatch at index {i}: {a:?} vs {b:?}"
1393 );
1394 }
1395 }
1396
1397 #[test]
1398 fn test_read_mesh_parses_and_triangulates_faces() {
1399 let file = NamedTempFile::new().unwrap();
1400 let ply = r#"ply
1401format ascii 1.0
1402element vertex 4
1403property float x
1404property float y
1405property float z
1406element face 2
1407property list uchar int vertex_indices
1408end_header
14090 0 0
14101 0 0
14111 1 0
14120 1 0
14133 0 1 2
14144 0 1 2 3
1415"#;
1416
1417 std::fs::write(file.path(), ply).unwrap();
1418
1419 let mut reader = PlyReader::open(file.path()).unwrap();
1420 let mesh = reader.read_mesh().unwrap();
1421
1422 assert_eq!(mesh.num_points(), 4);
1423 assert_eq!(mesh.num_faces(), 3);
1424 assert_eq!(
1425 mesh.face(draco_core::geometry_indices::FaceIndex(0)),
1426 [0u32.into(), 1u32.into(), 2u32.into()]
1427 );
1428 assert_eq!(
1429 mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1430 [0u32.into(), 1u32.into(), 2u32.into()]
1431 );
1432 assert_eq!(
1433 mesh.face(draco_core::geometry_indices::FaceIndex(2)),
1434 [0u32.into(), 2u32.into(), 3u32.into()]
1435 );
1436 }
1437
1438 #[test]
1439 fn test_read_mesh_parses_normals_and_colors() {
1440 let file = NamedTempFile::new().unwrap();
1441 let ply = r#"ply
1442format ascii 1.0
1443element vertex 2
1444property float x
1445property float y
1446property float z
1447property float nx
1448property float ny
1449property float nz
1450property uchar red
1451property uchar green
1452property uchar blue
1453property uchar alpha
1454end_header
14550 0 0 0 0 1 10 20 30 40
14561 0 0 0 1 0 50 60 70 80
1457"#;
1458
1459 std::fs::write(file.path(), ply).unwrap();
1460
1461 let mut reader = PlyReader::open(file.path()).unwrap();
1462 let mesh = reader.read_mesh().unwrap();
1463
1464 assert_eq!(mesh.num_points(), 2);
1465 assert_eq!(mesh.num_faces(), 0);
1466 assert_eq!(mesh.num_attributes(), 3);
1467
1468 let normal_att = mesh.named_attribute(GeometryAttributeType::Normal).unwrap();
1469 assert_eq!(normal_att.data_type(), DataType::Float32);
1470 assert_eq!(normal_att.num_components(), 3);
1471 assert!(!normal_att.normalized());
1472
1473 let normal_data = normal_att.buffer().data();
1474 let first_normal = [
1475 f32::from_le_bytes(normal_data[0..4].try_into().unwrap()),
1476 f32::from_le_bytes(normal_data[4..8].try_into().unwrap()),
1477 f32::from_le_bytes(normal_data[8..12].try_into().unwrap()),
1478 ];
1479 assert_eq!(first_normal, [0.0, 0.0, 1.0]);
1480
1481 let color_att = mesh.named_attribute(GeometryAttributeType::Color).unwrap();
1482 assert_eq!(color_att.data_type(), DataType::Uint8);
1483 assert_eq!(color_att.num_components(), 4);
1484 assert!(color_att.normalized());
1485 assert_eq!(color_att.buffer().data(), &[10, 20, 30, 40, 50, 60, 70, 80]);
1486 }
1487
1488 #[test]
1489 fn test_read_mesh_preserves_int32_positions() {
1490 let file = NamedTempFile::new().unwrap();
1491 let ply = r#"ply
1492format ascii 1.0
1493element vertex 2
1494property int x
1495property int y
1496property int z
1497end_header
14981 2 3
14994 5 6
1500"#;
1501
1502 std::fs::write(file.path(), ply).unwrap();
1503
1504 let mut reader = PlyReader::open(file.path()).unwrap();
1505 let mesh = reader.read_mesh().unwrap();
1506
1507 let position_att = mesh
1508 .named_attribute(GeometryAttributeType::Position)
1509 .unwrap();
1510 assert_eq!(position_att.data_type(), DataType::Int32);
1511 assert_eq!(position_att.num_components(), 3);
1512 assert!(!position_att.normalized());
1513
1514 let position_data = position_att.buffer().data();
1515 let first_position = [
1516 i32::from_le_bytes(position_data[0..4].try_into().unwrap()),
1517 i32::from_le_bytes(position_data[4..8].try_into().unwrap()),
1518 i32::from_le_bytes(position_data[8..12].try_into().unwrap()),
1519 ];
1520 assert_eq!(first_position, [1, 2, 3]);
1521 }
1522
1523 #[test]
1524 fn test_read_mesh_ignores_non_float_normals() {
1525 let file = NamedTempFile::new().unwrap();
1526 let ply = r#"ply
1527format ascii 1.0
1528element vertex 1
1529property float x
1530property float y
1531property float z
1532property int nx
1533property int ny
1534property int nz
1535end_header
15360 0 0 0 0 1
1537"#;
1538
1539 std::fs::write(file.path(), ply).unwrap();
1540
1541 let mut reader = PlyReader::open(file.path()).unwrap();
1542 let mesh = reader.read_mesh().unwrap();
1543
1544 assert_eq!(mesh.named_attribute_id(GeometryAttributeType::Normal), -1);
1545 }
1546
1547 #[test]
1548 fn test_read_mesh_rejects_non_uint8_colors() {
1549 let file = NamedTempFile::new().unwrap();
1550 let ply = r#"ply
1551format ascii 1.0
1552element vertex 1
1553property float x
1554property float y
1555property float z
1556property int red
1557property int green
1558property int blue
1559end_header
15600 0 0 1 2 3
1561"#;
1562
1563 std::fs::write(file.path(), ply).unwrap();
1564
1565 let mut reader = PlyReader::open(file.path()).unwrap();
1566 let error = reader.read_mesh().unwrap_err();
1567 assert_eq!(error.kind(), io::ErrorKind::InvalidData);
1568 assert!(error.to_string().contains("Color properties must be uint8"));
1569 }
1570
1571 #[test]
1576 fn test_read_mesh_skips_non_index_face_lists() {
1577 let file = NamedTempFile::new().unwrap();
1578 let ply = r#"ply
1579format ascii 1.0
1580element vertex 4
1581property float x
1582property float y
1583property float z
1584element face 2
1585property list uchar int vertex_indices
1586property list uchar float texcoord
1587end_header
15880 0 0
15891 0 0
15901 1 0
15910 1 0
15923 0 1 2 6 0 0 1 0 1 1
15934 0 1 2 3 8 0 0 1 0 1 1 0 1
1594"#;
1595
1596 std::fs::write(file.path(), ply).unwrap();
1597
1598 let mut reader = PlyReader::open(file.path()).unwrap();
1599 let mesh = reader.read_mesh().unwrap();
1600
1601 assert_eq!(mesh.num_points(), 4);
1602 assert_eq!(mesh.num_faces(), 3);
1603 assert_eq!(
1604 mesh.face(draco_core::geometry_indices::FaceIndex(2)),
1605 [0u32.into(), 2u32.into(), 3u32.into()]
1606 );
1607 }
1608
1609 #[test]
1613 fn test_read_binary_mesh_skips_non_index_face_lists() {
1614 let file = NamedTempFile::new().unwrap();
1615 let mut ply = Vec::new();
1616 ply.extend_from_slice(
1617 br#"ply
1618format binary_little_endian 1.0
1619element vertex 4
1620property float x
1621property float y
1622property float z
1623element face 2
1624property list uchar int vertex_indices
1625property list uchar float texcoord
1626end_header
1627"#,
1628 );
1629
1630 for vertex in [
1631 [0.0f32, 0.0, 0.0],
1632 [1.0, 0.0, 0.0],
1633 [1.0, 1.0, 0.0],
1634 [0.0, 1.0, 0.0],
1635 ] {
1636 for component in vertex {
1637 ply.extend_from_slice(&component.to_le_bytes());
1638 }
1639 }
1640
1641 for indices in [vec![0i32, 1, 2], vec![0, 2, 3]] {
1642 ply.push(indices.len() as u8);
1643 for index in &indices {
1644 ply.extend_from_slice(&index.to_le_bytes());
1645 }
1646 ply.push((indices.len() * 2) as u8);
1647 for corner in 0..indices.len() * 2 {
1648 ply.extend_from_slice(&(corner as f32).to_le_bytes());
1649 }
1650 }
1651
1652 std::fs::write(file.path(), ply).unwrap();
1653
1654 let mut reader = PlyReader::open(file.path()).unwrap();
1655 let mesh = reader.read_mesh().unwrap();
1656
1657 assert_eq!(mesh.num_points(), 4);
1658 assert_eq!(mesh.num_faces(), 2);
1659 assert_eq!(
1660 mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1661 [0u32.into(), 2u32.into(), 3u32.into()]
1662 );
1663 }
1664
1665 #[test]
1666 fn test_read_binary_little_endian_mesh() {
1667 let file = NamedTempFile::new().unwrap();
1668 let mut ply = Vec::new();
1669 ply.extend_from_slice(
1670 br#"ply
1671format binary_little_endian 1.0
1672element vertex 4
1673property float x
1674property float y
1675property float z
1676element face 2
1677property list uchar int vertex_indices
1678end_header
1679"#,
1680 );
1681
1682 for vertex in [
1683 [0.0f32, 0.0, 0.0],
1684 [1.0, 0.0, 0.0],
1685 [1.0, 1.0, 0.0],
1686 [0.0, 1.0, 0.0],
1687 ] {
1688 for component in vertex {
1689 ply.extend_from_slice(&component.to_le_bytes());
1690 }
1691 }
1692
1693 ply.push(3);
1694 for index in [0i32, 1, 2] {
1695 ply.extend_from_slice(&index.to_le_bytes());
1696 }
1697
1698 ply.push(4);
1699 for index in [0i32, 1, 2, 3] {
1700 ply.extend_from_slice(&index.to_le_bytes());
1701 }
1702
1703 std::fs::write(file.path(), ply).unwrap();
1704
1705 let mut reader = PlyReader::open(file.path()).unwrap();
1706 let mesh = reader.read_mesh().unwrap();
1707
1708 assert_eq!(mesh.num_points(), 4);
1709 assert_eq!(mesh.num_faces(), 3);
1710 assert_eq!(
1711 mesh.face(draco_core::geometry_indices::FaceIndex(0)),
1712 [0u32.into(), 1u32.into(), 2u32.into()]
1713 );
1714 assert_eq!(
1715 mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1716 [0u32.into(), 1u32.into(), 2u32.into()]
1717 );
1718 assert_eq!(
1719 mesh.face(draco_core::geometry_indices::FaceIndex(2)),
1720 [0u32.into(), 2u32.into(), 3u32.into()]
1721 );
1722 }
1723
1724 #[test]
1725 fn test_read_binary_little_endian_mesh_with_cr_only_header() {
1726 let mut ply = b"ply\rformat binary_little_endian 1.0\relement vertex 24\rproperty float x\rproperty float y\rproperty float z\relement face 1\rproperty list uchar int vertex_indices\rend_header\r".to_vec();
1727 for index in 0..24 {
1728 ply.extend_from_slice(&(index as f32).to_le_bytes());
1729 ply.extend_from_slice(&0.0f32.to_le_bytes());
1730 ply.extend_from_slice(&0.0f32.to_le_bytes());
1731 }
1732 ply.extend_from_slice(&[3]);
1733 for index in [0i32, 1, 2] {
1734 ply.extend_from_slice(&index.to_le_bytes());
1735 }
1736
1737 let mesh =
1738 PlyReader::read_from_bytes(&ply).expect("CR-only binary PLY header should parse");
1739
1740 assert_eq!(mesh.num_points(), 24);
1741 assert!(mesh.num_faces() > 0);
1742 }
1743
1744 #[test]
1745 fn test_read_binary_little_endian_attributes_and_int_positions() {
1746 let file = NamedTempFile::new().unwrap();
1747 let mut ply = Vec::new();
1748 ply.extend_from_slice(
1749 br#"ply
1750format binary_little_endian 1.0
1751element vertex 2
1752property int x
1753property int y
1754property int z
1755property float nx
1756property float ny
1757property float nz
1758property uchar red
1759property uchar green
1760property uchar blue
1761property uchar alpha
1762end_header
1763"#,
1764 );
1765
1766 for (position, normal, color) in [
1767 ([1i32, 2, 3], [0.0f32, 0.0, 1.0], [10u8, 20, 30, 40]),
1768 ([4i32, 5, 6], [0.0f32, 1.0, 0.0], [50u8, 60, 70, 80]),
1769 ] {
1770 for component in position {
1771 ply.extend_from_slice(&component.to_le_bytes());
1772 }
1773 for component in normal {
1774 ply.extend_from_slice(&component.to_le_bytes());
1775 }
1776 ply.extend_from_slice(&color);
1777 }
1778
1779 std::fs::write(file.path(), ply).unwrap();
1780
1781 let mut reader = PlyReader::open(file.path()).unwrap();
1782 let mesh = reader.read_mesh().unwrap();
1783
1784 let position_att = mesh
1785 .named_attribute(GeometryAttributeType::Position)
1786 .unwrap();
1787 assert_eq!(position_att.data_type(), DataType::Int32);
1788 assert_eq!(position_att.num_components(), 3);
1789
1790 let position_data = position_att.buffer().data();
1791 let first_position = [
1792 i32::from_le_bytes(position_data[0..4].try_into().unwrap()),
1793 i32::from_le_bytes(position_data[4..8].try_into().unwrap()),
1794 i32::from_le_bytes(position_data[8..12].try_into().unwrap()),
1795 ];
1796 assert_eq!(first_position, [1, 2, 3]);
1797
1798 let normal_att = mesh.named_attribute(GeometryAttributeType::Normal).unwrap();
1799 assert_eq!(normal_att.data_type(), DataType::Float32);
1800 assert_eq!(normal_att.num_components(), 3);
1801
1802 let normal_data = normal_att.buffer().data();
1803 let first_normal = [
1804 f32::from_le_bytes(normal_data[0..4].try_into().unwrap()),
1805 f32::from_le_bytes(normal_data[4..8].try_into().unwrap()),
1806 f32::from_le_bytes(normal_data[8..12].try_into().unwrap()),
1807 ];
1808 assert_eq!(first_normal, [0.0, 0.0, 1.0]);
1809
1810 let color_att = mesh.named_attribute(GeometryAttributeType::Color).unwrap();
1811 assert_eq!(color_att.data_type(), DataType::Uint8);
1812 assert_eq!(color_att.num_components(), 4);
1813 assert!(color_att.normalized());
1814 assert_eq!(color_att.buffer().data(), &[10, 20, 30, 40, 50, 60, 70, 80]);
1815 }
1816
1817 #[test]
1818 fn test_read_binary_big_endian_mesh() {
1819 let mut ply = Vec::new();
1820 ply.extend_from_slice(
1821 br#"ply
1822format binary_big_endian 1.0
1823element vertex 4
1824property float x
1825property float y
1826property float z
1827element face 1
1828property list uchar int vertex_indices
1829end_header
1830"#,
1831 );
1832
1833 for vertex in [
1834 [0.0f32, 0.0, 0.0],
1835 [1.0, 0.0, 0.0],
1836 [1.0, 1.0, 0.0],
1837 [0.0, 1.0, 0.0],
1838 ] {
1839 for component in vertex {
1840 ply.extend_from_slice(&component.to_be_bytes());
1841 }
1842 }
1843
1844 ply.push(4);
1845 for index in [0i32, 1, 2, 3] {
1846 ply.extend_from_slice(&index.to_be_bytes());
1847 }
1848
1849 let mesh = PlyReader::read_from_bytes(&ply).unwrap();
1850 assert_eq!(mesh.num_points(), 4);
1851 assert_eq!(mesh.num_faces(), 2);
1852 assert_eq!(
1853 mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1854 [0u32.into(), 2u32.into(), 3u32.into()]
1855 );
1856 }
1857}