oxihuman_morph/
freckle_map_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum FreckleDistribution {
11 Sparse,
12 Moderate,
13 Dense,
14 Clustered,
15 Uniform,
16}
17
18impl FreckleDistribution {
19 #[allow(dead_code)]
20 pub fn name(self) -> &'static str {
21 match self {
22 FreckleDistribution::Sparse => "sparse",
23 FreckleDistribution::Moderate => "moderate",
24 FreckleDistribution::Dense => "dense",
25 FreckleDistribution::Clustered => "clustered",
26 FreckleDistribution::Uniform => "uniform",
27 }
28 }
29
30 #[allow(dead_code)]
31 pub fn density_index(self) -> f32 {
32 match self {
33 FreckleDistribution::Sparse => 0.1,
34 FreckleDistribution::Moderate => 0.4,
35 FreckleDistribution::Dense => 0.7,
36 FreckleDistribution::Clustered => 0.6,
37 FreckleDistribution::Uniform => 0.5,
38 }
39 }
40}
41
42#[allow(dead_code)]
44#[derive(Debug, Clone)]
45pub struct FreckleParams {
46 pub distribution: FreckleDistribution,
47 pub density: f32,
48 pub size: f32,
49 pub darkness: f32,
50 pub sun_exposure: f32,
51 pub face_coverage: f32,
52 pub body_coverage: f32,
53}
54
55impl Default for FreckleParams {
56 fn default() -> Self {
57 FreckleParams {
58 distribution: FreckleDistribution::Sparse,
59 density: 0.0,
60 size: 0.0,
61 darkness: 0.0,
62 sun_exposure: 0.0,
63 face_coverage: 0.0,
64 body_coverage: 0.0,
65 }
66 }
67}
68
69#[allow(dead_code)]
70pub fn default_freckle_params() -> FreckleParams {
71 FreckleParams::default()
72}
73
74#[allow(dead_code)]
75pub fn fm_set_density(p: &mut FreckleParams, v: f32) {
76 p.density = v.clamp(0.0, 1.0);
77}
78
79#[allow(dead_code)]
80pub fn fm_set_size(p: &mut FreckleParams, v: f32) {
81 p.size = v.clamp(0.0, 1.0);
82}
83
84#[allow(dead_code)]
85pub fn fm_set_darkness(p: &mut FreckleParams, v: f32) {
86 p.darkness = v.clamp(0.0, 1.0);
87}
88
89#[allow(dead_code)]
90pub fn fm_set_sun_exposure(p: &mut FreckleParams, v: f32) {
91 p.sun_exposure = v.clamp(0.0, 1.0);
92}
93
94#[allow(dead_code)]
95pub fn fm_set_face_coverage(p: &mut FreckleParams, v: f32) {
96 p.face_coverage = v.clamp(0.0, 1.0);
97}
98
99#[allow(dead_code)]
100pub fn fm_set_body_coverage(p: &mut FreckleParams, v: f32) {
101 p.body_coverage = v.clamp(0.0, 1.0);
102}
103
104#[allow(dead_code)]
105pub fn fm_set_distribution(p: &mut FreckleParams, d: FreckleDistribution) {
106 p.distribution = d;
107}
108
109#[allow(dead_code)]
110pub fn fm_reset(p: &mut FreckleParams) {
111 *p = FreckleParams::default();
112}
113
114#[allow(dead_code)]
115pub fn fm_is_neutral(p: &FreckleParams) -> bool {
116 p.density < 1e-6 && p.face_coverage < 1e-6 && p.body_coverage < 1e-6
117}
118
119#[allow(dead_code)]
120pub fn fm_effective_density(p: &FreckleParams) -> f32 {
121 let sun_boost = p.sun_exposure * 0.3;
122 (p.density + sun_boost).clamp(0.0, 1.0)
123}
124
125#[allow(dead_code)]
126pub fn fm_blend(a: &FreckleParams, b: &FreckleParams, t: f32) -> FreckleParams {
127 let t = t.clamp(0.0, 1.0);
128 FreckleParams {
129 distribution: if t < 0.5 {
130 a.distribution
131 } else {
132 b.distribution
133 },
134 density: a.density + (b.density - a.density) * t,
135 size: a.size + (b.size - a.size) * t,
136 darkness: a.darkness + (b.darkness - a.darkness) * t,
137 sun_exposure: a.sun_exposure + (b.sun_exposure - a.sun_exposure) * t,
138 face_coverage: a.face_coverage + (b.face_coverage - a.face_coverage) * t,
139 body_coverage: a.body_coverage + (b.body_coverage - a.body_coverage) * t,
140 }
141}
142
143#[allow(dead_code)]
144pub fn fm_to_json(p: &FreckleParams) -> String {
145 format!(
146 r#"{{"distribution":"{}","density":{:.4},"size":{:.4},"darkness":{:.4}}}"#,
147 p.distribution.name(),
148 p.density,
149 p.size,
150 p.darkness
151 )
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn default_is_neutral() {
160 assert!(fm_is_neutral(&default_freckle_params()));
161 }
162
163 #[test]
164 fn set_density_clamps() {
165 let mut p = default_freckle_params();
166 fm_set_density(&mut p, 5.0);
167 assert!((p.density - 1.0).abs() < 1e-6);
168 }
169
170 #[test]
171 fn set_sun_exposure() {
172 let mut p = default_freckle_params();
173 fm_set_sun_exposure(&mut p, 0.7);
174 assert!((p.sun_exposure - 0.7).abs() < 1e-5);
175 }
176
177 #[test]
178 fn effective_density_boosted_by_sun() {
179 let mut p = default_freckle_params();
180 fm_set_density(&mut p, 0.5);
181 fm_set_sun_exposure(&mut p, 1.0);
182 assert!(fm_effective_density(&p) > 0.5);
183 }
184
185 #[test]
186 fn reset_clears() {
187 let mut p = default_freckle_params();
188 fm_set_density(&mut p, 0.8);
189 fm_reset(&mut p);
190 assert!(fm_is_neutral(&p));
191 }
192
193 #[test]
194 fn distribution_density_index() {
195 assert!((FreckleDistribution::Sparse.density_index() - 0.1).abs() < 1e-6);
196 assert!((FreckleDistribution::Dense.density_index() - 0.7).abs() < 1e-6);
197 }
198
199 #[test]
200 fn blend_midpoint() {
201 let a = default_freckle_params();
202 let mut b = default_freckle_params();
203 fm_set_density(&mut b, 1.0);
204 let m = fm_blend(&a, &b, 0.5);
205 assert!((m.density - 0.5).abs() < 1e-5);
206 }
207
208 #[test]
209 fn set_distribution() {
210 let mut p = default_freckle_params();
211 fm_set_distribution(&mut p, FreckleDistribution::Dense);
212 assert_eq!(p.distribution, FreckleDistribution::Dense);
213 }
214
215 #[test]
216 fn to_json_has_distribution() {
217 assert!(fm_to_json(&default_freckle_params()).contains("distribution"));
218 }
219}