stocking

git clone https://git.bradleygannon.com/stocking.git
Log | Files | Refs

commit 8eac42fedebf335e5ff23c47ea75ab69d0c4475c
parent 2613c5262757b718b325f79f7fb2b6734db4fe83
Author: Bradley Gannon <bradley@bradleygannon.com>
Date:   Sun, 14 Dec 2025 22:14:27 -0500

Add Camera struct and move almost everything from main into it

Complete Chapter 7

Diffstat:
Asrc/camera.rs | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 92++++---------------------------------------------------------------------------
2 files changed, 110 insertions(+), 88 deletions(-)

diff --git a/src/camera.rs b/src/camera.rs @@ -0,0 +1,106 @@ +use crate::{color::Color, hittable::Hittable, interval::Interval, ray::Ray, vec3::Vec3}; + +struct Image { + data: Vec<Vec<Color>>, +} + +fn scale_f64_to_u8(value: f64) -> u8 { + (value * 255.) as u8 +} + +impl Image { + fn new(width: usize, height: usize) -> Self { + Image { + data: vec![vec![Color::default(); width]; height], + } + } + fn to_ppm_string(&self) -> String { + let mut output = "P3\n".to_string(); + let height = self.data.len(); + let width = if height > 0 { self.data[0].len() } else { 0 }; + output += &format!("{width} {height}\n"); + output += "255\n"; + for (row_idx, row_data) in self.data.iter().enumerate() { + eprintln!("Scanlines remaining: {}", height - row_idx); + for color in row_data.iter() { + output += &format!( + "{} {} {}\n", + scale_f64_to_u8(color.red()), + scale_f64_to_u8(color.green()), + scale_f64_to_u8(color.blue()) + ); + } + } + eprintln!("Done"); + output + } +} + +pub struct Camera { + image_width: usize, + image_height: usize, + pixel_delta_u: Vec3, + pixel_delta_v: Vec3, + pixel_origin: Vec3, +} + +impl Camera { + const FOCAL_LENGTH: f64 = 1.; + const CAMERA_CENTER: Vec3 = Vec3::new(0., 0., 0.); + + pub fn new(image_width: usize, aspect_ratio: f64) -> Self { + let image_height = ((image_width as f64 / aspect_ratio) as usize).max(1); + + let viewport_height = 2.; + let viewport_width = viewport_height * aspect_ratio; + let viewport_u = Vec3::new(viewport_width, 0., 0.); + let viewport_v = Vec3::new(0., -viewport_height, 0.); + let viewport_upper_left = Self::CAMERA_CENTER + - Vec3::new(0., 0., Self::FOCAL_LENGTH) + - viewport_u / 2. + - viewport_v / 2.; + + let pixel_delta_u = viewport_u / image_width as f64; + let pixel_delta_v = viewport_v / image_height as f64; + let pixel_origin = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v); + + Camera { + image_width, + image_height, + pixel_delta_u, + pixel_delta_v, + pixel_origin, + } + } + + pub fn render(&self, world: &[Box<dyn Hittable>]) { + let mut image = Image::new(self.image_width, self.image_height); + for i in 0..self.image_width { + for j in 0..self.image_height { + let pixel_center = self.pixel_origin + + (i as f64 * self.pixel_delta_u) + + (j as f64 * self.pixel_delta_v); + let ray_direction = pixel_center - Self::CAMERA_CENTER; + let ray = Ray::new(Self::CAMERA_CENTER, ray_direction); + + image.data[j][i] = self.ray_color(&ray, world); + } + } + println!("{}", image.to_ppm_string()); + } + + fn ray_color(&self, r: &Ray, world: &[Box<dyn Hittable>]) -> Color { + if let Some(hit) = world.hit(r, Interval::new(0., f64::INFINITY)) { + let normal = hit.normal(); + Color::new( + normal.x() * 0.5 + 0.5, + normal.y() * 0.5 + 0.5, + normal.z() * 0.5 + 0.5, + ) + } else { + let unit_direction = r.direction().unit(); + let a = 0.5 * (unit_direction.y() + 1.); + (1. - a) * Color::new(1., 1., 1.) + a * Color::new(0.5, 0.7, 1.) + } + } +} diff --git a/src/main.rs b/src/main.rs @@ -1,105 +1,21 @@ use crate::{ - color::Color, + camera::Camera, hittable::{Hittable, Sphere}, - interval::Interval, - ray::Ray, vec3::Vec3, }; +mod camera; mod color; mod hittable; mod interval; mod ray; mod vec3; -struct Image { - data: Vec<Vec<Color>>, -} - -fn scale_f64_to_u8(value: f64) -> u8 { - (value * 255.) as u8 -} - -impl Image { - fn new(width: usize, height: usize) -> Self { - Image { - data: vec![vec![Color::default(); width]; height], - } - } - fn to_ppm_string(&self) -> String { - let mut output = "P3\n".to_string(); - let height = self.data.len(); - let width = if height > 0 { self.data[0].len() } else { 0 }; - output += &format!("{width} {height}\n"); - output += "255\n"; - for (row_idx, row_data) in self.data.iter().enumerate() { - eprintln!("Scanlines remaining: {}", height - row_idx); - for color in row_data.iter() { - output += &format!( - "{} {} {}\n", - scale_f64_to_u8(color.red()), - scale_f64_to_u8(color.green()), - scale_f64_to_u8(color.blue()) - ); - } - } - eprintln!("Done"); - output - } -} - -fn ray_color(r: &Ray, world: &[Box<dyn Hittable>]) -> Color { - if let Some(hit) = world.hit(r, Interval::new(0., f64::INFINITY)) { - let normal = hit.normal(); - Color::new( - normal.x() * 0.5 + 0.5, - normal.y() * 0.5 + 0.5, - normal.z() * 0.5 + 0.5, - ) - } else { - let unit_direction = r.direction().unit(); - let a = 0.5 * (unit_direction.y() + 1.); - (1. - a) * Color::new(1., 1., 1.) + a * Color::new(0.5, 0.7, 1.) - } -} - -const ASPECT_RATIO: f64 = 16. / 9.; -const IMAGE_WIDTH: usize = 400; -const IMAGE_HEIGHT: usize = { - let height = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as usize; - if height < 1 { 1 } else { height } -}; - -const VIEWPORT_HEIGHT: f64 = 2.; -const VIEWPORT_WIDTH: f64 = VIEWPORT_HEIGHT * (IMAGE_WIDTH as f64 / IMAGE_HEIGHT as f64); -const VIEWPORT_U: Vec3 = Vec3::new(VIEWPORT_WIDTH, 0., 0.); -const VIEWPORT_V: Vec3 = Vec3::new(0., -VIEWPORT_HEIGHT, 0.); - -const FOCAL_LENGTH: f64 = 1.; -const CAMERA_CENTER: Vec3 = Vec3::new(0., 0., 0.); - fn main() { - let pixel_delta_u: Vec3 = VIEWPORT_U / IMAGE_WIDTH as f64; - let pixel_delta_v: Vec3 = VIEWPORT_V / IMAGE_HEIGHT as f64; - let viewport_upper_left: Vec3 = - CAMERA_CENTER - Vec3::new(0., 0., FOCAL_LENGTH) - VIEWPORT_U / 2. - VIEWPORT_V / 2.; - let pixel_origin: Vec3 = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v); - let world: [Box<dyn Hittable>; 2] = [ Box::new(Sphere::new(Vec3::new(0., 0., -1.), 0.5)), Box::new(Sphere::new(Vec3::new(0., -100.5, -1.), 100.)), ]; - - let mut image = Image::new(IMAGE_WIDTH, IMAGE_HEIGHT); - for i in 0..IMAGE_WIDTH { - for j in 0..IMAGE_HEIGHT { - let pixel_center = - pixel_origin + (i as f64 * pixel_delta_u) + (j as f64 * pixel_delta_v); - let ray_direction = pixel_center - CAMERA_CENTER; - let ray = Ray::new(CAMERA_CENTER, ray_direction); - - image.data[j][i] = ray_color(&ray, &world); - } - } - println!("{}", image.to_ppm_string()); + let camera = Camera::new(400, 16. / 9.); + camera.render(&world); }