Skip to main content

oxihuman_export/
ptx_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! PTX (Leica scanner) point cloud format stub.
6
7/// PTX scan header.
8#[allow(dead_code)]
9pub struct PtxHeader {
10    pub cols: u32,
11    pub rows: u32,
12    pub scanner_pos: [f64; 3],
13    pub scanner_axes: [[f64; 3]; 3],
14}
15
16/// PTX point.
17#[allow(dead_code)]
18pub struct PtxPoint {
19    pub x: f64,
20    pub y: f64,
21    pub z: f64,
22    pub intensity: f32,
23    pub r: u8,
24    pub g: u8,
25    pub b: u8,
26}
27
28/// PTX export (one scan).
29#[allow(dead_code)]
30pub struct PtxExport {
31    pub header: PtxHeader,
32    pub points: Vec<PtxPoint>,
33}
34
35/// Create a new PTX export.
36#[allow(dead_code)]
37pub fn new_ptx_export(cols: u32, rows: u32) -> PtxExport {
38    PtxExport {
39        header: PtxHeader {
40            cols,
41            rows,
42            scanner_pos: [0.0; 3],
43            scanner_axes: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
44        },
45        points: Vec::new(),
46    }
47}
48
49/// Add a point to PTX export.
50#[allow(dead_code)]
51pub fn add_ptx_point(export: &mut PtxExport, x: f64, y: f64, z: f64, intensity: f32) {
52    export.points.push(PtxPoint {
53        x,
54        y,
55        z,
56        intensity,
57        r: 255,
58        g: 255,
59        b: 255,
60    });
61}
62
63/// Point count.
64#[allow(dead_code)]
65pub fn ptx_point_count(export: &PtxExport) -> usize {
66    export.points.len()
67}
68
69/// Build PTX header string.
70#[allow(dead_code)]
71pub fn build_ptx_header_string(export: &PtxExport) -> String {
72    format!(
73        "{}\n{}\n{:.6} {:.6} {:.6}\n",
74        export.header.cols,
75        export.header.rows,
76        export.header.scanner_pos[0],
77        export.header.scanner_pos[1],
78        export.header.scanner_pos[2],
79    )
80}
81
82/// Validate PTX.
83#[allow(dead_code)]
84pub fn validate_ptx(export: &PtxExport) -> bool {
85    export.header.cols > 0 && export.header.rows > 0
86}
87
88/// Export to string.
89#[allow(dead_code)]
90pub fn export_ptx_string(export: &PtxExport) -> String {
91    let mut s = build_ptx_header_string(export);
92    for p in &export.points {
93        s.push_str(&format!(
94            "{:.6} {:.6} {:.6} {:.3}\n",
95            p.x, p.y, p.z, p.intensity
96        ));
97    }
98    s
99}
100
101/// Load from positions.
102#[allow(dead_code)]
103pub fn ptx_from_positions(positions: &[[f32; 3]]) -> PtxExport {
104    let n = positions.len() as u32;
105    let mut e = new_ptx_export(n, 1);
106    for &p in positions {
107        add_ptx_point(&mut e, p[0] as f64, p[1] as f64, p[2] as f64, 1.0);
108    }
109    e
110}
111
112/// Estimate file size.
113#[allow(dead_code)]
114pub fn ptx_file_size_estimate(point_count: usize) -> usize {
115    point_count * 50 + 100
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn new_export() {
124        let e = new_ptx_export(100, 100);
125        assert_eq!(ptx_point_count(&e), 0);
126    }
127
128    #[test]
129    fn add_point() {
130        let mut e = new_ptx_export(100, 100);
131        add_ptx_point(&mut e, 1.0, 2.0, 3.0, 0.5);
132        assert_eq!(ptx_point_count(&e), 1);
133    }
134
135    #[test]
136    fn header_contains_dims() {
137        let e = new_ptx_export(128, 64);
138        let h = build_ptx_header_string(&e);
139        assert!(h.contains("128"));
140        assert!(h.contains("64"));
141    }
142
143    #[test]
144    fn validate_valid() {
145        let e = new_ptx_export(10, 10);
146        assert!(validate_ptx(&e));
147    }
148
149    #[test]
150    fn validate_zero_dims_fails() {
151        let e = new_ptx_export(0, 0);
152        assert!(!validate_ptx(&e));
153    }
154
155    #[test]
156    fn export_string_has_point() {
157        let mut e = new_ptx_export(1, 1);
158        add_ptx_point(&mut e, 1.0, 2.0, 3.0, 0.5);
159        let s = export_ptx_string(&e);
160        assert!(s.contains("1.000000"));
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 = ptx_from_positions(&pos);
167        assert_eq!(ptx_point_count(&e), 2);
168    }
169
170    #[test]
171    fn file_size_estimate() {
172        assert!(ptx_file_size_estimate(100) > 100);
173    }
174
175    #[test]
176    fn header_rows_correct() {
177        let e = new_ptx_export(100, 50);
178        assert_eq!(e.header.rows, 50);
179    }
180}