Skip to main content

oxihuman_morph/
scar_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Scar tissue morph (raised, depressed, linear).
6
7/// Type of scar.
8#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum ScarType {
11    Raised,
12    Depressed,
13    Linear,
14    Keloid,
15    Atrophic,
16}
17
18impl ScarType {
19    #[allow(dead_code)]
20    pub fn name(self) -> &'static str {
21        match self {
22            ScarType::Raised => "raised",
23            ScarType::Depressed => "depressed",
24            ScarType::Linear => "linear",
25            ScarType::Keloid => "keloid",
26            ScarType::Atrophic => "atrophic",
27        }
28    }
29}
30
31/// Scar morph parameters.
32#[allow(dead_code)]
33#[derive(Debug, Clone)]
34pub struct ScarMorphParams {
35    pub scar_type: ScarType,
36    pub prominence: f32,
37    pub width: f32,
38    pub length: f32,
39    pub roughness: f32,
40}
41
42impl ScarMorphParams {
43    #[allow(dead_code)]
44    pub fn new(scar_type: ScarType) -> Self {
45        ScarMorphParams {
46            scar_type,
47            prominence: 0.0,
48            width: 0.0,
49            length: 0.0,
50            roughness: 0.0,
51        }
52    }
53}
54
55#[allow(dead_code)]
56pub fn default_scar_morph_params() -> ScarMorphParams {
57    ScarMorphParams::new(ScarType::Linear)
58}
59
60#[allow(dead_code)]
61pub fn scar_set_prominence(p: &mut ScarMorphParams, v: f32) {
62    p.prominence = v.clamp(0.0, 1.0);
63}
64
65#[allow(dead_code)]
66pub fn scar_set_width(p: &mut ScarMorphParams, v: f32) {
67    p.width = v.clamp(0.0, 1.0);
68}
69
70#[allow(dead_code)]
71pub fn scar_set_length(p: &mut ScarMorphParams, v: f32) {
72    p.length = v.clamp(0.0, 1.0);
73}
74
75#[allow(dead_code)]
76pub fn scar_set_roughness(p: &mut ScarMorphParams, v: f32) {
77    p.roughness = v.clamp(0.0, 1.0);
78}
79
80#[allow(dead_code)]
81pub fn scar_reset(p: &mut ScarMorphParams) {
82    let t = p.scar_type;
83    *p = ScarMorphParams::new(t);
84}
85
86#[allow(dead_code)]
87pub fn scar_is_neutral(p: &ScarMorphParams) -> bool {
88    p.prominence.abs() < 1e-6 && p.width.abs() < 1e-6 && p.length.abs() < 1e-6
89}
90
91#[allow(dead_code)]
92pub fn scar_visibility(p: &ScarMorphParams) -> f32 {
93    p.prominence * 0.5 + p.roughness * 0.3 + (p.width * p.length).sqrt() * 0.2
94}
95
96#[allow(dead_code)]
97pub fn scar_blend(a: &ScarMorphParams, b: &ScarMorphParams, t: f32) -> ScarMorphParams {
98    let t = t.clamp(0.0, 1.0);
99    ScarMorphParams {
100        scar_type: if t < 0.5 { a.scar_type } else { b.scar_type },
101        prominence: a.prominence + (b.prominence - a.prominence) * t,
102        width: a.width + (b.width - a.width) * t,
103        length: a.length + (b.length - a.length) * t,
104        roughness: a.roughness + (b.roughness - a.roughness) * t,
105    }
106}
107
108#[allow(dead_code)]
109pub fn scar_to_json(p: &ScarMorphParams) -> String {
110    format!(
111        r#"{{"type":"{}","prominence":{:.4},"width":{:.4},"length":{:.4},"roughness":{:.4}}}"#,
112        p.scar_type.name(),
113        p.prominence,
114        p.width,
115        p.length,
116        p.roughness
117    )
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn default_is_neutral() {
126        assert!(scar_is_neutral(&default_scar_morph_params()));
127    }
128
129    #[test]
130    fn set_prominence_clamps() {
131        let mut p = default_scar_morph_params();
132        scar_set_prominence(&mut p, 5.0);
133        assert!((p.prominence - 1.0).abs() < 1e-6);
134    }
135
136    #[test]
137    fn scar_type_names() {
138        assert_eq!(ScarType::Raised.name(), "raised");
139        assert_eq!(ScarType::Keloid.name(), "keloid");
140    }
141
142    #[test]
143    fn reset_clears() {
144        let mut p = ScarMorphParams::new(ScarType::Raised);
145        scar_set_prominence(&mut p, 0.8);
146        scar_reset(&mut p);
147        assert!(scar_is_neutral(&p));
148    }
149
150    #[test]
151    fn reset_preserves_type() {
152        let mut p = ScarMorphParams::new(ScarType::Keloid);
153        scar_reset(&mut p);
154        assert_eq!(p.scar_type, ScarType::Keloid);
155    }
156
157    #[test]
158    fn visibility_zero_when_neutral() {
159        assert!(scar_visibility(&default_scar_morph_params()).abs() < 1e-6);
160    }
161
162    #[test]
163    fn visibility_positive_with_prominence() {
164        let mut p = default_scar_morph_params();
165        scar_set_prominence(&mut p, 1.0);
166        assert!(scar_visibility(&p) > 0.0);
167    }
168
169    #[test]
170    fn blend_midpoint() {
171        let a = default_scar_morph_params();
172        let mut b = default_scar_morph_params();
173        scar_set_prominence(&mut b, 1.0);
174        let m = scar_blend(&a, &b, 0.5);
175        assert!((m.prominence - 0.5).abs() < 1e-5);
176    }
177
178    #[test]
179    fn to_json_contains_type() {
180        assert!(scar_to_json(&default_scar_morph_params()).contains("type"));
181    }
182}