mod mesh_loader; use std::fs::File; use std::{env}; use std::error::Error; use std::f32::consts::PI; use std::io::{BufReader}; use glm::*; use show_image::{ImageInfo, ImageView, WindowOptions}; use crate::mesh_loader::{Face, load_mesh}; const W: i32 = 320; const H: i32 = 240; #[derive(Copy, Clone)] struct Color(u8, u8, u8); struct Image { w: i32, h: i32, data: Vec, } impl Image { fn data(&self) -> &[u8] { self.data.as_slice() } fn put_pixel(&mut self, x: i32, y: i32, color: Color) { if x < 0 || x >= self.w || y < 0 || y > self.h { return; } let index = 3 * (x + self.w * y) as usize; self.data[index] = color.0; self.data[index + 1] = color.1; self.data[index + 2] = color.2; } } fn ypr_to_mat(ypr: Vec3) -> Mat3 { let Vec3 { x: yaw, y: pitch, z: roll } = ypr; let m_roll = mat3( roll.cos(), roll.sin(), 0.0, -roll.sin(), roll.cos(), 0.0, 0.0, 0.0, 1.0); let m_yaw = mat3( yaw.cos(), 0.0, yaw.sin(), 0.0, 1.0, 0.0, -yaw.sin(), 0.0, yaw.cos()); let m_pitch = mat3( 1.0, 0.0, 0.0, 0.0, pitch.cos(), -pitch.sin(), 0.0, pitch.sin(), pitch.cos()); m_roll * m_pitch * m_yaw } struct TraceResult { distance: f32, normal: Vec3, } fn trace_to_mesh(mesh: &[Face], base: Vec3, ray: Vec3) -> Option { let mut ret: Option = None; let mut dist = f32::INFINITY; for f in mesh { let fs = (0..3).map(|k| edge_dist(f.vertices[k], f.vertices[(k + 1) % 3], base, ray)); if fs.into_iter().all(|f| f >= 0.0) { let m = Mat3 { c0: f.vertices[1] - f.vertices[0], c1: f.vertices[2] - f.vertices[0], c2: -ray }; if let Some(m) = m.inverse() { let rel = m * (base - f.vertices[0]); if rel.z > dist { continue; } dist = rel.z; ret = Some(TraceResult { distance: rel.z, normal: f.normal, }); } else { continue; } } } ret } fn render(mesh: &[Face], camera: impl Fn(Vec2) -> (Vec3, Vec3)) -> Image { let mut img = Image { w: W, h: H, data: vec![0; (3 * W * H) as usize], }; let img_size = vec2(W as f32, H as f32); for y in 0..H { for x in 0..W { let img_coords = vec2(x as f32, y as f32); let off = (img_coords - img_size * 0.5) / img_size.y; let (base, ray) = camera(off); if let Some(r) = trace_to_mesh(mesh, base, ray) { let color = clamp(to_ivec3(r.normal * 120.0 + 128.0), ivec3(0, 0, 0), ivec3(255, 255, 255)); img.put_pixel(x, y, Color(color.x as u8, color.y as u8, color.z as u8)); } } } img } #[show_image::main] fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); let mesh = { let f = File::open(&args[1])?; let mut f = BufReader::new(f); load_mesh(&mut f)? }; let window = show_image::create_window("Raytracing", WindowOptions::default())?; loop { for phi in 0..360 { let m_view = ypr_to_mat(vec3((135.0 + phi as f32) * PI / 180.0, -30.0 * PI / 180.0, 0.0f32)); let m_camera = transpose(&m_view); let img = render(mesh.as_slice(), |off| { // perspective projection let base = vec3(0.0, 0.0, -40.0); let ray = vec3(off.x, off.y, 2.0); // orthographic projection // let base = vec3(off.x, off.y, -10.0); // let ray = vec3(0.0, 0.0, 1.0); (m_camera * base, m_camera * ray) }); let image = ImageView::new(ImageInfo::rgb8(W as u32, H as u32), img.data()); window.set_image("image", image)?; } } } fn edge_dist(a: Vec3, b: Vec3, base: Vec3, dir: Vec3) -> f32 { // Note: given that the input is not arbitrary but comes from a cartesian product of certain (a, b) pairs and certain (base, dir) pairs, this can be optimized from Cnm to an+bm+cnm with c