Skip to main content

oxihuman_export/
ply_binary_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Binary PLY format export (little-endian).
6
7/// Binary PLY export container.
8#[derive(Clone, Debug, Default)]
9pub struct PlyBinaryExport {
10    pub positions: Vec<[f32; 3]>,
11    pub normals: Vec<[f32; 3]>,
12    pub indices: Vec<u32>,
13}
14
15/// Create a new binary PLY export.
16pub fn new_ply_binary_export() -> PlyBinaryExport {
17    PlyBinaryExport::default()
18}
19
20/// Set mesh data.
21pub fn ply_binary_set_mesh(
22    doc: &mut PlyBinaryExport,
23    positions: Vec<[f32; 3]>,
24    normals: Vec<[f32; 3]>,
25    indices: Vec<u32>,
26) {
27    doc.positions = positions;
28    doc.normals = normals;
29    doc.indices = indices;
30}
31
32/// Return the vertex count.
33pub fn ply_binary_vertex_count(doc: &PlyBinaryExport) -> usize {
34    doc.positions.len()
35}
36
37/// Return the face count.
38pub fn ply_binary_face_count(doc: &PlyBinaryExport) -> usize {
39    doc.indices.len() / 3
40}
41
42/// Build the PLY ASCII header string.
43pub fn ply_binary_header(doc: &PlyBinaryExport) -> String {
44    let has_normals = !doc.normals.is_empty();
45    let mut h = String::from("ply\nformat binary_little_endian 1.0\n");
46    h.push_str(&format!(
47        "element vertex {}\n",
48        ply_binary_vertex_count(doc)
49    ));
50    h.push_str("property float x\nproperty float y\nproperty float z\n");
51    if has_normals {
52        h.push_str("property float nx\nproperty float ny\nproperty float nz\n");
53    }
54    h.push_str(&format!("element face {}\n", ply_binary_face_count(doc)));
55    h.push_str("property list uchar uint vertex_indices\n");
56    h.push_str("end_header\n");
57    h
58}
59
60/// Serialize vertex data to little-endian bytes.
61pub fn ply_binary_vertex_bytes(doc: &PlyBinaryExport) -> Vec<u8> {
62    let has_normals = !doc.normals.is_empty();
63    let mut out = Vec::new();
64    for (i, &p) in doc.positions.iter().enumerate() {
65        out.extend_from_slice(&p[0].to_le_bytes());
66        out.extend_from_slice(&p[1].to_le_bytes());
67        out.extend_from_slice(&p[2].to_le_bytes());
68        if has_normals {
69            if let Some(&n) = doc.normals.get(i) {
70                out.extend_from_slice(&n[0].to_le_bytes());
71                out.extend_from_slice(&n[1].to_le_bytes());
72                out.extend_from_slice(&n[2].to_le_bytes());
73            }
74        }
75    }
76    out
77}
78
79/// Serialize face data to little-endian bytes.
80pub fn ply_binary_face_bytes(doc: &PlyBinaryExport) -> Vec<u8> {
81    let mut out = Vec::new();
82    let tri_count = doc.indices.len() / 3;
83    for t in 0..tri_count {
84        out.push(3u8); // vertex count per face
85        for k in 0..3 {
86            let idx = doc.indices[t * 3 + k];
87            out.extend_from_slice(&idx.to_le_bytes());
88        }
89    }
90    out
91}
92
93/// Build the full binary PLY file as bytes (header + vertex data + face data).
94pub fn export_ply_binary(doc: &PlyBinaryExport) -> Vec<u8> {
95    let header = ply_binary_header(doc);
96    let mut out = header.into_bytes();
97    out.extend(ply_binary_vertex_bytes(doc));
98    out.extend(ply_binary_face_bytes(doc));
99    out
100}
101
102/// Estimate the file size in bytes.
103pub fn ply_binary_size_estimate(doc: &PlyBinaryExport) -> usize {
104    let has_normals = !doc.normals.is_empty();
105    let verts_per_vertex = if has_normals { 6 } else { 3 };
106    let vertex_bytes = ply_binary_vertex_count(doc) * verts_per_vertex * 4;
107    let face_bytes = ply_binary_face_count(doc) * (1 + 3 * 4);
108    ply_binary_header(doc).len() + vertex_bytes + face_bytes
109}
110
111/// Validate the export.
112pub fn validate_ply_binary(doc: &PlyBinaryExport) -> bool {
113    if doc.positions.is_empty() {
114        return false;
115    }
116    let n = doc.positions.len() as u32;
117    doc.indices.iter().all(|&i| i < n)
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    fn simple() -> PlyBinaryExport {
125        let mut d = new_ply_binary_export();
126        ply_binary_set_mesh(
127            &mut d,
128            vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
129            vec![],
130            vec![0, 1, 2],
131        );
132        d
133    }
134
135    #[test]
136    fn vertex_count() {
137        let d = simple();
138        assert_eq!(ply_binary_vertex_count(&d), 3);
139    }
140
141    #[test]
142    fn face_count() {
143        let d = simple();
144        assert_eq!(ply_binary_face_count(&d), 1);
145    }
146
147    #[test]
148    fn header_contains_ply() {
149        let d = simple();
150        let h = ply_binary_header(&d);
151        assert!(h.starts_with("ply\n"));
152    }
153
154    #[test]
155    fn header_contains_binary() {
156        let d = simple();
157        let h = ply_binary_header(&d);
158        assert!(h.contains("binary_little_endian"));
159    }
160
161    #[test]
162    fn vertex_bytes_correct_size() {
163        let d = simple();
164        let bytes = ply_binary_vertex_bytes(&d);
165        assert_eq!(bytes.len(), 3 * 3 * 4); // 3 verts × 3 floats × 4 bytes
166    }
167
168    #[test]
169    fn face_bytes_correct_size() {
170        let d = simple();
171        let bytes = ply_binary_face_bytes(&d);
172        assert_eq!(bytes.len(), 1 + 3 * 4); // 1 count byte + 3 uint32
173    }
174
175    #[test]
176    fn export_ply_binary_non_empty() {
177        let d = simple();
178        let bytes = export_ply_binary(&d);
179        assert!(!bytes.is_empty());
180    }
181
182    #[test]
183    fn validate_valid() {
184        let d = simple();
185        assert!(validate_ply_binary(&d));
186    }
187}