Skip to main content

oxihuman_export/
pivot_point_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Pivot point export: object pivot/origin point data.
6
7/// A named pivot point.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct PivotPoint {
11    pub name: String,
12    pub position: [f32; 3],
13    pub orientation: [f32; 4],
14}
15
16/// Pivot point collection export.
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct PivotPointExport {
20    pub pivots: Vec<PivotPoint>,
21}
22
23#[allow(dead_code)]
24pub fn new_pivot_point_export() -> PivotPointExport {
25    PivotPointExport { pivots: Vec::new() }
26}
27
28#[allow(dead_code)]
29pub fn pp_add(e: &mut PivotPointExport, name: &str, pos: [f32; 3]) {
30    e.pivots.push(PivotPoint {
31        name: name.to_string(),
32        position: pos,
33        orientation: [0.0, 0.0, 0.0, 1.0],
34    });
35}
36
37#[allow(dead_code)]
38pub fn pp_add_with_orientation(
39    e: &mut PivotPointExport,
40    name: &str,
41    pos: [f32; 3],
42    orient: [f32; 4],
43) {
44    e.pivots.push(PivotPoint {
45        name: name.to_string(),
46        position: pos,
47        orientation: orient,
48    });
49}
50
51#[allow(dead_code)]
52pub fn pp_count(e: &PivotPointExport) -> usize {
53    e.pivots.len()
54}
55
56#[allow(dead_code)]
57pub fn pp_get(e: &PivotPointExport, idx: usize) -> Option<&PivotPoint> {
58    e.pivots.get(idx)
59}
60
61#[allow(dead_code)]
62pub fn pp_find_by_name(e: &PivotPointExport, name: &str) -> Option<usize> {
63    e.pivots.iter().position(|p| p.name == name)
64}
65
66#[allow(dead_code)]
67pub fn pp_set_position(e: &mut PivotPointExport, idx: usize, pos: [f32; 3]) {
68    if let Some(p) = e.pivots.get_mut(idx) {
69        p.position = pos;
70    }
71}
72
73#[allow(dead_code)]
74pub fn pp_distance(a: &PivotPoint, b: &PivotPoint) -> f32 {
75    let dx = a.position[0] - b.position[0];
76    let dy = a.position[1] - b.position[1];
77    let dz = a.position[2] - b.position[2];
78    (dx * dx + dy * dy + dz * dz).sqrt()
79}
80
81#[allow(dead_code)]
82pub fn pp_centroid(e: &PivotPointExport) -> [f32; 3] {
83    if e.pivots.is_empty() {
84        return [0.0; 3];
85    }
86    let n = e.pivots.len() as f32;
87    let mut sum = [0.0_f32; 3];
88    for p in &e.pivots {
89        for (k, s) in sum.iter_mut().enumerate() {
90            *s += p.position[k];
91        }
92    }
93    [sum[0] / n, sum[1] / n, sum[2] / n]
94}
95
96#[allow(dead_code)]
97pub fn pp_validate(e: &PivotPointExport) -> bool {
98    e.pivots.iter().all(|p| {
99        !p.name.is_empty()
100            && p.position.iter().all(|v| v.is_finite())
101            && p.orientation.iter().all(|v| v.is_finite())
102    })
103}
104
105#[allow(dead_code)]
106pub fn pivot_point_to_json(e: &PivotPointExport) -> String {
107    format!("{{\"pivots\":{}}}", e.pivots.len())
108}
109
110#[allow(dead_code)]
111pub fn pp_clear(e: &mut PivotPointExport) {
112    e.pivots.clear();
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_new() {
121        assert_eq!(pp_count(&new_pivot_point_export()), 0);
122    }
123
124    #[test]
125    fn test_add() {
126        let mut e = new_pivot_point_export();
127        pp_add(&mut e, "root", [0.0, 0.0, 0.0]);
128        assert_eq!(pp_count(&e), 1);
129    }
130
131    #[test]
132    fn test_add_with_orientation() {
133        let mut e = new_pivot_point_export();
134        pp_add_with_orientation(&mut e, "hand", [1.0, 0.0, 0.0], [0.0, 0.707, 0.0, 0.707]);
135        let p = pp_get(&e, 0).expect("should succeed");
136        assert!((p.orientation[1] - 0.707).abs() < 1e-3);
137    }
138
139    #[test]
140    fn test_get() {
141        let mut e = new_pivot_point_export();
142        pp_add(&mut e, "hip", [0.0, 1.0, 0.0]);
143        let p = pp_get(&e, 0).expect("should succeed");
144        assert_eq!(p.name, "hip");
145    }
146
147    #[test]
148    fn test_get_oob() {
149        assert!(pp_get(&new_pivot_point_export(), 0).is_none());
150    }
151
152    #[test]
153    fn test_find_by_name() {
154        let mut e = new_pivot_point_export();
155        pp_add(&mut e, "a", [0.0; 3]);
156        pp_add(&mut e, "b", [1.0; 3]);
157        assert_eq!(pp_find_by_name(&e, "b"), Some(1));
158        assert!(pp_find_by_name(&e, "c").is_none());
159    }
160
161    #[test]
162    fn test_set_position() {
163        let mut e = new_pivot_point_export();
164        pp_add(&mut e, "root", [0.0; 3]);
165        pp_set_position(&mut e, 0, [5.0, 6.0, 7.0]);
166        assert!((pp_get(&e, 0).expect("should succeed").position[0] - 5.0).abs() < 1e-6);
167    }
168
169    #[test]
170    fn test_distance() {
171        let a = PivotPoint {
172            name: "a".into(),
173            position: [0.0, 0.0, 0.0],
174            orientation: [0.0; 4],
175        };
176        let b = PivotPoint {
177            name: "b".into(),
178            position: [3.0, 4.0, 0.0],
179            orientation: [0.0; 4],
180        };
181        assert!((pp_distance(&a, &b) - 5.0).abs() < 1e-6);
182    }
183
184    #[test]
185    fn test_centroid() {
186        let mut e = new_pivot_point_export();
187        pp_add(&mut e, "a", [0.0, 0.0, 0.0]);
188        pp_add(&mut e, "b", [2.0, 4.0, 6.0]);
189        let c = pp_centroid(&e);
190        assert!((c[0] - 1.0).abs() < 1e-6);
191        assert!((c[1] - 2.0).abs() < 1e-6);
192    }
193
194    #[test]
195    fn test_validate() {
196        let mut e = new_pivot_point_export();
197        pp_add(&mut e, "ok", [1.0, 2.0, 3.0]);
198        assert!(pp_validate(&e));
199    }
200
201    #[test]
202    fn test_to_json() {
203        let e = new_pivot_point_export();
204        assert!(pivot_point_to_json(&e).contains("\"pivots\":0"));
205    }
206}