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 parse_ascii_face_line(
701 header: &PlyHeader,
702 line: &str,
703 faces: &mut Vec<[u32; 3]>,
704) -> io::Result<()> {
705 let parts: Vec<&str> = line.split_whitespace().collect();
706 if parts.is_empty() {
707 return Ok(());
708 }
709
710 if header.face_properties.is_empty() {
711 let indices: Vec<u32> = parts
712 .iter()
713 .map(|part| {
714 part.parse::<u32>()
715 .map_err(|_| invalid_ply("Bad face index value"))
716 })
717 .collect::<io::Result<Vec<u32>>>()?;
718
719 if indices.is_empty() {
720 return Ok(());
721 }
722
723 let polygon_size = indices[0] as usize;
724 if polygon_size < 3 || indices.len() < polygon_size + 1 {
725 return Ok(());
726 }
727
728 triangulate_vertex_indices(&indices[1..polygon_size + 1], faces);
729 return Ok(());
730 }
731
732 let mut cursor = 0usize;
733 let mut polygon_indices: Option<Vec<u32>> = None;
734
735 for property in &header.face_properties {
736 match property.kind {
737 PlyPropertyKind::Scalar(_) => {
738 if cursor >= parts.len() {
739 return Ok(());
740 }
741 cursor += 1;
742 }
743 PlyPropertyKind::List { .. } => {
744 if cursor >= parts.len() {
745 return Ok(());
746 }
747 let count: usize = parts[cursor]
748 .parse()
749 .map_err(|_| invalid_ply("Bad face list size"))?;
750 cursor += 1;
751 if parts.len() < cursor + count {
752 return Ok(());
753 }
754
755 let values = parts[cursor..cursor + count]
756 .iter()
757 .map(|part| {
758 part.parse::<u32>()
759 .map_err(|_| invalid_ply("Bad face index value"))
760 })
761 .collect::<io::Result<Vec<u32>>>()?;
762 cursor += count;
763
764 if property.name == "vertex_indices" || polygon_indices.is_none() {
765 polygon_indices = Some(values);
766 }
767 }
768 }
769 }
770
771 if let Some(indices) = polygon_indices {
772 triangulate_vertex_indices(&indices, faces);
773 }
774
775 Ok(())
776}
777
778fn parse_ascii_f32(token: &str, label: &str) -> io::Result<f32> {
779 token
780 .parse()
781 .map_err(|_| invalid_ply(format!("Bad {label} value")))
782}
783
784fn parse_ascii_i32(token: &str, label: &str) -> io::Result<i32> {
785 token
786 .parse()
787 .map_err(|_| invalid_ply(format!("Bad {label} value")))
788}
789
790fn parse_ascii_u8(token: &str) -> io::Result<u8> {
791 token
792 .parse()
793 .map_err(|_| invalid_ply("Bad color component value"))
794}
795
796fn read_ply_ascii_body(header: &PlyHeader, body: &[u8]) -> io::Result<ParsedPlyData> {
797 let schema = build_read_schema(header)?;
798 let body_text = std::str::from_utf8(body)
799 .map_err(|_| invalid_ply("ASCII PLY payload must be valid UTF-8/ASCII"))?;
800 let (vertex_lines, face_lines) = split_ascii_vertex_lines(header, body_text)?;
801
802 let mut float_positions = matches!(schema.position_data_type, DataType::Float32)
803 .then(|| Vec::with_capacity(header.vertex_count));
804 let mut int_positions = matches!(schema.position_data_type, DataType::Int32)
805 .then(|| Vec::with_capacity(header.vertex_count));
806 let mut normals = schema
807 .has_normals
808 .then(|| Vec::with_capacity(header.vertex_count));
809 let mut colors = (schema.color_components > 0).then(|| ParsedPlyColorData {
810 num_components: schema.color_components,
811 values: Vec::with_capacity(header.vertex_count),
812 });
813 let mut texcoords = schema
814 .texcoord_pair
815 .is_some()
816 .then(|| Vec::with_capacity(header.vertex_count));
817
818 for line in vertex_lines {
819 let trimmed = line.trim();
820 if trimmed.is_empty() {
821 continue;
822 }
823
824 let parts: Vec<&str> = trimmed.split_whitespace().collect();
825 let mut float_position = [0.0f32; 3];
826 let mut int_position = [0i32; 3];
827 let mut normal = [0.0f32; 3];
828 let mut color = [0u8; 4];
829 let mut texcoord = [0.0f32; 2];
830 let mut color_component = 0usize;
831 let mut cursor = 0usize;
832
833 for property in &header.vertex_properties {
834 let Some(data_type) = property.scalar_type() else {
835 if cursor >= parts.len() {
836 break;
837 }
838 let count: usize = parts[cursor]
839 .parse()
840 .map_err(|_| invalid_ply("Bad vertex list size"))?;
841 cursor = cursor
842 .checked_add(1 + count)
843 .ok_or_else(|| invalid_ply("ASCII PLY line is too large"))?;
844 continue;
845 };
846 if cursor >= parts.len() {
847 break;
848 }
849 let token = parts[cursor];
850 cursor += ascii_scalar_token_count(data_type);
851
852 match property.name.as_str() {
853 "x" => match schema.position_data_type {
854 DataType::Int32 => int_position[0] = parse_ascii_i32(token, "x")?,
855 _ => float_position[0] = parse_ascii_f32(token, "x")?,
856 },
857 "y" => match schema.position_data_type {
858 DataType::Int32 => int_position[1] = parse_ascii_i32(token, "y")?,
859 _ => float_position[1] = parse_ascii_f32(token, "y")?,
860 },
861 "z" => match schema.position_data_type {
862 DataType::Int32 => int_position[2] = parse_ascii_i32(token, "z")?,
863 _ => float_position[2] = parse_ascii_f32(token, "z")?,
864 },
865 "nx" if schema.has_normals => normal[0] = parse_ascii_f32(token, "nx")?,
866 "ny" if schema.has_normals => normal[1] = parse_ascii_f32(token, "ny")?,
867 "nz" if schema.has_normals => normal[2] = parse_ascii_f32(token, "nz")?,
868 "red" | "green" | "blue" | "alpha" if schema.color_components > 0 => {
869 color[color_component] = parse_ascii_u8(token)?;
870 color_component += 1;
871 }
872 name if schema.texcoord_pair.is_some_and(|pair| name == pair.u) => {
873 texcoord[0] = parse_ascii_f32(token, name)?;
874 }
875 name if schema.texcoord_pair.is_some_and(|pair| name == pair.v) => {
876 texcoord[1] = parse_ascii_f32(token, name)?;
877 }
878 _ => {}
879 }
880 }
881
882 match schema.position_data_type {
883 DataType::Int32 => int_positions.as_mut().unwrap().push(int_position),
884 _ => float_positions.as_mut().unwrap().push(float_position),
885 }
886
887 if let Some(normals) = normals.as_mut() {
888 normals.push(normal);
889 }
890
891 if let Some(colors) = colors.as_mut() {
892 colors.values.push(color);
893 }
894
895 if let Some(texcoords) = texcoords.as_mut() {
896 texcoords.push(texcoord);
897 }
898 }
899
900 let mut faces = Vec::with_capacity(header.face_count);
901 for line in face_lines {
902 let trimmed = line.trim();
903 if trimmed.is_empty() {
904 continue;
905 }
906 parse_ascii_face_line(header, trimmed, &mut faces)?;
907 }
908
909 Ok(ParsedPlyData {
910 positions: match schema.position_data_type {
911 DataType::Int32 => ParsedPlyPositionData::Int32(int_positions.unwrap_or_default()),
912 _ => ParsedPlyPositionData::Float32(float_positions.unwrap_or_default()),
913 },
914 faces,
915 normals,
916 colors,
917 texcoords,
918 })
919}
920
921fn ensure_remaining(cursor: &Cursor<&[u8]>, bytes_needed: usize) -> io::Result<()> {
922 let position = cursor.position() as usize;
923 let end = position
924 .checked_add(bytes_needed)
925 .ok_or_else(|| invalid_ply("PLY payload is too large"))?;
926 if end > cursor.get_ref().len() {
927 return Err(io::Error::new(
928 io::ErrorKind::UnexpectedEof,
929 "Unexpected end of binary PLY payload",
930 ));
931 }
932 Ok(())
933}
934
935fn skip_binary_scalar(cursor: &mut Cursor<&[u8]>, data_type: DataType) -> io::Result<()> {
936 ensure_remaining(cursor, data_type.byte_length())?;
937 cursor.set_position(cursor.position() + data_type.byte_length() as u64);
938 Ok(())
939}
940
941#[derive(Debug, Clone, Copy)]
942enum BinaryEndian {
943 Little,
944 Big,
945}
946
947fn read_binary_scalar_as_f32(
948 cursor: &mut Cursor<&[u8]>,
949 data_type: DataType,
950 endian: BinaryEndian,
951) -> io::Result<f32> {
952 ensure_remaining(cursor, data_type.byte_length())?;
953 match data_type {
954 DataType::Int8 => cursor.read_i8().map(|value| value as f32),
955 DataType::Uint8 => cursor.read_u8().map(|value| value as f32),
956 DataType::Int16 => match endian {
957 BinaryEndian::Little => cursor.read_i16::<LittleEndian>().map(|value| value as f32),
958 BinaryEndian::Big => cursor.read_i16::<BigEndian>().map(|value| value as f32),
959 },
960 DataType::Uint16 => match endian {
961 BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as f32),
962 BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as f32),
963 },
964 DataType::Int32 => match endian {
965 BinaryEndian::Little => cursor.read_i32::<LittleEndian>().map(|value| value as f32),
966 BinaryEndian::Big => cursor.read_i32::<BigEndian>().map(|value| value as f32),
967 },
968 DataType::Uint32 => match endian {
969 BinaryEndian::Little => cursor.read_u32::<LittleEndian>().map(|value| value as f32),
970 BinaryEndian::Big => cursor.read_u32::<BigEndian>().map(|value| value as f32),
971 },
972 DataType::Int64 => match endian {
973 BinaryEndian::Little => cursor.read_i64::<LittleEndian>().map(|value| value as f32),
974 BinaryEndian::Big => cursor.read_i64::<BigEndian>().map(|value| value as f32),
975 },
976 DataType::Uint64 => match endian {
977 BinaryEndian::Little => cursor.read_u64::<LittleEndian>().map(|value| value as f32),
978 BinaryEndian::Big => cursor.read_u64::<BigEndian>().map(|value| value as f32),
979 },
980 DataType::Float32 => match endian {
981 BinaryEndian::Little => cursor.read_f32::<LittleEndian>(),
982 BinaryEndian::Big => cursor.read_f32::<BigEndian>(),
983 },
984 DataType::Float64 => match endian {
985 BinaryEndian::Little => cursor.read_f64::<LittleEndian>().map(|value| value as f32),
986 BinaryEndian::Big => cursor.read_f64::<BigEndian>().map(|value| value as f32),
987 },
988 _ => Err(invalid_ply("Unsupported binary scalar type")),
989 }
990}
991
992fn read_binary_scalar_as_i32(
993 cursor: &mut Cursor<&[u8]>,
994 data_type: DataType,
995 endian: BinaryEndian,
996) -> io::Result<i32> {
997 ensure_remaining(cursor, data_type.byte_length())?;
998 match data_type {
999 DataType::Int8 => cursor.read_i8().map(|value| value as i32),
1000 DataType::Uint8 => cursor.read_u8().map(|value| value as i32),
1001 DataType::Int16 => match endian {
1002 BinaryEndian::Little => cursor.read_i16::<LittleEndian>().map(|value| value as i32),
1003 BinaryEndian::Big => cursor.read_i16::<BigEndian>().map(|value| value as i32),
1004 },
1005 DataType::Uint16 => match endian {
1006 BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as i32),
1007 BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as i32),
1008 },
1009 DataType::Int32 => match endian {
1010 BinaryEndian::Little => cursor.read_i32::<LittleEndian>(),
1011 BinaryEndian::Big => cursor.read_i32::<BigEndian>(),
1012 },
1013 DataType::Uint32 => {
1014 let value = match endian {
1015 BinaryEndian::Little => cursor.read_u32::<LittleEndian>()?,
1016 BinaryEndian::Big => cursor.read_u32::<BigEndian>()?,
1017 };
1018 i32::try_from(value).map_err(|_| invalid_ply("Binary PLY value does not fit in int32"))
1019 }
1020 _ => Err(invalid_ply("Unsupported binary int32 scalar type")),
1021 }
1022}
1023
1024fn read_binary_scalar_as_u8(cursor: &mut Cursor<&[u8]>, data_type: DataType) -> io::Result<u8> {
1025 ensure_remaining(cursor, data_type.byte_length())?;
1026 match data_type {
1027 DataType::Uint8 => cursor.read_u8(),
1028 DataType::Int8 => {
1029 let value = cursor.read_i8()?;
1030 u8::try_from(value).map_err(|_| invalid_ply("Negative color component value"))
1031 }
1032 _ => Err(invalid_ply("Color properties must be uint8")),
1033 }
1034}
1035
1036fn read_binary_scalar_as_u32(
1037 cursor: &mut Cursor<&[u8]>,
1038 data_type: DataType,
1039 endian: BinaryEndian,
1040) -> io::Result<u32> {
1041 ensure_remaining(cursor, data_type.byte_length())?;
1042 match data_type {
1043 DataType::Uint8 => cursor.read_u8().map(|value| value as u32),
1044 DataType::Int8 => {
1045 let value = cursor.read_i8()?;
1046 u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1047 }
1048 DataType::Uint16 => match endian {
1049 BinaryEndian::Little => cursor.read_u16::<LittleEndian>().map(|value| value as u32),
1050 BinaryEndian::Big => cursor.read_u16::<BigEndian>().map(|value| value as u32),
1051 },
1052 DataType::Int16 => {
1053 let value = match endian {
1054 BinaryEndian::Little => cursor.read_i16::<LittleEndian>()?,
1055 BinaryEndian::Big => cursor.read_i16::<BigEndian>()?,
1056 };
1057 u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1058 }
1059 DataType::Uint32 => match endian {
1060 BinaryEndian::Little => cursor.read_u32::<LittleEndian>(),
1061 BinaryEndian::Big => cursor.read_u32::<BigEndian>(),
1062 },
1063 DataType::Int32 => {
1064 let value = match endian {
1065 BinaryEndian::Little => cursor.read_i32::<LittleEndian>()?,
1066 BinaryEndian::Big => cursor.read_i32::<BigEndian>()?,
1067 };
1068 u32::try_from(value).map_err(|_| invalid_ply("Negative face index value"))
1069 }
1070 _ => Err(invalid_ply("Unsupported face index scalar type")),
1071 }
1072}
1073
1074fn read_binary_scalar_as_usize(
1075 cursor: &mut Cursor<&[u8]>,
1076 data_type: DataType,
1077 endian: BinaryEndian,
1078) -> io::Result<usize> {
1079 let value = read_binary_scalar_as_u32(cursor, data_type, endian)?;
1080 usize::try_from(value).map_err(|_| invalid_ply("Binary list size is too large"))
1081}
1082
1083fn skip_binary_element(
1084 cursor: &mut Cursor<&[u8]>,
1085 element: &PlyElementDef,
1086 endian: BinaryEndian,
1087) -> io::Result<()> {
1088 for _ in 0..element.count {
1089 for property in &element.properties {
1090 match property.kind {
1091 PlyPropertyKind::Scalar(data_type) => skip_binary_scalar(cursor, data_type)?,
1092 PlyPropertyKind::List {
1093 count_type,
1094 item_type,
1095 } => {
1096 let count = read_binary_scalar_as_usize(cursor, count_type, endian)?;
1097 for _ in 0..count {
1098 skip_binary_scalar(cursor, item_type)?;
1099 }
1100 }
1101 }
1102 }
1103 }
1104 Ok(())
1105}
1106
1107fn read_ply_binary_body(
1108 header: &PlyHeader,
1109 body: &[u8],
1110 endian: BinaryEndian,
1111) -> io::Result<ParsedPlyData> {
1112 let schema = build_read_schema(header)?;
1113 let mut cursor = Cursor::new(body);
1114 let vertex_element_index = header
1115 .elements
1116 .iter()
1117 .position(|element| element.name == "vertex")
1118 .ok_or_else(|| invalid_ply("Missing vertex element"))?;
1119 for element in &header.elements[..vertex_element_index] {
1120 skip_binary_element(&mut cursor, element, endian)?;
1121 }
1122
1123 let mut float_positions = matches!(schema.position_data_type, DataType::Float32)
1124 .then(|| Vec::with_capacity(header.vertex_count));
1125 let mut int_positions = matches!(schema.position_data_type, DataType::Int32)
1126 .then(|| Vec::with_capacity(header.vertex_count));
1127 let mut normals = schema
1128 .has_normals
1129 .then(|| Vec::with_capacity(header.vertex_count));
1130 let mut colors = (schema.color_components > 0).then(|| ParsedPlyColorData {
1131 num_components: schema.color_components,
1132 values: Vec::with_capacity(header.vertex_count),
1133 });
1134 let mut texcoords = schema
1135 .texcoord_pair
1136 .is_some()
1137 .then(|| Vec::with_capacity(header.vertex_count));
1138
1139 for _ in 0..header.vertex_count {
1140 let mut float_position = [0.0f32; 3];
1141 let mut int_position = [0i32; 3];
1142 let mut normal = [0.0f32; 3];
1143 let mut color = [0u8; 4];
1144 let mut texcoord = [0.0f32; 2];
1145 let mut color_component = 0usize;
1146
1147 for property in &header.vertex_properties {
1148 match property.kind {
1149 PlyPropertyKind::Scalar(data_type) => match property.name.as_str() {
1150 "x" => match schema.position_data_type {
1151 DataType::Int32 => {
1152 int_position[0] =
1153 read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1154 }
1155 _ => {
1156 float_position[0] =
1157 read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1158 }
1159 },
1160 "y" => match schema.position_data_type {
1161 DataType::Int32 => {
1162 int_position[1] =
1163 read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1164 }
1165 _ => {
1166 float_position[1] =
1167 read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1168 }
1169 },
1170 "z" => match schema.position_data_type {
1171 DataType::Int32 => {
1172 int_position[2] =
1173 read_binary_scalar_as_i32(&mut cursor, data_type, endian)?
1174 }
1175 _ => {
1176 float_position[2] =
1177 read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1178 }
1179 },
1180 "nx" if schema.has_normals => {
1181 normal[0] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1182 }
1183 "ny" if schema.has_normals => {
1184 normal[1] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1185 }
1186 "nz" if schema.has_normals => {
1187 normal[2] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1188 }
1189 "red" | "green" | "blue" | "alpha" if schema.color_components > 0 => {
1190 color[color_component] = read_binary_scalar_as_u8(&mut cursor, data_type)?;
1191 color_component += 1;
1192 }
1193 name if schema.texcoord_pair.is_some_and(|pair| name == pair.u) => {
1194 texcoord[0] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1195 }
1196 name if schema.texcoord_pair.is_some_and(|pair| name == pair.v) => {
1197 texcoord[1] = read_binary_scalar_as_f32(&mut cursor, data_type, endian)?
1198 }
1199 _ => skip_binary_scalar(&mut cursor, data_type)?,
1200 },
1201 PlyPropertyKind::List {
1202 count_type,
1203 item_type,
1204 } => {
1205 let count = read_binary_scalar_as_usize(&mut cursor, count_type, endian)?;
1206 for _ in 0..count {
1207 skip_binary_scalar(&mut cursor, item_type)?;
1208 }
1209 }
1210 }
1211 }
1212
1213 match schema.position_data_type {
1214 DataType::Int32 => int_positions.as_mut().unwrap().push(int_position),
1215 _ => float_positions.as_mut().unwrap().push(float_position),
1216 }
1217
1218 if let Some(normals) = normals.as_mut() {
1219 normals.push(normal);
1220 }
1221
1222 if let Some(colors) = colors.as_mut() {
1223 colors.values.push(color);
1224 }
1225
1226 if let Some(texcoords) = texcoords.as_mut() {
1227 texcoords.push(texcoord);
1228 }
1229 }
1230
1231 let face_element_index = header
1232 .elements
1233 .iter()
1234 .position(|element| element.name == "face");
1235 if let Some(face_element_index) = face_element_index {
1236 if face_element_index < vertex_element_index {
1237 return Err(invalid_ply(
1238 "PLY face element before vertex element is not supported",
1239 ));
1240 }
1241 for element in &header.elements[vertex_element_index + 1..face_element_index] {
1242 skip_binary_element(&mut cursor, element, endian)?;
1243 }
1244 }
1245
1246 if header.face_count > 0 && header.face_properties.is_empty() {
1247 return Err(invalid_ply(
1248 "Binary PLY faces require a face property declaration",
1249 ));
1250 }
1251
1252 let mut faces = Vec::with_capacity(header.face_count);
1253 for _ in 0..header.face_count {
1254 let mut polygon_indices: Option<Vec<u32>> = None;
1255
1256 for property in &header.face_properties {
1257 match property.kind {
1258 PlyPropertyKind::Scalar(data_type) => skip_binary_scalar(&mut cursor, data_type)?,
1259 PlyPropertyKind::List {
1260 count_type,
1261 item_type,
1262 } => {
1263 let count = read_binary_scalar_as_usize(&mut cursor, count_type, endian)?;
1264 let mut values = Vec::with_capacity(count);
1265 for _ in 0..count {
1266 values.push(read_binary_scalar_as_u32(&mut cursor, item_type, endian)?);
1267 }
1268
1269 if property.name == "vertex_indices" || polygon_indices.is_none() {
1270 polygon_indices = Some(values);
1271 }
1272 }
1273 }
1274 }
1275
1276 if let Some(indices) = polygon_indices {
1277 triangulate_vertex_indices(&indices, &mut faces);
1278 }
1279 }
1280
1281 Ok(ParsedPlyData {
1282 positions: match schema.position_data_type {
1283 DataType::Int32 => ParsedPlyPositionData::Int32(int_positions.unwrap_or_default()),
1284 _ => ParsedPlyPositionData::Float32(float_positions.unwrap_or_default()),
1285 },
1286 faces,
1287 normals,
1288 colors,
1289 texcoords,
1290 })
1291}
1292
1293fn read_ply<P: AsRef<Path>>(path: P) -> io::Result<ParsedPlyData> {
1294 let bytes = fs::read(path)?;
1295 read_ply_bytes(&bytes)
1296}
1297
1298fn read_ply_source(source: &PlyReaderSource) -> io::Result<ParsedPlyData> {
1299 match source {
1300 PlyReaderSource::Path(path) => read_ply(path),
1301 PlyReaderSource::Bytes(bytes) => read_ply_bytes(bytes),
1302 }
1303}
1304
1305fn read_ply_bytes(bytes: &[u8]) -> io::Result<ParsedPlyData> {
1306 let (header, body_offset) = parse_ply_header(bytes)?;
1307
1308 match header.format {
1309 PlyFormat::Ascii => read_ply_ascii_body(&header, &bytes[body_offset..]),
1310 PlyFormat::BinaryLittleEndian => {
1311 read_ply_binary_body(&header, &bytes[body_offset..], BinaryEndian::Little)
1312 }
1313 PlyFormat::BinaryBigEndian => {
1314 read_ply_binary_body(&header, &bytes[body_offset..], BinaryEndian::Big)
1315 }
1316 }
1317}
1318
1319pub fn write_ply_positions<P: AsRef<Path>>(path: P, points: &[[f32; 3]]) -> io::Result<()> {
1321 let mut file = fs::File::create(path)?;
1322
1323 writeln!(file, "ply")?;
1324 writeln!(file, "format ascii 1.0")?;
1325 writeln!(file, "element vertex {}", points.len())?;
1326 writeln!(file, "property float x")?;
1327 writeln!(file, "property float y")?;
1328 writeln!(file, "property float z")?;
1329 writeln!(file, "end_header")?;
1330
1331 for p in points {
1332 writeln!(file, "{:.6} {:.6} {:.6}", p[0], p[1], p[2])?;
1333 }
1334
1335 Ok(())
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340 use super::*;
1341 use draco_core::geometry_attribute::GeometryAttributeType;
1342 use tempfile::NamedTempFile;
1343
1344 #[test]
1345 fn test_read_write_ply() {
1346 let expected = vec![
1347 [0.0, 0.0, 0.0],
1348 [1.0, 0.0, 0.0],
1349 [0.0, 1.0, 0.0],
1350 [0.0, 0.0, 1.0],
1351 [-1.0, -1.0, -1.0],
1352 ];
1353
1354 let file = NamedTempFile::new().unwrap();
1355 write_ply_positions(file.path(), &expected).unwrap();
1356
1357 let positions = read_ply_positions(file.path()).unwrap();
1358 assert_eq!(positions.len(), expected.len());
1359
1360 for (i, (a, b)) in positions.iter().zip(expected.iter()).enumerate() {
1361 let diff = (a[0] - b[0]).abs() + (a[1] - b[1]).abs() + (a[2] - b[2]).abs();
1362 assert!(
1363 diff < 1e-5,
1364 "Position mismatch at index {i}: {a:?} vs {b:?}"
1365 );
1366 }
1367 }
1368
1369 #[test]
1370 fn test_read_mesh_parses_and_triangulates_faces() {
1371 let file = NamedTempFile::new().unwrap();
1372 let ply = r#"ply
1373format ascii 1.0
1374element vertex 4
1375property float x
1376property float y
1377property float z
1378element face 2
1379property list uchar int vertex_indices
1380end_header
13810 0 0
13821 0 0
13831 1 0
13840 1 0
13853 0 1 2
13864 0 1 2 3
1387"#;
1388
1389 std::fs::write(file.path(), ply).unwrap();
1390
1391 let mut reader = PlyReader::open(file.path()).unwrap();
1392 let mesh = reader.read_mesh().unwrap();
1393
1394 assert_eq!(mesh.num_points(), 4);
1395 assert_eq!(mesh.num_faces(), 3);
1396 assert_eq!(
1397 mesh.face(draco_core::geometry_indices::FaceIndex(0)),
1398 [0u32.into(), 1u32.into(), 2u32.into()]
1399 );
1400 assert_eq!(
1401 mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1402 [0u32.into(), 1u32.into(), 2u32.into()]
1403 );
1404 assert_eq!(
1405 mesh.face(draco_core::geometry_indices::FaceIndex(2)),
1406 [0u32.into(), 2u32.into(), 3u32.into()]
1407 );
1408 }
1409
1410 #[test]
1411 fn test_read_mesh_parses_normals_and_colors() {
1412 let file = NamedTempFile::new().unwrap();
1413 let ply = r#"ply
1414format ascii 1.0
1415element vertex 2
1416property float x
1417property float y
1418property float z
1419property float nx
1420property float ny
1421property float nz
1422property uchar red
1423property uchar green
1424property uchar blue
1425property uchar alpha
1426end_header
14270 0 0 0 0 1 10 20 30 40
14281 0 0 0 1 0 50 60 70 80
1429"#;
1430
1431 std::fs::write(file.path(), ply).unwrap();
1432
1433 let mut reader = PlyReader::open(file.path()).unwrap();
1434 let mesh = reader.read_mesh().unwrap();
1435
1436 assert_eq!(mesh.num_points(), 2);
1437 assert_eq!(mesh.num_faces(), 0);
1438 assert_eq!(mesh.num_attributes(), 3);
1439
1440 let normal_att = mesh.named_attribute(GeometryAttributeType::Normal).unwrap();
1441 assert_eq!(normal_att.data_type(), DataType::Float32);
1442 assert_eq!(normal_att.num_components(), 3);
1443 assert!(!normal_att.normalized());
1444
1445 let normal_data = normal_att.buffer().data();
1446 let first_normal = [
1447 f32::from_le_bytes(normal_data[0..4].try_into().unwrap()),
1448 f32::from_le_bytes(normal_data[4..8].try_into().unwrap()),
1449 f32::from_le_bytes(normal_data[8..12].try_into().unwrap()),
1450 ];
1451 assert_eq!(first_normal, [0.0, 0.0, 1.0]);
1452
1453 let color_att = mesh.named_attribute(GeometryAttributeType::Color).unwrap();
1454 assert_eq!(color_att.data_type(), DataType::Uint8);
1455 assert_eq!(color_att.num_components(), 4);
1456 assert!(color_att.normalized());
1457 assert_eq!(color_att.buffer().data(), &[10, 20, 30, 40, 50, 60, 70, 80]);
1458 }
1459
1460 #[test]
1461 fn test_read_mesh_preserves_int32_positions() {
1462 let file = NamedTempFile::new().unwrap();
1463 let ply = r#"ply
1464format ascii 1.0
1465element vertex 2
1466property int x
1467property int y
1468property int z
1469end_header
14701 2 3
14714 5 6
1472"#;
1473
1474 std::fs::write(file.path(), ply).unwrap();
1475
1476 let mut reader = PlyReader::open(file.path()).unwrap();
1477 let mesh = reader.read_mesh().unwrap();
1478
1479 let position_att = mesh
1480 .named_attribute(GeometryAttributeType::Position)
1481 .unwrap();
1482 assert_eq!(position_att.data_type(), DataType::Int32);
1483 assert_eq!(position_att.num_components(), 3);
1484 assert!(!position_att.normalized());
1485
1486 let position_data = position_att.buffer().data();
1487 let first_position = [
1488 i32::from_le_bytes(position_data[0..4].try_into().unwrap()),
1489 i32::from_le_bytes(position_data[4..8].try_into().unwrap()),
1490 i32::from_le_bytes(position_data[8..12].try_into().unwrap()),
1491 ];
1492 assert_eq!(first_position, [1, 2, 3]);
1493 }
1494
1495 #[test]
1496 fn test_read_mesh_ignores_non_float_normals() {
1497 let file = NamedTempFile::new().unwrap();
1498 let ply = r#"ply
1499format ascii 1.0
1500element vertex 1
1501property float x
1502property float y
1503property float z
1504property int nx
1505property int ny
1506property int nz
1507end_header
15080 0 0 0 0 1
1509"#;
1510
1511 std::fs::write(file.path(), ply).unwrap();
1512
1513 let mut reader = PlyReader::open(file.path()).unwrap();
1514 let mesh = reader.read_mesh().unwrap();
1515
1516 assert_eq!(mesh.named_attribute_id(GeometryAttributeType::Normal), -1);
1517 }
1518
1519 #[test]
1520 fn test_read_mesh_rejects_non_uint8_colors() {
1521 let file = NamedTempFile::new().unwrap();
1522 let ply = r#"ply
1523format ascii 1.0
1524element vertex 1
1525property float x
1526property float y
1527property float z
1528property int red
1529property int green
1530property int blue
1531end_header
15320 0 0 1 2 3
1533"#;
1534
1535 std::fs::write(file.path(), ply).unwrap();
1536
1537 let mut reader = PlyReader::open(file.path()).unwrap();
1538 let error = reader.read_mesh().unwrap_err();
1539 assert_eq!(error.kind(), io::ErrorKind::InvalidData);
1540 assert!(error.to_string().contains("Color properties must be uint8"));
1541 }
1542
1543 #[test]
1544 fn test_read_binary_little_endian_mesh() {
1545 let file = NamedTempFile::new().unwrap();
1546 let mut ply = Vec::new();
1547 ply.extend_from_slice(
1548 br#"ply
1549format binary_little_endian 1.0
1550element vertex 4
1551property float x
1552property float y
1553property float z
1554element face 2
1555property list uchar int vertex_indices
1556end_header
1557"#,
1558 );
1559
1560 for vertex in [
1561 [0.0f32, 0.0, 0.0],
1562 [1.0, 0.0, 0.0],
1563 [1.0, 1.0, 0.0],
1564 [0.0, 1.0, 0.0],
1565 ] {
1566 for component in vertex {
1567 ply.extend_from_slice(&component.to_le_bytes());
1568 }
1569 }
1570
1571 ply.push(3);
1572 for index in [0i32, 1, 2] {
1573 ply.extend_from_slice(&index.to_le_bytes());
1574 }
1575
1576 ply.push(4);
1577 for index in [0i32, 1, 2, 3] {
1578 ply.extend_from_slice(&index.to_le_bytes());
1579 }
1580
1581 std::fs::write(file.path(), ply).unwrap();
1582
1583 let mut reader = PlyReader::open(file.path()).unwrap();
1584 let mesh = reader.read_mesh().unwrap();
1585
1586 assert_eq!(mesh.num_points(), 4);
1587 assert_eq!(mesh.num_faces(), 3);
1588 assert_eq!(
1589 mesh.face(draco_core::geometry_indices::FaceIndex(0)),
1590 [0u32.into(), 1u32.into(), 2u32.into()]
1591 );
1592 assert_eq!(
1593 mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1594 [0u32.into(), 1u32.into(), 2u32.into()]
1595 );
1596 assert_eq!(
1597 mesh.face(draco_core::geometry_indices::FaceIndex(2)),
1598 [0u32.into(), 2u32.into(), 3u32.into()]
1599 );
1600 }
1601
1602 #[test]
1603 fn test_read_binary_little_endian_mesh_with_cr_only_header() {
1604 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();
1605 for index in 0..24 {
1606 ply.extend_from_slice(&(index as f32).to_le_bytes());
1607 ply.extend_from_slice(&0.0f32.to_le_bytes());
1608 ply.extend_from_slice(&0.0f32.to_le_bytes());
1609 }
1610 ply.extend_from_slice(&[3]);
1611 for index in [0i32, 1, 2] {
1612 ply.extend_from_slice(&index.to_le_bytes());
1613 }
1614
1615 let mesh =
1616 PlyReader::read_from_bytes(&ply).expect("CR-only binary PLY header should parse");
1617
1618 assert_eq!(mesh.num_points(), 24);
1619 assert!(mesh.num_faces() > 0);
1620 }
1621
1622 #[test]
1623 fn test_read_binary_little_endian_attributes_and_int_positions() {
1624 let file = NamedTempFile::new().unwrap();
1625 let mut ply = Vec::new();
1626 ply.extend_from_slice(
1627 br#"ply
1628format binary_little_endian 1.0
1629element vertex 2
1630property int x
1631property int y
1632property int z
1633property float nx
1634property float ny
1635property float nz
1636property uchar red
1637property uchar green
1638property uchar blue
1639property uchar alpha
1640end_header
1641"#,
1642 );
1643
1644 for (position, normal, color) in [
1645 ([1i32, 2, 3], [0.0f32, 0.0, 1.0], [10u8, 20, 30, 40]),
1646 ([4i32, 5, 6], [0.0f32, 1.0, 0.0], [50u8, 60, 70, 80]),
1647 ] {
1648 for component in position {
1649 ply.extend_from_slice(&component.to_le_bytes());
1650 }
1651 for component in normal {
1652 ply.extend_from_slice(&component.to_le_bytes());
1653 }
1654 ply.extend_from_slice(&color);
1655 }
1656
1657 std::fs::write(file.path(), ply).unwrap();
1658
1659 let mut reader = PlyReader::open(file.path()).unwrap();
1660 let mesh = reader.read_mesh().unwrap();
1661
1662 let position_att = mesh
1663 .named_attribute(GeometryAttributeType::Position)
1664 .unwrap();
1665 assert_eq!(position_att.data_type(), DataType::Int32);
1666 assert_eq!(position_att.num_components(), 3);
1667
1668 let position_data = position_att.buffer().data();
1669 let first_position = [
1670 i32::from_le_bytes(position_data[0..4].try_into().unwrap()),
1671 i32::from_le_bytes(position_data[4..8].try_into().unwrap()),
1672 i32::from_le_bytes(position_data[8..12].try_into().unwrap()),
1673 ];
1674 assert_eq!(first_position, [1, 2, 3]);
1675
1676 let normal_att = mesh.named_attribute(GeometryAttributeType::Normal).unwrap();
1677 assert_eq!(normal_att.data_type(), DataType::Float32);
1678 assert_eq!(normal_att.num_components(), 3);
1679
1680 let normal_data = normal_att.buffer().data();
1681 let first_normal = [
1682 f32::from_le_bytes(normal_data[0..4].try_into().unwrap()),
1683 f32::from_le_bytes(normal_data[4..8].try_into().unwrap()),
1684 f32::from_le_bytes(normal_data[8..12].try_into().unwrap()),
1685 ];
1686 assert_eq!(first_normal, [0.0, 0.0, 1.0]);
1687
1688 let color_att = mesh.named_attribute(GeometryAttributeType::Color).unwrap();
1689 assert_eq!(color_att.data_type(), DataType::Uint8);
1690 assert_eq!(color_att.num_components(), 4);
1691 assert!(color_att.normalized());
1692 assert_eq!(color_att.buffer().data(), &[10, 20, 30, 40, 50, 60, 70, 80]);
1693 }
1694
1695 #[test]
1696 fn test_read_binary_big_endian_mesh() {
1697 let mut ply = Vec::new();
1698 ply.extend_from_slice(
1699 br#"ply
1700format binary_big_endian 1.0
1701element vertex 4
1702property float x
1703property float y
1704property float z
1705element face 1
1706property list uchar int vertex_indices
1707end_header
1708"#,
1709 );
1710
1711 for vertex in [
1712 [0.0f32, 0.0, 0.0],
1713 [1.0, 0.0, 0.0],
1714 [1.0, 1.0, 0.0],
1715 [0.0, 1.0, 0.0],
1716 ] {
1717 for component in vertex {
1718 ply.extend_from_slice(&component.to_be_bytes());
1719 }
1720 }
1721
1722 ply.push(4);
1723 for index in [0i32, 1, 2, 3] {
1724 ply.extend_from_slice(&index.to_be_bytes());
1725 }
1726
1727 let mesh = PlyReader::read_from_bytes(&ply).unwrap();
1728 assert_eq!(mesh.num_points(), 4);
1729 assert_eq!(mesh.num_faces(), 2);
1730 assert_eq!(
1731 mesh.face(draco_core::geometry_indices::FaceIndex(1)),
1732 [0u32.into(), 2u32.into(), 3u32.into()]
1733 );
1734 }
1735}