turbulence + tuning

This commit is contained in:
Michael Zhang 2023-01-21 02:22:22 -06:00
parent 00000010da
commit 0000002087
6 changed files with 160 additions and 90 deletions

View file

@ -1,33 +1,48 @@
use std::{fs::File, path::PathBuf}; use std::{fs::File, path::PathBuf};
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::{Parser, ValueEnum};
use turbulence::generate_turbulence;
use crate::value_noise::generate_noise; use crate::value_noise::generate_noise;
mod ppm; mod ppm;
mod turbulence;
mod value_noise; mod value_noise;
mod vec2; mod vec2;
#[derive(Parser)] #[derive(Parser)]
struct Opt { struct Opt {
#[clap(short = 'o', long = "out", default_value = "out.ppm")] #[clap(long = "algorithm", default_value = "turbulence")]
output_path: PathBuf, algorithm: Algorithm,
#[clap(long = "size", default_value = "1024")]
size: usize,
#[clap(short = 'o', long = "out", default_value = "out.ppm")]
output_path: PathBuf,
}
#[derive(ValueEnum, Clone)]
enum Algorithm {
Noise,
Turbulence,
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let opt = Opt::parse(); let opt = Opt::parse();
let rng = rand::thread_rng(); let rng = rand::thread_rng();
let width = 256; let ppm = match opt.algorithm {
let height = 256; Algorithm::Noise => generate_noise(opt.size, opt.size, rng),
let ppm = generate_noise(width, height, rng); Algorithm::Turbulence => generate_turbulence(opt.size, opt.size, rng),
};
{ {
let file = File::create(opt.output_path)?; let file = File::create(opt.output_path)?;
ppm.write(file)?; ppm.write(file)?;
} }
Ok(()) Ok(())
} }

View file

@ -3,24 +3,24 @@ use std::io::{Result, Write};
pub struct Pixel(pub u8, pub u8, pub u8); pub struct Pixel(pub u8, pub u8, pub u8);
pub struct Ppm { pub struct Ppm {
pub(crate) width: usize, pub(crate) width: usize,
pub(crate) height: usize, pub(crate) height: usize,
pub(crate) data: Vec<Pixel>, pub(crate) data: Vec<Pixel>,
} }
impl Ppm { impl Ppm {
pub fn write(&self, mut w: impl Write) -> Result<()> { pub fn write(&self, mut w: impl Write) -> Result<()> {
// Header // Header
let header = format!("P3 {} {} 255\n", self.width, self.height); let header = format!("P3 {} {} 255\n", self.width, self.height);
w.write_all(header.as_bytes())?; w.write_all(header.as_bytes())?;
// Pixel data // Pixel data
for pixel in self.data.iter() { for pixel in self.data.iter() {
let Pixel(red, green, blue) = pixel; let Pixel(red, green, blue) = pixel;
let pixel = format!("{red} {green} {blue}\n"); let pixel = format!("{red} {green} {blue}\n");
w.write_all(pixel.as_bytes())?; w.write_all(pixel.as_bytes())?;
}
Ok(())
} }
Ok(())
}
} }

View file

@ -0,0 +1,51 @@
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/simple-pattern-examples.html
use rand::RngCore;
use crate::ppm::{Pixel, Ppm};
use crate::value_noise::ValueNoise;
use crate::vec2::Vec2;
pub fn generate_turbulence(
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.02;
let frequency_mult = 1.8;
let amplitude_mult = 0.35;
let num_layers = 5;
let mut max_noise_val = 0.0f64;
for j in 0..height {
for i in 0..width {
let mut noise_point = Vec2::new(i as f64, j as f64) * frequency;
let mut amplitude = 1.0;
for _ in 0..num_layers {
noise_map[j * width + i] =
(2.0 * noise.eval(noise_point) - 1.0).abs() * amplitude;
noise_point = noise_point * frequency_mult;
amplitude *= amplitude_mult;
}
max_noise_val = max_noise_val.max(noise_map[j * width + i]);
}
}
let data = noise_map
.into_iter()
.map(|f| {
let v = (f / max_noise_val * 192.0 + 32.0).floor() as u8;
Pixel(v, v, v)
})
.collect();
Ppm {
width,
height,
data,
}
}

View file

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

View file

@ -1,23 +1,24 @@
use std::ops::Mul; use std::ops::Mul;
#[derive(Copy, Clone)]
pub struct Vec2<T = f64> { pub struct Vec2<T = f64> {
pub x: T, pub x: T,
pub y: T, pub y: T,
} }
impl<T> Vec2<T> { impl<T> Vec2<T> {
pub fn new(x: T, y: T) -> Self { pub fn new(x: T, y: T) -> Self {
Self { x, y } Self { x, y }
} }
} }
impl<S: Copy, T: Mul<S>> Mul<S> for Vec2<T> { impl<S: Copy, T: Mul<S>> Mul<S> for Vec2<T> {
type Output = Vec2<T::Output>; type Output = Vec2<T::Output>;
fn mul(self, rhs: S) -> Self::Output { fn mul(self, rhs: S) -> Self::Output {
Vec2 { Vec2 {
x: self.x * rhs, x: self.x * rhs,
y: self.y * rhs, y: self.y * rhs,
}
} }
}
} }

2
rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
max_width = 80
tab_spaces = 2