Compare commits

..

8 commits
action ... main

Author SHA1 Message Date
k
b9ac41b453 Minor changes for speed up
All checks were successful
CI / test (push) Successful in 26s
Render Image / render (push) Successful in 28s
2026-04-28 19:32:16 -04:00
k
8df48d3824 Replace arc
All checks were successful
CI / test (push) Successful in 26s
Render Image / render (push) Successful in 28s
2026-04-28 16:21:18 -04:00
k
5857244ca8 better image
All checks were successful
CI / test (push) Successful in 27s
Render Image / render (push) Successful in 29s
2026-04-26 21:54:39 -04:00
k
63d4cbb042 remove unused dep 2026-04-26 21:54:19 -04:00
k
b73b1209e4 Movable camera
All checks were successful
CI / test (push) Successful in 29s
Render Image / render (push) Successful in 26s
2026-04-13 14:51:19 -04:00
k
33f3c5fab1 Merge branch 'action'
All checks were successful
CI / test (push) Successful in 27s
Render Image / render (push) Successful in 26s
2026-04-13 14:12:57 -04:00
k
8ec807c147 Working threading 2026-04-13 14:02:39 -04:00
k
993136ea9e Updated types to play nicer with threading 2026-04-13 11:19:45 -04:00
7 changed files with 92 additions and 113 deletions

52
Cargo.lock generated
View file

@ -40,37 +40,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -217,32 +186,11 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "rayon"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]] [[package]]
name = "rust-ray" name = "rust-ray"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"rand", "rand",
"rayon",
] ]
[[package]] [[package]]

View file

@ -5,4 +5,3 @@ edition = "2024"
[dependencies] [dependencies]
rand = "0.10.0" rand = "0.10.0"
rayon = "1.11.0"

View file

@ -11,33 +11,25 @@ use vec3::{Point3,Vec3};
use material::{Metal,Lambertian,Material}; use material::{Metal,Lambertian,Material};
fn main() { fn main() {
let aspect_ratio = 16.0 / 9.0; let aspect_ratio = 16.0 / 9.0;
let image_width = 800; let image_width = 1920;
let focal_length = 1.0; let focal_length = 1.0;
let viewport_height = 2.0; let viewport_height = 2.0;
let camera_center = Point3::new(0.0, 0.0, 0.0);
let m = Lambertian::new(Vec3::new(0.5,0.5,0.5)); let m = Lambertian::new(Vec3::new(0.5,0.5,0.5));
let b: Box <dyn Material> = Box::new(m); let world_m: Box <dyn Material> = Box::new(m);
let world_m = std::rc::Rc::new(b);
let m = Lambertian::new(Vec3::new(0.1, 0.2, 0.5)); let m = Lambertian::new(Vec3::new(0.1, 0.2, 0.5));
let b: Box <dyn Material> = Box::new(m); let center_m: Box <dyn Material> = Box::new(m);
let center_m = std::rc::Rc::new(b);
let m = Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.3); let m = Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.3);
let b: Box <dyn Material> = Box::new(m); let left_m: Box <dyn Material> = Box::new(m);
let left_m = std::rc::Rc::new(b);
let m = Metal::new(Vec3::new(0.8, 0.6, 0.2), 0.5); let m = Metal::new(Vec3::new(0.8, 0.6, 0.2), 0.5);
let b: Box <dyn Material> = Box::new(m); let right_m: Box <dyn Material> = Box::new(m);
let right_m = std::rc::Rc::new(b);
let mut camera = Camera::new(aspect_ratio,image_width,focal_length,viewport_height,camera_center); let mut camera = Camera::new(aspect_ratio,image_width,focal_length,Point3::new(-1.0,1.0,1.0),Point3::new(0.0,0.0,-1.0),90.0);
let mut world = World::new(); let mut world = World::new();
world.add(Sphere::new(0.5, Point3::new(0.0, 0.0, -1.0),center_m)); world.add(Sphere::new(0.5, Point3::new(0.0, 0.0, -1.0),center_m));
world.add(Sphere::new(0.5, Point3::new(-1.0, 0.0, -1.0),left_m)); world.add(Sphere::new(0.5, Point3::new(-1.0, 0.0, -1.0),left_m));

View file

@ -9,7 +9,7 @@ pub struct Scatter {
pub ray: Ray, pub ray: Ray,
} }
pub trait Material { pub trait Material: Send + Sync {
fn scatter(&self, r_in: &Ray, rec: &HitRecord, rng: &mut ThreadRng) -> Option<Scatter>; fn scatter(&self, r_in: &Ray, rec: &HitRecord, rng: &mut ThreadRng) -> Option<Scatter>;
} }

View file

@ -4,19 +4,19 @@ use crate::material::{Material,Scatter};
use rand::prelude::*; use rand::prelude::*;
pub trait Hittable { pub trait Hittable: Send + Sync{
fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord>; fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord<'_>>;
} }
pub struct HitRecord { pub struct HitRecord<'a> {
point: Point3, point: Point3,
normal: Vec3, normal: Vec3,
root: f64, root: f64,
front_face: bool, front_face: bool,
material: std::rc::Rc<Box<dyn Material>>, material: &'a dyn Material,
} }
impl HitRecord { impl HitRecord <'_> {
pub fn normal(&self) -> Vec3 { pub fn normal(&self) -> Vec3 {
self.normal self.normal
} }
@ -43,17 +43,17 @@ impl HitRecord {
pub struct Sphere { pub struct Sphere {
radius: f64, radius: f64,
center: Point3, center: Point3,
material: std::rc::Rc<Box<dyn Material>> material: Box<dyn Material>
} }
impl Sphere { impl Sphere {
pub fn new(radius: f64, center: Point3, material:std::rc::Rc<Box<dyn Material>>) -> Self { pub fn new(radius: f64, center: Point3, material: Box<dyn Material>) -> Self {
Sphere { radius, center , material} Sphere { radius, center , material}
} }
} }
impl Hittable for Sphere { impl Hittable for Sphere {
fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord> { fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord<'_>> {
let oc = self.center - ray.origin(); let oc = self.center - ray.origin();
let a = ray.direction().length_squared(); let a = ray.direction().length_squared();
let h = Vec3::dot(ray.direction(), oc.to_vec()); let h = Vec3::dot(ray.direction(), oc.to_vec());
@ -79,7 +79,7 @@ impl Hittable for Sphere {
point, point,
normal, normal,
front_face: false, front_face: false,
material: self.material.clone() material: self.material.as_ref(),
}; };
tmp.set_face_normal(ray, normal); tmp.set_face_normal(ray, normal);
Some(tmp) Some(tmp)

View file

@ -1,11 +1,13 @@
use crate::object::{HitRecord, Hittable}; use crate::object::{HitRecord, Hittable};
use crate::vec3::{Interval, Ray, Vec3, Point3}; use crate::vec3::{Interval, Ray, Vec3, Point3};
use crate::image::Image; use crate::image::Image;
use std::sync::Arc;
use std::thread;
use rand::prelude::*; use rand::prelude::*;
pub struct World { pub struct World {
pub objects: Vec<Box<dyn Hittable>>, pub objects: Vec<Arc<dyn Hittable>>,
} }
impl World { impl World {
@ -16,10 +18,10 @@ impl World {
} }
pub fn add(&mut self, object: impl Hittable + 'static) { pub fn add(&mut self, object: impl Hittable + 'static) {
self.objects.push(Box::new(object)); self.objects.push(Arc::new(object));
} }
pub fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord> { pub fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord<'_>> {
let mut hit_anything = None; let mut hit_anything = None;
let mut closest_so_far = interval.max(); let mut closest_so_far = interval.max();
@ -44,54 +46,88 @@ pub struct Camera {
} }
impl Camera { impl Camera {
pub fn new(aspect_ratio:f64,image_width: i32, focal_length:f64, viewport_height:f64, camera_center: Point3) -> Self{ pub fn new(aspect_ratio:f64,image_width: i32, focal_length:f64, look_from: Point3, look_at: Point3,fov:f64) -> Self{
let image_height = (image_width as f64 / aspect_ratio) as i32; let image_height = (image_width as f64 / aspect_ratio) as i32;
let theta = (fov as f64).to_radians();
let h = (theta/2.0).tan();
let viewport_height = 2.0 * h * focal_length;
let viewport_width = viewport_height * (image_width as f64 / image_height as f64); let viewport_width = viewport_height * (image_width as f64 / image_height as f64);
let viewport_u = Vec3::new(viewport_width as f64, 0.0, 0.0); let w = (look_from - look_at).to_vec().unit();
let viewport_v = Vec3::new(0.0, -viewport_height as f64, 0.0); let u = Vec3::new(0.0,1.0,0.0).cross(w).unit();
let v = w.cross(u);
let viewport_u = u * viewport_width;
let viewport_v = (v*-1.0)*viewport_height;
let pixel_delta_u = viewport_u / image_width as f64; let pixel_delta_u = viewport_u / image_width as f64;
let pixel_delta_v = viewport_v / image_height as f64; let pixel_delta_v = viewport_v / image_height as f64;
let viewport_upper_left = (camera_center.to_vec() let viewport_upper_left = look_from.to_vec() - (w * focal_length) - viewport_u/2.0 - viewport_v/2.0;
- 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 pixel00_loc = (viewport_upper_left + ((pixel_delta_u + pixel_delta_v) * 0.5)).to_point();
let img = Image::new(image_width, image_height); let img = Image::new(image_width, image_height);
Self{img,pixel00_loc,camera_center,pixel_delta_u,pixel_delta_v} Self{img,pixel00_loc,camera_center:look_from,pixel_delta_u,pixel_delta_v}
} }
pub fn render(&mut self, world: &World){ pub fn render(&mut self, world: &World){
let thread_count = thread::available_parallelism().unwrap().get() as i32;
let height = self.img.height;
let width = self.img.width;
let total_pixels = height*width;
let num_samples = 25;
//cast to const ref
let const_self = &*self;
let threads:Vec<Vec<((i32,i32),(f64,f64,f64))>> = thread::scope(|s|{
(0..thread_count).map(|i|{
s.spawn(move ||{
let mut rng:ThreadRng = rand::rng(); let mut rng:ThreadRng = rand::rng();
for y in 0..self.img.height { let start = (i*total_pixels)/thread_count;
for x in 0..self.img.width { let end = ((i+1)*total_pixels)/thread_count;
(start..end).map(|i|{
let x = i%width;
let y = i/width;
let (mut r,mut g,mut b) = (0.0,0.0,0.0); let (mut r,mut g,mut b) = (0.0,0.0,0.0);
let num_samples=10.0; for _ in 0..num_samples{
let depth=10; let ray = const_self.get_ray(x,y,&mut rng);
for _ in 0..num_samples as i32{ let (tr,tg,tb) = trace(ray, &world,10,&mut rng);
let ray = self.get_ray(x,y,&mut rng); r+=tr;
let (tr,tg,tb)= trace(ray, &world,depth,&mut rng); g+=tg;
r+= tr; b+=tb;
g+= tg;
b+= tb;
} }
r /= num_samples;
g /= num_samples; r/=num_samples as f64;
b /= num_samples; g/=num_samples as f64;
b/=num_samples as f64;
r = r.clamp(0.0,1.0).sqrt(); r = r.clamp(0.0,1.0).sqrt();
g = g.clamp(0.0,1.0).sqrt(); g = g.clamp(0.0,1.0).sqrt();
b = b.clamp(0.0,1.0).sqrt(); b = b.clamp(0.0,1.0).sqrt();
((x,y),(r,g,b))
}).collect::<Vec<_>>()
})
}).collect::<Vec<_>>().into_iter().map(|t|{t.join().unwrap()}).collect()
});
for t in threads {
for c in t {
let ((x,y),rgb) = c;
if let Some(px) = self.img.get_pixel(x,y){ if let Some(px) = self.img.get_pixel(x,y){
let (r,g,b)= rgb;
px.set_color(r,g,b); px.set_color(r,g,b);
} else{/*ignore*/} } else {
eprintln!("Invalid pixel: ({} {})",x,y);
}
} }
} }
self.img.save("./foo.ppm").unwrap(); self.img.save("./foo.ppm").unwrap();

View file

@ -1,5 +1,6 @@
use std::ops::{Add, Div, Mul, Sub}; use std::ops::{Add, Div, Mul, Sub};
#[repr(align(32))]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Vec3 { pub struct Vec3 {
x: f64, x: f64,
@ -7,6 +8,7 @@ pub struct Vec3 {
z: f64, z: f64,
} }
#[repr(align(32))]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Point3 { pub struct Point3 {
x: f64, x: f64,
@ -132,6 +134,7 @@ impl Vec3 {
z: self.z, z: self.z,
} }
} }
pub fn dot(self, other: Vec3) -> f64 { pub fn dot(self, other: Vec3) -> f64 {
self.x * other.x + self.y * other.y + self.z * other.z self.x * other.x + self.y * other.y + self.z * other.z
} }
@ -149,10 +152,11 @@ impl Vec3 {
} }
pub fn cross(self, other: Vec3) -> Vec3 { pub fn cross(self, other: Vec3) -> Vec3 {
let x = self.y * other.z - self.z * other.y; Vec3{
let y = self.z * other.x - self.x * other.z; x: self.y * other.z - self.z * other.y,
let z = self.x * other.y - self.y * other.x; y: self.z * other.x - self.x * other.z,
Vec3 { x, y, z } z: self.x * other.y - self.y * other.x,
}
} }
pub fn length_squared(self) -> f64 { pub fn length_squared(self) -> f64 {