Skip to main content

oxihuman_export/
envelope_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Bone envelope export for skinning weight falloff definitions.
6
7/// Envelope for a bone (capsule shape for weight calculation).
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct EnvelopeExport {
11    pub bone_name: String,
12    pub head_radius: f32,
13    pub tail_radius: f32,
14    pub distance: f32,
15}
16
17/// Collection of envelopes.
18#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct EnvelopeBundle {
21    pub envelopes: Vec<EnvelopeExport>,
22}
23
24/// Create new bundle.
25#[allow(dead_code)]
26pub fn new_envelope_bundle() -> EnvelopeBundle {
27    EnvelopeBundle { envelopes: vec![] }
28}
29
30/// Add envelope.
31#[allow(dead_code)]
32pub fn add_envelope(b: &mut EnvelopeBundle, bone: &str, head_r: f32, tail_r: f32, dist: f32) {
33    b.envelopes.push(EnvelopeExport {
34        bone_name: bone.to_string(),
35        head_radius: head_r,
36        tail_radius: tail_r,
37        distance: dist,
38    });
39}
40
41/// Envelope count.
42#[allow(dead_code)]
43pub fn env_count(b: &EnvelopeBundle) -> usize {
44    b.envelopes.len()
45}
46
47/// Get by name.
48#[allow(dead_code)]
49pub fn get_envelope<'a>(b: &'a EnvelopeBundle, name: &str) -> Option<&'a EnvelopeExport> {
50    b.envelopes.iter().find(|e| e.bone_name == name)
51}
52
53/// Average radius.
54#[allow(dead_code)]
55pub fn avg_radius(e: &EnvelopeExport) -> f32 {
56    (e.head_radius + e.tail_radius) * 0.5
57}
58
59/// Approximate volume of envelope capsule.
60#[allow(dead_code)]
61pub fn envelope_volume(e: &EnvelopeExport) -> f32 {
62    let r = avg_radius(e);
63    std::f32::consts::PI * r * r * e.distance + (4.0 / 3.0) * std::f32::consts::PI * r * r * r
64}
65
66/// Validate.
67#[allow(dead_code)]
68pub fn env_validate(b: &EnvelopeBundle) -> bool {
69    b.envelopes
70        .iter()
71        .all(|e| e.head_radius >= 0.0 && e.tail_radius >= 0.0 && e.distance >= 0.0)
72}
73
74/// Export to JSON.
75#[allow(dead_code)]
76pub fn envelope_bundle_to_json(b: &EnvelopeBundle) -> String {
77    format!("{{\"count\":{}}}", env_count(b))
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    #[test]
84    fn test_new() {
85        let b = new_envelope_bundle();
86        assert_eq!(env_count(&b), 0);
87    }
88    #[test]
89    fn test_add() {
90        let mut b = new_envelope_bundle();
91        add_envelope(&mut b, "arm", 0.1, 0.05, 0.3);
92        assert_eq!(env_count(&b), 1);
93    }
94    #[test]
95    fn test_get() {
96        let mut b = new_envelope_bundle();
97        add_envelope(&mut b, "leg", 0.1, 0.1, 0.5);
98        assert!(get_envelope(&b, "leg").is_some());
99    }
100    #[test]
101    fn test_get_missing() {
102        let b = new_envelope_bundle();
103        assert!(get_envelope(&b, "x").is_none());
104    }
105    #[test]
106    fn test_avg_radius() {
107        let e = EnvelopeExport {
108            bone_name: "a".to_string(),
109            head_radius: 0.2,
110            tail_radius: 0.4,
111            distance: 1.0,
112        };
113        assert!((avg_radius(&e) - 0.3).abs() < 1e-6);
114    }
115    #[test]
116    fn test_volume() {
117        let e = EnvelopeExport {
118            bone_name: "a".to_string(),
119            head_radius: 1.0,
120            tail_radius: 1.0,
121            distance: 1.0,
122        };
123        assert!(envelope_volume(&e) > 0.0);
124    }
125    #[test]
126    fn test_validate() {
127        let mut b = new_envelope_bundle();
128        add_envelope(&mut b, "a", 0.1, 0.1, 0.5);
129        assert!(env_validate(&b));
130    }
131    #[test]
132    fn test_validate_bad() {
133        let mut b = new_envelope_bundle();
134        add_envelope(&mut b, "a", -0.1, 0.1, 0.5);
135        assert!(!env_validate(&b));
136    }
137    #[test]
138    fn test_to_json() {
139        let b = new_envelope_bundle();
140        assert!(envelope_bundle_to_json(&b).contains("\"count\":0"));
141    }
142    #[test]
143    fn test_zero_distance() {
144        let e = EnvelopeExport {
145            bone_name: "a".to_string(),
146            head_radius: 0.5,
147            tail_radius: 0.5,
148            distance: 0.0,
149        };
150        assert!(envelope_volume(&e) > 0.0);
151    }
152}