turbulence + tuning
This commit is contained in:
parent
caed160def
commit
8a68e44e3d
6 changed files with 160 additions and 90 deletions
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
51
assignment-0/src/turbulence.rs
Normal file
51
assignment-0/src/turbulence.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
max_width = 80
|
||||||
|
tab_spaces = 2
|
Loading…
Reference in a new issue