Skip to main content

oxihuman_morph/
nail_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Fingernail shape morph stub.
6
7/// Nail shape style.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum NailShape {
10    Square,
11    Oval,
12    Round,
13    Almond,
14    Stiletto,
15    Coffin,
16}
17
18/// Nail morph controller.
19#[derive(Debug, Clone)]
20pub struct NailMorph {
21    pub shape: NailShape,
22    pub length: f32,
23    pub thickness: f32,
24    pub curvature: f32,
25    pub morph_count: usize,
26    pub enabled: bool,
27}
28
29impl NailMorph {
30    pub fn new(morph_count: usize) -> Self {
31        NailMorph {
32            shape: NailShape::Square,
33            length: 0.5,
34            thickness: 0.5,
35            curvature: 0.3,
36            morph_count,
37            enabled: true,
38        }
39    }
40}
41
42/// Create a new nail morph controller.
43pub fn new_nail_morph(morph_count: usize) -> NailMorph {
44    NailMorph::new(morph_count)
45}
46
47/// Set nail shape.
48pub fn nm_set_shape(morph: &mut NailMorph, shape: NailShape) {
49    morph.shape = shape;
50}
51
52/// Set nail length.
53pub fn nm_set_length(morph: &mut NailMorph, length: f32) {
54    morph.length = length.clamp(0.0, 1.0);
55}
56
57/// Set nail thickness.
58pub fn nm_set_thickness(morph: &mut NailMorph, thickness: f32) {
59    morph.thickness = thickness.clamp(0.0, 1.0);
60}
61
62/// Set nail curvature.
63pub fn nm_set_curvature(morph: &mut NailMorph, curvature: f32) {
64    morph.curvature = curvature.clamp(0.0, 1.0);
65}
66
67/// Evaluate morph weights (stub: length-driven).
68pub fn nm_evaluate(morph: &NailMorph) -> Vec<f32> {
69    /* Stub: weight driven by length and curvature */
70    if !morph.enabled || morph.morph_count == 0 {
71        return vec![];
72    }
73    let w = (morph.length + morph.curvature * 0.5) / 1.5;
74    vec![w.clamp(0.0, 1.0); morph.morph_count]
75}
76
77/// Enable or disable.
78pub fn nm_set_enabled(morph: &mut NailMorph, enabled: bool) {
79    morph.enabled = enabled;
80}
81
82/// Serialize to JSON-like string.
83pub fn nm_to_json(morph: &NailMorph) -> String {
84    let shape = match morph.shape {
85        NailShape::Square => "square",
86        NailShape::Oval => "oval",
87        NailShape::Round => "round",
88        NailShape::Almond => "almond",
89        NailShape::Stiletto => "stiletto",
90        NailShape::Coffin => "coffin",
91    };
92    format!(
93        r#"{{"shape":"{}","length":{},"thickness":{},"curvature":{},"enabled":{}}}"#,
94        shape, morph.length, morph.thickness, morph.curvature, morph.enabled
95    )
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_default_shape() {
104        let m = new_nail_morph(4);
105        assert_eq!(
106            m.shape,
107            NailShape::Square /* default shape must be Square */
108        );
109    }
110
111    #[test]
112    fn test_set_shape() {
113        let mut m = new_nail_morph(4);
114        nm_set_shape(&mut m, NailShape::Almond);
115        assert_eq!(m.shape, NailShape::Almond /* shape must be set */);
116    }
117
118    #[test]
119    fn test_length_clamped() {
120        let mut m = new_nail_morph(4);
121        nm_set_length(&mut m, 2.0);
122        assert!((m.length - 1.0).abs() < 1e-6 /* length clamped to 1.0 */);
123    }
124
125    #[test]
126    fn test_thickness_clamped() {
127        let mut m = new_nail_morph(4);
128        nm_set_thickness(&mut m, -1.0);
129        assert!((m.thickness).abs() < 1e-6 /* thickness clamped to 0.0 */);
130    }
131
132    #[test]
133    fn test_curvature_clamped() {
134        let mut m = new_nail_morph(4);
135        nm_set_curvature(&mut m, 5.0);
136        assert!((m.curvature - 1.0).abs() < 1e-6 /* curvature clamped to 1.0 */);
137    }
138
139    #[test]
140    fn test_evaluate_length() {
141        let m = new_nail_morph(6);
142        assert_eq!(
143            nm_evaluate(&m).len(),
144            6 /* output must match morph_count */
145        );
146    }
147
148    #[test]
149    fn test_evaluate_disabled() {
150        let mut m = new_nail_morph(4);
151        nm_set_enabled(&mut m, false);
152        assert!(nm_evaluate(&m).is_empty() /* disabled must return empty */);
153    }
154
155    #[test]
156    fn test_to_json_has_shape() {
157        let m = new_nail_morph(4);
158        let j = nm_to_json(&m);
159        assert!(j.contains("\"shape\"") /* JSON must have shape */);
160    }
161
162    #[test]
163    fn test_enabled_default() {
164        let m = new_nail_morph(4);
165        assert!(m.enabled /* must be enabled by default */);
166    }
167
168    #[test]
169    fn test_evaluate_in_range() {
170        let m = new_nail_morph(2);
171        let out = nm_evaluate(&m);
172        assert!(out[0] >= 0.0 && out[0] <= 1.0 /* evaluated weight must be in [0, 1] */);
173    }
174}