summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/image.rs62
-rw-r--r--src/main.rs139
-rw-r--r--src/perlin.rs112
-rw-r--r--src/vec3.rs260
4 files changed, 573 insertions, 0 deletions
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) });