diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 82 | ||||
-rw-r--r-- | Cargo.toml | 10 | ||||
-rw-r--r-- | src/image.rs | 62 | ||||
-rw-r--r-- | src/main.rs | 139 | ||||
-rw-r--r-- | src/perlin.rs | 112 | ||||
-rw-r--r-- | src/vec3.rs | 260 |
7 files changed, 666 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..de0cfd8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,82 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "auto_ops" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7460f7dd8e100147b82a63afca1a20eb6c231ee36b90ba7272e14951cb58af59" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "perlin-test" +version = "0.1.0" +dependencies = [ + "auto_ops", + "rand", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c4e3dee --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "perlin-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8" +auto_ops = "0.3" diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..365596b --- /dev/null +++ b/src/image.rs @@ -0,0 +1,62 @@ +use std::io::Write; + +use crate::vec3::Color; + +pub struct Image { + width: usize, + height: usize, + data: Vec<Color>, +} + +impl Image { + pub fn new(width: usize, height: usize) -> Image { + let data = vec![ + Color { + x: 0.0, + y: 0.0, + z: 0.0 + }; + width * height + ]; + Image { + width, + height, + data, + } + } + + pub fn set(&mut self, x: usize, y: usize, color: &Color) -> Result<(), ()> { + *self.data.get_mut(y * self.width + x).ok_or(())? = color.clone(); + Ok(()) + } + + pub fn get(&self, x: usize, y: usize) -> Option<&Color> { + self.data.get(y * self.width + x) + } + + pub fn write(&self, output: &mut impl Write) { + output.write_fmt(format_args!("P3\n{} {}\n255\n", self.width, self.height)).unwrap(); + for y in (0..self.height).rev() { + for x in 0..self.width { + let pixel = self.get(x, y).unwrap(); + let mut r = pixel.x; + let mut g = pixel.y; + let mut b = pixel.z; + + // Divide by the number of samples and perform gamma correction for gamma 2 + r = r.sqrt(); + g = g.sqrt(); + b = b.sqrt(); + + output + .write_fmt(format_args!( + "{} {} {}\n", + (256.0 * r.clamp(0.0, 0.999)) as u32, + (256.0 * g.clamp(0.0, 0.999)) as u32, + (256.0 * b.clamp(0.0, 0.999)) as u32, + )) + .unwrap(); + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5acfbd1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,139 @@ +mod perlin; +mod vec3; +mod image; + +use std::{println, format}; + +use crate::perlin::Perlin; +use crate::vec3::{Vec3,Point3, Color}; +use crate::image::Image; + +fn hex_color(string: &str) -> Color { + Color { + x: i64::from_str_radix(&string[0..2], 16).unwrap() as f64 / 255.0, + y: i64::from_str_radix(&string[2..4], 16).unwrap() as f64 / 255.0, + z: i64::from_str_radix(&string[4..6], 16).unwrap() as f64 / 255.0, + } +} + +// maps [0.0, 1.0] -> Color[0.0, 1.0] +trait Palette { + fn sample(&self, index: f64) -> Color; +} + +struct IdenPalette {} + +impl Palette for IdenPalette { + fn sample(&self, index: f64) -> Color { + Color{x: index, y: index, z: index} + } +} + +struct FirePalette {} + +impl Palette for FirePalette { + fn sample(&self, index: f64) -> Color { + Color { + //x: index / 3.0, + x: 0.46 - (index / 3.0), + y: 1.0, + z: ((index + 0.03) * 2.0).min(1.0), + }.hsl_to_rgb() + } +} + +struct GradientPalette { + start: Color, + end: Color, +} + +impl Palette for GradientPalette { + fn sample(&self, index: f64) -> Color { + Color { + x: (index * self.start.x) + ((1.0 - index) * self.end.x), + y: (index * self.start.y) + ((1.0 - index) * self.end.y), + z: (index * self.start.z) + ((1.0 - index) * self.end.z), + } + } +} + +// fn palette_func_pink(index: f64) -> Color { +// Color { +// x: 1.0 - (index / 3.0), +// y: 1.0, +// z: (index * 2.0).min(1.0), +// }.hsl_to_rgb() +// } + + +// fn palette_func_quant(index: f64) -> Color { +// Color { +// x: (index * 8.0).round() / 8.0, +// y: 1.0, +// z: (index * 2.0).min(1.0), +// }.hsl_to_rgb() +// } + +fn sample(h: f64, l: f64) -> Color { + let start_hue = 0.75; + let end_hue = start_hue + 0.2; + let lightness_scale = 0.5; + Color { + x: (h * (end_hue - start_hue)) + start_hue, + y: 1.0, + z: (l * lightness_scale) + (lightness_scale / 2.0), + }.hsl_to_rgb() +} + +// fn main() { +// // params +// let height = 512; +// let width = 512; +// let scale = 1.0; +// let turb_depth = 20; +// let dsw_factor = 1.5; +// //let palette = GradientPalette{start: hex_color("0000ff"), end: hex_color("ff0000")}; +// //let palette = IdenPalette{}; +// let palette = FirePalette{}; + +// let hue_perlin = Perlin::new(); +// //let light_perlin = Perlin::new(); +// let mut image = Image::new(width, height); +// for x in 0..width { +// for y in 0..height { +// let hue_noise = hue_perlin.turb_dsw(&Point3{x: (x as f64 / width as f64) * scale, y: (y as f64 / height as f64) * scale, z: 0.0}, turb_depth, dsw_factor); +// //let light_noise = light_perlin.turb_dsw(&Point3{x: (x as f64 / width as f64) * scale, y: (y as f64 / height as f64) * scale, z: 0.0}, 7); +// //image.set(x, y, &sample(hue_noise, light_noise)).unwrap(); +// //image.set(x, y, &Color{x: hue_noise, y: hue_noise, z: hue_noise}).unwrap(); +// image.set(x, y, &palette.sample(hue_noise)).unwrap(); +// } +// } +// image.write(&mut std::io::stdout()); +// } + +fn main() { + // params + let height = 512; + let width = 512; + let frames = 600; + let scale = 1.0; + let turb_depth = 20; + let dsw_factor = 1.5; + let palette = FirePalette{}; + + let hue_perlin = Perlin::new(); + let mut image = Image::new(width, height); + for f in 0..frames { + for x in 0..width { + for y in 0..height { + let hue_noise = hue_perlin.turb_dsw(&Point3{x: (x as f64 / width as f64) * scale, y: (y as f64 / height as f64) * scale, z: (f as f64 / frames as f64) * scale}, turb_depth, dsw_factor); + image.set(x, y, &palette.sample(hue_noise)).unwrap(); + } + } + let path = format!("./anim/{}.ppm", f); + image.write(&mut std::fs::File::create(path).unwrap()); + + println!("Finished frame {} of {}!", f, frames); + } +} + diff --git a/src/perlin.rs b/src/perlin.rs new file mode 100644 index 0000000..393582b --- /dev/null +++ b/src/perlin.rs @@ -0,0 +1,112 @@ +use crate::vec3::{Vec3, Point3}; + +const POINT_COUNT: usize = 256; + +pub struct Perlin { + ranvec: Vec<Vec3>, + perm_x: Vec<usize>, + perm_y: Vec<usize>, + perm_z: Vec<usize>, +} + +impl Perlin { + pub fn new() -> Self { + let mut ranvec = Vec::with_capacity(POINT_COUNT); + for _ in 0..POINT_COUNT { + ranvec.push(Vec3::random_in_range(-1.0, 1.0).unit_vector()); + } + Self { + ranvec, + perm_x: Self::generate_perm(), + perm_y: Self::generate_perm(), + perm_z: Self::generate_perm(), + } + } + + pub fn turb(&self, point: &Point3, depth: u32) -> f64 { + let mut accum = 0.0; + let mut temp_point = point.clone(); + let mut weight = 1.0; + + for _ in 0..depth { + accum += weight * self.noise(&temp_point); + weight *= 0.5; + temp_point *= 2.0; + } + accum.abs() + } + + pub fn turb_dsw(&self, point: &Point3, depth: u32, factor: f64) -> f64 { + let mut accum = 0.0; + let mut temp_point = point.clone(); + let mut weight = 1.0; + + for _ in 0..depth { + accum += weight * self.noise(&temp_point); + temp_point *= 2.0; + temp_point += temp_point.xy_diff(&|p: &Vec3| -> f64 {weight * self.noise(p)}) * factor; + weight *= 0.5; + } + accum.abs() + } + + pub fn noise(&self, point: &Point3) -> f64 { + let u = point.x - point.x.floor(); + let v = point.y - point.y.floor(); + let w = point.z - point.z.floor(); + let i = point.x.floor() as i32; + let j = point.y.floor() as i32; + let k = point.z.floor() as i32; + let mut c: [[[Vec3; 2]; 2]; 2] = Default::default(); + for di in 0..2 { + for dj in 0..2 { + for dk in 0..2 { + c[di][dj][dk] = self + .ranvec + .get( + (self.perm_x.get(((i + di as i32) & 255) as usize).unwrap() + ^ self.perm_y.get(((j + dj as i32) & 255) as usize).unwrap() + ^ self.perm_z.get(((k + dk as i32) & 255) as usize).unwrap()) + as usize, + ) + .unwrap().clone(); + } + } + } + Self::trilinear_interpolate(c, u, v, w) + } + + fn trilinear_interpolate(c: [[[Vec3; 2]; 2]; 2], u: f64, v: f64, w: f64) -> f64 { + let uu = u * u * (3.0 - 2.0 * u); + let vv = v * v * (3.0 - 2.0 * v); + let ww = w * w * (3.0 - 2.0 * w); + let mut accum: f64 = 0.0; + for i in 0..2 { + for j in 0..2 { + for k in 0..2 { + let i_f = i as f64; + let j_f = j as f64; + let k_f = k as f64; + let weight_v = Vec3 { x: u - i_f, y: v - j_f, z: w - k_f }; + accum += (i_f * uu + (1.0 - i_f) * (1.0 - uu)) * + (j_f * vv + (1.0 - j_f) * (1.0 - vv)) * + (k_f * ww + (1.0 - k_f) * (1.0 - ww)) * + c[i][j][k].dot(&weight_v); + } + } + } + accum + } + + fn generate_perm() -> Vec<usize> { + let mut p = (0..POINT_COUNT).collect(); + Self::permute(&mut p, POINT_COUNT); + p + } + + fn permute(p: &mut Vec<usize>, n: usize) { + for i in (1..n).rev() { + p.swap(i, rand::random::<usize>() % i); + } + } +} diff --git a/src/vec3.rs b/src/vec3.rs new file mode 100644 index 0000000..1bf864d --- /dev/null +++ b/src/vec3.rs @@ -0,0 +1,260 @@ +use auto_ops::{impl_op_ex, impl_op_ex_commutative}; +use std::fmt; + +pub type Point3 = Vec3; +pub type Color = Vec3; + +#[derive(Clone, Default, Debug)] +pub struct Vec3 { + pub x: f64, + pub y: f64, + pub z: f64, +} + +impl Vec3 { + pub fn new() -> Vec3 { + Vec3 { + x: 0.0, + y: 0.0, + z: 0.0, + } + } + + pub fn get(&self, index: usize) -> Option<&f64> { + match index { + 0 => Some(&self.x), + 1 => Some(&self.y), + 2 => Some(&self.z), + _ => None, + } + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut f64> { + match index { + 0 => Some(&mut self.x), + 1 => Some(&mut self.y), + 2 => Some(&mut self.z), + _ => None, + } + } + + pub fn length(&self) -> f64 { + self.length_squared().sqrt() + } + + pub fn length_squared(&self) -> f64 { + self.x * self.x + self.y * self.y + self.z * self.z + } + + pub fn dot(&self, other: &Vec3) -> f64 { + self.x * other.x + self.y * other.y + self.z * other.z + } + + pub fn cross(&self, other: &Vec3) -> Vec3 { + Vec3 { + x: self.y * other.z - self.z * other.y, + y: self.z * other.x - self.x * other.z, + z: self.x * other.y - self.y * other.x, + } + } + + pub fn unit_vector(&self) -> Vec3 { + self / self.length() + } + + pub fn random() -> Vec3 { + Vec3 { + x: rand::random::<f64>(), + y: rand::random::<f64>(), + z: rand::random::<f64>(), + } + } + + pub fn random_in_range(min: f64, max: f64) -> Vec3 { + Vec3 { + x: min + (max - min) * rand::random::<f64>(), + y: min + (max - min) * rand::random::<f64>(), + z: min + (max - min) * rand::random::<f64>(), + } + } + + pub fn random_in_unit_sphere() -> Vec3 { + loop { + let p = Vec3 { + x: 2.0 * rand::random::<f64>() - 1.0, + y: 2.0 * rand::random::<f64>() - 1.0, + z: 2.0 * rand::random::<f64>() - 1.0, + }; + if p.length_squared() < 1.0 { + return p; + } + }; + } + + pub fn random_unit_vector() -> Vec3 { + Self::random_in_unit_sphere().unit_vector() + } + + pub fn random_in_unit_disk() -> Vec3 { + loop { + let p = Vec3 { + x: 2.0 * rand::random::<f64>() -1.0, + y: 2.0 * rand::random::<f64>() -1.0, + z: 0.0, + }; + if p.length_squared() < 1.0 { + return p; + } + } + } + + pub fn near_zero(&self) -> bool { + const S: f64 = 1e-8; + self.x.abs() < S && self.y.abs() < S && self.z.abs() < S + } + + pub fn reflect(&self, normal: &Vec3) -> Vec3 { + self - 2.0 * self.dot(normal) * normal + } + + pub fn refract(&self, normal: &Vec3, etai_over_etat: f64) -> Vec3 { + let cos_theta = normal.dot(&-self).min(1.0); + let r_out_perp = etai_over_etat * (self + cos_theta * normal); + let r_out_parallel = -((1.0 - r_out_perp.length_squared()).abs().sqrt()) * normal; + r_out_perp + r_out_parallel + } + + pub fn has_infinite_member(&self) -> bool { + self.x.is_infinite() || self.y.is_infinite() || self.z.is_infinite() + } + + // fn hue_to_rgb(&self) -> f64 { + // let mut z = self.z; + // if z < 0.0 { z += 1.0 } + // if z > 1.0 { z -= 1.0 } + // if z < 1.0/6.0 { + // self.x + (self.y - self.x) * 6.0 * z + // } else if z < 1.0/2.0 { + // self.y + // } else if z < 2.0/3.0 { + // self.x + (self.y - self.x) * (2.0/3.0 - z) * 6.0 + // } else { + // self.x + // } + // } + + fn hue_to_rgb(p: f64, q: f64, t:f64) -> f64 { + let mut t = t; + if t < 0.0 { t += 1.0 } + if t > 1.0 { t -= 1.0 } + if t < 1.0/6.0 { + p + (q - p) * 6.0 * t + } else if t < 1.0/2.0 { + q + } else if t < 2.0/3.0 { + p + (q - p) * (2.0/3.0 - t) * 6.0 + } else { + p + } + } + + pub fn hsl_to_rgb(&self) -> Vec3 { + if self.y == 0.0 { + Vec3 {x: self.z, y: self.z, z: self.z} + } else { + let q = if self.z < 0.5 { + self.z * (1.0 + self.y) + } else { + self.z + self.y - self.z * self.y + }; + let p = 2.0 * self.z - q; + Vec3{x: Self::hue_to_rgb(p, q, self.x + 1.0/3.0), y: Self::hue_to_rgb(p, q, self.x), z: Self::hue_to_rgb(p, q, self.x - 1.0/3.0)} + } + } + + pub fn xy_diff(&self, func: &dyn Fn(&Vec3) -> f64) -> Vec3 { + let h = f64::EPSILON.powf(1.0 / 3.0); + let here = func(self); + let x_there = func(&(self + Vec3{x: h, y: 0.0, z: 0.0 })); + let y_there = func(&(self + Vec3{x: 0.0, y: h, z: 0.0 })); + let x_diff = (x_there - here) / h; + let y_diff = (y_there - here) / h; + Vec3 { + x: x_diff, + y: y_diff, + z: 0.0, + } + } + +} + +impl fmt::Display for Vec3 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {} {}", self.x, self.y, self.z) + } +} + +impl<'a> IntoIterator for &'a Vec3 { + type Item = f64; + type IntoIter = Vec3Iterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + Vec3Iterator { + vec3: self, + index: 0, + } + } +} + +pub struct Vec3Iterator<'a> { + vec3: &'a Vec3, + index: usize, +} + +impl<'a> Iterator for Vec3Iterator<'a> { + type Item = f64; + fn next(&mut self) -> Option<f64> { + let result = match self.index { + 0 => self.vec3.x, + 1 => self.vec3.y, + 2 => self.vec3.z, + _ => return None, + }; + self.index += 1; + Some(result) + } +} + +impl_op_ex!(- |a: &Vec3| -> Vec3 { + Vec3 { + x: -a.x, + y: -a.y, + z: -a.z, + } +}); +impl_op_ex!(+= |lhs: &mut Vec3, rhs: Vec3| { *lhs = Vec3 { x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z } }); +impl_op_ex!(*= |lhs: &mut Vec3, rhs: &f64| { *lhs = Vec3 { x: lhs.x * rhs, y: lhs.y * rhs, z: lhs.z * rhs } }); +impl_op_ex!(/= |lhs: &mut Vec3, rhs: &f64| { *lhs *= 1.0 / rhs }); +impl_op_ex!(+ |lhs: &Vec3, rhs: &Vec3| -> Vec3 { Vec3 { x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z } }); +impl_op_ex!(-|lhs: &Vec3, rhs: &Vec3| -> Vec3 { + Vec3 { + x: lhs.x - rhs.x, + y: lhs.y - rhs.y, + z: lhs.z - rhs.z, + } +}); +impl_op_ex!(*|lhs: &Vec3, rhs: &Vec3| -> Vec3 { + Vec3 { + x: lhs.x * rhs.x, + y: lhs.y * rhs.y, + z: lhs.z * rhs.z, + } +}); +impl_op_ex_commutative!(*|lhs: &Vec3, rhs: &f64| -> Vec3 { + Vec3 { + x: lhs.x * rhs, + y: lhs.y * rhs, + z: lhs.z * rhs, + } +}); +impl_op_ex!(/ |lhs: &Vec3, rhs: &f64| -> Vec3 { lhs * (1.0/rhs) }); |