Skip to main content

oxihuman_morph/
mole_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Mole/nevus placement morph stub.
6
7/// Mole type classification.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum MoleType {
10    CommonNevus,
11    DysplasticNevus,
12    CongenitalNevus,
13    BluNevus,
14}
15
16/// A mole placement entry.
17#[derive(Debug, Clone)]
18pub struct MoleEntry {
19    pub mole_type: MoleType,
20    pub position: [f32; 3],
21    pub radius: f32,
22    pub elevation: f32,
23}
24
25/// Mole morph controller.
26#[derive(Debug, Clone)]
27pub struct MoleMorph {
28    pub moles: Vec<MoleEntry>,
29    pub global_opacity: f32,
30    pub morph_count: usize,
31    pub enabled: bool,
32}
33
34impl MoleMorph {
35    pub fn new(morph_count: usize) -> Self {
36        MoleMorph {
37            moles: Vec::new(),
38            global_opacity: 1.0,
39            morph_count,
40            enabled: true,
41        }
42    }
43}
44
45/// Create a new mole morph.
46pub fn new_mole_morph(morph_count: usize) -> MoleMorph {
47    MoleMorph::new(morph_count)
48}
49
50/// Add a mole entry.
51pub fn mom_add_mole(morph: &mut MoleMorph, entry: MoleEntry) {
52    morph.moles.push(entry);
53}
54
55/// Set global opacity.
56pub fn mom_set_opacity(morph: &mut MoleMorph, opacity: f32) {
57    morph.global_opacity = opacity.clamp(0.0, 1.0);
58}
59
60/// Clear all moles.
61pub fn mom_clear(morph: &mut MoleMorph) {
62    morph.moles.clear();
63}
64
65/// Evaluate morph weights (stub: uniform from global_opacity).
66pub fn mom_evaluate(morph: &MoleMorph) -> Vec<f32> {
67    /* Stub: uniform weight from global_opacity */
68    if !morph.enabled || morph.morph_count == 0 {
69        return vec![];
70    }
71    vec![morph.global_opacity; morph.morph_count]
72}
73
74/// Enable or disable.
75pub fn mom_set_enabled(morph: &mut MoleMorph, enabled: bool) {
76    morph.enabled = enabled;
77}
78
79/// Return mole count.
80pub fn mom_mole_count(morph: &MoleMorph) -> usize {
81    morph.moles.len()
82}
83
84/// Serialize to JSON-like string.
85pub fn mom_to_json(morph: &MoleMorph) -> String {
86    format!(
87        r#"{{"mole_count":{},"global_opacity":{},"morph_count":{},"enabled":{}}}"#,
88        morph.moles.len(),
89        morph.global_opacity,
90        morph.morph_count,
91        morph.enabled
92    )
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    fn make_mole() -> MoleEntry {
100        MoleEntry {
101            mole_type: MoleType::CommonNevus,
102            position: [0.1, 0.2, 0.3],
103            radius: 0.02,
104            elevation: 0.01,
105        }
106    }
107
108    #[test]
109    fn test_initial_empty() {
110        let m = new_mole_morph(4);
111        assert_eq!(mom_mole_count(&m), 0 /* no moles initially */);
112    }
113
114    #[test]
115    fn test_add_mole() {
116        let mut m = new_mole_morph(4);
117        mom_add_mole(&mut m, make_mole());
118        assert_eq!(mom_mole_count(&m), 1 /* one mole after add */);
119    }
120
121    #[test]
122    fn test_clear() {
123        let mut m = new_mole_morph(4);
124        mom_add_mole(&mut m, make_mole());
125        mom_clear(&mut m);
126        assert_eq!(mom_mole_count(&m), 0 /* cleared */);
127    }
128
129    #[test]
130    fn test_opacity_clamp() {
131        let mut m = new_mole_morph(4);
132        mom_set_opacity(&mut m, 2.0);
133        assert!((m.global_opacity - 1.0).abs() < 1e-6 /* clamped to 1.0 */);
134    }
135
136    #[test]
137    fn test_evaluate_length() {
138        let m = new_mole_morph(5);
139        assert_eq!(
140            mom_evaluate(&m).len(),
141            5 /* output must match morph_count */
142        );
143    }
144
145    #[test]
146    fn test_evaluate_disabled() {
147        let mut m = new_mole_morph(4);
148        mom_set_enabled(&mut m, false);
149        assert!(mom_evaluate(&m).is_empty() /* disabled must return empty */);
150    }
151
152    #[test]
153    fn test_to_json_has_mole_count() {
154        let m = new_mole_morph(4);
155        let j = mom_to_json(&m);
156        assert!(j.contains("\"mole_count\"") /* JSON must have mole_count */);
157    }
158
159    #[test]
160    fn test_enabled_default() {
161        let m = new_mole_morph(4);
162        assert!(m.enabled /* must be enabled by default */);
163    }
164
165    #[test]
166    fn test_evaluate_matches_opacity() {
167        let mut m = new_mole_morph(3);
168        mom_set_opacity(&mut m, 0.7);
169        let out = mom_evaluate(&m);
170        assert!((out[0] - 0.7).abs() < 1e-5 /* evaluate must match global_opacity */);
171    }
172}