use cgmath::prelude::*; use cgmath::{vec3, vec4, InnerSpace, Matrix4, Point3, Vector3}; use image::ImageBuffer; use utilities::prelude::*; use std::f32::{consts::PI as M_PI, MAX}; struct Triangle { points: [Vector3; 3], } impl Triangle { pub fn new(v0: Vector3, v1: Vector3, v2: Vector3) -> Triangle { Triangle { points: [v0, v1, v2], } } } struct View { position: Vector3, look_at: Vector3, fov: f32, } fn main() { let input_data = [Triangle::new( vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 1.0, 0.0), )]; let view = View { position: vec3(0.0, -4.0, 4.0), look_at: vec3(0.0, 0.0, 0.0), fov: 45.0, }; let (origin, direction) = calculate_ray(640, 360, 1280, 720, &view); let color = pixel_color(origin, direction, &input_data); //debug_raytracer(1280, 720, &view, &input_data); } fn f_to_u(color: f32) -> u8 { ((color * 255.0) / 1.0) as u8 } fn calculate_ray( x: u32, y: u32, dim_x: u32, dim_y: u32, view: &View, ) -> (Vector3, Vector3) { let aspect_ratio = dim_x as f32 / dim_y as f32; let p_x = (2.0 * ((x as f32 + 0.5) / dim_x as f32) - 1.0) * (view.fov / 2.0 * M_PI / 180.0).tan() * aspect_ratio; let p_y = (1.0 - 2.0 * (((dim_y - y) as f32 + 0.5) / dim_y as f32)) * (view.fov / 2.0 * M_PI / 180.0).tan(); let camera_to_world = Matrix4::look_at( Point3::from_vec(view.position), Point3::from_vec(view.look_at), vec3(0.0, 0.0, 1.0), ) .inverse_transform() .unwrap(); let origin = camera_to_world * vec4(0.0, 0.0, 0.0, 1.0); let direction = camera_to_world * vec4(p_x, p_y, -1.0, 1.0); (origin.truncate(), direction.truncate().normalize()) } fn debug_raytracer(dim_x: u32, dim_y: u32, view: &View, data: &[Triangle]) { let mut imgbuf = ImageBuffer::new(dim_x, dim_y); for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { let (origin, direction) = calculate_ray(x, y, dim_x, dim_y, view); let color = pixel_color(origin, direction, data); *pixel = image::Rgb([f_to_u(color.x), f_to_u(color.y), f_to_u(color.z)]); } imgbuf.save("raytrace.png").unwrap(); } fn pixel_color(orig: Vector3, dir: Vector3, data: &[Triangle]) -> Vector3 { let mut final_color = vec3(0.0, 1.0, 0.0); let mut closest_value = MAX; let mut closest_index = -1; for i in 0..data.len() { let v0 = data[i].points[0]; let v1 = data[i].points[1]; let v2 = data[i].points[2]; let mut t = 0.0; if ray_triangle_intersect_naive(orig, dir, v0, v1, v2, &mut t) { if t < closest_value { closest_index = i as i32; closest_value = t; } } } if closest_index != -1 { final_color = vec3(1.0, 0.0, 0.0); } return final_color; } // source: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection fn ray_triangle_intersect_mt( orig: Vector3, dir: Vector3, v0: Vector3, v1: Vector3, v2: Vector3, t: &mut f32, ) -> bool { let v0v1 = v1 - v0; let v0v2 = v2 - v0; let pvec = dir.cross(v0v2); let det = v0v1.dot(pvec); // ray and triangle are parallel if det is close to 0 if det.abs() < 0.001 { return false; } let inv_det = 1.0 / det; let tvec = orig - v0; let u = tvec.dot(pvec) * inv_det; if u < 0.0 || u > 1.0 { return false; } let qvec = tvec.cross(v0v1); let v = dir.dot(qvec) * inv_det; if v < 0.0 || u + v > 1.0 { return false; } *t = v0v2.dot(qvec) * inv_det; true } fn ray_triangle_intersect_naive( orig: Vector3, dir: Vector3, v0: Vector3, v1: Vector3, v2: Vector3, t: &mut f32, ) -> bool { // compute plane's normal let v0v1 = v1 - v0; let v0v2 = v2 - v0; // no need to normalize let N = v0v1.cross(v0v2); let area2 = N.magnitude(); // Step 1: finding P // check if ray and plane are parallel ? let NdotRayDirection = N.dot(dir); if NdotRayDirection.abs() < 0.001 // almost 0 { return false; } // they are parallel so they don't intersect ! // compute d parameter using equation 2 let d = N.dot(v0); // compute t (equation 3) *t = (N.dot(orig) + d) / NdotRayDirection; // check if the triangle is in behind the ray if *t < 0.0 { return false; } // the triangle is behind // compute the intersection point using equation 1 let P = orig + *t * dir; // Step 2: inside-outside test let mut C; // vector perpendicular to triangle's plane // edge 0 let edge0 = v1 - v0; let vp0 = P - v0; C = edge0.cross(vp0); if N.dot(C) < 0.0 { return false; } // P is on the right side // edge 1 let edge1 = v2 - v1; let vp1 = P - v1; C = edge1.cross(vp1); if N.dot(C) < 0.0 { return false; } // P is on the right side // edge 2 let edge2 = v0 - v2; let vp2 = P - v2; C = edge2.cross(vp2); if N.dot(C) < 0.0 { return false; } // P is on the right side; true // this ray hits the triangle }