// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-2D-noise.html use std::mem::{self}; use rand::{Rng, RngCore}; use crate::ppm::{Pixel, Ppm}; use crate::vec2::Vec2; const MAX_TABLE_SIZE: usize = 256; const MAX_TABLE_SIZE_MASK: usize = MAX_TABLE_SIZE - 1; pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Ppm { let mut noise_map = vec![0.0; width * height]; let noise = ValueNoise::new(rng); let frequency = 0.05; for j in 0..height { for i in 0..width { let vec = Vec2::new(i as f64, j as f64); noise_map[j * width + i] = noise.eval(vec * frequency); } } let data = noise_map .into_iter() .map(|f| { let v = (f * 256.0).floor() as u8; Pixel(v, v, v) }) .collect(); Ppm { width, height, data, } } pub struct ValueNoise { r: [f64; MAX_TABLE_SIZE * MAX_TABLE_SIZE], } impl ValueNoise { pub fn new(mut rng: impl RngCore) -> Self { let mut r: [f64; MAX_TABLE_SIZE * MAX_TABLE_SIZE] = unsafe { mem::zeroed() }; for k in 0..MAX_TABLE_SIZE * MAX_TABLE_SIZE { r[k] = rng.gen_range(0.0..1.0); } ValueNoise { r } } pub fn eval(&self, point: Vec2) -> f64 { let xi = point.x.floor(); let yi = point.y.floor(); let tx = point.x - xi; let ty = point.y - yi; let xi = xi as usize; let yi = yi as usize; let rx0 = xi & MAX_TABLE_SIZE_MASK; let rx1 = (rx0 + 1) & MAX_TABLE_SIZE_MASK; let ry0 = yi & MAX_TABLE_SIZE_MASK; let ry1 = (ry0 + 1) & MAX_TABLE_SIZE_MASK; let c00 = self.r[ry0 * MAX_TABLE_SIZE_MASK + rx0]; let c10 = self.r[ry0 * MAX_TABLE_SIZE_MASK + rx1]; let c01 = self.r[ry1 * MAX_TABLE_SIZE_MASK + rx0]; let c11 = self.r[ry1 * MAX_TABLE_SIZE_MASK + rx1]; let sx = smooth_step(tx); let sy = smooth_step(ty); let nx0 = lerp(c00, c10, sx); let nx1 = lerp(c01, c11, sx); lerp(nx0, nx1, sy) } } fn smooth_step(t: f64) -> f64 { t * t * (3.0 - 2.0 * t) } fn lerp(lo: f64, hi: f64, t: f64) -> f64 { lo * (1.0 - t) + hi * t }