Skip to main content

oxihuman_morph/
dental_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Dental/teeth shape morph stub.
6
7/// Dental alignment type.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum DentalAlignment {
10    Perfect,
11    SlightOverjet,
12    Crowded,
13    Gapped,
14    Underbite,
15    Overbite,
16}
17
18/// Dental morph controller.
19#[derive(Debug, Clone)]
20pub struct DentalMorph {
21    pub alignment: DentalAlignment,
22    pub tooth_size: f32,
23    pub gum_exposure: f32,
24    pub whitening: f32,
25    pub morph_count: usize,
26    pub enabled: bool,
27}
28
29impl DentalMorph {
30    pub fn new(morph_count: usize) -> Self {
31        DentalMorph {
32            alignment: DentalAlignment::Perfect,
33            tooth_size: 1.0,
34            gum_exposure: 0.3,
35            whitening: 0.8,
36            morph_count,
37            enabled: true,
38        }
39    }
40}
41
42/// Create a new dental morph controller.
43pub fn new_dental_morph(morph_count: usize) -> DentalMorph {
44    DentalMorph::new(morph_count)
45}
46
47/// Set dental alignment.
48pub fn dm_set_alignment(morph: &mut DentalMorph, alignment: DentalAlignment) {
49    morph.alignment = alignment;
50}
51
52/// Set tooth size scale.
53pub fn dm_set_tooth_size(morph: &mut DentalMorph, size: f32) {
54    morph.tooth_size = size.clamp(0.5, 2.0);
55}
56
57/// Set gum exposure.
58pub fn dm_set_gum_exposure(morph: &mut DentalMorph, exposure: f32) {
59    morph.gum_exposure = exposure.clamp(0.0, 1.0);
60}
61
62/// Set whitening level.
63pub fn dm_set_whitening(morph: &mut DentalMorph, whitening: f32) {
64    morph.whitening = whitening.clamp(0.0, 1.0);
65}
66
67/// Evaluate morph weights (stub: tooth_size-normalized).
68pub fn dm_evaluate(morph: &DentalMorph) -> Vec<f32> {
69    /* Stub: weight from tooth_size and gum_exposure */
70    if !morph.enabled || morph.morph_count == 0 {
71        return vec![];
72    }
73    let w = ((morph.tooth_size - 0.5) / 1.5) * (1.0 - morph.gum_exposure);
74    vec![w.clamp(0.0, 1.0); morph.morph_count]
75}
76
77/// Enable or disable.
78pub fn dm_set_enabled(morph: &mut DentalMorph, enabled: bool) {
79    morph.enabled = enabled;
80}
81
82/// Serialize to JSON-like string.
83pub fn dm_to_json(morph: &DentalMorph) -> String {
84    let align = match morph.alignment {
85        DentalAlignment::Perfect => "perfect",
86        DentalAlignment::SlightOverjet => "slight_overjet",
87        DentalAlignment::Crowded => "crowded",
88        DentalAlignment::Gapped => "gapped",
89        DentalAlignment::Underbite => "underbite",
90        DentalAlignment::Overbite => "overbite",
91    };
92    format!(
93        r#"{{"alignment":"{}","tooth_size":{},"gum_exposure":{},"enabled":{}}}"#,
94        align, morph.tooth_size, morph.gum_exposure, morph.enabled
95    )
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_default_alignment() {
104        let m = new_dental_morph(4);
105        assert_eq!(
106            m.alignment,
107            DentalAlignment::Perfect /* default must be Perfect */
108        );
109    }
110
111    #[test]
112    fn test_set_alignment() {
113        let mut m = new_dental_morph(4);
114        dm_set_alignment(&mut m, DentalAlignment::Crowded);
115        assert_eq!(
116            m.alignment,
117            DentalAlignment::Crowded /* alignment must be set */
118        );
119    }
120
121    #[test]
122    fn test_tooth_size_clamped() {
123        let mut m = new_dental_morph(4);
124        dm_set_tooth_size(&mut m, 5.0);
125        assert!((m.tooth_size - 2.0).abs() < 1e-6 /* tooth_size clamped to 2.0 */);
126    }
127
128    #[test]
129    fn test_gum_exposure_clamped() {
130        let mut m = new_dental_morph(4);
131        dm_set_gum_exposure(&mut m, -0.5);
132        assert!((m.gum_exposure).abs() < 1e-6 /* gum_exposure clamped to 0.0 */);
133    }
134
135    #[test]
136    fn test_whitening_clamped() {
137        let mut m = new_dental_morph(4);
138        dm_set_whitening(&mut m, 1.5);
139        assert!((m.whitening - 1.0).abs() < 1e-6 /* whitening clamped to 1.0 */);
140    }
141
142    #[test]
143    fn test_evaluate_length() {
144        let m = new_dental_morph(5);
145        assert_eq!(
146            dm_evaluate(&m).len(),
147            5 /* output must match morph_count */
148        );
149    }
150
151    #[test]
152    fn test_evaluate_disabled() {
153        let mut m = new_dental_morph(4);
154        dm_set_enabled(&mut m, false);
155        assert!(dm_evaluate(&m).is_empty() /* disabled must return empty */);
156    }
157
158    #[test]
159    fn test_to_json_has_alignment() {
160        let m = new_dental_morph(4);
161        let j = dm_to_json(&m);
162        assert!(j.contains("\"alignment\"") /* JSON must have alignment */);
163    }
164
165    #[test]
166    fn test_enabled_default() {
167        let m = new_dental_morph(4);
168        assert!(m.enabled /* must be enabled by default */);
169    }
170}