commit 4f5afe74f9d1a18f02b93604bbb546a8f6e65a59
parent d60d1c0ee69d379f622f276e9c2bbfd3e0c3d622
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Tue, 16 Dec 2025 13:27:30 -0500
Make Camera more configurable
Complete Chapter 12
Diffstat:
3 files changed, 79 insertions(+), 18 deletions(-)
diff --git a/src/camera.rs b/src/camera.rs
@@ -38,6 +38,7 @@ impl Image {
}
}
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Camera {
image_width: usize,
image_height: usize,
@@ -47,29 +48,66 @@ pub struct Camera {
samples_per_pixel: usize,
rng: RandomNumberGenerator,
max_bounces: usize,
+ center: Vec3,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
+pub struct CameraConfig {
+ pub image_width: usize,
+ pub aspect_ratio: f64,
+ pub samples_per_pixel: usize,
+ pub max_bounces: usize,
+ pub field_of_view_degrees: f64,
+ pub look_from: Vec3,
+ pub look_at: Vec3,
+ pub view_up: Vec3,
+}
+
+impl Default for CameraConfig {
+ fn default() -> Self {
+ CameraConfig {
+ image_width: 100,
+ aspect_ratio: 1.,
+ samples_per_pixel: 10,
+ max_bounces: 10,
+ field_of_view_degrees: 90.,
+ look_from: Vec3::new(0., 0., 0.),
+ look_at: Vec3::new(0., 0., -1.),
+ view_up: Vec3::new(0., 1., 0.),
+ }
+ }
}
impl Camera {
- const FOCAL_LENGTH: f64 = 1.;
- const CAMERA_CENTER: Vec3 = Vec3::new(0., 0., 0.);
const RNG_SEED: u64 = 42;
- pub fn new(
- image_width: usize,
- aspect_ratio: f64,
- samples_per_pixel: usize,
- max_bounces: usize,
- ) -> Self {
+ pub fn new(config: &CameraConfig) -> Self {
+ let CameraConfig {
+ image_width,
+ aspect_ratio,
+ samples_per_pixel,
+ max_bounces,
+ field_of_view_degrees,
+ look_from,
+ look_at,
+ view_up,
+ } = *config;
+
let image_height = ((image_width as f64 / aspect_ratio) as usize).max(1);
- let viewport_height = 2.;
+ let theta = field_of_view_degrees.to_radians();
+ let h = (theta / 2.).tan();
+ let focal_length = (look_from - look_at).length();
+ let viewport_height = 2. * h * focal_length;
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 w = (look_from - look_at).unit();
+ let u = view_up.cross(&w).unit();
+ let v = w.cross(&u);
+
+ let viewport_u = viewport_width * u;
+ let viewport_v = viewport_height * -v;
+ let viewport_upper_left = look_from - focal_length * w - 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;
@@ -86,6 +124,7 @@ impl Camera {
samples_per_pixel,
rng,
max_bounces,
+ center: look_from,
}
}
@@ -127,6 +166,6 @@ impl Camera {
let pixel_sample = self.pixel_origin
+ (i as f64 + offset.x()) * self.pixel_delta_u
+ (j as f64 + offset.y()) * self.pixel_delta_v;
- Ray::new(Self::CAMERA_CENTER, pixel_sample - Self::CAMERA_CENTER)
+ Ray::new(self.center, pixel_sample - self.center)
}
}
diff --git a/src/main.rs b/src/main.rs
@@ -1,7 +1,7 @@
use std::rc::Rc;
use crate::{
- camera::Camera,
+ camera::{Camera, CameraConfig},
color::LinearColor,
hittable::{Hittable, Sphere},
material::{Dielectric, Lambertian, Metal},
@@ -52,6 +52,16 @@ fn main() {
)),
];
- let mut camera = Camera::new(400, 16. / 9., 100, 50);
+ let config = CameraConfig {
+ aspect_ratio: 16. / 9.,
+ image_width: 400,
+ samples_per_pixel: 100,
+ max_bounces: 50,
+ field_of_view_degrees: 20.,
+ look_from: Vec3::new(-2., 2., 1.),
+ look_at: Vec3::new(0., 0., -1.),
+ view_up: Vec3::new(0., 1., 0.),
+ };
+ let mut camera = Camera::new(&config);
camera.render(&world);
}
diff --git a/src/vec3.rs b/src/vec3.rs
@@ -171,6 +171,18 @@ impl Mul<Vec3> for f64 {
}
}
+impl Mul<Vec3> for Vec3 {
+ type Output = Vec3;
+
+ fn mul(self, rhs: Vec3) -> Self::Output {
+ Vec3 {
+ x: self.x * rhs.x,
+ y: self.y * rhs.y,
+ z: self.z * rhs.z,
+ }
+ }
+}
+
impl Neg for Vec3 {
type Output = Self;