diff --git a/src/main.rs b/src/main.rs index c99377c..13f4c78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use shader::{get_frame, View}; mod linear_alg; mod shader; -fn main() { +fn main() -> std::io::Result<()> { let height = 1.2; let camera = Vector { x: 0.0, @@ -50,13 +50,18 @@ fn main() { let mut theta_x = 0.0; let mut next_frame = Instant::now(); + let mut frame = shader::Frame::with_capacity(120, 120); + + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + loop { thread::sleep_until(next_frame); next_frame += Duration::from_secs_f32(1.0 / 30.0); let [camera, top_left, top_right, bottom_left, bottom_right, light] = coords.map(|c| rotate_y(rotate_z(c, theta_z), theta_x)); - let frame = get_frame( + get_frame( 0.8, 1.5, View { @@ -65,27 +70,19 @@ fn main() { top_right, bottom_left, bottom_right, - width: 190, - height: 90, + view: &mut frame, }, light, ); - // let grey_scale = - // r##".'`^",:;Il!i><~+_-?][}{1)(|\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"##.as_bytes(); - let grey_scale = ".......::::::-----====+++**#%@".as_bytes(); - for line in frame { - for char in line { - let char = char as usize; - if char > 0 { - let i = char * grey_scale.len() / (u8::MAX as usize + 1); - print!("{}", grey_scale[i] as char); - } else { - print!(" "); - } + use std::io::Write; + for line in frame.into_iter() { + for char in line.chars() { + write!(stdout, "{char}")?; } - println!(); + writeln!(stdout)?; } + stdout.flush()?; theta_x += theta_x_frame; theta_z += theta_z_frame; diff --git a/src/shader.rs b/src/shader.rs index 197f269..ae53388 100644 --- a/src/shader.rs +++ b/src/shader.rs @@ -2,14 +2,146 @@ use roots::find_roots_quartic; use crate::linear_alg::Vector; -pub struct View { +pub struct View<'a> { pub camera: Vector, pub top_left: Vector, pub top_right: Vector, pub bottom_left: Vector, pub bottom_right: Vector, - pub width: usize, - pub height: usize, + pub view: &'a mut Frame, +} + +pub struct Frame { + height: usize, + width: usize, + inner: Vec, +} + +pub struct FrameIter<'a> { + iter: &'a Frame, + row: usize, +} + +impl<'a> Iterator for FrameIter<'a> { + type Item = FrameRow<'a>; + fn next(&mut self) -> Option { + if self.row >= self.iter.height { + return None; + } + + let out = FrameRow { + bytes: self.iter, + row: self.row, + idx: 0, + }; + self.row += 1; + + Some(out) + } +} + +impl<'a> IntoIterator for &'a Frame { + type IntoIter = FrameIter<'a>; + type Item = FrameRow<'a>; + fn into_iter(self) -> Self::IntoIter { + FrameIter { iter: self, row: 0 } + } +} + +pub struct FrameRow<'a> { + bytes: &'a Frame, + row: usize, + idx: usize, +} + +impl<'a> FrameRow<'a> { + pub fn as_bytes(&self) -> &[u8] { + let start = self.row * self.bytes.height; + let end = start + self.bytes.width; + &self.bytes.inner[start..end] + } + + pub fn row(&self) -> usize { + self.row + } + + pub fn chars(self) -> FrameChars<'a> { + let Self { bytes, row, idx } = self; + FrameChars { bytes, row, idx } + } +} + +pub struct FrameChars<'a> { + bytes: &'a Frame, + row: usize, + idx: usize, +} + +// let grey_scale = +// r##".'`^",:;Il!i><~+_-?][}{1)(|\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"##.as_bytes(); +const GREY_SCALE: &[u8] = b".......::::::-----====+++**#%@"; + +impl<'a> Iterator for FrameChars<'a> { + type Item = char; + fn next(&mut self) -> Option { + if self.idx >= self.bytes.width { + return None; + } + let start = self.row * self.bytes.width; + let idx = start + self.idx; + self.idx += 1; + + let char = self.bytes.inner[idx] as usize; + + let c = if char > 0 { + let i = char * GREY_SCALE.len() / (u8::MAX as usize + 1); + GREY_SCALE[i] as char + } else { + ' ' + }; + Some(c) + } +} + +impl<'a> Iterator for FrameRow<'a> { + type Item = u8; + + fn next(&mut self) -> Option { + if self.idx >= self.bytes.width { + return None; + } + let start = self.row * self.bytes.width; + let idx = start + self.idx; + self.idx += 1; + Some(self.bytes.inner[idx]) + } +} + +impl Frame { + pub fn with_capacity(width: usize, height: usize) -> Self { + let inner = Vec::with_capacity(width * height); + Self { + inner, + width, + height, + } + } + + pub fn fill_char(&mut self, i: usize, j: usize, char: u8) { + let pos = j * self.width + i; + assert!(pos <= self.inner.len()); + + if pos == self.inner.len() { + self.push(char) + } else { + let a: &mut [u8] = self.inner.as_mut(); + a[pos] = char; + } + } + + pub fn push(&mut self, val: u8) { + self.inner.push(val) + } } pub fn get_frame( @@ -21,33 +153,35 @@ pub fn get_frame( top_right, bottom_left, bottom_right, - width, - height, + view, }: View, light: Vector, -) -> Vec> { - let mut frame = Vec::with_capacity(height); +) { + let height = view.height; + let width = view.width; + for i in 0..height { - let mut row = Vec::with_capacity(width); for j in 0..width { let bottom = i as f64 / (height as f64 - 1.0); let top = 1.0 - bottom; let right = j as f64 / (width as f64 - 1.0); let left = 1.0 - right; - row.push(get_pixel( - inner_radius, - outer_radius, - camera, - top_left * top * left - + top_right * top * right - + bottom_left * bottom * left - + bottom_right * bottom * right, - light, - )); + view.fill_char( + j, + i, + get_pixel( + inner_radius, + outer_radius, + camera, + top_left * top * left + + top_right * top * right + + bottom_left * bottom * left + + bottom_right * bottom * right, + light, + ), + ); } - frame.push(row); } - frame } /// The donut lay flat on the x-y plane centered around (0, 0, 0)