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:
| A | src/camera.rs | | | 106 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/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);
}