Skip to main content

oxihuman_morph/
body_hair_density.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Body hair density morph parameter stub.
6
7/// Body region for hair density control.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum HairRegion {
10    Arms,
11    Legs,
12    Chest,
13    Back,
14    Abdomen,
15    Face,
16}
17
18/// Body hair density controller.
19#[derive(Debug, Clone)]
20pub struct BodyHairDensity {
21    pub global_density: f32,
22    pub region_densities: Vec<(HairRegion, f32)>,
23    pub coarseness: f32,
24    pub enabled: bool,
25}
26
27impl BodyHairDensity {
28    pub fn new() -> Self {
29        BodyHairDensity {
30            global_density: 0.5,
31            region_densities: Vec::new(),
32            coarseness: 0.5,
33            enabled: true,
34        }
35    }
36}
37
38impl Default for BodyHairDensity {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44/// Create a new body hair density controller.
45pub fn new_body_hair_density() -> BodyHairDensity {
46    BodyHairDensity::new()
47}
48
49/// Set global hair density.
50pub fn bhd_set_density(ctrl: &mut BodyHairDensity, density: f32) {
51    ctrl.global_density = density.clamp(0.0, 1.0);
52}
53
54/// Set density for a specific region.
55pub fn bhd_set_region(ctrl: &mut BodyHairDensity, region: HairRegion, density: f32) {
56    let clamped = density.clamp(0.0, 1.0);
57    if let Some(entry) = ctrl.region_densities.iter_mut().find(|(r, _)| *r == region) {
58        entry.1 = clamped;
59    } else {
60        ctrl.region_densities.push((region, clamped));
61    }
62}
63
64/// Set hair coarseness (thickness of individual strands).
65pub fn bhd_set_coarseness(ctrl: &mut BodyHairDensity, coarseness: f32) {
66    ctrl.coarseness = coarseness.clamp(0.0, 1.0);
67}
68
69/// Enable or disable.
70pub fn bhd_set_enabled(ctrl: &mut BodyHairDensity, enabled: bool) {
71    ctrl.enabled = enabled;
72}
73
74/// Return region override count.
75pub fn bhd_region_count(ctrl: &BodyHairDensity) -> usize {
76    ctrl.region_densities.len()
77}
78
79/// Serialize to JSON-like string.
80pub fn bhd_to_json(ctrl: &BodyHairDensity) -> String {
81    format!(
82        r#"{{"global_density":{},"coarseness":{},"regions":{},"enabled":{}}}"#,
83        ctrl.global_density,
84        ctrl.coarseness,
85        ctrl.region_densities.len(),
86        ctrl.enabled
87    )
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_default_density() {
96        let c = new_body_hair_density();
97        assert!((c.global_density - 0.5).abs() < 1e-6 /* default density must be 0.5 */);
98    }
99
100    #[test]
101    fn test_set_density_clamps() {
102        let mut c = new_body_hair_density();
103        bhd_set_density(&mut c, 2.0);
104        assert!((c.global_density - 1.0).abs() < 1e-6 /* density clamped to 1.0 */);
105    }
106
107    #[test]
108    fn test_set_region_adds_entry() {
109        let mut c = new_body_hair_density();
110        bhd_set_region(&mut c, HairRegion::Chest, 0.7);
111        assert_eq!(
112            bhd_region_count(&c),
113            1 /* one region entry must be added */
114        );
115    }
116
117    #[test]
118    fn test_set_region_updates_existing() {
119        let mut c = new_body_hair_density();
120        bhd_set_region(&mut c, HairRegion::Arms, 0.3);
121        bhd_set_region(&mut c, HairRegion::Arms, 0.8);
122        assert_eq!(
123            bhd_region_count(&c),
124            1 /* duplicate region must not add new entry */
125        );
126    }
127
128    #[test]
129    fn test_coarseness_clamped() {
130        let mut c = new_body_hair_density();
131        bhd_set_coarseness(&mut c, -0.5);
132        assert!((c.coarseness).abs() < 1e-6 /* coarseness clamped to 0 */);
133    }
134
135    #[test]
136    fn test_set_enabled_false() {
137        let mut c = new_body_hair_density();
138        bhd_set_enabled(&mut c, false);
139        assert!(!c.enabled /* must be disabled */);
140    }
141
142    #[test]
143    fn test_to_json_has_density() {
144        let c = new_body_hair_density();
145        let j = bhd_to_json(&c);
146        assert!(j.contains("\"global_density\"") /* JSON must contain global_density */);
147    }
148
149    #[test]
150    fn test_enabled_default() {
151        let c = new_body_hair_density();
152        assert!(c.enabled /* must be enabled by default */);
153    }
154
155    #[test]
156    fn test_default_trait() {
157        let c = BodyHairDensity::default();
158        assert!((c.global_density - 0.5).abs() < 1e-6 /* Default trait must give 0.5 density */);
159    }
160}