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