commit 01ef5128cb055813bc648c8f36374f2a041c406e
parent 8f21b8640274351d594fd25ca9d1e47b295fa116
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Sat, 13 Dec 2025 15:27:51 -0500
Replace vec3! macro with Vec3 struct, add separate Color and Ray structs
Complete Chapter 4
Diffstat:
| A | src/color.rs | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/main.rs | | | 61 | +++++++++++++++++++++++++++++++++++++++++-------------------- |
| A | src/ray.rs | | | 27 | +++++++++++++++++++++++++++ |
| M | src/vec3.rs | | | 269 | +++++++++++++++++++++++++++++++++++-------------------------------------------- |
4 files changed, 253 insertions(+), 169 deletions(-)
diff --git a/src/color.rs b/src/color.rs
@@ -0,0 +1,65 @@
+use std::ops::{Add, Mul};
+
+#[derive(Clone, Copy, Debug, Default)]
+pub struct Color {
+ red: f64,
+ green: f64,
+ blue: f64,
+}
+
+impl Color {
+ const MIN: f64 = 0.;
+ const MAX: f64 = 1.;
+
+ pub const fn new(red: f64, green: f64, blue: f64) -> Self {
+ Color {
+ red: red.clamp(Color::MIN, Color::MAX),
+ green: green.clamp(Color::MIN, Color::MAX),
+ blue: blue.clamp(Color::MIN, Color::MAX),
+ }
+ }
+
+ pub const fn red(&self) -> f64 {
+ self.red
+ }
+
+ pub const fn green(&self) -> f64 {
+ self.green
+ }
+
+ pub const fn blue(&self) -> f64 {
+ self.blue
+ }
+}
+
+impl Add<Color> for Color {
+ type Output = Color;
+
+ fn add(self, rhs: Color) -> Self::Output {
+ Color {
+ red: (self.red + rhs.red).clamp(Color::MIN, Color::MAX),
+ green: (self.green + rhs.green).clamp(Color::MIN, Color::MAX),
+ blue: (self.blue + rhs.blue).clamp(Color::MIN, Color::MAX),
+ }
+ }
+}
+
+impl Mul<f64> for Color {
+ type Output = Color;
+
+ fn mul(self, rhs: f64) -> Self::Output {
+ Color {
+ red: (self.red * rhs).clamp(Color::MIN, Color::MAX),
+ green: (self.green * rhs).clamp(Color::MIN, Color::MAX),
+ blue: (self.blue * rhs).clamp(Color::MIN, Color::MAX),
+ }
+ }
+}
+
+impl Mul<Color> for f64 {
+ type Output = Color;
+
+ fn mul(self, rhs: Color) -> Self::Output {
+ rhs * self
+ }
+}
diff --git a/src/main.rs b/src/main.rs
@@ -1,14 +1,9 @@
-use crate::vec3::Vec3;
-use std::{
- fmt::{Debug, Display},
- ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub},
-};
+use crate::{color::Color, ray::Ray, vec3::Vec3};
+mod color;
+mod ray;
mod vec3;
-vec3!(Color, f64, red, green, blue);
-vec3!(Point3, f64, x, y, z);
-
struct Image {
data: Vec<Vec<Color>>,
}
@@ -31,12 +26,12 @@ impl Image {
output += "255\n";
for (row_idx, row_data) in self.data.iter().enumerate() {
eprintln!("Scanlines remaining: {}", height - row_idx);
- for Color { red, green, blue } in row_data.iter() {
+ for color in row_data.iter() {
output += &format!(
"{} {} {}\n",
- scale_f64_to_u8(*red),
- scale_f64_to_u8(*green),
- scale_f64_to_u8(*blue)
+ scale_f64_to_u8(color.red()),
+ scale_f64_to_u8(color.green()),
+ scale_f64_to_u8(color.blue())
);
}
}
@@ -45,15 +40,41 @@ impl Image {
}
}
+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 (width, height) = (256, 256);
- let mut image = Image::new(width, height);
- for i in 0..width {
- for j in 0..height {
- let red = i as f64 / (width - 1) as f64;
- let green = j as f64 / (height - 1) as f64;
- let blue = 0.;
- image.data[j][i] = Color { red, green, blue };
+ 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 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);
+ let color = {
+ let unit_direction = ray.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.)
+ };
+ image.data[j][i] = color;
}
}
println!("{}", image.to_ppm_string());
diff --git a/src/ray.rs b/src/ray.rs
@@ -0,0 +1,27 @@
+use crate::vec3::Vec3;
+
+pub struct Ray {
+ _origin: Vec3,
+ direction: Vec3,
+}
+
+impl Ray {
+ pub const fn new(origin: Vec3, direction: Vec3) -> Self {
+ Ray {
+ _origin: origin,
+ direction,
+ }
+ }
+
+ pub const fn _origin(&self) -> Vec3 {
+ self._origin
+ }
+
+ pub const fn direction(&self) -> Vec3 {
+ self.direction
+ }
+
+ pub fn _at(&self, t: f64) -> Vec3 {
+ self._origin + t * self.direction
+ }
+}
diff --git a/src/vec3.rs b/src/vec3.rs
@@ -3,179 +3,150 @@ use std::{
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub},
};
-#[allow(dead_code)]
-pub trait Vec3:
- Add
- + AddAssign
- + Clone
- + Copy
- + Debug
- + Default
- + Display
- + DivAssign<f64>
- + Div<f64>
- + MulAssign<f64>
- + Mul<f64>
- + Neg
- + Sub
-{
+#[derive(Copy, Clone, Debug, Default)]
+pub struct Vec3 {
+ x: f64,
+ y: f64,
+ z: f64,
}
-#[macro_export]
-macro_rules! vec3 {
- ( $name:ident, $type:ty, $dim0:ident, $dim1:ident, $dim2:ident ) => {
- #[derive(Copy, Clone, Debug, Default)]
- struct $name {
- $dim0: $type,
- $dim1: $type,
- $dim2: $type,
+#[allow(dead_code)]
+impl Vec3 {
+ pub const fn new(x: f64, y: f64, z: f64) -> Self {
+ Vec3 { x, y, z }
+ }
+
+ pub const fn x(&self) -> f64 {
+ self.x
+ }
+
+ pub const fn y(&self) -> f64 {
+ self.y
+ }
+
+ pub const fn z(&self) -> f64 {
+ self.z
+ }
+
+ pub const 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,
}
+ }
- #[allow(dead_code)]
- impl $name {
- pub fn new($dim0: $type, $dim1: $type, $dim2: $type) -> Self {
- $name {
- $dim0,
- $dim1,
- $dim2,
- }
- }
-
- pub fn $dim0(&self) -> $type {
- self.$dim0
- }
-
- pub fn $dim1(&self) -> $type {
- self.$dim1
- }
-
- pub fn $dim2(&self) -> $type {
- self.$dim2
- }
-
- pub fn cross(&self, other: &$name) -> $name {
- $name {
- $dim0: self.$dim1 * other.$dim2 - self.$dim2 * other.$dim1,
- $dim1: self.$dim2 * other.$dim0 - self.$dim0 * other.$dim2,
- $dim2: self.$dim0 * other.$dim1 - self.$dim1 * other.$dim0,
- }
- }
-
- pub fn dot(&self, other: &$name) -> $type {
- self.$dim0 * other.$dim0 + self.$dim1 * other.$dim1 + self.$dim2 * other.$dim2
- }
-
- pub fn length(&self) -> $type {
- self.length_squared().sqrt()
- }
-
- pub fn length_squared(&self) -> $type {
- self.$dim0.powi(2) + self.$dim1.powi(2) + self.$dim2.powi(2)
- }
-
- pub fn unit(&self) -> $name {
- *self / self.length()
- }
- }
+ pub const fn dot(&self, other: &Vec3) -> f64 {
+ self.x * other.x + self.y * other.y + self.z * other.z
+ }
- impl Vec3 for $name {}
+ pub fn length(&self) -> f64 {
+ self.length_squared().sqrt()
+ }
- impl Add for $name {
- type Output = Self;
+ pub fn length_squared(&self) -> f64 {
+ self.x.powi(2) + self.y.powi(2) + self.z.powi(2)
+ }
- fn add(self, rhs: Self) -> Self::Output {
- $name {
- $dim0: self.$dim0 + rhs.$dim0,
- $dim1: self.$dim1 + rhs.$dim1,
- $dim2: self.$dim2 + rhs.$dim2,
- }
- }
- }
+ pub fn unit(&self) -> Vec3 {
+ *self / self.length()
+ }
+}
- impl AddAssign for $name {
- fn add_assign(&mut self, rhs: Self) {
- self.$dim0 += rhs.$dim0;
- self.$dim1 += rhs.$dim1;
- self.$dim2 += rhs.$dim2;
- }
- }
+impl Add for Vec3 {
+ type Output = Self;
- impl Display for $name {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{} {} {}", self.$dim0, self.$dim1, self.$dim2)
- }
+ fn add(self, rhs: Self) -> Self::Output {
+ Vec3 {
+ x: self.x + rhs.x,
+ y: self.y + rhs.y,
+ z: self.z + rhs.z,
}
+ }
+}
- impl DivAssign<$type> for $name {
- fn div_assign(&mut self, rhs: $type) {
- self.$dim0 /= rhs;
- self.$dim1 /= rhs;
- self.$dim2 /= rhs;
- }
- }
+impl AddAssign for Vec3 {
+ fn add_assign(&mut self, rhs: Self) {
+ self.x += rhs.x;
+ self.y += rhs.y;
+ self.z += rhs.z;
+ }
+}
+
+impl Display for Vec3 {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{} {} {}", self.x, self.y, self.z)
+ }
+}
- impl Div<$type> for $name {
- type Output = $name;
+impl DivAssign<f64> for Vec3 {
+ fn div_assign(&mut self, rhs: f64) {
+ self.x /= rhs;
+ self.y /= rhs;
+ self.z /= rhs;
+ }
+}
- fn div(self, rhs: $type) -> Self::Output {
- $name {
- $dim0: self.$dim0 / rhs,
- $dim1: self.$dim1 / rhs,
- $dim2: self.$dim2 / rhs,
- }
- }
- }
+impl Div<f64> for Vec3 {
+ type Output = Vec3;
- impl MulAssign<$type> for $name {
- fn mul_assign(&mut self, rhs: $type) {
- self.$dim0 *= rhs;
- self.$dim1 *= rhs;
- self.$dim2 *= rhs;
- }
+ fn div(self, rhs: f64) -> Self::Output {
+ Vec3 {
+ x: self.x / rhs,
+ y: self.y / rhs,
+ z: self.z / rhs,
}
+ }
+}
+
+impl MulAssign<f64> for Vec3 {
+ fn mul_assign(&mut self, rhs: f64) {
+ self.x *= rhs;
+ self.y *= rhs;
+ self.z *= rhs;
+ }
+}
- impl Mul<$type> for $name {
- type Output = $name;
+impl Mul<f64> for Vec3 {
+ type Output = Vec3;
- fn mul(self, rhs: $type) -> Self::Output {
- $name {
- $dim0: self.$dim0 * rhs,
- $dim1: self.$dim1 * rhs,
- $dim2: self.$dim2 * rhs,
- }
- }
+ fn mul(self, rhs: f64) -> Self::Output {
+ Vec3 {
+ x: self.x * rhs,
+ y: self.y * rhs,
+ z: self.z * rhs,
}
+ }
+}
- impl Mul<$name> for $type {
- type Output = $name;
+impl Mul<Vec3> for f64 {
+ type Output = Vec3;
- fn mul(self, rhs: $name) -> Self::Output {
- rhs * self
- }
- }
+ fn mul(self, rhs: Vec3) -> Self::Output {
+ rhs * self
+ }
+}
- impl Neg for $name {
- type Output = Self;
+impl Neg for Vec3 {
+ type Output = Self;
- fn neg(self) -> Self::Output {
- $name {
- $dim0: -self.$dim0,
- $dim1: -self.$dim1,
- $dim2: -self.$dim2,
- }
- }
+ fn neg(self) -> Self::Output {
+ Vec3 {
+ x: -self.x,
+ y: -self.y,
+ z: -self.z,
}
+ }
+}
- impl Sub for $name {
- type Output = $name;
+impl Sub for Vec3 {
+ type Output = Vec3;
- fn sub(self, rhs: Self) -> Self::Output {
- $name {
- $dim0: self.$dim0 - rhs.$dim0,
- $dim1: self.$dim1 - rhs.$dim1,
- $dim2: self.$dim2 - rhs.$dim2,
- }
- }
+ fn sub(self, rhs: Self) -> Self::Output {
+ Vec3 {
+ x: self.x - rhs.x,
+ y: self.y - rhs.y,
+ z: self.z - rhs.z,
}
- };
+ }
}