use rayon::prelude::*;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BoundaryMode {
Wrap,
Clamp,
}
pub fn height_to_normal(
heights: &[f64],
width: u32,
height: u32,
strength: f32,
boundary: BoundaryMode,
) -> Vec<u8> {
if width == 0 || height == 0 {
return Vec::new();
}
let w = width as usize;
let h = height as usize;
let s = (strength as f64) / 256.0;
let mut out = vec![0u8; w * h * 4];
out.par_chunks_mut(w * 4).enumerate().for_each(|(y, row)| {
for x in 0..w {
let (xm, xp, ym, yp) = match boundary {
BoundaryMode::Wrap => ((x + w - 1) % w, (x + 1) % w, (y + h - 1) % h, (y + 1) % h),
BoundaryMode::Clamp => (
x.saturating_sub(1),
(x + 1).min(w - 1),
y.saturating_sub(1),
(y + 1).min(h - 1),
),
};
let left = heights[y * w + xm];
let right = heights[y * w + xp];
let above = heights[ym * w + x];
let below = heights[yp * w + x];
let (x_dist, y_dist) = match boundary {
BoundaryMode::Wrap => (2.0f64, 2.0f64),
BoundaryMode::Clamp => ((xp - xm).max(1) as f64, (yp - ym).max(1) as f64),
};
let dx = (right - left) * s * w as f64 / x_dist;
let dy = (below - above) * s * h as f64 / y_dist;
let len = (dx * dx + dy * dy + 1.0).sqrt();
let nx = -dx / len;
let ny = -dy / len;
let nz = 1.0 / len;
let idx = x * 4;
row[idx] = encode_normal(nx);
row[idx + 1] = encode_normal(ny);
row[idx + 2] = encode_normal(nz);
row[idx + 3] = 255;
}
});
out
}
pub(crate) fn dilate_heights(heights: &mut [f64], albedo: &[u8], w: usize, h: usize) {
let mut dilated = heights.to_vec();
dilated.par_chunks_mut(w).enumerate().for_each(|(y, drow)| {
for (x, slot) in drow.iter_mut().enumerate() {
let idx = y * w + x;
if albedo[idx * 4 + 3] != 0 {
continue; }
let mut sum = 0.0f64;
let mut count = 0usize;
for (dy, dx) in [(-1i32, 0i32), (1, 0), (0, -1), (0, 1)] {
let ny = y as i32 + dy;
let nx = x as i32 + dx;
if ny < 0 || ny >= h as i32 || nx < 0 || nx >= w as i32 {
continue;
}
let nidx = ny as usize * w + nx as usize;
if albedo[nidx * 4 + 3] != 0 {
sum += heights[nidx];
count += 1;
}
}
if count > 0 {
*slot = sum / count as f64;
}
}
});
heights.copy_from_slice(&dilated);
}
#[inline]
fn encode_normal(n: f64) -> u8 {
((n * 0.5 + 0.5).clamp(0.0, 1.0) * 255.0).round() as u8
}