diff --git a/models/spacecraft2-shape.png b/models/spacecraft2-shape.png new file mode 100644 index 0000000..b7c9965 Binary files /dev/null and b/models/spacecraft2-shape.png differ diff --git a/models/spacecraft2.obj b/models/spacecraft2.obj index cd571ee..6a7c9cc 100644 --- a/models/spacecraft2.obj +++ b/models/spacecraft2.obj @@ -1,6 +1,5 @@ # Blender 3.6.5 # www.blender.org -mtllib spacecraft2.mtl o Cube v -1.000000 0.000000 3.000000 v -2.000000 0.000000 2.000000 @@ -60,50 +59,68 @@ vn 0.3487 -0.9300 -0.1162 vn -0.4444 -0.8889 -0.1111 vn -0.3015 -0.9045 0.3015 vn 0.2182 -0.4364 0.8729 -vt 0.000000 0.000000 +vt 0.342147 0.420195 +vt 0.509380 0.482116 +vt 0.342147 0.586861 +vt 0.513220 0.668431 +vt 0.680454 0.813685 +vt 0.000000 0.840390 +vt 0.684294 1.000000 +vt 0.855367 0.914903 +vt 0.855367 0.748236 +vt 0.684294 0.666667 +vt 0.594917 0.481234 +vt 0.680454 0.147018 +vt 0.769831 0.415785 +vt 0.769830 0.582451 +vt 0.513220 0.335097 +vt 0.000000 0.173723 +vt 0.684294 0.000000 +vt 0.855367 0.248236 +vt 0.855367 0.081569 +vt 0.684294 0.333333 s 0 -usemtl Material -f 8/1/1 18/1/1 16/1/1 -f 16/1/2 18/1/2 15/1/2 -f 18/1/3 19/1/3 15/1/3 -f 15/1/4 19/1/4 14/1/4 -f 14/1/5 19/1/5 13/1/5 -f 13/1/6 19/1/6 12/1/6 -f 12/1/7 19/1/7 11/1/7 -f 10/1/8 11/1/8 19/1/8 -f 20/1/9 18/1/9 17/1/9 -f 1/1/7 9/1/7 20/1/7 -f 9/1/10 10/1/10 20/1/10 -f 20/1/11 10/1/11 19/1/11 -f 6/1/12 18/1/12 8/1/12 -f 18/1/13 6/1/13 17/1/13 -f 6/1/14 7/1/14 17/1/14 -f 5/1/15 17/1/15 7/1/15 -f 3/1/7 17/1/7 4/1/7 -f 4/1/8 17/1/8 5/1/8 -f 2/1/6 17/1/6 3/1/6 -f 1/1/16 20/1/16 2/1/16 -f 17/1/17 2/1/17 20/1/17 -f 20/1/9 19/1/9 18/1/9 -f 8/1/18 16/1/18 22/1/18 -f 16/1/19 15/1/19 22/1/19 -f 22/1/20 15/1/20 23/1/20 -f 15/1/21 14/1/21 23/1/21 -f 14/1/22 13/1/22 23/1/22 -f 13/1/23 12/1/23 23/1/23 -f 12/1/24 11/1/24 23/1/24 -f 10/1/25 23/1/25 11/1/25 -f 24/1/26 21/1/26 22/1/26 -f 1/1/24 24/1/24 9/1/24 -f 9/1/27 24/1/27 10/1/27 -f 24/1/28 23/1/28 10/1/28 -f 6/1/29 8/1/29 22/1/29 -f 22/1/30 21/1/30 6/1/30 -f 6/1/31 21/1/31 7/1/31 -f 5/1/32 7/1/32 21/1/32 -f 3/1/24 4/1/24 21/1/24 -f 4/1/25 5/1/25 21/1/25 -f 2/1/23 3/1/23 21/1/23 -f 1/1/33 2/1/33 24/1/33 -f 21/1/34 24/1/34 2/1/34 -f 24/1/26 22/1/26 23/1/26 +f 8/1/1 18/2/1 16/3/1 +f 16/3/2 18/2/2 15/4/2 +f 18/2/3 19/5/3 15/4/3 +f 15/4/4 19/5/4 14/6/4 +f 14/6/5 19/5/5 13/7/5 +f 13/7/6 19/5/6 12/8/6 +f 12/8/7 19/5/7 11/9/7 +f 10/10/8 11/9/8 19/5/8 +f 20/11/9 18/2/9 17/12/9 +f 1/13/7 9/14/7 20/11/7 +f 9/14/10 10/10/10 20/11/10 +f 20/11/11 10/10/11 19/5/11 +f 6/15/12 18/2/12 8/1/12 +f 18/2/13 6/15/13 17/12/13 +f 6/15/14 7/16/14 17/12/14 +f 5/17/15 17/12/15 7/16/15 +f 3/18/7 17/12/7 4/19/7 +f 4/19/8 17/12/8 5/17/8 +f 2/20/6 17/12/6 3/18/6 +f 1/13/16 20/11/16 2/20/16 +f 17/12/17 2/20/17 20/11/17 +f 20/11/9 19/5/9 18/2/9 +f 8/1/18 16/3/18 22/2/18 +f 16/3/19 15/4/19 22/2/19 +f 22/2/20 15/4/20 23/5/20 +f 15/4/21 14/6/21 23/5/21 +f 14/6/22 13/7/22 23/5/22 +f 13/7/23 12/8/23 23/5/23 +f 12/8/24 11/9/24 23/5/24 +f 10/10/25 23/5/25 11/9/25 +f 24/11/26 21/12/26 22/2/26 +f 1/13/24 24/11/24 9/14/24 +f 9/14/27 24/11/27 10/10/27 +f 24/11/28 23/5/28 10/10/28 +f 6/15/29 8/1/29 22/2/29 +f 22/2/30 21/12/30 6/15/30 +f 6/15/31 21/12/31 7/16/31 +f 5/17/32 7/16/32 21/12/32 +f 3/18/24 4/19/24 21/12/24 +f 4/19/25 5/17/25 21/12/25 +f 2/20/23 3/18/23 21/12/23 +f 1/13/33 2/20/33 24/11/33 +f 21/12/34 24/11/34 2/20/34 +f 24/11/26 22/2/26 23/5/26 diff --git a/src/bin/textured/main.rs b/src/bin/textured/main.rs new file mode 100644 index 0000000..b9e19c2 --- /dev/null +++ b/src/bin/textured/main.rs @@ -0,0 +1,153 @@ +use glam::*; +use refraction::mesh_loader::load_mesh; +use refraction::mesh_tracer::{trace_to_mesh, Mesh}; +use show_image::event::{ElementState, VirtualKeyCode, WindowEvent}; +use show_image::{exit, ImageInfo, ImageView, WindowOptions}; +use std::env; +use std::error::Error; +use std::f32::consts::PI; +use std::fs::File; +use std::io::BufReader; +use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + +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( + vec3(roll.cos(), roll.sin(), 0.0), + vec3(-roll.sin(), roll.cos(), 0.0), + vec3(0.0, 0.0, 1.0), + ); + let m_yaw = mat3( + vec3(yaw.cos(), 0.0, yaw.sin()), + vec3(0.0, 1.0, 0.0), + vec3(-yaw.sin(), 0.0, yaw.cos()), + ); + let m_pitch = mat3( + vec3(1.0, 0.0, 0.0), + vec3(0.0, pitch.cos(), -pitch.sin()), + vec3(0.0, pitch.sin(), pitch.cos()), + ); + m_roll * m_pitch * m_yaw +} + +fn render(mesh: &Mesh, camera: impl Fn(Vec2) -> (Vec3, Vec3)) -> Image { + let bkg = vec3(0.0, 0.0, 0.0); + 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); + let color = if let Some(r) = trace_to_mesh(mesh, base, ray.normalize()) { + // to_vec3(0.45) * dot(r.normal, normalize(vec3(-1.0, 1.0, -1.0))) + 0.50 + vec3(r.tex_coords.x, r.tex_coords.y, 0.) + } else { + bkg + }; + let color = (color * 255.0).as_ivec3().clamp(IVec3::splat(0), IVec3::splat(255)); + img.put_pixel(x, y, Color(color.x as u8, color.y as u8, color.z as u8)); + } + } + img +} + +fn persp(dist: f32, off: Vec2) -> (Vec3, Vec3) { + (vec3(0., 0., -dist), vec3(off.x, off.y, dist)) +} + +fn ortho(dist: f32, off: Vec2) -> (Vec3, Vec3) { + (vec3(off.x, off.y, -dist), vec3(0., 0., 1.)) +} + +#[test] +fn test_projs() { + fn check(f: fn(dist: f32, off: Vec2) -> (Vec3, Vec3), x: f32, y: f32, z: f32) { + let (base, ray) = f(z, vec2(x, y)); + let at_dist = base + ray * (z / ray.z); + assert_eq!(at_dist, vec3(x, y, 0.)); + } + check(persp, 1., 2., 3.); + check(ortho, 1., 2., 3.); + check(persp, 5., 3., 7.); + check(ortho, 9., 1., 8.); +} + +// add_event_handler wants 'static + Send. Let it be so. +static PROJ_INDEX: AtomicUsize = AtomicUsize::new(0); +static PROJS: [fn(dist: f32, off: Vec2) -> (Vec3, Vec3); 2] = [persp, ortho]; + +#[show_image::main] +fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + if args.len() != 2 { + println!("Usage: {} path/to/model.obj", args[0]); + exit(1); + } + 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())?; + window.add_event_handler(|_wnd, ev, _ctl| { + if let WindowEvent::KeyboardInput(ev) = ev { + if ev.input.state != ElementState::Pressed { + return; + } + if let Some(VirtualKeyCode::Tab) = ev.input.key_code { + PROJ_INDEX.store((PROJ_INDEX.load(Relaxed) + 1) % PROJS.len(), Relaxed); + } + } + })?; + loop { + for phi in 0..360 { + let proj = PROJS[PROJ_INDEX.load(Relaxed)]; + 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 = m_view.transpose(); + let img = render(mesh.as_slice(), |off| { + let (base, ray) = proj(40., 20. * off); + (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)?; + } + } +} diff --git a/src/mesh_loader.rs b/src/mesh_loader.rs index 16d1f84..6d121a9 100644 --- a/src/mesh_loader.rs +++ b/src/mesh_loader.rs @@ -5,7 +5,7 @@ use std::io; struct ObjVertex { vertex: usize, normal: usize, - // tex_coord: usize, + tex_coord: usize, } #[derive(Copy, Clone, Debug)] @@ -39,7 +39,7 @@ impl ObjMesh { assert_eq!(tokens.len(), 3); ObjVertex { vertex: tokens[0], - // tex_coord: tokens[1], + tex_coord: tokens[1], normal: tokens[2], } } @@ -80,6 +80,7 @@ impl ObjMesh { .iter() .map(|face| Face { vertices: face.vertices.map(|iv| self.vertices[iv.vertex]), + tex_coords: face.vertices.map(|iv| self.tex_coords[iv.tex_coord]), normal: self.normals[face.vertices[0].normal], }) .collect() @@ -89,6 +90,7 @@ impl ObjMesh { #[derive(Copy, Clone, Debug)] pub struct Face { pub vertices: [Vec3; 3], + pub tex_coords: [Vec2; 3], pub normal: Vec3, } diff --git a/src/mesh_tracer.rs b/src/mesh_tracer.rs index 047cbef..753c751 100644 --- a/src/mesh_tracer.rs +++ b/src/mesh_tracer.rs @@ -1,10 +1,11 @@ use crate::mesh_loader::Face; -use glam::{mat3, Vec3}; +use glam::{mat3, vec3, Vec2, Vec3}; pub type Mesh = [Face]; pub struct TraceResult { pub distance: f32, + pub tex_coords: Vec2, pub normal: Vec3, } @@ -17,8 +18,10 @@ pub fn trace_to_mesh_all(mesh: &Mesh, base: Vec3, ray: Vec3) -> impl Iterator