use crate::HeightMap;
#[derive(Debug, Clone)]
pub struct SplatRule {
pub height_range: (f32, f32),
pub slope_range: (f32, f32),
pub sharpness: f32,
}
impl SplatRule {
pub fn new(height_range: (f32, f32), slope_range: (f32, f32), sharpness: f32) -> Self {
Self {
height_range,
slope_range,
sharpness,
}
}
fn weight(&self, height: f32, slope: f32) -> f32 {
let h_w = smooth_range(
height,
self.height_range.0,
self.height_range.1,
self.sharpness,
);
let s_w = smooth_range(
slope,
self.slope_range.0,
self.slope_range.1,
self.sharpness,
);
h_w * s_w
}
}
#[derive(Debug, Clone)]
pub struct WeightMap {
pub data: Vec<[u8; 4]>,
pub width: usize,
pub height: usize,
}
impl WeightMap {
pub fn new(width: usize, height: usize) -> Self {
let data = vec![[255, 0, 0, 0]; width * height];
Self {
data,
width,
height,
}
}
}
#[derive(Debug, Clone)]
pub struct SplatMapper {
pub rules: [SplatRule; 4],
}
impl SplatMapper {
pub fn new(rules: [SplatRule; 4]) -> Self {
Self { rules }
}
pub fn generate(&self, heightmap: &HeightMap) -> WeightMap {
let w = heightmap.width();
let h = heightmap.height();
let mut wm = WeightMap::new(w, h);
for z in 0..h {
for x in 0..w {
let wx = x as f32 * heightmap.scale();
let wz = z as f32 * heightmap.scale();
let height = heightmap.get(x, z);
let normal = heightmap.get_normal_at(wx, wz);
let slope = 1.0 - normal[1];
let weights: [f32; 4] = [
self.rules[0].weight(height, slope),
self.rules[1].weight(height, slope),
self.rules[2].weight(height, slope),
self.rules[3].weight(height, slope),
];
let total: f32 = weights.iter().sum();
let pixel = if total > f32::EPSILON {
[
(weights[0] / total * 255.0).round() as u8,
(weights[1] / total * 255.0).round() as u8,
(weights[2] / total * 255.0).round() as u8,
(weights[3] / total * 255.0).round() as u8,
]
} else {
[0, 0, 255, 0]
};
wm.data[z * w + x] = pixel;
}
}
wm
}
}
impl Default for SplatMapper {
fn default() -> Self {
Self::new([
SplatRule::new((0.0, 0.45), (0.0, 0.3), 4.0),
SplatRule::new((0.3, 0.65), (0.0, 0.6), 2.0),
SplatRule::new((0.0, 1.0), (0.25, 1.0), 3.0),
SplatRule::new((0.7, 1.0), (0.0, 0.35), 4.0),
])
}
}
fn smooth_range(value: f32, lo: f32, hi: f32, sharpness: f32) -> f32 {
if lo >= hi {
return if (value - lo).abs() < f32::EPSILON {
1.0
} else {
0.0
};
}
let mid = (lo + hi) * 0.5;
let half = (hi - lo) * 0.5;
let dist = (value - mid).abs();
(1.0 - (dist / half).min(1.0)).powf(sharpness)
}