Skip to main content

oxihuman_morph/
ear_concha_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4#![allow(dead_code)]
5
6//! Ear concha control: adjusts the depth and shape of the ear concha cavity.
7
8use std::f32::consts::PI;
9
10#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct EarConchaConfig {
13    pub min_depth: f32,
14    pub max_depth: f32,
15}
16
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct EarConchaState {
20    pub depth: f32,
21    pub width: f32,
22    pub symmetry: f32,
23}
24
25#[allow(dead_code)]
26pub fn default_ear_concha_config() -> EarConchaConfig {
27    EarConchaConfig {
28        min_depth: 0.0,
29        max_depth: 1.0,
30    }
31}
32
33#[allow(dead_code)]
34pub fn new_ear_concha_state() -> EarConchaState {
35    EarConchaState {
36        depth: 0.5,
37        width: 0.5,
38        symmetry: 1.0,
39    }
40}
41
42#[allow(dead_code)]
43pub fn ec_set_depth(state: &mut EarConchaState, cfg: &EarConchaConfig, v: f32) {
44    state.depth = v.clamp(cfg.min_depth, cfg.max_depth);
45}
46
47#[allow(dead_code)]
48pub fn ec_set_width(state: &mut EarConchaState, v: f32) {
49    state.width = v.clamp(0.0, 1.0);
50}
51
52#[allow(dead_code)]
53pub fn ec_set_symmetry(state: &mut EarConchaState, v: f32) {
54    state.symmetry = v.clamp(0.0, 1.0);
55}
56
57#[allow(dead_code)]
58pub fn ec_reset(state: &mut EarConchaState) {
59    *state = new_ear_concha_state();
60}
61
62#[allow(dead_code)]
63pub fn ec_cavity_volume(state: &EarConchaState) -> f32 {
64    PI * state.width * state.width * state.depth * 0.25
65}
66
67#[allow(dead_code)]
68pub fn ec_to_weights(state: &EarConchaState) -> Vec<(String, f32)> {
69    vec![
70        ("ear_concha_depth".to_string(), state.depth),
71        ("ear_concha_width".to_string(), state.width),
72        ("ear_concha_symmetry".to_string(), state.symmetry),
73    ]
74}
75
76#[allow(dead_code)]
77pub fn ec_to_json(state: &EarConchaState) -> String {
78    format!(
79        r#"{{"depth":{:.4},"width":{:.4},"symmetry":{:.4}}}"#,
80        state.depth, state.width, state.symmetry
81    )
82}
83
84#[allow(dead_code)]
85pub fn ec_blend(a: &EarConchaState, b: &EarConchaState, t: f32) -> EarConchaState {
86    let t = t.clamp(0.0, 1.0);
87    EarConchaState {
88        depth: a.depth + (b.depth - a.depth) * t,
89        width: a.width + (b.width - a.width) * t,
90        symmetry: a.symmetry + (b.symmetry - a.symmetry) * t,
91    }
92}
93
94#[allow(dead_code)]
95pub fn ec_effective_left(state: &EarConchaState) -> f32 {
96    state.depth * state.symmetry
97}
98
99#[allow(dead_code)]
100pub fn ec_effective_right(state: &EarConchaState) -> f32 {
101    state.depth * (2.0 - state.symmetry)
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_default_config() {
110        let cfg = default_ear_concha_config();
111        assert!(cfg.min_depth.abs() < 1e-6);
112    }
113
114    #[test]
115    fn test_new_state() {
116        let s = new_ear_concha_state();
117        assert!((s.depth - 0.5).abs() < 1e-6);
118    }
119
120    #[test]
121    fn test_set_depth_clamps() {
122        let cfg = default_ear_concha_config();
123        let mut s = new_ear_concha_state();
124        ec_set_depth(&mut s, &cfg, 5.0);
125        assert!((s.depth - 1.0).abs() < 1e-6);
126    }
127
128    #[test]
129    fn test_set_width() {
130        let mut s = new_ear_concha_state();
131        ec_set_width(&mut s, 0.8);
132        assert!((s.width - 0.8).abs() < 1e-6);
133    }
134
135    #[test]
136    fn test_set_symmetry() {
137        let mut s = new_ear_concha_state();
138        ec_set_symmetry(&mut s, 0.7);
139        assert!((s.symmetry - 0.7).abs() < 1e-6);
140    }
141
142    #[test]
143    fn test_reset() {
144        let cfg = default_ear_concha_config();
145        let mut s = new_ear_concha_state();
146        ec_set_depth(&mut s, &cfg, 0.9);
147        ec_reset(&mut s);
148        assert!((s.depth - 0.5).abs() < 1e-6);
149    }
150
151    #[test]
152    fn test_cavity_volume() {
153        let s = new_ear_concha_state();
154        assert!(ec_cavity_volume(&s) > 0.0);
155    }
156
157    #[test]
158    fn test_to_weights() {
159        let s = new_ear_concha_state();
160        assert_eq!(ec_to_weights(&s).len(), 3);
161    }
162
163    #[test]
164    fn test_blend() {
165        let a = new_ear_concha_state();
166        let mut b = new_ear_concha_state();
167        b.depth = 1.0;
168        let mid = ec_blend(&a, &b, 0.5);
169        assert!((mid.depth - 0.75).abs() < 1e-6);
170    }
171
172    #[test]
173    fn test_effective_sides() {
174        let s = new_ear_concha_state();
175        assert!((ec_effective_left(&s) - ec_effective_right(&s)).abs() < 1e-6);
176    }
177}