Skip to main content

oxihuman_morph/
hair_wave_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Hair wave and curl pattern morph control.
6
7use std::f32::consts::PI;
8
9/// Hair curl pattern type.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum CurlPattern {
12    Straight,
13    Wavy,
14    Curly,
15    Coily,
16}
17
18/// Hair wave morph configuration.
19#[derive(Debug, Clone)]
20pub struct HairWaveMorph {
21    pub pattern: CurlPattern,
22    pub amplitude: f32,
23    pub frequency: f32,
24    pub tightness: f32,
25}
26
27impl HairWaveMorph {
28    pub fn new() -> Self {
29        Self {
30            pattern: CurlPattern::Straight,
31            amplitude: 0.0,
32            frequency: 1.0,
33            tightness: 0.0,
34        }
35    }
36}
37
38impl Default for HairWaveMorph {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44/// Create a new hair wave morph.
45pub fn new_hair_wave_morph() -> HairWaveMorph {
46    HairWaveMorph::new()
47}
48
49/// Set wave amplitude in normalized range.
50pub fn hair_wave_set_amplitude(morph: &mut HairWaveMorph, amplitude: f32) {
51    morph.amplitude = amplitude.clamp(0.0, 1.0);
52}
53
54/// Set spatial frequency of the wave pattern (cycles per unit length).
55pub fn hair_wave_set_frequency(morph: &mut HairWaveMorph, frequency: f32) {
56    morph.frequency = frequency.clamp(0.1, 10.0);
57}
58
59/// Set curl tightness in normalized range.
60pub fn hair_wave_set_tightness(morph: &mut HairWaveMorph, tightness: f32) {
61    morph.tightness = tightness.clamp(0.0, 1.0);
62}
63
64/// Evaluate wave displacement at normalized position t along strand.
65pub fn hair_wave_displacement_at(morph: &HairWaveMorph, t: f32) -> f32 {
66    morph.amplitude * (morph.frequency * t * PI * 2.0).sin()
67}
68
69/// Serialize to JSON-like string.
70pub fn hair_wave_morph_to_json(morph: &HairWaveMorph) -> String {
71    let pattern_str = match morph.pattern {
72        CurlPattern::Straight => "straight",
73        CurlPattern::Wavy => "wavy",
74        CurlPattern::Curly => "curly",
75        CurlPattern::Coily => "coily",
76    };
77    format!(
78        r#"{{"pattern":"{pattern_str}","amplitude":{:.4},"frequency":{:.4},"tightness":{:.4}}}"#,
79        morph.amplitude, morph.frequency, morph.tightness
80    )
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_defaults() {
89        let m = new_hair_wave_morph();
90        assert_eq!(m.pattern, CurlPattern::Straight);
91        assert_eq!(m.amplitude, 0.0);
92    }
93
94    #[test]
95    fn test_amplitude_clamp() {
96        let mut m = new_hair_wave_morph();
97        hair_wave_set_amplitude(&mut m, 5.0);
98        assert_eq!(m.amplitude, 1.0);
99    }
100
101    #[test]
102    fn test_frequency_clamp_low() {
103        let mut m = new_hair_wave_morph();
104        hair_wave_set_frequency(&mut m, 0.0);
105        assert!((m.frequency - 0.1).abs() < 1e-5);
106    }
107
108    #[test]
109    fn test_tightness() {
110        let mut m = new_hair_wave_morph();
111        hair_wave_set_tightness(&mut m, 0.6);
112        assert!((m.tightness - 0.6).abs() < 1e-6);
113    }
114
115    #[test]
116    fn test_displacement_zero_amplitude() {
117        let m = new_hair_wave_morph();
118        assert_eq!(hair_wave_displacement_at(&m, 0.5), 0.0);
119    }
120
121    #[test]
122    fn test_json_contains_pattern() {
123        let m = new_hair_wave_morph();
124        let s = hair_wave_morph_to_json(&m);
125        assert!(s.contains("straight"));
126    }
127
128    #[test]
129    fn test_clone() {
130        let m = new_hair_wave_morph();
131        let m2 = m.clone();
132        assert_eq!(m2.pattern, m.pattern);
133    }
134
135    #[test]
136    fn test_displacement_nonzero() {
137        let mut m = new_hair_wave_morph();
138        hair_wave_set_amplitude(&mut m, 1.0);
139        hair_wave_set_frequency(&mut m, 1.0);
140        let d = hair_wave_displacement_at(&m, 0.25);
141        assert!(d.abs() > 0.9);
142    }
143
144    #[test]
145    fn test_default_trait() {
146        let m: HairWaveMorph = Default::default();
147        assert!((m.frequency - 1.0).abs() < 1e-6);
148    }
149}