use std::f64::consts::TAU;
use noise::{Fbm, MultiFractal, Perlin};
use crate::{
generator::{TextureError, TextureGenerator, TextureMap, linear_to_srgb, validate_dimensions},
noise::ToroidalNoise,
normal::height_to_normal,
};
#[derive(Clone, Debug)]
pub struct BarkConfig {
pub seed: u32,
pub scale: f64,
pub octaves: usize,
pub warp_u: f64,
pub warp_v: f64,
pub color_light: [f32; 3],
pub color_dark: [f32; 3],
pub normal_strength: f32,
}
impl Default for BarkConfig {
fn default() -> Self {
Self {
seed: 42,
scale: 4.0,
octaves: 6,
warp_u: 0.15,
warp_v: 0.55,
color_light: [0.45, 0.28, 0.14],
color_dark: [0.18, 0.10, 0.05],
normal_strength: 3.0,
}
}
}
pub struct BarkGenerator {
config: BarkConfig,
}
impl BarkGenerator {
pub fn new(config: BarkConfig) -> Self {
Self { config }
}
}
impl TextureGenerator for BarkGenerator {
fn generate(&self, width: u32, height: u32) -> Result<TextureMap, TextureError> {
validate_dimensions(width, height)?;
let c = &self.config;
let fbm_warp_u: Fbm<Perlin> = Fbm::new(c.seed).set_octaves(c.octaves);
let fbm_warp_v: Fbm<Perlin> = Fbm::new(c.seed.wrapping_add(100)).set_octaves(c.octaves);
let fbm_base: Fbm<Perlin> = Fbm::new(c.seed.wrapping_add(200)).set_octaves(c.octaves);
let warp_u_noise = ToroidalNoise::new(fbm_warp_u, c.scale);
let warp_v_noise = ToroidalNoise::new(fbm_warp_v, c.scale);
let base_noise = ToroidalNoise::new(fbm_base, c.scale);
let w = width as usize;
let h = height as usize;
let n = w * h;
let freq = c.scale;
let col_cos: Vec<f64> = (0..w)
.map(|x| (TAU * x as f64 / w as f64).cos() * freq)
.collect();
let col_sin: Vec<f64> = (0..w)
.map(|x| (TAU * x as f64 / w as f64).sin() * freq)
.collect();
let row_cos: Vec<f64> = (0..h)
.map(|y| (TAU * y as f64 / h as f64).cos() * freq)
.collect();
let row_sin: Vec<f64> = (0..h)
.map(|y| (TAU * y as f64 / h as f64).sin() * freq)
.collect();
let mut heights = vec![0.0f64; n];
let mut albedo = vec![0u8; n * 4];
let mut roughness = vec![0u8; n * 4];
for y in 0..h {
let nz = row_cos[y];
let nw = row_sin[y];
let v = y as f64 / h as f64;
for x in 0..w {
let nx = col_cos[x];
let ny = col_sin[x];
let u = x as f64 / w as f64;
let du = warp_u_noise.get_precomputed(nx, ny, nz, nw) * c.warp_u;
let dv = warp_v_noise.get_precomputed(nx, ny, nz, nw) * c.warp_v;
let raw = base_noise.get(u + du, v + dv);
let t = normalize(raw);
let idx = y * w + x;
heights[idx] = t;
let r = lerp(c.color_dark[0], c.color_light[0], t as f32);
let g = lerp(c.color_dark[1], c.color_light[1], t as f32);
let b = lerp(c.color_dark[2], c.color_light[2], t as f32);
let ai = idx * 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.6 + (1.0 - t as f32) * 0.35;
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);
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)
}
#[inline]
fn normalize(v: f64) -> f64 {
v * 0.5 + 0.5
}