1use std::fs::File;
31use std::io::{self, BufWriter, Write};
32use std::path::Path;
33
34use draco_core::draco_types::DataType;
35use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
36use draco_core::geometry_indices::FaceIndex;
37use draco_core::mesh::Mesh;
38
39pub use crate::ply_format::PlyFormat;
40use crate::traits::{PointCloudWriter, WriteToBytes, Writer};
41
42#[derive(Debug, Clone, Default)]
57pub struct PlyWriter {
58 format: PlyFormat,
60 positions: PlyPositionData,
62 normals: Vec<[f32; 3]>,
64 colors: Vec<[u8; 4]>,
66 color_components: u8,
67 texcoords: Vec<[f32; 2]>,
69 faces: Vec<[u32; 3]>,
71}
72
73#[derive(Debug, Clone)]
74enum PlyPositionData {
75 Float32(Vec<[f32; 3]>),
76 Float64(Vec<[f64; 3]>),
77 Int32(Vec<[i32; 3]>),
78 Uint32(Vec<[u32; 3]>),
79}
80
81impl Default for PlyPositionData {
82 fn default() -> Self {
83 PlyPositionData::Float32(Vec::new())
84 }
85}
86
87impl PlyPositionData {
88 fn len(&self) -> usize {
89 match self {
90 PlyPositionData::Float32(values) => values.len(),
91 PlyPositionData::Float64(values) => values.len(),
92 PlyPositionData::Int32(values) => values.len(),
93 PlyPositionData::Uint32(values) => values.len(),
94 }
95 }
96
97 fn data_type(&self) -> draco_core::draco_types::DataType {
98 match self {
99 PlyPositionData::Float32(_) => draco_core::draco_types::DataType::Float32,
100 PlyPositionData::Float64(_) => draco_core::draco_types::DataType::Float64,
101 PlyPositionData::Int32(_) => draco_core::draco_types::DataType::Int32,
102 PlyPositionData::Uint32(_) => draco_core::draco_types::DataType::Uint32,
103 }
104 }
105
106 fn type_name(&self) -> &'static str {
107 match self.data_type() {
108 draco_core::draco_types::DataType::Float64 => "double",
109 draco_core::draco_types::DataType::Int32 => "int",
110 draco_core::draco_types::DataType::Uint32 => "uint",
111 _ => "float",
112 }
113 }
114
115 fn push_f32_slice(&mut self, points: &[[f32; 3]]) {
116 self.ensure_float32();
117 if let PlyPositionData::Float32(values) = self {
118 values.extend_from_slice(points);
119 }
120 }
121
122 fn ensure_float32(&mut self) {
123 if matches!(self, PlyPositionData::Float32(_)) {
124 return;
125 }
126 let converted = self.iter_as_f32().collect();
127 *self = PlyPositionData::Float32(converted);
128 }
129
130 fn iter_as_f32(&self) -> Box<dyn Iterator<Item = [f32; 3]> + '_> {
131 match self {
132 PlyPositionData::Float32(values) => Box::new(values.iter().copied()),
133 PlyPositionData::Float64(values) => Box::new(
134 values
135 .iter()
136 .map(|v| [v[0] as f32, v[1] as f32, v[2] as f32]),
137 ),
138 PlyPositionData::Int32(values) => Box::new(
139 values
140 .iter()
141 .map(|v| [v[0] as f32, v[1] as f32, v[2] as f32]),
142 ),
143 PlyPositionData::Uint32(values) => Box::new(
144 values
145 .iter()
146 .map(|v| [v[0] as f32, v[1] as f32, v[2] as f32]),
147 ),
148 }
149 }
150}
151
152impl PlyWriter {
153 pub fn new() -> Self {
155 Self::default()
156 }
157
158 pub fn with_binary_little_endian(mut self) -> Self {
160 self.format = PlyFormat::BinaryLittleEndian;
161 self
162 }
163
164 pub fn with_format(mut self, format: PlyFormat) -> Self {
166 self.format = format;
167 self
168 }
169
170 pub fn set_format(&mut self, format: PlyFormat) -> &mut Self {
172 self.format = format;
173 self
174 }
175
176 pub fn format(&self) -> PlyFormat {
178 self.format
179 }
180
181 pub fn set_binary_little_endian(&mut self, enabled: bool) -> &mut Self {
183 self.format = if enabled {
184 PlyFormat::BinaryLittleEndian
185 } else {
186 PlyFormat::Ascii
187 };
188 self
189 }
190
191 pub fn is_binary_little_endian(&self) -> bool {
193 self.format == PlyFormat::BinaryLittleEndian
194 }
195
196 pub fn add_points(&mut self, points: &[[f32; 3]]) {
198 self.positions.push_f32_slice(points);
199 }
200
201 pub fn add_point(&mut self, point: [f32; 3]) {
203 self.add_points(&[point]);
204 }
205
206 pub fn add_points_with_colors(&mut self, points: &[[f32; 3]], colors: &[[u8; 4]]) {
208 while self.colors.len() < self.positions.len() {
210 self.colors.push([255, 255, 255, 255]);
211 }
212 self.positions.push_f32_slice(points);
213 self.color_components = self.color_components.max(4);
214 self.colors.extend_from_slice(colors);
215 }
216
217 pub fn vertex_count(&self) -> usize {
219 self.positions.len()
220 }
221
222 pub fn face_count(&self) -> usize {
224 self.faces.len()
225 }
226
227 pub fn has_normals(&self) -> bool {
229 !self.normals.is_empty()
230 }
231
232 pub fn has_colors(&self) -> bool {
234 !self.colors.is_empty()
235 }
236
237 pub fn write<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
239 let file = File::create(path)?;
240 let mut writer = BufWriter::new(file);
241 self.write_to(&mut writer)
242 }
243
244 pub fn write_to_vec(&self) -> io::Result<Vec<u8>> {
246 let mut out = Vec::new();
247 self.write_to(&mut out)?;
248 Ok(out)
249 }
250
251 pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
253 let has_normals = self.normals.len() == self.positions.len();
254 let has_colors = self.colors.len() == self.positions.len() && self.color_components > 0;
255
256 let has_texcoords = self.texcoords.len() == self.positions.len();
257 self.write_header(writer, has_normals, has_colors, has_texcoords)?;
258
259 match self.format {
260 PlyFormat::Ascii => {
261 self.write_ascii_body(writer, has_normals, has_colors, has_texcoords)
262 }
263 PlyFormat::BinaryLittleEndian => {
264 self.write_binary_body(writer, has_normals, has_colors, has_texcoords, false)
265 }
266 PlyFormat::BinaryBigEndian => {
267 self.write_binary_body(writer, has_normals, has_colors, has_texcoords, true)
268 }
269 }
270 }
271
272 fn write_header<W: Write>(
273 &self,
274 writer: &mut W,
275 has_normals: bool,
276 has_colors: bool,
277 has_texcoords: bool,
278 ) -> io::Result<()> {
279 writeln!(writer, "ply")?;
280 match self.format {
281 PlyFormat::Ascii => writeln!(writer, "format ascii 1.0")?,
282 PlyFormat::BinaryLittleEndian => writeln!(writer, "format binary_little_endian 1.0")?,
283 PlyFormat::BinaryBigEndian => writeln!(writer, "format binary_big_endian 1.0")?,
284 }
285 writeln!(writer, "comment Generated by draco-io")?;
286 writeln!(writer, "element vertex {}", self.positions.len())?;
287 writeln!(writer, "property {} x", self.positions.type_name())?;
288 writeln!(writer, "property {} y", self.positions.type_name())?;
289 writeln!(writer, "property {} z", self.positions.type_name())?;
290
291 if has_normals {
292 writeln!(writer, "property float nx")?;
293 writeln!(writer, "property float ny")?;
294 writeln!(writer, "property float nz")?;
295 }
296
297 if has_colors {
298 writeln!(writer, "property uchar red")?;
299 writeln!(writer, "property uchar green")?;
300 writeln!(writer, "property uchar blue")?;
301 if self.color_components > 3 {
302 writeln!(writer, "property uchar alpha")?;
303 }
304 }
305
306 if has_texcoords {
307 writeln!(writer, "property float texture_u")?;
308 writeln!(writer, "property float texture_v")?;
309 }
310
311 if !self.faces.is_empty() {
312 writeln!(writer, "element face {}", self.faces.len())?;
313 writeln!(writer, "property list uchar int vertex_indices")?;
314 }
315
316 writeln!(writer, "end_header")?;
317 Ok(())
318 }
319
320 fn write_ascii_body<W: Write>(
321 &self,
322 writer: &mut W,
323 has_normals: bool,
324 has_colors: bool,
325 has_texcoords: bool,
326 ) -> io::Result<()> {
327 for i in 0..self.positions.len() {
328 match &self.positions {
329 PlyPositionData::Float32(values) => {
330 let [x, y, z] = values[i];
331 write!(writer, "{:.6} {:.6} {:.6}", x, y, z)?;
332 }
333 PlyPositionData::Float64(values) => {
334 let [x, y, z] = values[i];
335 write!(writer, "{:.6} {:.6} {:.6}", x, y, z)?;
336 }
337 PlyPositionData::Int32(values) => {
338 let [x, y, z] = values[i];
339 write!(writer, "{} {} {}", x, y, z)?;
340 }
341 PlyPositionData::Uint32(values) => {
342 let [x, y, z] = values[i];
343 write!(writer, "{} {} {}", x, y, z)?;
344 }
345 }
346
347 if has_normals {
348 let [nx, ny, nz] = self.normals[i];
349 write!(writer, " {:.6} {:.6} {:.6}", nx, ny, nz)?;
350 }
351
352 if has_colors {
353 let [r, g, b, a] = self.colors[i];
354 write!(writer, " {} {} {}", r, g, b)?;
355 if self.color_components > 3 {
356 write!(writer, " {}", a)?;
357 }
358 }
359
360 if has_texcoords {
361 let [u, v] = self.texcoords[i];
362 write!(writer, " {:.6} {:.6}", u, v)?;
363 }
364
365 writeln!(writer)?;
366 }
367
368 for face in &self.faces {
370 write!(writer, "3 {} {} {}", face[0], face[1], face[2])?;
371 writeln!(writer)?;
372 }
373
374 Ok(())
375 }
376
377 fn write_binary_body<W: Write>(
378 &self,
379 writer: &mut W,
380 has_normals: bool,
381 has_colors: bool,
382 has_texcoords: bool,
383 big_endian: bool,
384 ) -> io::Result<()> {
385 for i in 0..self.positions.len() {
386 match &self.positions {
387 PlyPositionData::Float32(values) => {
388 for component in values[i] {
389 writer.write_all(&if big_endian {
390 component.to_be_bytes()
391 } else {
392 component.to_le_bytes()
393 })?;
394 }
395 }
396 PlyPositionData::Float64(values) => {
397 for component in values[i] {
398 writer.write_all(&if big_endian {
399 component.to_be_bytes()
400 } else {
401 component.to_le_bytes()
402 })?;
403 }
404 }
405 PlyPositionData::Int32(values) => {
406 for component in values[i] {
407 writer.write_all(&if big_endian {
408 component.to_be_bytes()
409 } else {
410 component.to_le_bytes()
411 })?;
412 }
413 }
414 PlyPositionData::Uint32(values) => {
415 for component in values[i] {
416 writer.write_all(&if big_endian {
417 component.to_be_bytes()
418 } else {
419 component.to_le_bytes()
420 })?;
421 }
422 }
423 }
424
425 if has_normals {
426 let [nx, ny, nz] = self.normals[i];
427 writer.write_all(&if big_endian {
428 nx.to_be_bytes()
429 } else {
430 nx.to_le_bytes()
431 })?;
432 writer.write_all(&if big_endian {
433 ny.to_be_bytes()
434 } else {
435 ny.to_le_bytes()
436 })?;
437 writer.write_all(&if big_endian {
438 nz.to_be_bytes()
439 } else {
440 nz.to_le_bytes()
441 })?;
442 }
443
444 if has_colors {
445 writer.write_all(&self.colors[i][..self.color_components as usize])?;
446 }
447
448 if has_texcoords {
449 let [u, v] = self.texcoords[i];
450 writer.write_all(&if big_endian {
451 u.to_be_bytes()
452 } else {
453 u.to_le_bytes()
454 })?;
455 writer.write_all(&if big_endian {
456 v.to_be_bytes()
457 } else {
458 v.to_le_bytes()
459 })?;
460 }
461 }
462
463 for face in &self.faces {
464 writer.write_all(&[3u8])?;
465 for index in face {
466 let index = i32::try_from(*index).map_err(|_| {
467 io::Error::new(
468 io::ErrorKind::InvalidInput,
469 "PLY binary writer only supports face indices up to i32::MAX",
470 )
471 })?;
472 writer.write_all(&if big_endian {
473 index.to_be_bytes()
474 } else {
475 index.to_le_bytes()
476 })?;
477 }
478 }
479
480 Ok(())
481 }
482}
483
484fn read_float3(mesh: &Mesh, att_id: i32, point_idx: usize) -> [f32; 3] {
486 let att = mesh.attribute(att_id);
487 let byte_stride = att.byte_stride() as usize;
488 let buffer = att.buffer();
489 let mut bytes = [0u8; 12];
490 buffer.read(point_idx * byte_stride, &mut bytes);
491 [
492 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
493 f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
494 f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
495 ]
496}
497
498fn read_color(mesh: &Mesh, att_id: i32, point_idx: usize) -> [u8; 4] {
500 let att = mesh.attribute(att_id);
501 let byte_stride = att.byte_stride() as usize;
502 let buffer = att.buffer();
503
504 let num_components = att.num_components() as usize;
506 let component_size = byte_stride / num_components;
507
508 if component_size == 1 {
509 let mut bytes = [255u8; 4];
511 let read_len = num_components.min(4);
512 buffer.read(point_idx * byte_stride, &mut bytes[..read_len]);
513 bytes
514 } else if component_size == 4 {
515 let mut float_bytes = [0u8; 16];
517 let read_len = (num_components * 4).min(16);
518 buffer.read(point_idx * byte_stride, &mut float_bytes[..read_len]);
519
520 let mut result = [255u8; 4];
521 for i in 0..num_components.min(4) {
522 let f = f32::from_le_bytes([
523 float_bytes[i * 4],
524 float_bytes[i * 4 + 1],
525 float_bytes[i * 4 + 2],
526 float_bytes[i * 4 + 3],
527 ]);
528 result[i] = (f.clamp(0.0, 1.0) * 255.0) as u8;
529 }
530 result
531 } else {
532 [255, 255, 255, 255] }
534}
535
536impl Writer for PlyWriter {
541 fn new() -> Self {
542 Self::default()
543 }
544
545 fn add_mesh(&mut self, mesh: &Mesh, _name: Option<&str>) -> io::Result<()> {
546 let vertex_offset = self.positions.len() as u32;
548
549 let pos_att_id = mesh.named_attribute_id(GeometryAttributeType::Position);
551 if pos_att_id >= 0 {
552 let att = mesh.attribute(pos_att_id);
553 append_positions_from_attribute(&mut self.positions, att, mesh.num_points());
554 }
555
556 let normal_att_id = mesh.named_attribute_id(GeometryAttributeType::Normal);
558 if normal_att_id >= 0 {
559 while self.normals.len() < self.positions.len() - mesh.num_points() {
561 self.normals.push([0.0, 0.0, 0.0]);
562 }
563 for i in 0..mesh.num_points() {
564 self.normals.push(read_float3(mesh, normal_att_id, i));
565 }
566 }
567
568 let color_att_id = mesh.named_attribute_id(GeometryAttributeType::Color);
570 if color_att_id >= 0 {
571 let color_att = mesh.attribute(color_att_id);
572 let components = color_att.num_components().clamp(1, 4);
573 self.color_components = self.color_components.max(components);
574 while self.colors.len() < self.positions.len() - mesh.num_points() {
576 self.colors.push([255, 255, 255, 255]);
577 }
578 for i in 0..mesh.num_points() {
579 self.colors.push(read_color(mesh, color_att_id, i));
580 }
581 }
582
583 let texcoord_att_id = mesh.named_attribute_id(GeometryAttributeType::TexCoord);
584 if texcoord_att_id >= 0 {
585 let texcoord_att = mesh.attribute(texcoord_att_id);
586 if texcoord_att.num_components() == 2 && texcoord_att.data_type() == DataType::Float32 {
587 while self.texcoords.len() < self.positions.len() - mesh.num_points() {
588 self.texcoords.push([0.0, 0.0]);
589 }
590 for i in 0..mesh.num_points() {
591 self.texcoords.push(read_float2(mesh, texcoord_att_id, i));
592 }
593 }
594 }
595
596 for i in 0..mesh.num_faces() as u32 {
598 let face = mesh.face(FaceIndex(i));
599 self.faces.push([
600 face[0].0 + vertex_offset,
601 face[1].0 + vertex_offset,
602 face[2].0 + vertex_offset,
603 ]);
604 }
605 Ok(())
606 }
607
608 fn write<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
609 self.write(path)
610 }
611
612 fn vertex_count(&self) -> usize {
613 self.vertex_count()
614 }
615
616 fn face_count(&self) -> usize {
617 self.face_count()
618 }
619}
620
621impl PointCloudWriter for PlyWriter {
622 fn add_points(&mut self, points: &[[f32; 3]]) {
623 self.add_points(points);
624 }
625
626 fn add_point(&mut self, point: [f32; 3]) {
627 self.add_point(point);
628 }
629}
630
631impl WriteToBytes for PlyWriter {
632 fn write_to_vec(&self) -> io::Result<Vec<u8>> {
633 PlyWriter::write_to_vec(self)
634 }
635
636 fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
637 PlyWriter::write_to(self, writer)
638 }
639}
640
641pub fn write_ply_mesh<P: AsRef<Path>>(path: P, mesh: &Mesh) -> io::Result<()> {
649 let mut writer = PlyWriter::new();
650 Writer::add_mesh(&mut writer, mesh, None)?;
651 writer.write(path)
652}
653
654pub fn write_ply_positions<P: AsRef<Path>>(path: P, points: &[[f32; 3]]) -> io::Result<()> {
658 let mut writer = PlyWriter::new();
659 writer.add_points(points);
660 writer.write(path)
661}
662
663#[cfg(test)]
668mod tests {
669 use super::*;
670 #[cfg(feature = "ply-reader")]
671 use crate::ply_reader::PlyReader;
672 use draco_core::draco_types::DataType;
673 use draco_core::geometry_attribute::PointAttribute;
674 use draco_core::geometry_indices::PointIndex;
675 use std::fs;
676 use tempfile::NamedTempFile;
677
678 fn create_triangle_mesh() -> Mesh {
679 let mut mesh = Mesh::new();
680 let mut pos_att = PointAttribute::new();
681
682 pos_att.init(
683 GeometryAttributeType::Position,
684 3,
685 DataType::Float32,
686 false,
687 3,
688 );
689 let buffer = pos_att.buffer_mut();
690 let positions: [[f32; 3]; 3] = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
691 for (i, pos) in positions.iter().enumerate() {
692 let bytes: Vec<u8> = pos.iter().flat_map(|v| v.to_le_bytes()).collect();
693 buffer.write(i * 12, &bytes);
694 }
695 mesh.add_attribute(pos_att);
696
697 mesh.set_num_faces(1);
698 mesh.set_face(FaceIndex(0), [PointIndex(0), PointIndex(1), PointIndex(2)]);
699
700 mesh
701 }
702
703 #[test]
704 fn test_ply_writer_new() {
705 let writer = PlyWriter::new();
706 assert_eq!(writer.vertex_count(), 0);
707 assert_eq!(writer.face_count(), 0);
708 assert!(!writer.has_normals());
709 assert!(!writer.has_colors());
710 assert!(!writer.is_binary_little_endian());
711 }
712
713 #[test]
714 fn test_ply_writer_add_mesh() {
715 let mesh = create_triangle_mesh();
716 let mut writer = PlyWriter::new();
717 Writer::add_mesh(&mut writer, &mesh, None).unwrap();
718 assert_eq!(writer.vertex_count(), 3);
719 assert_eq!(writer.face_count(), 1);
720 }
721
722 #[test]
723 fn test_ply_writer_add_points() {
724 let mut writer = PlyWriter::new();
725 writer.add_points(&[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
726 assert_eq!(writer.vertex_count(), 2);
727 assert_eq!(writer.face_count(), 0);
728 }
729
730 #[test]
731 fn test_ply_writer_add_points_with_colors() {
732 let mut writer = PlyWriter::new();
733 writer.add_points_with_colors(
734 &[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]],
735 &[[255, 0, 0, 255], [0, 255, 0, 255]],
736 );
737 assert_eq!(writer.vertex_count(), 2);
738 assert!(writer.has_colors());
739 }
740
741 #[test]
742 fn test_write_ply_positions() {
743 let points = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
744
745 let file = NamedTempFile::new().unwrap();
746 write_ply_positions(file.path(), &points).unwrap();
747
748 let content = fs::read_to_string(file.path()).unwrap();
749 assert!(content.contains("ply"));
750 assert!(content.contains("format ascii 1.0"));
751 assert!(content.contains("element vertex 3"));
752 assert!(content.contains("property float x"));
753 assert!(content.contains("end_header"));
754 assert!(content.contains("0.000000 0.000000 0.000000"));
755 assert!(content.contains("1.000000 0.000000 0.000000"));
756 }
757
758 #[test]
759 fn test_write_ply_mesh() {
760 let mesh = create_triangle_mesh();
761 let file = NamedTempFile::new().unwrap();
762 write_ply_mesh(file.path(), &mesh).unwrap();
763
764 let content = fs::read_to_string(file.path()).unwrap();
765 assert!(content.contains("ply"));
766 assert!(content.contains("element vertex 3"));
767 assert!(content.contains("element face 1"));
768 assert!(content.contains("property list uchar int vertex_indices"));
769 assert!(content.contains("3 0 1 2")); }
771
772 #[test]
773 fn test_multiple_meshes() {
774 let mesh1 = create_triangle_mesh();
775 let mesh2 = create_triangle_mesh();
776
777 let mut writer = PlyWriter::new();
778 Writer::add_mesh(&mut writer, &mesh1, None).unwrap();
779 Writer::add_mesh(&mut writer, &mesh2, None).unwrap();
780
781 assert_eq!(writer.vertex_count(), 6);
782 assert_eq!(writer.face_count(), 2);
783
784 let file = NamedTempFile::new().unwrap();
785 writer.write(file.path()).unwrap();
786
787 let content = fs::read_to_string(file.path()).unwrap();
788 assert!(content.contains("element vertex 6"));
789 assert!(content.contains("element face 2"));
790 assert!(content.contains("3 3 4 5"));
792 }
793
794 #[test]
795 fn test_ply_with_colors() {
796 let mut writer = PlyWriter::new();
797 writer.add_points_with_colors(
798 &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]],
799 &[[255, 0, 0, 255], [0, 255, 0, 255]],
800 );
801
802 let file = NamedTempFile::new().unwrap();
803 writer.write(file.path()).unwrap();
804
805 let content = fs::read_to_string(file.path()).unwrap();
806 assert!(content.contains("property uchar red"));
807 assert!(content.contains("property uchar green"));
808 assert!(content.contains("property uchar blue"));
809 assert!(content.contains("property uchar alpha"));
810 assert!(content.contains("255 0 0 255"));
811 assert!(content.contains("0 255 0 255"));
812 }
813
814 #[test]
815 fn test_ply_writer_can_switch_to_binary_little_endian() {
816 let writer = PlyWriter::new().with_binary_little_endian();
817 assert!(writer.is_binary_little_endian());
818
819 let mut writer = PlyWriter::new();
820 writer.set_binary_little_endian(true);
821 assert!(writer.is_binary_little_endian());
822 writer.set_binary_little_endian(false);
823 assert!(!writer.is_binary_little_endian());
824 }
825
826 #[cfg(feature = "ply-reader")]
827 #[test]
828 fn test_write_binary_little_endian_positions_roundtrip() {
829 let points = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
830
831 let file = NamedTempFile::new().unwrap();
832 let mut writer = PlyWriter::new().with_binary_little_endian();
833 writer.add_points(&points);
834 writer.write(file.path()).unwrap();
835
836 let content = fs::read(file.path()).unwrap();
837 let header_end = content
838 .windows(b"end_header\n".len())
839 .position(|window| window == b"end_header\n")
840 .map(|idx| idx + b"end_header\n".len())
841 .unwrap();
842 let header = std::str::from_utf8(&content[..header_end]).unwrap();
843 assert!(header.contains("format binary_little_endian 1.0"));
844
845 let mut reader = PlyReader::open(file.path()).unwrap();
846 let positions = reader.read_positions().unwrap();
847 assert_eq!(positions, points);
848 }
849
850 #[cfg(feature = "ply-reader")]
851 #[test]
852 fn test_write_binary_little_endian_mesh_roundtrip() {
853 let mesh = create_triangle_mesh();
854 let file = NamedTempFile::new().unwrap();
855
856 let mut writer = PlyWriter::new().with_binary_little_endian();
857 Writer::add_mesh(&mut writer, &mesh, None).unwrap();
858 writer.write(file.path()).unwrap();
859
860 let bytes = fs::read(file.path()).unwrap();
861 let header_end = bytes
862 .windows(b"end_header\n".len())
863 .position(|window| window == b"end_header\n")
864 .map(|idx| idx + b"end_header\n".len())
865 .unwrap();
866 let header = std::str::from_utf8(&bytes[..header_end]).unwrap();
867 assert!(header.contains("format binary_little_endian 1.0"));
868 assert!(header.contains("element vertex 3"));
869 assert!(header.contains("element face 1"));
870
871 let mut reader = PlyReader::open(file.path()).unwrap();
872 let mesh = reader.read_mesh().unwrap();
873 assert_eq!(mesh.num_points(), 3);
874 assert_eq!(mesh.num_faces(), 1);
875 assert_eq!(
876 mesh.face(FaceIndex(0)),
877 [PointIndex(0), PointIndex(1), PointIndex(2)]
878 );
879 }
880
881 #[cfg(feature = "ply-reader")]
882 #[test]
883 fn test_write_binary_big_endian_mesh_roundtrip() {
884 let mesh = create_triangle_mesh();
885 let mut writer = PlyWriter::new().with_format(PlyFormat::BinaryBigEndian);
886 Writer::add_mesh(&mut writer, &mesh, None).unwrap();
887 let bytes = writer.write_to_vec().unwrap();
888 let header_end = bytes
889 .windows(b"end_header\n".len())
890 .position(|window| window == b"end_header\n")
891 .map(|idx| idx + b"end_header\n".len())
892 .unwrap();
893 let header = std::str::from_utf8(&bytes[..header_end]).unwrap();
894 assert!(header.contains("format binary_big_endian 1.0"));
895
896 let mesh = PlyReader::read_from_bytes(&bytes).unwrap();
897 assert_eq!(mesh.num_points(), 3);
898 assert_eq!(mesh.num_faces(), 1);
899 }
900
901 #[test]
902 fn test_write_preserves_int32_positions() {
903 let mut mesh = Mesh::new();
904 let mut pos_att = PointAttribute::new();
905 pos_att.init(
906 GeometryAttributeType::Position,
907 3,
908 DataType::Int32,
909 false,
910 2,
911 );
912 pos_att
913 .buffer_mut()
914 .write(0, &[1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]);
915 pos_att
916 .buffer_mut()
917 .write(12, &[4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0]);
918 mesh.add_attribute(pos_att);
919
920 let mut writer = PlyWriter::new();
921 Writer::add_mesh(&mut writer, &mesh, None).unwrap();
922 let output = String::from_utf8(writer.write_to_vec().unwrap()).unwrap();
923 assert!(output.contains("property int x"));
924 assert!(output.contains("1 2 3"));
925 }
926}
927
928fn read_float2(mesh: &Mesh, att_id: i32, point_idx: usize) -> [f32; 2] {
929 let att = mesh.attribute(att_id);
930 let byte_stride = att.byte_stride() as usize;
931 let buffer = att.buffer();
932 let mut bytes = [0u8; 8];
933 buffer.read(point_idx * byte_stride, &mut bytes);
934 [
935 f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
936 f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
937 ]
938}
939
940fn read_f64x3(att: &PointAttribute, point_idx: usize) -> [f64; 3] {
941 let byte_stride = att.byte_stride() as usize;
942 let buffer = att.buffer();
943 let mut bytes = [0u8; 24];
944 buffer.read(point_idx * byte_stride, &mut bytes);
945 [
946 f64::from_le_bytes(bytes[0..8].try_into().unwrap()),
947 f64::from_le_bytes(bytes[8..16].try_into().unwrap()),
948 f64::from_le_bytes(bytes[16..24].try_into().unwrap()),
949 ]
950}
951
952fn read_i32x3(att: &PointAttribute, point_idx: usize) -> [i32; 3] {
953 let byte_stride = att.byte_stride() as usize;
954 let buffer = att.buffer();
955 let mut bytes = [0u8; 12];
956 buffer.read(point_idx * byte_stride, &mut bytes);
957 [
958 i32::from_le_bytes(bytes[0..4].try_into().unwrap()),
959 i32::from_le_bytes(bytes[4..8].try_into().unwrap()),
960 i32::from_le_bytes(bytes[8..12].try_into().unwrap()),
961 ]
962}
963
964fn read_u32x3(att: &PointAttribute, point_idx: usize) -> [u32; 3] {
965 let byte_stride = att.byte_stride() as usize;
966 let buffer = att.buffer();
967 let mut bytes = [0u8; 12];
968 buffer.read(point_idx * byte_stride, &mut bytes);
969 [
970 u32::from_le_bytes(bytes[0..4].try_into().unwrap()),
971 u32::from_le_bytes(bytes[4..8].try_into().unwrap()),
972 u32::from_le_bytes(bytes[8..12].try_into().unwrap()),
973 ]
974}
975
976fn append_positions_from_attribute(
977 positions: &mut PlyPositionData,
978 att: &PointAttribute,
979 num_points: usize,
980) {
981 if att.num_components() != 3 {
982 return;
983 }
984
985 match att.data_type() {
986 DataType::Float32 => {
987 let values: Vec<[f32; 3]> = (0..num_points)
988 .map(|i| {
989 let byte_stride = att.byte_stride() as usize;
990 let mut bytes = [0u8; 12];
991 att.buffer().read(i * byte_stride, &mut bytes);
992 [
993 f32::from_le_bytes(bytes[0..4].try_into().unwrap()),
994 f32::from_le_bytes(bytes[4..8].try_into().unwrap()),
995 f32::from_le_bytes(bytes[8..12].try_into().unwrap()),
996 ]
997 })
998 .collect();
999 match positions {
1000 PlyPositionData::Float32(existing) => existing.extend(values),
1001 _ => {
1002 positions.ensure_float32();
1003 if let PlyPositionData::Float32(existing) = positions {
1004 existing.extend(values);
1005 }
1006 }
1007 }
1008 }
1009 DataType::Float64
1010 if positions.len() == 0 || matches!(positions, PlyPositionData::Float64(_)) =>
1011 {
1012 let values: Vec<[f64; 3]> = (0..num_points).map(|i| read_f64x3(att, i)).collect();
1013 match positions {
1014 PlyPositionData::Float32(existing) if existing.is_empty() => {
1015 *positions = PlyPositionData::Float64(values);
1016 }
1017 PlyPositionData::Float64(existing) => existing.extend(values),
1018 _ => unreachable!(),
1019 }
1020 }
1021 DataType::Int32
1022 if positions.len() == 0 || matches!(positions, PlyPositionData::Int32(_)) =>
1023 {
1024 let values: Vec<[i32; 3]> = (0..num_points).map(|i| read_i32x3(att, i)).collect();
1025 match positions {
1026 PlyPositionData::Float32(existing) if existing.is_empty() => {
1027 *positions = PlyPositionData::Int32(values);
1028 }
1029 PlyPositionData::Int32(existing) => existing.extend(values),
1030 _ => unreachable!(),
1031 }
1032 }
1033 DataType::Uint32
1034 if positions.len() == 0 || matches!(positions, PlyPositionData::Uint32(_)) =>
1035 {
1036 let values: Vec<[u32; 3]> = (0..num_points).map(|i| read_u32x3(att, i)).collect();
1037 match positions {
1038 PlyPositionData::Float32(existing) if existing.is_empty() => {
1039 *positions = PlyPositionData::Uint32(values);
1040 }
1041 PlyPositionData::Uint32(existing) => existing.extend(values),
1042 _ => unreachable!(),
1043 }
1044 }
1045 _ => {
1046 let converted: Vec<[f32; 3]> = (0..num_points)
1047 .map(|i| read_numeric3_as_f32(att, i))
1048 .collect();
1049 positions.push_f32_slice(&converted);
1050 }
1051 }
1052}
1053
1054fn read_numeric3_as_f32(att: &PointAttribute, point_idx: usize) -> [f32; 3] {
1055 match att.data_type() {
1056 DataType::Float64 => {
1057 let v = read_f64x3(att, point_idx);
1058 [v[0] as f32, v[1] as f32, v[2] as f32]
1059 }
1060 DataType::Int32 => {
1061 let v = read_i32x3(att, point_idx);
1062 [v[0] as f32, v[1] as f32, v[2] as f32]
1063 }
1064 DataType::Uint32 => {
1065 let v = read_u32x3(att, point_idx);
1066 [v[0] as f32, v[1] as f32, v[2] as f32]
1067 }
1068 _ => {
1069 let mut bytes = [0u8; 12];
1070 att.buffer()
1071 .read(point_idx * att.byte_stride() as usize, &mut bytes);
1072 [
1073 f32::from_le_bytes(bytes[0..4].try_into().unwrap()),
1074 f32::from_le_bytes(bytes[4..8].try_into().unwrap()),
1075 f32::from_le_bytes(bytes[8..12].try_into().unwrap()),
1076 ]
1077 }
1078 }
1079}