use noise::{MultiFractal, Perlin, RidgedMulti};
use crate::{
generator::{TextureError, TextureGenerator, TextureMap, linear_to_srgb, validate_dimensions},
noise::{ToroidalNoise, normalize, sample_grid},
normal::height_to_normal,
};
#[derive(Clone, Debug)]
pub struct RockConfig {
pub seed: u32,
pub scale: f64,
pub octaves: usize,
pub attenuation: f64,
pub color_light: [f32; 3],
pub color_dark: [f32; 3],
pub normal_strength: f32,
}
impl Default for RockConfig {
fn default() -> Self {
Self {
seed: 7,
scale: 3.0,
octaves: 8,
attenuation: 2.0,
color_light: [0.55, 0.52, 0.48],
color_dark: [0.22, 0.20, 0.18],
normal_strength: 4.0,
}
}
}
pub struct RockGenerator {
config: RockConfig,
}
impl RockGenerator {
pub fn new(config: RockConfig) -> Self {
Self { config }
}
}
impl TextureGenerator for RockGenerator {
fn generate(&self, width: u32, height: u32) -> Result<TextureMap, TextureError> {
validate_dimensions(width, height)?;
let c = &self.config;
let ridged: RidgedMulti<Perlin> = RidgedMulti::new(c.seed)
.set_octaves(c.octaves)
.set_attenuation(c.attenuation);
let noise = ToroidalNoise::new(ridged, c.scale);
let heights = sample_grid(&noise, width, height);
let n = (width as usize) * (height as usize);
let mut albedo = vec![0u8; n * 4];
let mut roughness = vec![0u8; n * 4];
for (i, &height) in heights.iter().enumerate().take(n) {
let t = normalize(height) as f32;
let r = lerp(c.color_dark[0], c.color_light[0], t);
let g = lerp(c.color_dark[1], c.color_light[1], t);
let b = lerp(c.color_dark[2], c.color_light[2], t);
let ai = i * 4;
albedo[ai] = linear_to_srgb(r);
albedo[ai + 1] = linear_to_srgb(g);
albedo[ai + 2] = linear_to_srgb(b);
albedo[ai + 3] = 255;
let rough = (0.75 - t * 0.25).clamp(0.0, 1.0);
roughness[ai] = 255; roughness[ai + 1] = (rough * 255.0).round() as u8;
roughness[ai + 2] = 0; roughness[ai + 3] = 255;
}
let normal = height_to_normal(&heights, width, height, c.normal_strength * 0.5);
Ok(TextureMap {
albedo,
normal,
roughness,
width,
height,
})
}
}
#[inline]
fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t.clamp(0.0, 1.0)
}