Skip to main content

oxihuman_morph/
wrinkle_depth_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Wrinkle depth and density morph control.
6
7/// Wrinkle zone identifier.
8#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum WrinkleZone {
11    Forehead,
12    Glabella,
13    EyeCorner,
14    NasolabialFold,
15    Mouth,
16    Neck,
17    Custom,
18}
19
20/// Parameters for a single wrinkle zone.
21#[allow(dead_code)]
22#[derive(Debug, Clone)]
23pub struct WrinkleDepthParams {
24    pub zone: WrinkleZone,
25    pub depth: f32,
26    pub density: f32,
27    pub softness: f32,
28}
29
30impl WrinkleDepthParams {
31    #[allow(dead_code)]
32    pub fn new(zone: WrinkleZone) -> Self {
33        WrinkleDepthParams {
34            zone,
35            depth: 0.0,
36            density: 0.0,
37            softness: 0.5,
38        }
39    }
40}
41
42#[allow(dead_code)]
43pub fn wd_set_depth(p: &mut WrinkleDepthParams, v: f32) {
44    p.depth = v.clamp(0.0, 1.0);
45}
46
47#[allow(dead_code)]
48pub fn wd_set_density(p: &mut WrinkleDepthParams, v: f32) {
49    p.density = v.clamp(0.0, 1.0);
50}
51
52#[allow(dead_code)]
53pub fn wd_set_softness(p: &mut WrinkleDepthParams, v: f32) {
54    p.softness = v.clamp(0.0, 1.0);
55}
56
57#[allow(dead_code)]
58pub fn wd_reset(p: &mut WrinkleDepthParams) {
59    let zone = p.zone;
60    *p = WrinkleDepthParams::new(zone);
61}
62
63#[allow(dead_code)]
64pub fn wd_is_neutral(p: &WrinkleDepthParams) -> bool {
65    p.depth.abs() < 1e-6 && p.density.abs() < 1e-6
66}
67
68#[allow(dead_code)]
69pub fn wd_visibility(p: &WrinkleDepthParams) -> f32 {
70    p.depth * 0.6 + p.density * 0.4
71}
72
73#[allow(dead_code)]
74pub fn wd_blend(a: &WrinkleDepthParams, b: &WrinkleDepthParams, t: f32) -> WrinkleDepthParams {
75    let t = t.clamp(0.0, 1.0);
76    WrinkleDepthParams {
77        zone: a.zone,
78        depth: a.depth + (b.depth - a.depth) * t,
79        density: a.density + (b.density - a.density) * t,
80        softness: a.softness + (b.softness - a.softness) * t,
81    }
82}
83
84#[allow(dead_code)]
85pub fn wd_to_json(p: &WrinkleDepthParams) -> String {
86    format!(
87        r#"{{"depth":{:.4},"density":{:.4},"softness":{:.4}}}"#,
88        p.depth, p.density, p.softness
89    )
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn new_is_neutral() {
98        assert!(wd_is_neutral(&WrinkleDepthParams::new(
99            WrinkleZone::Forehead
100        )));
101    }
102
103    #[test]
104    fn set_depth_clamps() {
105        let mut p = WrinkleDepthParams::new(WrinkleZone::Glabella);
106        wd_set_depth(&mut p, 3.0);
107        assert!((p.depth - 1.0).abs() < 1e-6);
108    }
109
110    #[test]
111    fn set_density_clamps_negative() {
112        let mut p = WrinkleDepthParams::new(WrinkleZone::Neck);
113        wd_set_density(&mut p, -1.0);
114        assert!(p.density.abs() < 1e-6);
115    }
116
117    #[test]
118    fn reset_clears() {
119        let mut p = WrinkleDepthParams::new(WrinkleZone::Mouth);
120        wd_set_depth(&mut p, 0.7);
121        wd_reset(&mut p);
122        assert!(wd_is_neutral(&p));
123    }
124
125    #[test]
126    fn visibility_zero_when_neutral() {
127        let p = WrinkleDepthParams::new(WrinkleZone::EyeCorner);
128        assert!(wd_visibility(&p).abs() < 1e-6);
129    }
130
131    #[test]
132    fn visibility_increases() {
133        let mut p = WrinkleDepthParams::new(WrinkleZone::Forehead);
134        wd_set_depth(&mut p, 1.0);
135        assert!(wd_visibility(&p) > 0.0);
136    }
137
138    #[test]
139    fn blend_midpoint() {
140        let a = WrinkleDepthParams::new(WrinkleZone::Forehead);
141        let mut b = WrinkleDepthParams::new(WrinkleZone::Forehead);
142        wd_set_depth(&mut b, 1.0);
143        let m = wd_blend(&a, &b, 0.5);
144        assert!((m.depth - 0.5).abs() < 1e-5);
145    }
146
147    #[test]
148    fn zone_preserved_after_reset() {
149        let mut p = WrinkleDepthParams::new(WrinkleZone::NasolabialFold);
150        wd_reset(&mut p);
151        assert_eq!(p.zone, WrinkleZone::NasolabialFold);
152    }
153
154    #[test]
155    fn to_json_contains_depth() {
156        let p = WrinkleDepthParams::new(WrinkleZone::Forehead);
157        assert!(wd_to_json(&p).contains("depth"));
158    }
159}