Skip to main content

oxihuman_export/
bone_length_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Bone length measurement and export utilities.
6
7#[allow(dead_code)]
8#[derive(Debug, Clone)]
9pub struct BoneLengthEntry {
10    pub name: String,
11    pub head: [f32; 3],
12    pub tail: [f32; 3],
13}
14
15#[allow(dead_code)]
16#[derive(Debug, Clone)]
17pub struct BoneLengthExport {
18    pub bones: Vec<BoneLengthEntry>,
19}
20
21#[allow(dead_code)]
22pub fn new_bone_length_export() -> BoneLengthExport {
23    BoneLengthExport { bones: Vec::new() }
24}
25
26#[allow(dead_code)]
27pub fn add_bone_length(exp: &mut BoneLengthExport, name: &str, head: [f32; 3], tail: [f32; 3]) {
28    exp.bones.push(BoneLengthEntry {
29        name: name.to_string(),
30        head,
31        tail,
32    });
33}
34
35#[allow(dead_code)]
36pub fn bone_count_ble(exp: &BoneLengthExport) -> usize {
37    exp.bones.len()
38}
39
40#[allow(dead_code)]
41pub fn bone_length(entry: &BoneLengthEntry) -> f32 {
42    let d = [
43        entry.tail[0] - entry.head[0],
44        entry.tail[1] - entry.head[1],
45        entry.tail[2] - entry.head[2],
46    ];
47    (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
48}
49
50#[allow(dead_code)]
51pub fn total_bone_length_ble(exp: &BoneLengthExport) -> f32 {
52    exp.bones.iter().map(bone_length).sum()
53}
54
55#[allow(dead_code)]
56pub fn max_bone_length(exp: &BoneLengthExport) -> f32 {
57    exp.bones.iter().map(bone_length).fold(0.0_f32, f32::max)
58}
59
60#[allow(dead_code)]
61pub fn min_bone_length(exp: &BoneLengthExport) -> f32 {
62    if exp.bones.is_empty() {
63        return 0.0;
64    }
65    exp.bones.iter().map(bone_length).fold(f32::MAX, f32::min)
66}
67
68#[allow(dead_code)]
69pub fn find_bone_ble<'a>(exp: &'a BoneLengthExport, name: &str) -> Option<&'a BoneLengthEntry> {
70    exp.bones.iter().find(|b| b.name == name)
71}
72
73#[allow(dead_code)]
74pub fn bone_length_to_json(exp: &BoneLengthExport) -> String {
75    format!(
76        "{{\"bone_count\":{},\"total_length\":{}}}",
77        bone_count_ble(exp),
78        total_bone_length_ble(exp)
79    )
80}
81
82#[allow(dead_code)]
83pub fn bone_lengths_to_csv(exp: &BoneLengthExport) -> String {
84    let mut s = "name,length\n".to_string();
85    for b in &exp.bones {
86        s.push_str(&format!("{},{}\n", b.name, bone_length(b)));
87    }
88    s
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    fn sample() -> BoneLengthExport {
96        let mut exp = new_bone_length_export();
97        add_bone_length(&mut exp, "upper_arm", [0.0, 0.0, 0.0], [0.3, 0.0, 0.0]);
98        add_bone_length(&mut exp, "lower_arm", [0.3, 0.0, 0.0], [0.55, 0.0, 0.0]);
99        exp
100    }
101
102    #[test]
103    fn test_empty() {
104        let exp = new_bone_length_export();
105        assert_eq!(bone_count_ble(&exp), 0);
106    }
107
108    #[test]
109    fn test_add_bone() {
110        let exp = sample();
111        assert_eq!(bone_count_ble(&exp), 2);
112    }
113
114    #[test]
115    fn test_bone_length() {
116        let exp = sample();
117        assert!((bone_length(&exp.bones[0]) - 0.3).abs() < 1e-5);
118    }
119
120    #[test]
121    fn test_total_length() {
122        let exp = sample();
123        assert!((total_bone_length_ble(&exp) - 0.55).abs() < 1e-4);
124    }
125
126    #[test]
127    fn test_max_length() {
128        let exp = sample();
129        assert!(max_bone_length(&exp) >= 0.3);
130    }
131
132    #[test]
133    fn test_min_length() {
134        let exp = sample();
135        assert!(min_bone_length(&exp) > 0.0);
136    }
137
138    #[test]
139    fn test_find_bone() {
140        let exp = sample();
141        assert!(find_bone_ble(&exp, "upper_arm").is_some());
142    }
143
144    #[test]
145    fn test_json_output() {
146        let exp = sample();
147        let j = bone_length_to_json(&exp);
148        assert!(j.contains("bone_count"));
149    }
150
151    #[test]
152    fn test_csv_header() {
153        let exp = sample();
154        let csv = bone_lengths_to_csv(&exp);
155        assert!(csv.starts_with("name,length"));
156    }
157
158    #[test]
159    fn test_min_empty() {
160        let exp = new_bone_length_export();
161        assert!((min_bone_length(&exp)).abs() < 1e-6);
162    }
163}