commit 67fc7ea21de728b3937c194ee41d0fd6341c19ff Author: k Date: Wed Mar 25 19:29:35 2026 -0400 Basic blue-to-white gradient diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6ff48c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +*.ppm diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dc3f4b2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "rust-ray" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2b8d2b2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rust-ray" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..5ecf5fc --- /dev/null +++ b/shell.nix @@ -0,0 +1,8 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + cargo + rust-analyzer + ]; +} diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..3000679 --- /dev/null +++ b/src/image.rs @@ -0,0 +1,67 @@ +use std::fs::File; +use std::io::{BufWriter, Write}; + +#[derive(Clone)] +pub struct Pixel { + red: u8, + green: u8, + blue: u8, +} + +impl Pixel { + pub fn set_color(&mut self, r: f64, g: f64, b: f64) { + self.red = (r * 255.999) as u8; + self.green = (g * 255.999) as u8; + self.blue = (b * 255.999) as u8; + } +} + +pub struct Image { + data: Vec, + pub width: u16, + pub height: u16, +} + +impl Image { + pub fn new(width: u16, height: u16) -> Self { + let total_pixels = (width as usize) * (height as usize); + let data = vec![ + Pixel { + red: 0, + green: 0, + blue: 0 + }; + total_pixels + ]; + Image { + data, + width, + height, + } + } + + pub fn save(&self, path: &str) -> std::io::Result<()> { + let file = File::create(path)?; + let mut writer = BufWriter::new(file); + + writeln!(writer, "P3")?; + writeln!(writer, "{} {}", self.width, self.height)?; + writeln!(writer, "255")?; + + self.data.iter().try_for_each(|pixel| { + writeln!(writer, "{} {} {}", pixel.red, pixel.green, pixel.blue) + })?; + + writer.flush()?; + Ok(()) + } + + pub fn get_pixel(&mut self, x: u16, y: u16) -> Option<&mut Pixel> { + if x >= self.width || y >= self.height { + return None; + } + + let index = (y as usize * self.width as usize) + x as usize; + Some(&mut self.data[index]) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0e54876 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,48 @@ +mod image; +mod vec3; +use image::Image; +use vec3::{Point3, Ray, Vec3}; + +fn main() { + let aspect_ratio = 16.0 / 9.0; + let image_width = 800; + let image_height = (image_width as f64 / aspect_ratio) as i32; + + let focal_length = 1.0; + let viewport_height = 2.0; + let viewport_width = (viewport_height * (image_width / image_height) as f64) as i32; + let camera_center = Point3::new(0.0, 0.0, 0.0); + + let viewport_u = Vec3::new(viewport_width as f64, 0.0, 0.0); + let viewport_v = Vec3::new(0.0, -viewport_height as f64, 0.0); + + let pixel_delta_u = viewport_u / image_width as f64; + let pixel_delta_v = viewport_v / image_height as f64; + + let viewport_upper_left = camera_center + - (Vec3::new(0.0, 0.0, focal_length) - viewport_u / 2.0 - viewport_v / 2.0).to_point(); + let pixel00_loc = viewport_upper_left + ((pixel_delta_u + pixel_delta_v) * 0.5).to_point(); + + let mut img = Image::new(800, 600); + for y in 0..img.height { + for x in 0..img.width { + if let Some(px) = img.get_pixel(x, y) { + let pixel_center = pixel00_loc + + ((pixel_delta_u * x as f64) + (pixel_delta_v * y as f64)).to_point(); + let ray_direction = (pixel_center - camera_center).to_vec(); + let ray = Ray::new(camera_center, ray_direction); + let (r, g, b) = color_ray(ray); + px.set_color(r, g, b); + } else { + } + } + } + img.save("./foo.ppm").unwrap(); +} + +fn color_ray(ray: Ray) -> (f64, f64, f64) { + let unit_direction = ray.direction().unit(); + let a = 0.5 * unit_direction.y() + 1.0; + let b = 1.0 - a; + (b + 0.5 * a, b + 0.7 * a, b + 1.0 * a) +} diff --git a/src/vec3.rs b/src/vec3.rs new file mode 100644 index 0000000..0d166d5 --- /dev/null +++ b/src/vec3.rs @@ -0,0 +1,137 @@ +use std::ops::{Add, Div, Mul, Sub}; + +#[derive(Clone, Copy)] +pub struct Vec3 { + x: f64, + y: f64, + z: f64, +} + +#[derive(Clone, Copy)] +pub struct Point3 { + x: f64, + y: f64, + z: f64, +} + +#[derive(Clone, Copy)] +pub struct Ray { + direction: Vec3, + origin: Point3, +} + +impl Ray { + pub fn new(origin: Point3, direction: Vec3) -> Ray { + Ray { direction, origin } + } + pub fn at(self, t: f64) -> Point3 { + (self.origin.to_vec() + Vec3 { x: t, y: t, z: t } * self.direction).to_point() + } + pub fn direction(self) -> Vec3 { + self.direction + } + pub fn origin(self) -> Point3 { + self.origin + } +} + +macro_rules! impl_op { + ($trait:ident, $func:ident, $op:tt, $who:ident) => { + impl $trait for $who { + type Output = Self; + fn $func(self, other: Self) -> Self { + Self { + x: self.x $op other.x, + y: self.y $op other.y, + z: self.z $op other.z, + } + } + } + + impl $trait for $who { + type Output = Self; + fn $func(self, value: f64) -> Self { + Self { + x: self.x $op value, + y: self.y $op value, + z: self.z $op value, + } + } + } + }; +} + +impl_op!(Add, add, +,Vec3); +impl_op!(Sub, sub, -,Vec3); +impl_op!(Mul, mul, *,Vec3); +impl_op!(Div, div, /,Vec3); + +impl_op!(Add, add, +,Point3); +impl_op!(Sub, sub, -,Point3); +impl_op!(Mul, mul, *,Point3); +impl_op!(Div, div, /,Point3); + +impl Point3 { + pub fn new(x: f64, y: f64, z: f64) -> Point3 { + Point3 { x, y, z } + } + pub fn to_vec(self) -> Vec3 { + Vec3 { + x: self.x, + y: self.y, + z: self.z, + } + } +} + +impl Vec3 { + pub fn new(x: f64, y: f64, z: f64) -> Vec3 { + Vec3 { x, y, z } + } + + pub fn full(v: f64) -> Vec3 { + Vec3 { x: v, y: v, z: v } + } + + pub fn to_point(self) -> Point3 { + Point3 { + x: self.x, + y: self.y, + z: self.z, + } + } + pub fn dot(self, other: Vec3) -> f64 { + self.x * other.x + self.y * other.y + self.z * other.z + } + + pub fn x(self) -> f64 { + self.x + } + + pub fn y(self) -> f64 { + self.y + } + + pub fn z(self) -> f64 { + self.z + } + + pub fn cross(self, other: Vec3) -> Vec3 { + let x = self.y * other.z - self.z * other.y; + let y = self.y * other.x - self.x * other.y; + let z = self.x * other.y - self.y * other.x; + Vec3 { x, y, z } + } + + pub fn length_squared(self) -> f64 { + self.x * self.x + self.y * self.y + self.z * self.z + } + + pub fn length(self) -> f64 { + self.length_squared().sqrt() + } + + pub fn unit(self) -> Vec3 { + self / self.length() + } +}