Skip to main content

oxihuman_export/
pcd_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! PCD (Point Cloud Data) format export.
6
7/// PCD data type.
8#[allow(dead_code)]
9pub enum PcdDataType {
10    Ascii,
11    Binary,
12}
13
14/// PCD export structure.
15#[allow(dead_code)]
16pub struct PcdExport {
17    pub points: Vec<[f32; 3]>,
18    pub data_type: PcdDataType,
19    pub fields: Vec<String>,
20}
21
22/// Create a new PCD export.
23#[allow(dead_code)]
24pub fn new_pcd_export() -> PcdExport {
25    PcdExport {
26        points: Vec::new(),
27        data_type: PcdDataType::Ascii,
28        fields: vec!["x".to_string(), "y".to_string(), "z".to_string()],
29    }
30}
31
32/// Add a point.
33#[allow(dead_code)]
34pub fn add_pcd_point(export: &mut PcdExport, x: f32, y: f32, z: f32) {
35    export.points.push([x, y, z]);
36}
37
38/// Point count.
39#[allow(dead_code)]
40pub fn pcd_point_count(export: &PcdExport) -> usize {
41    export.points.len()
42}
43
44/// Build PCD header string.
45#[allow(dead_code)]
46pub fn build_pcd_header(export: &PcdExport) -> String {
47    let fields = export.fields.join(" ");
48    format!(
49        "VERSION 0.7\nFIELDS {}\nSIZE 4 4 4\nTYPE F F F\nCOUNT 1 1 1\nWIDTH {}\nHEIGHT 1\nPOINTS {}\nDATA ascii\n",
50        fields,
51        export.points.len(),
52        export.points.len()
53    )
54}
55
56/// Export to ASCII PCD string.
57#[allow(dead_code)]
58pub fn export_pcd_ascii(export: &PcdExport) -> String {
59    let mut s = build_pcd_header(export);
60    for &p in &export.points {
61        s.push_str(&format!("{} {} {}\n", p[0], p[1], p[2]));
62    }
63    s
64}
65
66/// Export to binary bytes stub.
67#[allow(dead_code)]
68pub fn export_pcd_binary(export: &PcdExport) -> Vec<u8> {
69    let mut buf = Vec::new();
70    for &p in &export.points {
71        buf.extend_from_slice(&p[0].to_le_bytes());
72        buf.extend_from_slice(&p[1].to_le_bytes());
73        buf.extend_from_slice(&p[2].to_le_bytes());
74    }
75    buf
76}
77
78/// Validate PCD export.
79#[allow(dead_code)]
80pub fn validate_pcd(export: &PcdExport) -> bool {
81    !export.points.is_empty() && !export.fields.is_empty()
82}
83
84/// Load from positions.
85#[allow(dead_code)]
86pub fn pcd_from_positions(positions: &[[f32; 3]]) -> PcdExport {
87    let mut e = new_pcd_export();
88    for &p in positions {
89        add_pcd_point(&mut e, p[0], p[1], p[2]);
90    }
91    e
92}
93
94/// Compute centroid.
95#[allow(dead_code)]
96pub fn pcd_centroid(export: &PcdExport) -> [f32; 3] {
97    if export.points.is_empty() {
98        return [0.0; 3];
99    }
100    let mut sum = [0.0f32; 3];
101    for &p in &export.points {
102        sum[0] += p[0];
103        sum[1] += p[1];
104        sum[2] += p[2];
105    }
106    let n = export.points.len() as f32;
107    [sum[0] / n, sum[1] / n, sum[2] / n]
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn new_export_empty() {
116        let e = new_pcd_export();
117        assert_eq!(pcd_point_count(&e), 0);
118    }
119
120    #[test]
121    fn add_point() {
122        let mut e = new_pcd_export();
123        add_pcd_point(&mut e, 1.0, 2.0, 3.0);
124        assert_eq!(pcd_point_count(&e), 1);
125    }
126
127    #[test]
128    fn header_contains_version() {
129        let e = new_pcd_export();
130        let h = build_pcd_header(&e);
131        assert!(h.contains("VERSION 0.7"));
132    }
133
134    #[test]
135    fn ascii_export_contains_coords() {
136        let mut e = new_pcd_export();
137        add_pcd_point(&mut e, 1.5, 2.5, 3.5);
138        let s = export_pcd_ascii(&e);
139        assert!(s.contains("1.5"));
140    }
141
142    #[test]
143    fn binary_export_size() {
144        let mut e = new_pcd_export();
145        add_pcd_point(&mut e, 0.0, 0.0, 0.0);
146        let b = export_pcd_binary(&e);
147        assert_eq!(b.len(), 12);
148    }
149
150    #[test]
151    fn validate_fails_empty() {
152        let e = new_pcd_export();
153        assert!(!validate_pcd(&e));
154    }
155
156    #[test]
157    fn validate_passes() {
158        let mut e = new_pcd_export();
159        add_pcd_point(&mut e, 0.0, 0.0, 0.0);
160        assert!(validate_pcd(&e));
161    }
162
163    #[test]
164    fn from_positions() {
165        let pos = vec![[1.0f32, 2.0, 3.0], [4.0, 5.0, 6.0]];
166        let e = pcd_from_positions(&pos);
167        assert_eq!(pcd_point_count(&e), 2);
168    }
169
170    #[test]
171    fn centroid_correct() {
172        let pos = vec![[0.0f32, 0.0, 0.0], [2.0, 0.0, 0.0]];
173        let e = pcd_from_positions(&pos);
174        let c = pcd_centroid(&e);
175        assert!((c[0] - 1.0).abs() < 1e-5);
176    }
177}