Skip to main content

oxihuman_export/
binary_stl_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Binary STL with attribute bytes.
6
7#[derive(Debug, Clone)]
8pub struct BinaryStlTriangle {
9    pub normal: [f32; 3],
10    pub v0: [f32; 3],
11    pub v1: [f32; 3],
12    pub v2: [f32; 3],
13    pub attribute: u16,
14}
15
16#[derive(Debug, Clone)]
17pub struct BinaryStlMesh {
18    pub header: [u8; 80],
19    pub triangles: Vec<BinaryStlTriangle>,
20}
21
22pub fn new_binary_stl_mesh() -> BinaryStlMesh {
23    let mut header = [0u8; 80];
24    let msg = b"OxiHuman Binary STL";
25    header[..msg.len()].copy_from_slice(msg);
26    BinaryStlMesh {
27        header,
28        triangles: Vec::new(),
29    }
30}
31
32pub fn add_binary_stl_triangle(mesh: &mut BinaryStlMesh, tri: BinaryStlTriangle) {
33    mesh.triangles.push(tri);
34}
35
36fn normalize3(v: [f32; 3]) -> [f32; 3] {
37    let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
38    if l < 1e-10 {
39        [0.0, 0.0, 1.0]
40    } else {
41        [v[0] / l, v[1] / l, v[2] / l]
42    }
43}
44
45fn write_f32(buf: &mut Vec<u8>, v: f32) {
46    buf.extend_from_slice(&v.to_le_bytes());
47}
48fn write_u16(buf: &mut Vec<u8>, v: u16) {
49    buf.extend_from_slice(&v.to_le_bytes());
50}
51fn write_u32(buf: &mut Vec<u8>, v: u32) {
52    buf.extend_from_slice(&v.to_le_bytes());
53}
54fn write_vec3(buf: &mut Vec<u8>, v: [f32; 3]) {
55    write_f32(buf, v[0]);
56    write_f32(buf, v[1]);
57    write_f32(buf, v[2]);
58}
59
60pub fn encode_binary_stl(mesh: &BinaryStlMesh) -> Vec<u8> {
61    let mut buf = Vec::with_capacity(84 + mesh.triangles.len() * 50);
62    buf.extend_from_slice(&mesh.header);
63    write_u32(&mut buf, mesh.triangles.len() as u32);
64    for tri in &mesh.triangles {
65        write_vec3(&mut buf, tri.normal);
66        write_vec3(&mut buf, tri.v0);
67        write_vec3(&mut buf, tri.v1);
68        write_vec3(&mut buf, tri.v2);
69        write_u16(&mut buf, tri.attribute);
70    }
71    buf
72}
73
74pub fn mesh_to_binary_stl(positions: &[[f32; 3]], indices: &[u32]) -> BinaryStlMesh {
75    let mut mesh = new_binary_stl_mesh();
76    for tri in indices.chunks(3) {
77        if tri.len() < 3 {
78            continue;
79        }
80        let (a, b, c) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
81        if a >= positions.len() || b >= positions.len() || c >= positions.len() {
82            continue;
83        }
84        let v0 = positions[a];
85        let v1 = positions[b];
86        let v2 = positions[c];
87        let ab = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
88        let ac = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
89        let n = normalize3([
90            ab[1] * ac[2] - ab[2] * ac[1],
91            ab[2] * ac[0] - ab[0] * ac[2],
92            ab[0] * ac[1] - ab[1] * ac[0],
93        ]);
94        add_binary_stl_triangle(
95            &mut mesh,
96            BinaryStlTriangle {
97                normal: n,
98                v0,
99                v1,
100                v2,
101                attribute: 0,
102            },
103        );
104    }
105    mesh
106}
107
108pub fn binary_stl_triangle_count(mesh: &BinaryStlMesh) -> usize {
109    mesh.triangles.len()
110}
111pub fn binary_stl_size_bytes(mesh: &BinaryStlMesh) -> usize {
112    84 + mesh.triangles.len() * 50
113}
114pub fn validate_binary_stl(mesh: &BinaryStlMesh) -> bool {
115    !mesh.triangles.is_empty()
116}
117
118pub fn parse_binary_stl_header(data: &[u8]) -> Option<u32> {
119    if data.len() < 84 {
120        return None;
121    }
122    let count = u32::from_le_bytes([data[80], data[81], data[82], data[83]]);
123    Some(count)
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_new_binary_stl_mesh() {
132        let m = new_binary_stl_mesh();
133        assert_eq!(m.triangles.len(), 0);
134        assert_eq!(m.header.len(), 80);
135    }
136
137    #[test]
138    fn test_mesh_to_binary_stl() {
139        let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]];
140        let idx = vec![0u32, 1, 2];
141        let m = mesh_to_binary_stl(&pos, &idx);
142        assert_eq!(binary_stl_triangle_count(&m), 1);
143    }
144
145    #[test]
146    fn test_encode_binary_stl_size() {
147        let m = new_binary_stl_mesh();
148        let bytes = encode_binary_stl(&m);
149        assert_eq!(bytes.len(), 84);
150    }
151
152    #[test]
153    fn test_encode_binary_stl_with_tri() {
154        let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]];
155        let idx = vec![0u32, 1, 2];
156        let m = mesh_to_binary_stl(&pos, &idx);
157        let bytes = encode_binary_stl(&m);
158        assert_eq!(bytes.len(), 84 + 50);
159    }
160
161    #[test]
162    fn test_binary_stl_size_bytes() {
163        let m = new_binary_stl_mesh();
164        assert_eq!(binary_stl_size_bytes(&m), 84);
165    }
166
167    #[test]
168    fn test_validate_binary_stl_empty_fails() {
169        let m = new_binary_stl_mesh();
170        assert!(!validate_binary_stl(&m));
171    }
172
173    #[test]
174    fn test_parse_binary_stl_header() {
175        let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]];
176        let idx = vec![0u32, 1, 2];
177        let m = mesh_to_binary_stl(&pos, &idx);
178        let bytes = encode_binary_stl(&m);
179        let count = parse_binary_stl_header(&bytes).expect("should succeed");
180        assert_eq!(count, 1);
181    }
182
183    #[test]
184    fn test_header_contains_marker() {
185        let m = new_binary_stl_mesh();
186        let s = std::str::from_utf8(&m.header[..19]).expect("should succeed");
187        assert!(s.contains("OxiHuman"));
188    }
189}