Skip to main content

oxihuman_morph/
wrinkle_map.rs

1//! Wrinkle map generation based on mesh deformation.
2
3#[allow(dead_code)]
4pub struct WrinkleConfig {
5    pub frequency: f32,
6    pub amplitude: f32,
7    pub threshold: f32,
8    pub blend_sharpness: f32,
9}
10
11#[allow(dead_code)]
12pub struct WrinkleRegion {
13    pub name: String,
14    pub vertex_indices: Vec<usize>,
15    pub direction: [f32; 3],
16    pub intensity: f32,
17}
18
19#[allow(dead_code)]
20pub struct WrinkleMap {
21    pub values: Vec<f32>,
22    pub vertex_count: usize,
23    pub config: WrinkleConfig,
24}
25
26#[allow(dead_code)]
27pub fn default_wrinkle_config() -> WrinkleConfig {
28    WrinkleConfig {
29        frequency: 1.0,
30        amplitude: 1.0,
31        threshold: 0.01,
32        blend_sharpness: 2.0,
33    }
34}
35
36#[allow(dead_code)]
37pub fn new_wrinkle_map(vertex_count: usize, cfg: WrinkleConfig) -> WrinkleMap {
38    WrinkleMap {
39        values: vec![0.0; vertex_count],
40        vertex_count,
41        config: cfg,
42    }
43}
44
45#[allow(dead_code)]
46pub fn compute_wrinkle_from_deformation(
47    original: &[[f32; 3]],
48    deformed: &[[f32; 3]],
49    cfg: &WrinkleConfig,
50) -> WrinkleMap {
51    let n = original.len().min(deformed.len());
52    let mut values = vec![0.0f32; n];
53    for (i, (orig, def)) in original.iter().zip(deformed.iter()).enumerate() {
54        let dx = def[0] - orig[0];
55        let dy = def[1] - orig[1];
56        let dz = def[2] - orig[2];
57        let mag = (dx * dx + dy * dy + dz * dz).sqrt();
58        if mag > cfg.threshold {
59            values[i] = (mag * cfg.amplitude).min(1.0);
60        }
61    }
62    WrinkleMap {
63        values,
64        vertex_count: n,
65        config: WrinkleConfig {
66            frequency: cfg.frequency,
67            amplitude: cfg.amplitude,
68            threshold: cfg.threshold,
69            blend_sharpness: cfg.blend_sharpness,
70        },
71    }
72}
73
74#[allow(dead_code)]
75pub fn add_procedural_wrinkles(
76    map: &mut WrinkleMap,
77    region: &WrinkleRegion,
78    positions: &[[f32; 3]],
79) {
80    let dir = region.direction;
81    let dir_len = (dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]).sqrt();
82    let dir_norm = if dir_len > 1e-6 {
83        [dir[0] / dir_len, dir[1] / dir_len, dir[2] / dir_len]
84    } else {
85        [1.0, 0.0, 0.0]
86    };
87
88    for &idx in &region.vertex_indices {
89        if idx >= map.vertex_count || idx >= positions.len() {
90            continue;
91        }
92        let pos = positions[idx];
93        let proj = pos[0] * dir_norm[0] + pos[1] * dir_norm[1] + pos[2] * dir_norm[2];
94        let wave = ((proj * map.config.frequency * std::f32::consts::TAU).sin() * 0.5 + 0.5)
95            * region.intensity;
96        map.values[idx] = (map.values[idx] + wave).min(1.0);
97    }
98}
99
100#[allow(dead_code)]
101pub fn smooth_wrinkle_map(map: &mut WrinkleMap, adjacency: &[Vec<usize>], iterations: u32) {
102    for _ in 0..iterations {
103        let old = map.values.clone();
104        for (i, neighbors) in adjacency.iter().enumerate() {
105            if i >= map.vertex_count {
106                continue;
107            }
108            if neighbors.is_empty() {
109                continue;
110            }
111            let sum: f32 = neighbors
112                .iter()
113                .filter(|&&n| n < map.vertex_count)
114                .map(|&n| old[n])
115                .sum();
116            let valid_count = neighbors.iter().filter(|&&n| n < map.vertex_count).count();
117            if valid_count > 0 {
118                map.values[i] = (old[i] + sum / valid_count as f32) * 0.5;
119            }
120        }
121    }
122}
123
124#[allow(dead_code)]
125pub fn wrinkle_map_min(map: &WrinkleMap) -> f32 {
126    map.values.iter().cloned().fold(f32::INFINITY, f32::min)
127}
128
129#[allow(dead_code)]
130pub fn wrinkle_map_max(map: &WrinkleMap) -> f32 {
131    map.values.iter().cloned().fold(f32::NEG_INFINITY, f32::max)
132}
133
134#[allow(dead_code)]
135pub fn normalize_wrinkle_map(map: &mut WrinkleMap) {
136    let mn = wrinkle_map_min(map);
137    let mx = wrinkle_map_max(map);
138    let range = mx - mn;
139    if range > 1e-6 {
140        for v in &mut map.values {
141            *v = (*v - mn) / range;
142        }
143    }
144}
145
146#[allow(dead_code)]
147pub fn blend_wrinkle_maps(a: &WrinkleMap, b: &WrinkleMap, t: f32) -> WrinkleMap {
148    let t = t.clamp(0.0, 1.0);
149    let n = a.vertex_count.min(b.vertex_count);
150    let values: Vec<f32> = a.values[..n]
151        .iter()
152        .zip(b.values[..n].iter())
153        .map(|(av, bv)| av * (1.0 - t) + bv * t)
154        .collect();
155    WrinkleMap {
156        vertex_count: n,
157        values,
158        config: WrinkleConfig {
159            frequency: a.config.frequency,
160            amplitude: a.config.amplitude,
161            threshold: a.config.threshold,
162            blend_sharpness: a.config.blend_sharpness,
163        },
164    }
165}
166
167#[allow(dead_code)]
168pub fn wrinkle_to_normal_delta(
169    map: &WrinkleMap,
170    positions: &[[f32; 3]],
171    normals: &[[f32; 3]],
172) -> Vec<[f32; 3]> {
173    let n = map.vertex_count.min(positions.len()).min(normals.len());
174    (0..n)
175        .map(|i| {
176            let w = map.values[i] * map.config.amplitude;
177            let nrm = normals[i];
178            [nrm[0] * w, nrm[1] * w, nrm[2] * w]
179        })
180        .collect()
181}
182
183#[allow(dead_code)]
184pub fn threshold_wrinkle_map(map: &WrinkleMap, threshold: f32) -> Vec<bool> {
185    map.values.iter().map(|&v| v >= threshold).collect()
186}
187
188#[allow(dead_code)]
189pub fn wrinkle_region_average(map: &WrinkleMap, indices: &[usize]) -> f32 {
190    if indices.is_empty() {
191        return 0.0;
192    }
193    let valid: Vec<f32> = indices
194        .iter()
195        .filter(|&&i| i < map.vertex_count)
196        .map(|&i| map.values[i])
197        .collect();
198    if valid.is_empty() {
199        return 0.0;
200    }
201    valid.iter().sum::<f32>() / valid.len() as f32
202}
203
204#[allow(dead_code)]
205pub fn apply_wrinkle_weight(map: &mut WrinkleMap, weight: f32) {
206    for v in &mut map.values {
207        *v = (*v * weight).clamp(0.0, 1.0);
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_new_wrinkle_map() {
217        let cfg = default_wrinkle_config();
218        let map = new_wrinkle_map(10, cfg);
219        assert_eq!(map.vertex_count, 10);
220        assert_eq!(map.values.len(), 10);
221        assert!(map.values.iter().all(|&v| v == 0.0));
222    }
223
224    #[test]
225    fn test_default_wrinkle_config() {
226        let cfg = default_wrinkle_config();
227        assert!(cfg.frequency > 0.0);
228        assert!(cfg.amplitude > 0.0);
229        assert!(cfg.threshold >= 0.0);
230        assert!(cfg.blend_sharpness > 0.0);
231    }
232
233    #[test]
234    fn test_compute_wrinkle_from_deformation_no_deform() {
235        let positions = vec![[0.0f32; 3]; 5];
236        let cfg = default_wrinkle_config();
237        let map = compute_wrinkle_from_deformation(&positions, &positions, &cfg);
238        assert_eq!(map.vertex_count, 5);
239        assert!(map.values.iter().all(|&v| v == 0.0));
240    }
241
242    #[test]
243    fn test_compute_wrinkle_from_deformation_with_deform() {
244        let original = vec![[0.0f32; 3]; 3];
245        let deformed = vec![[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.5, 0.5, 0.0]];
246        let cfg = default_wrinkle_config();
247        let map = compute_wrinkle_from_deformation(&original, &deformed, &cfg);
248        assert!(map.values[0] > 0.0);
249        assert_eq!(map.values[1], 0.0);
250        assert!(map.values[2] > 0.0);
251    }
252
253    #[test]
254    fn test_normalize_flat() {
255        let cfg = default_wrinkle_config();
256        let mut map = new_wrinkle_map(3, cfg);
257        map.values = vec![0.2, 0.6, 1.0];
258        normalize_wrinkle_map(&mut map);
259        assert!((map.values[0]).abs() < 1e-5);
260        assert!((map.values[2] - 1.0).abs() < 1e-5);
261    }
262
263    #[test]
264    fn test_normalize_uniform() {
265        let cfg = default_wrinkle_config();
266        let mut map = new_wrinkle_map(3, cfg);
267        map.values = vec![0.5, 0.5, 0.5];
268        // Range = 0, should not change values
269        normalize_wrinkle_map(&mut map);
270        assert!(map.values.iter().all(|&v| (v - 0.5).abs() < 1e-5));
271    }
272
273    #[test]
274    fn test_blend_wrinkle_maps() {
275        let cfg_a = default_wrinkle_config();
276        let cfg_b = default_wrinkle_config();
277        let mut a = new_wrinkle_map(4, cfg_a);
278        let mut b = new_wrinkle_map(4, cfg_b);
279        a.values = vec![0.0; 4];
280        b.values = vec![1.0; 4];
281        let blended = blend_wrinkle_maps(&a, &b, 0.5);
282        assert!(blended.values.iter().all(|&v| (v - 0.5).abs() < 1e-5));
283    }
284
285    #[test]
286    fn test_blend_wrinkle_maps_clamp() {
287        let a = new_wrinkle_map(2, default_wrinkle_config());
288        let b = new_wrinkle_map(2, default_wrinkle_config());
289        let blended = blend_wrinkle_maps(&a, &b, 2.0);
290        assert_eq!(blended.vertex_count, 2);
291    }
292
293    #[test]
294    fn test_threshold_wrinkle_map() {
295        let cfg = default_wrinkle_config();
296        let mut map = new_wrinkle_map(4, cfg);
297        map.values = vec![0.1, 0.5, 0.3, 0.8];
298        let mask = threshold_wrinkle_map(&map, 0.4);
299        assert_eq!(mask, vec![false, true, false, true]);
300    }
301
302    #[test]
303    fn test_smooth_wrinkle_map_no_panic() {
304        let cfg = default_wrinkle_config();
305        let mut map = new_wrinkle_map(3, cfg);
306        map.values = vec![1.0, 0.0, 0.5];
307        let adjacency = vec![vec![1usize], vec![0usize, 2usize], vec![1usize]];
308        smooth_wrinkle_map(&mut map, &adjacency, 2);
309        assert_eq!(map.values.len(), 3);
310    }
311
312    #[test]
313    fn test_wrinkle_region_average() {
314        let cfg = default_wrinkle_config();
315        let mut map = new_wrinkle_map(5, cfg);
316        map.values = vec![0.2, 0.4, 0.6, 0.8, 1.0];
317        let avg = wrinkle_region_average(&map, &[0, 1, 2]);
318        assert!((avg - 0.4).abs() < 1e-5);
319    }
320
321    #[test]
322    fn test_wrinkle_region_average_empty() {
323        let map = new_wrinkle_map(3, default_wrinkle_config());
324        let avg = wrinkle_region_average(&map, &[]);
325        assert_eq!(avg, 0.0);
326    }
327
328    #[test]
329    fn test_wrinkle_map_min_max() {
330        let cfg = default_wrinkle_config();
331        let mut map = new_wrinkle_map(4, cfg);
332        map.values = vec![0.1, 0.5, 0.9, 0.3];
333        assert!((wrinkle_map_min(&map) - 0.1).abs() < 1e-5);
334        assert!((wrinkle_map_max(&map) - 0.9).abs() < 1e-5);
335    }
336
337    #[test]
338    fn test_apply_wrinkle_weight() {
339        let cfg = default_wrinkle_config();
340        let mut map = new_wrinkle_map(3, cfg);
341        map.values = vec![0.5, 1.0, 0.25];
342        apply_wrinkle_weight(&mut map, 0.5);
343        assert!((map.values[0] - 0.25).abs() < 1e-5);
344        assert!((map.values[1] - 0.5).abs() < 1e-5);
345    }
346
347    #[test]
348    fn test_wrinkle_to_normal_delta() {
349        let cfg = default_wrinkle_config();
350        let mut map = new_wrinkle_map(2, cfg);
351        map.values = vec![0.5, 1.0];
352        let positions = vec![[0.0f32; 3]; 2];
353        let normals = vec![[0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
354        let deltas = wrinkle_to_normal_delta(&map, &positions, &normals);
355        assert_eq!(deltas.len(), 2);
356        assert!((deltas[0][1] - 0.5).abs() < 1e-5);
357        assert!((deltas[1][2] - 1.0).abs() < 1e-5);
358    }
359
360    #[test]
361    fn test_add_procedural_wrinkles() {
362        let cfg = default_wrinkle_config();
363        let mut map = new_wrinkle_map(3, cfg);
364        let region = WrinkleRegion {
365            name: "test".to_string(),
366            vertex_indices: vec![0, 1, 2],
367            direction: [1.0, 0.0, 0.0],
368            intensity: 0.5,
369        };
370        let positions = vec![[0.0f32; 3]; 3];
371        add_procedural_wrinkles(&mut map, &region, &positions);
372        assert_eq!(map.values.len(), 3);
373    }
374}