Skip to main content

oxihuman_export/
bump_map_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Bump/displacement map generation and export.
5
6/// Bump map mode selection.
7#[allow(dead_code)]
8#[derive(Clone, PartialEq, Debug)]
9pub enum BumpMapMode {
10    HeightMap,
11    DisplacementMap,
12}
13
14/// Configuration for bump map generation.
15#[allow(dead_code)]
16pub struct BumpMapConfig {
17    pub width: u32,
18    pub height: u32,
19    pub mode: BumpMapMode,
20    pub scale: f32,
21}
22
23/// Pixel buffer storing bump/height values in [0, 1].
24#[allow(dead_code)]
25pub struct BumpMapBuffer {
26    pub pixels: Vec<f32>,
27    pub width: u32,
28    pub height: u32,
29}
30
31/// Min/max range for a bump map.
32#[allow(dead_code)]
33pub struct BumpMapRange {
34    pub min: f32,
35    pub max: f32,
36}
37
38/// Type alias for normal vector output.
39#[allow(dead_code)]
40pub type NormalVector = [f32; 3];
41
42#[allow(dead_code)]
43pub fn default_bump_map_config(width: u32, height: u32) -> BumpMapConfig {
44    BumpMapConfig {
45        width,
46        height,
47        mode: BumpMapMode::HeightMap,
48        scale: 1.0,
49    }
50}
51
52#[allow(dead_code)]
53pub fn new_bump_map_buffer(width: u32, height: u32) -> BumpMapBuffer {
54    let count = (width * height) as usize;
55    BumpMapBuffer {
56        pixels: vec![0.0; count],
57        width,
58        height,
59    }
60}
61
62#[allow(dead_code)]
63pub fn set_bump_value(buffer: &mut BumpMapBuffer, x: u32, y: u32, value: f32) {
64    if x < buffer.width && y < buffer.height {
65        let idx = (y * buffer.width + x) as usize;
66        buffer.pixels[idx] = value;
67    }
68}
69
70#[allow(dead_code)]
71pub fn get_bump_value(buffer: &BumpMapBuffer, x: u32, y: u32) -> f32 {
72    if x < buffer.width && y < buffer.height {
73        let idx = (y * buffer.width + x) as usize;
74        buffer.pixels[idx]
75    } else {
76        0.0
77    }
78}
79
80/// Compute height values from vertex positions relative to a reference plane.
81/// The plane is defined by a point and normal. Heights are projected onto the normal.
82#[allow(dead_code)]
83pub fn bump_from_positions(
84    positions: &[[f32; 3]],
85    uvs: &[[f32; 2]],
86    plane_point: [f32; 3],
87    plane_normal: [f32; 3],
88    width: u32,
89    height: u32,
90) -> BumpMapBuffer {
91    let mut buffer = new_bump_map_buffer(width, height);
92    let count = positions.len().min(uvs.len());
93    let nlen = (plane_normal[0] * plane_normal[0]
94        + plane_normal[1] * plane_normal[1]
95        + plane_normal[2] * plane_normal[2])
96        .sqrt()
97        .max(1e-8);
98    let nn = [
99        plane_normal[0] / nlen,
100        plane_normal[1] / nlen,
101        plane_normal[2] / nlen,
102    ];
103    for i in 0..count {
104        let dx = positions[i][0] - plane_point[0];
105        let dy = positions[i][1] - plane_point[1];
106        let dz = positions[i][2] - plane_point[2];
107        let h = dx * nn[0] + dy * nn[1] + dz * nn[2];
108        let u = uvs[i][0].clamp(0.0, 1.0);
109        let v = uvs[i][1].clamp(0.0, 1.0);
110        let px = (u * (width as f32 - 1.0)).round() as u32;
111        let py = (v * (height as f32 - 1.0)).round() as u32;
112        set_bump_value(&mut buffer, px, py, h);
113    }
114    buffer
115}
116
117/// Encode bump map as grayscale PGM (P5).
118#[allow(dead_code)]
119pub fn encode_bump_map_ppm(buffer: &BumpMapBuffer) -> Vec<u8> {
120    let header = format!("P5\n{} {}\n255\n", buffer.width, buffer.height);
121    let mut out = header.into_bytes();
122    for &val in &buffer.pixels {
123        let byte = (val.clamp(0.0, 1.0) * 255.0).round() as u8;
124        out.push(byte);
125    }
126    out
127}
128
129/// Convert a height map to a normal map via finite differences.
130/// Returns normal vectors encoded as [nx, ny, nz] per pixel.
131#[allow(dead_code)]
132pub fn bump_to_normal_map(buffer: &BumpMapBuffer, strength: f32) -> Vec<NormalVector> {
133    let w = buffer.width as i32;
134    let h = buffer.height as i32;
135    let count = (buffer.width * buffer.height) as usize;
136    let mut normals = Vec::with_capacity(count);
137
138    let sample = |x: i32, y: i32| -> f32 {
139        let cx = x.clamp(0, w - 1) as u32;
140        let cy = y.clamp(0, h - 1) as u32;
141        get_bump_value(buffer, cx, cy)
142    };
143
144    for y in 0..h {
145        for x in 0..w {
146            let left = sample(x - 1, y);
147            let right = sample(x + 1, y);
148            let up = sample(x, y - 1);
149            let down = sample(x, y + 1);
150            let dx = (right - left) * strength;
151            let dy = (down - up) * strength;
152            let nz = 1.0f32;
153            let len = (dx * dx + dy * dy + nz * nz).sqrt().max(1e-8);
154            normals.push([-dx / len, -dy / len, nz / len]);
155        }
156    }
157    normals
158}
159
160/// Scale all bump values by a factor.
161#[allow(dead_code)]
162pub fn scale_bump_values(buffer: &mut BumpMapBuffer, factor: f32) {
163    for px in buffer.pixels.iter_mut() {
164        *px *= factor;
165    }
166}
167
168/// Invert the bump map (1.0 - value for each pixel).
169#[allow(dead_code)]
170pub fn invert_bump_map(buffer: &mut BumpMapBuffer) {
171    for px in buffer.pixels.iter_mut() {
172        *px = 1.0 - *px;
173    }
174}
175
176/// Apply a box blur to the bump map.
177#[allow(dead_code)]
178pub fn blur_bump_map(buffer: &mut BumpMapBuffer, radius: u32) {
179    if radius == 0 {
180        return;
181    }
182    let w = buffer.width;
183    let h = buffer.height;
184    let r = radius as i32;
185    let src = buffer.pixels.clone();
186
187    for y in 0..h {
188        for x in 0..w {
189            let mut sum = 0.0f64;
190            let mut count = 0u32;
191            for dy in -r..=r {
192                for dx in -r..=r {
193                    let sx = (x as i32 + dx).clamp(0, w as i32 - 1) as u32;
194                    let sy = (y as i32 + dy).clamp(0, h as i32 - 1) as u32;
195                    sum += src[(sy * w + sx) as usize] as f64;
196                    count += 1;
197                }
198            }
199            buffer.pixels[(y * w + x) as usize] = (sum / count as f64) as f32;
200        }
201    }
202}
203
204/// Return min/max of the bump map.
205#[allow(dead_code)]
206pub fn bump_map_range(buffer: &BumpMapBuffer) -> BumpMapRange {
207    if buffer.pixels.is_empty() {
208        return BumpMapRange { min: 0.0, max: 0.0 };
209    }
210    let mut min = f32::MAX;
211    let mut max = f32::MIN;
212    for &v in &buffer.pixels {
213        if v < min {
214            min = v;
215        }
216        if v > max {
217            max = v;
218        }
219    }
220    BumpMapRange { min, max }
221}
222
223/// Return total pixel count.
224#[allow(dead_code)]
225pub fn bump_map_pixel_count(buffer: &BumpMapBuffer) -> usize {
226    buffer.pixels.len()
227}
228
229/// Clamp all bump values to [lo, hi].
230#[allow(dead_code)]
231pub fn clamp_bump_values(buffer: &mut BumpMapBuffer, lo: f32, hi: f32) {
232    for px in buffer.pixels.iter_mut() {
233        *px = px.clamp(lo, hi);
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_default_config() {
243        let cfg = default_bump_map_config(512, 256);
244        assert_eq!(cfg.width, 512);
245        assert_eq!(cfg.height, 256);
246        assert_eq!(cfg.mode, BumpMapMode::HeightMap);
247        assert!((cfg.scale - 1.0).abs() < f32::EPSILON);
248    }
249
250    #[test]
251    fn test_new_buffer() {
252        let buf = new_bump_map_buffer(8, 8);
253        assert_eq!(buf.width, 8);
254        assert_eq!(buf.height, 8);
255        assert_eq!(buf.pixels.len(), 64);
256    }
257
258    #[test]
259    fn test_set_get_value() {
260        let mut buf = new_bump_map_buffer(4, 4);
261        set_bump_value(&mut buf, 2, 1, 0.5);
262        assert!((get_bump_value(&buf, 2, 1) - 0.5).abs() < f32::EPSILON);
263    }
264
265    #[test]
266    fn test_get_out_of_bounds() {
267        let buf = new_bump_map_buffer(4, 4);
268        assert!((get_bump_value(&buf, 10, 10) - 0.0).abs() < f32::EPSILON);
269    }
270
271    #[test]
272    fn test_set_out_of_bounds() {
273        let mut buf = new_bump_map_buffer(4, 4);
274        set_bump_value(&mut buf, 10, 10, 1.0);
275        for &px in &buf.pixels {
276            assert!((px - 0.0).abs() < f32::EPSILON);
277        }
278    }
279
280    #[test]
281    fn test_bump_from_positions() {
282        let positions = [[0.0f32, 0.0, 1.0], [1.0, 0.0, 2.0]];
283        let uvs = [[0.0f32, 0.0], [1.0, 1.0]];
284        let buf = bump_from_positions(&positions, &uvs, [0.0, 0.0, 0.0], [0.0, 0.0, 1.0], 8, 8);
285        assert_eq!(buf.width, 8);
286        assert!((get_bump_value(&buf, 0, 0) - 1.0).abs() < f32::EPSILON);
287        assert!((get_bump_value(&buf, 7, 7) - 2.0).abs() < f32::EPSILON);
288    }
289
290    #[test]
291    fn test_encode_ppm_starts_with_p5() {
292        let buf = new_bump_map_buffer(2, 2);
293        let ppm = encode_bump_map_ppm(&buf);
294        assert!(ppm.starts_with(b"P5"));
295    }
296
297    #[test]
298    fn test_encode_ppm_size() {
299        let buf = new_bump_map_buffer(4, 4);
300        let ppm = encode_bump_map_ppm(&buf);
301        let header = "P5\n4 4\n255\n".to_string();
302        assert_eq!(ppm.len(), header.len() + 16);
303    }
304
305    #[test]
306    fn test_bump_to_normal_map() {
307        let mut buf = new_bump_map_buffer(4, 4);
308        // Flat surface => normals should be ~(0, 0, 1)
309        for px in buf.pixels.iter_mut() {
310            *px = 0.5;
311        }
312        let normals = bump_to_normal_map(&buf, 1.0);
313        assert_eq!(normals.len(), 16);
314        for n in &normals {
315            assert!((n[2] - 1.0).abs() < 0.01);
316        }
317    }
318
319    #[test]
320    fn test_scale_bump_values() {
321        let mut buf = new_bump_map_buffer(2, 2);
322        for px in buf.pixels.iter_mut() {
323            *px = 0.5;
324        }
325        scale_bump_values(&mut buf, 2.0);
326        for &px in &buf.pixels {
327            assert!((px - 1.0).abs() < f32::EPSILON);
328        }
329    }
330
331    #[test]
332    fn test_invert_bump_map() {
333        let mut buf = new_bump_map_buffer(2, 2);
334        set_bump_value(&mut buf, 0, 0, 0.3);
335        invert_bump_map(&mut buf);
336        assert!((get_bump_value(&buf, 0, 0) - 0.7).abs() < 1e-6);
337        // Default 0.0 inverts to 1.0
338        assert!((get_bump_value(&buf, 1, 0) - 1.0).abs() < f32::EPSILON);
339    }
340
341    #[test]
342    fn test_blur_bump_map() {
343        let mut buf = new_bump_map_buffer(4, 4);
344        set_bump_value(&mut buf, 2, 2, 1.0);
345        blur_bump_map(&mut buf, 1);
346        // Center should be less than 1.0 after blur
347        let center = get_bump_value(&buf, 2, 2);
348        assert!(center < 1.0);
349        assert!(center > 0.0);
350    }
351
352    #[test]
353    fn test_blur_radius_zero() {
354        let mut buf = new_bump_map_buffer(4, 4);
355        set_bump_value(&mut buf, 1, 1, 0.5);
356        blur_bump_map(&mut buf, 0);
357        assert!((get_bump_value(&buf, 1, 1) - 0.5).abs() < f32::EPSILON);
358    }
359
360    #[test]
361    fn test_bump_map_range() {
362        let mut buf = new_bump_map_buffer(4, 4);
363        set_bump_value(&mut buf, 0, 0, 0.1);
364        set_bump_value(&mut buf, 1, 0, 0.9);
365        let range = bump_map_range(&buf);
366        assert!((range.min - 0.0).abs() < f32::EPSILON);
367        assert!((range.max - 0.9).abs() < f32::EPSILON);
368    }
369
370    #[test]
371    fn test_bump_map_range_empty() {
372        let buf = BumpMapBuffer {
373            pixels: vec![],
374            width: 0,
375            height: 0,
376        };
377        let range = bump_map_range(&buf);
378        assert!((range.min - 0.0).abs() < f32::EPSILON);
379        assert!((range.max - 0.0).abs() < f32::EPSILON);
380    }
381
382    #[test]
383    fn test_bump_map_pixel_count() {
384        let buf = new_bump_map_buffer(8, 4);
385        assert_eq!(bump_map_pixel_count(&buf), 32);
386    }
387
388    #[test]
389    fn test_clamp_bump_values() {
390        let mut buf = new_bump_map_buffer(2, 2);
391        set_bump_value(&mut buf, 0, 0, -0.5);
392        set_bump_value(&mut buf, 1, 0, 1.5);
393        clamp_bump_values(&mut buf, 0.0, 1.0);
394        assert!((get_bump_value(&buf, 0, 0) - 0.0).abs() < f32::EPSILON);
395        assert!((get_bump_value(&buf, 1, 0) - 1.0).abs() < f32::EPSILON);
396    }
397}