use glam::{mat4, vec2, vec3, vec4, Mat4, Vec3}; use glium::{ backend::glutin::SimpleWindowBuilder, implement_vertex, uniform, winit::event::{Event, WindowEvent}, Program, Surface, VertexBuffer, }; use winit::event_loop::EventLoop; #[derive(Copy, Clone)] struct Vertex { position: [f32; 3], } implement_vertex!(Vertex, position); fn main() { let event_loop = EventLoop::builder().build().unwrap(); let (window, display) = SimpleWindowBuilder::new() .with_title("Refraction: Wireframe") .build(&event_loop); let vs_src = include_str!("ray.v.glsl"); let fs_src = include_str!("ray.f.glsl"); let program = Program::from_source(&display, vs_src, fs_src, None).unwrap(); let indices = glium::index::NoIndices(glium::index::PrimitiveType::LineLoop); let shape = vec![ Vertex { position: [-1., 0., 4.], }, Vertex { position: [0., -1., 4.], }, Vertex { position: [1., 0., 4.], }, Vertex { position: [0., 1., 4.], }, ]; let vertex_buffer = VertexBuffer::new(&display, &shape).unwrap(); let mut t: f32 = -0.5; #[allow(deprecated)] event_loop .run(move |ev, window_target| match ev { Event::WindowEvent { event, .. } => match event { WindowEvent::RedrawRequested => { let size = window.inner_size(); let size = vec2(size.width as f32, size.height as f32).normalize() * std::f32::consts::SQRT_2; let proj = make_proj_matrix(vec3(size.x, size.y, 2.), (0.125, 1024.125)); t += 0.02; let x = t.sin() * 0.5; let mdl = mat4( vec4(1., 0., 0., 0.), vec4(0., 1., 0., 0.), vec4(0., 0., 1., 0.), vec4(x, 0., 0., 1.), ); let mut target = display.draw(); target.clear_color(0.0, 0.0, 0.2, 1.0); let mvp = proj * mdl; let uniforms = uniform! { mvp: mvp.to_cols_array_2d(), }; target .draw( &vertex_buffer, &indices, &program, &uniforms, &Default::default(), ) .unwrap(); target.finish().unwrap(); } WindowEvent::Resized(window_size) => { display.resize(window_size.into()); } WindowEvent::CloseRequested => { window_target.exit(); } _ => (), }, Event::AboutToWait => { window.request_redraw(); } _ => (), }) .unwrap(); } /// Make a projection matrix, assuming input coordinates are (right, up, forward). /// /// `corner` is a vector that will be mapped to (x=1, y=1) after the perspective division. /// `zrange` is the Z range that will be mapped to z∈[-1, 1]. It has no other effect. Both ends have to be positive though. fn make_proj_matrix(corner: Vec3, zrange: (f32, f32)) -> Mat4 { let scale = 1.0 / corner; let zspan = zrange.1 - zrange.0; mat4( scale.x * vec4(1., 0., 0., 0.), scale.y * vec4(0., 1., 0., 0.), scale.z * vec4(0., 0., (zrange.0 + zrange.1) / zspan, 1.), scale.z * vec4(0., 0., -2. * zrange.0 * zrange.1 / zspan, 0.), ) } #[cfg(test)] mod tests { use super::*; use approx::assert_abs_diff_eq; use glam::vec3; #[test] fn test_proj_matrix() { let m = make_proj_matrix(vec3(2., 3., 4.), (0.5, 20.0)); let v = m * vec4(2., 3., 4., 1.); assert_abs_diff_eq!(v.x / v.w, 1.0); assert_abs_diff_eq!(v.y / v.w, 1.0); assert!(-v.w < v.z && v.z < v.w, "z out of range in {v}"); let v = m * vec4(2., 3., 0.5, 1.); assert_abs_diff_eq!(v.x / v.w, 8.0); assert_abs_diff_eq!(v.y / v.w, 8.0); assert_abs_diff_eq!(v.z / v.w, -1.0); let v = m * vec4(2., 3., 20.0, 1.); assert_abs_diff_eq!(v.x / v.w, 0.2); assert_abs_diff_eq!(v.y / v.w, 0.2); assert_abs_diff_eq!(v.z / v.w, 1.0); } }