use std::f64::consts::TAU;
use noise::{Fbm, MultiFractal, Perlin};
use crate::{
generator::{TextureError, TextureGenerator, TextureMap, linear_to_srgb, validate_dimensions},
noise::{ToroidalNoise, normalize, sample_grid},
normal::{BoundaryMode, height_to_normal},
};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct CorrugatedConfig {
pub seed: u32,
pub ridges: f64,
pub ridge_depth: f64,
pub roughness: f64,
pub rust_level: f64,
pub metallic: f32,
pub color_metal: [f32; 3],
pub color_rust: [f32; 3],
pub normal_strength: f32,
}
impl Default for CorrugatedConfig {
fn default() -> Self {
Self {
seed: 31,
ridges: 8.0,
ridge_depth: 1.0,
roughness: 0.35,
rust_level: 0.25,
metallic: 0.85,
color_metal: [0.72, 0.74, 0.76],
color_rust: [0.55, 0.30, 0.12],
normal_strength: 4.0,
}
}
}
pub struct CorrugatedGenerator {
config: CorrugatedConfig,
micro_noise: ToroidalNoise<Fbm<Perlin>>,
rust_noise: ToroidalNoise<Fbm<Perlin>>,
}
impl CorrugatedGenerator {
pub fn new(config: CorrugatedConfig) -> Self {
let fbm_micro: Fbm<Perlin> = Fbm::new(config.seed).set_octaves(4);
let micro_noise = ToroidalNoise::new(fbm_micro, config.ridges * 0.5);
let fbm_rust: Fbm<Perlin> = Fbm::new(config.seed.wrapping_add(100)).set_octaves(3);
let rust_noise = ToroidalNoise::new(fbm_rust, config.ridges * 0.25);
Self {
config,
micro_noise,
rust_noise,
}
}
}
impl TextureGenerator for CorrugatedGenerator {
fn generate(&self, width: u32, height: u32) -> Result<TextureMap, TextureError> {
validate_dimensions(width, height)?;
let c = &self.config;
let micro_grid = sample_grid(&self.micro_noise, width, height);
let rust_grid = sample_grid(&self.rust_noise, width, height);
let w = width as usize;
let h = height as usize;
let n = w * h;
let mut heights = vec![0.0f64; n];
let mut albedo = vec![0u8; n * 4];
let mut roughness_buf = vec![0u8; n * 4];
let ridges = c.ridges.round();
for y in 0..h {
let v = y as f64 / h as f64;
for x in 0..w {
let u = x as f64 / w as f64;
let idx = y * w + x;
let ridge_h = (u * ridges * TAU).sin() * 0.5 + 0.5;
let surf = normalize(micro_grid[idx]);
let rust_n = normalize(rust_grid[idx]);
let streak_v = (v + rust_n * 0.15).rem_euclid(1.0);
let streak_bias = (streak_v * TAU).sin() * 0.5 + 0.5;
let valley_factor = (1.0 - ridge_h).powf(1.5);
let rust_mask = (valley_factor * rust_n * (0.7 + streak_bias * 0.3) * c.rust_level)
.clamp(0.0, 1.0);
let h_val = (ridge_h * c.ridge_depth + surf * 0.05).clamp(0.0, 1.0);
heights[idx] = h_val;
let metal_bright = lerp(0.85, 1.0, surf as f32);
let rust_mask_f = rust_mask as f32;
let r = lerp(
c.color_metal[0] * metal_bright,
c.color_rust[0],
rust_mask_f,
);
let g = lerp(
c.color_metal[1] * metal_bright,
c.color_rust[1],
rust_mask_f,
);
let b = lerp(
c.color_metal[2] * metal_bright,
c.color_rust[2],
rust_mask_f,
);
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 = (c.roughness as f32 + rust_mask_f * 0.4).clamp(0.0, 1.0);
let met = (c.metallic - rust_mask_f * 0.7 * c.metallic).clamp(0.0, 1.0);
roughness_buf[ai] = 255; roughness_buf[ai + 1] = (rough * 255.0).round() as u8;
roughness_buf[ai + 2] = (met * 255.0).round() as u8;
roughness_buf[ai + 3] = 255;
}
}
let normal = height_to_normal(
&heights,
width,
height,
c.normal_strength,
BoundaryMode::Wrap,
);
Ok(TextureMap {
albedo,
normal,
roughness: roughness_buf,
width,
height,
})
}
}
#[inline]
fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t.clamp(0.0, 1.0)
}