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