diff --git a/src/image.rs b/src/image.rs index 3000679..6011551 100644 --- a/src/image.rs +++ b/src/image.rs @@ -18,12 +18,12 @@ impl Pixel { pub struct Image { data: Vec, - pub width: u16, - pub height: u16, + pub width: i32, + pub height: i32, } impl Image { - pub fn new(width: u16, height: u16) -> Self { + pub fn new(width: i32, height: i32) -> Self { let total_pixels = (width as usize) * (height as usize); let data = vec![ Pixel { @@ -56,7 +56,7 @@ impl Image { Ok(()) } - pub fn get_pixel(&mut self, x: u16, y: u16) -> Option<&mut Pixel> { + pub fn get_pixel(&mut self, x: i32, y: i32) -> Option<&mut Pixel> { if x >= self.width || y >= self.height { return None; } diff --git a/src/main.rs b/src/main.rs index 0e54876..acdcb19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,15 @@ mod image; +mod object; +mod render; mod vec3; + use image::Image; +use object::Sphere; +use render::World; use vec3::{Point3, Ray, Vec3}; +use crate::vec3::Interval; + fn main() { let aspect_ratio = 16.0 / 9.0; let image_width = 800; @@ -10,7 +17,7 @@ fn main() { let focal_length = 1.0; let viewport_height = 2.0; - let viewport_width = (viewport_height * (image_width / image_height) as f64) as i32; + let viewport_width = viewport_height * (image_width as f64 / image_height as f64); let camera_center = Point3::new(0.0, 0.0, 0.0); let viewport_u = Vec3::new(viewport_width as f64, 0.0, 0.0); @@ -19,11 +26,20 @@ fn main() { 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 viewport_upper_left = (camera_center.to_vec() + - 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); + let mut img = Image::new(image_width, image_height); + + let mut world = World::new(); + world.add(Sphere::new(0.5, Point3::new(0.0, 0.0, -1.0))); + world.add(Sphere::new(100.0, Point3::new(0.0, -100.5, -1.0))); + for y in 0..img.height { for x in 0..img.width { if let Some(px) = img.get_pixel(x, y) { @@ -32,7 +48,13 @@ fn main() { 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); + let i = Interval::new(0.001,f64::INFINITY); + if let Some(hit) = world.hit(ray, i) { + let (r,g,b) = hit.color(); + px.set_color(r,g,b); + } else { + px.set_color(r, g, b); + } } else { } } diff --git a/src/object.rs b/src/object.rs new file mode 100644 index 0000000..68e0ea5 --- /dev/null +++ b/src/object.rs @@ -0,0 +1,82 @@ +use crate::vec3::{Point3, Ray, Vec3, Interval}; + +pub trait Hittable { + fn hit(&self, ray: Ray, interval:Interval) -> Option; +} + +#[derive(Clone, Copy)] +pub struct HitRecord { + point: Point3, + normal: Vec3, + root: f64, + front_face: bool, + color: (f64,f64,f64) +} + +impl HitRecord { + pub fn normal(self) -> Vec3 { + self.normal + } + + pub fn point(self) -> Point3 { + self.point + } + + pub fn root(self) -> f64 { + self.root + } + + pub fn color(self) -> (f64,f64,f64) { + self.color + } + + pub fn set_face_normal(&mut self, ray: Ray, norm: Vec3) { + self.front_face = ray.direction().dot(norm) < 0.0; + self.normal = if self.front_face { norm } else { norm * -1.0 }; + } +} + +pub struct Sphere { + radius: f64, + center: Point3, +} + +impl Sphere { + pub fn new(radius: f64, center: Point3) -> Self { + Sphere { radius, center } + } +} + +impl Hittable for Sphere { + fn hit(&self, ray: Ray, interval:Interval) -> Option { + let oc = self.center - ray.origin(); + let a = ray.direction().length_squared(); + let h = Vec3::dot(ray.direction(), oc.to_vec()); + let c = oc.to_vec().length_squared() - self.radius * self.radius; + let discriminant = h * h - a * c; + if discriminant < 0.0 { + return None; + } + + let sqrtd = discriminant.sqrt(); + let mut root = (h - sqrtd) / a; + if !interval.surounds(root) { + root = (h + sqrtd) / a; + if !interval.surounds(root) { + return None; + } + } + + let point: Point3 = ray.at(root); + let normal = ((point - self.center) / self.radius).to_vec(); + let mut tmp = HitRecord { + root, + point, + normal, + front_face: false, + color: (normal.x(),normal.y(),normal.z()) + }; + tmp.set_face_normal(ray, normal); + Some(tmp) + } +} diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..404ae30 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,32 @@ +use crate::object::{HitRecord, Hittable}; +use crate::vec3::{Interval, Ray}; + +pub struct World { + pub objects: Vec>, +} + +impl World { + pub fn new() -> Self { + World { + objects: Vec::new(), + } + } + + pub fn add(&mut self, object: impl Hittable + 'static) { + self.objects.push(Box::new(object)); + } + + pub fn hit(&self, ray: Ray, interval:Interval) -> Option { + let mut hit_anything = None; + let mut closest_so_far = interval.max(); + + for object in &self.objects { + let i = Interval::new(interval.min(),closest_so_far); + if let Some(record) = object.hit(ray, i) { + closest_so_far = record.root(); + hit_anything = Some(record); + } + } + hit_anything + } +} diff --git a/src/vec3.rs b/src/vec3.rs index 0d166d5..95a4077 100644 --- a/src/vec3.rs +++ b/src/vec3.rs @@ -14,6 +14,38 @@ pub struct Point3 { z: f64, } +#[derive(Clone, Copy)] +pub struct Interval { + min: f64, + max: f64 +} + +impl Interval { + pub fn new(min:f64,max:f64) ->Self{ + Self{min,max} + } + + pub fn min(self) -> f64{ + self.min + } + + pub fn max(self) -> f64{ + self.max + } + + pub fn size(self) -> f64{ + self.max - self.min + } + + pub fn contains(self, x:f64) -> bool { + self.min <= x && x <= self.max + } + + pub fn surounds(self, x:f64) -> bool { + self.min < x && x < self.max + } +} + #[derive(Clone, Copy)] pub struct Ray { direction: Vec3,