use glam::{Mat4, Vec3, Vec4}; use sdl2::event::Event; use sdl2::keyboard::{Keycode, Scancode}; use std::collections::*; use std::iter::FromIterator; use std::str; use std::time::{Duration}; use opengl_rust::*; use file::load_small_file; use iqm::Model; use renderable_model::RenderableModel; use shader::{ShaderProgram, ShaderObject}; use texture::Texture; use timestep::TimeStep; pub fn color_from_255 (rgb: V) -> Vec3 where V: Into { let rgb: Vec3 = rgb.into (); Vec3::from (( rgb.x () / 255.0, rgb.y () / 255.0, rgb.z () / 255.0 )) } const KEY_LEFT: usize = 0; const KEY_RIGHT: usize = KEY_LEFT + 1; const KEY_UP: usize = KEY_RIGHT + 1; const KEY_DOWN: usize = KEY_UP + 1; struct ControllerState { keys: Vec , } impl ControllerState { pub fn from_sdl_keyboard (k: &sdl2::keyboard::KeyboardState) -> Self { let f = |c| k.is_scancode_pressed (c); Self { keys: vec! [ f (Scancode::Left), f (Scancode::Right), f (Scancode::Up), f (Scancode::Down), ], } } pub fn is_pressed (&self, code: usize) -> bool { self.keys [code] } } struct WorldState { azimuth: f32, altitude: f32, spin_speed: i32, } impl WorldState { pub fn new () -> Self { Self { azimuth: 0.0, altitude: 0.0, spin_speed: 0, } } pub fn step (&mut self, controller: &ControllerState) { const SPIN_RAMP_TIME: i32 = 30; let spin_f = 4.0 * self.spin_speed as f32 / SPIN_RAMP_TIME as f32; if controller.is_pressed (KEY_LEFT) { self.spin_speed = std::cmp::min (self.spin_speed + 1, SPIN_RAMP_TIME); self.azimuth += spin_f; } else if controller.is_pressed (KEY_RIGHT) { self.spin_speed = std::cmp::min (self.spin_speed + 1, SPIN_RAMP_TIME); self.azimuth -= spin_f; } else if controller.is_pressed (KEY_UP) { self.spin_speed = std::cmp::min (self.spin_speed + 1, SPIN_RAMP_TIME); self.altitude = f32::min (90.0, self.altitude + spin_f); } else if controller.is_pressed (KEY_DOWN) { self.spin_speed = std::cmp::min (self.spin_speed + 1, SPIN_RAMP_TIME); self.altitude = f32::max (-90.0, self.altitude - spin_f); } else { self.spin_speed = 0; } } } mod uniforms { use iota::iota; iota! { pub const MVP: u32 = iota; , OBJECT_SPACE_LIGHT , OBJECT_SPACE_SKY , ALBEDO , MIN_ALBEDO , MIN_BRIGHT , TEXTURE } } fn make_object_space_vec (inverse_model_mat: &Mat4, world_space_vec: &Vec3) -> Vec3 { let v = world_space_vec; let v4 = *inverse_model_mat * Vec4::from ((v.x (), v.y (), v.z (), 0.0)); Vec3::from ((v4.x (), v4.y (), v4.z ())) } fn renderable_from_iqm_file

(filename: P) -> RenderableModel where P: AsRef { let data = load_small_file (filename, 1024 * 1024).unwrap (); let model = Model::from_slice (&data).unwrap (); RenderableModel::from_iqm (&model) } fn shader_from_files

(vert: P, frag: P) -> ShaderProgram where P: AsRef { let vert_shader = ShaderObject::from_file (gl::VERTEX_SHADER, vert).unwrap (); let frag_shader = ShaderObject::from_file (gl::FRAGMENT_SHADER, frag).unwrap (); ShaderProgram::new (&vert_shader, &frag_shader).unwrap () } struct ShaderClosure { program: ShaderProgram, uniforms: HashMap , attributes: renderable_model::AttrMap, } struct BorrowedShaderVars <'a> { unis: &'a HashMap , attrs: &'a renderable_model::AttrMap, } fn get_shader_attrs ( shader: &ShaderProgram, attr_names: &[(usize, &str)] ) -> renderable_model::AttrMap { let mut v = vec! [None; attr_names.iter ().map (|(i, _)| i).max ().unwrap () + 1]; let attrs = shader.get_attribute_locations (attr_names.iter ().map (|(_, v)| v).copied ()); for ((i, _), loc) in attr_names.iter ().zip (attrs.into_iter ()) { v [*i] = loc; } v } fn get_shader_uniforms ( shader: &ShaderProgram, uni_names: &[(u32, &str)] ) -> HashMap { let unis = shader.get_uniform_locations (uni_names.iter ().map (|(_, v)| v).copied ()); let pairs = uni_names.iter ().map (|(i, _)| i).copied () .zip (unis.into_iter ()) .filter (|(_, v)| *v >= 0) ; HashMap::from_iter (pairs) } impl ShaderClosure { pub fn new ( program: ShaderProgram, uniform_names: &[(u32, &str)], attr_names: &[(usize, &str)] ) -> Self { let uniforms = get_shader_uniforms (&program, uniform_names); let attributes = get_shader_attrs (&program, attr_names); Self { program, uniforms, attributes, } } pub fn with (&self, callback: F) where F: Fn (BorrowedShaderVars) { self.program.use_program (); callback (BorrowedShaderVars { unis: &self.uniforms, attrs: &self.attributes, }); } } fn main () { let sdl_context = sdl2::init ().unwrap (); let video_subsystem = sdl_context.video ().unwrap (); let window = video_subsystem.window ("OpenGL? In my Rust?", 1280, 720) .position_centered () .opengl () .build () .unwrap (); gl::load_with (|s| { video_subsystem.gl_get_proc_address (s) as *const _ }); assert! (gl::ClearColor::is_loaded ()); let gl_ctx = window.gl_create_context ().unwrap (); window.gl_make_current (&gl_ctx).unwrap (); let uniform_names = { use uniforms::*; vec! [ (MVP, "uni_mvp"), (OBJECT_SPACE_LIGHT, "uni_object_space_light"), (OBJECT_SPACE_SKY, "uni_object_space_sky"), (ALBEDO, "uni_albedo"), (MIN_ALBEDO, "uni_min_albedo"), (MIN_BRIGHT, "uni_min_bright"), (TEXTURE, "uni_texture"), ] }; let attr_names = { use renderable_model::attributes::*; vec! [ (POS, "attr_pos"), (UV, "attr_uv"), (NORMAL, "attr_normal"), ] }; let shader_diffuse = ShaderClosure::new (shader_from_files ("shaders/pumpkin-vert.glsl", "shaders/pumpkin-frag.glsl"), &uniform_names, &attr_names); let shader_shadow = ShaderClosure::new (shader_from_files ("shaders/shadow-vert.glsl", "shaders/shadow-frag.glsl"), &uniform_names, &attr_names); shader_diffuse.with (|shader_vars| { let attrs = shader_vars.attrs; use renderable_model::attributes::*; glezz::enable_vertex_attrib_array (attrs [POS]); glezz::enable_vertex_attrib_array (attrs [UV]); glezz::enable_vertex_attrib_array (attrs [NORMAL]); }); let texture = Texture::from_file ("sky.png"); texture.bind (); let mesh_pumpkin = renderable_from_iqm_file ("pumpking.iqm"); let mesh_sky = renderable_from_iqm_file ("sky-sphere.iqm"); let mesh_pitch = renderable_from_iqm_file ("pitch.iqm"); let mesh_arrow = renderable_from_iqm_file ("arrow.iqm"); let orange = color_from_255 ((210.0, 125.0, 44.0)); let green = color_from_255 ((52.0, 101.0, 36.0)); let white = color_from_255 ((255.0, 255.0, 255.0)); let _off_white = color_from_255 ((222.0, 238.0, 214.0)); let black = color_from_255 ((0.0, 0.0, 0.0)); let _off_black = color_from_255 ((20.0, 12.0, 28.0)); let pumpkin_colors = vec! [ orange, green, ]; let (pitch_colors, grass_index) = { let silver = (255.0, 255.0, 255.0); let wood = (133.0, 76.0, 48.0); let color_lookup: HashMap <&str, _> = HashMap::from_iter (vec! [ ("GoalN1", silver), ("GoalN2", silver), ("GoalN3", silver), ("GoalS1", silver), ("GoalS2", silver), ("GoalS3", silver), ("TowerNW", wood), ("TowerNE", wood), ("TowerSW", wood), ("TowerSE", wood), ("Wall", wood), ("Grass", (52.0, 101.0, 36.0)), ].into_iter ()); let mut grass_index = None; let colors: Vec <_> = (0..mesh_pitch.meshes.len ()).map (|i| { let name = str::from_utf8 (&mesh_pitch.meshes [i].name).unwrap (); if name == "Grass" { grass_index = Some (i); } match color_lookup.get (name) { Some (t) => color_from_255 (*t), _ => (0.0, 0.0, 0.0).into (), } }).collect (); (colors, grass_index.unwrap ()) }; glezz::enable (gl::DEPTH_TEST); glezz::enable (gl::TEXTURE_2D); let mut time_step = TimeStep::new (60, 1000); let mut state = WorldState::new (); let mut event_pump = sdl_context.event_pump ().unwrap (); 'running: loop { let frames_to_do = time_step.step (); let controller = ControllerState::from_sdl_keyboard (&event_pump.keyboard_state ()); for _ in 0..frames_to_do { state.step (&controller); } let _mouse = event_pump.mouse_state (); for event in event_pump.poll_iter() { match event { Event::Quit {..} | Event::KeyDown { keycode: Some (Keycode::Escape), .. } => { break 'running }, _ => (), } } window.gl_make_current (&gl_ctx).unwrap (); let longitude = state.azimuth.to_radians (); let latitude = (state.altitude - 90.0).to_radians (); let proj_mat = Mat4::perspective_rh_gl (30.0f32.to_radians (), 1280.0 / 720.0, 0.5, 500.0); let shadow_mat = { let mut mat = Mat4::identity (); mat.set_z_axis ((0.25, 0.125, 0.0, 0.0).into ()); mat }; let view_mat = proj_mat * Mat4::from_translation (Vec3::from ((0.0, 0.0, -8.0))) * Mat4::from_rotation_x (latitude) * Mat4::from_rotation_z (longitude) * Mat4::from_translation ((0.0, 0.0, -2.7 * 0.5).into ()) ; let sky_mvp_mat = view_mat * Mat4::from_scale ((16.0, 16.0, 16.0).into ()); let light = Vec3::from ((2.0, 0.0, 5.0)).normalize (); glezz::clear_color (1.0f32, 0.0f32, 1.0f32, 1.0f32); glezz::clear (gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT); glezz::enable (gl::CULL_FACE); let pumpkin_model_mat = Mat4::from_translation ((0.0, 0.0, 2.7 * 0.5).into ()) * Mat4::from_scale ((0.125, 0.125, 0.125).into ()) * Mat4::from_translation ((0.0, 0.0, -2.7 * 0.5).into ()) ; let world_model_mat = Mat4::identity (); use uniforms::*; shader_diffuse.with (|shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; glezz::disable (gl::STENCIL_TEST); glezz::front_face (gl::CW); let mvp = view_mat * pumpkin_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); let inverse_pumpkin = pumpkin_model_mat.inverse (); let object_space_light = make_object_space_vec (&inverse_pumpkin, &light); let object_space_sky = make_object_space_vec (&inverse_pumpkin, &Vec3::from ((0.0, 0.0, 1.0))); glezz::uniform_3fv (unis [&MIN_BRIGHT], &black); glezz::uniform_3fv (unis [&MIN_ALBEDO], &white); glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &object_space_light); glezz::uniform_3fv (unis [&OBJECT_SPACE_SKY], &object_space_sky); mesh_pumpkin.draw_all (attrs, |i| { glezz::uniform_3fv (unis [&ALBEDO], &pumpkin_colors [i]); true }); let mvp = view_mat * world_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); mesh_pitch.draw_all (attrs, |i| { glezz::uniform_3fv (unis [&ALBEDO], &pitch_colors [i]); i != grass_index }); let draw_sky = true; if draw_sky { glezz::front_face (gl::CCW); glezz::uniform_matrix_4fv (unis [&MVP], &sky_mvp_mat); glezz::uniform_3fv (unis [&ALBEDO], &white); glezz::uniform_3fv (unis [&MIN_BRIGHT], &white); glezz::uniform_3fv (unis [&MIN_ALBEDO], &black); glezz::uniform_1i (unis [&TEXTURE], 0); mesh_sky.draw_all (attrs, |_| true); } glezz::enable (gl::STENCIL_TEST); unsafe { gl::StencilFunc (gl::ALWAYS, 1, 1); gl::StencilOp (gl::KEEP, gl::KEEP, gl::REPLACE); gl::ColorMask (0, 0, 0, 0); gl::DepthMask (0); gl::StencilMask (1); } }); shader_shadow.with (|shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; let view_mat = view_mat * shadow_mat; let mvp = view_mat * pumpkin_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); mesh_pumpkin.draw_all (attrs, |_| true); let mvp = view_mat * world_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); mesh_pitch.draw_all (attrs, |i| i != grass_index); unsafe { gl::ColorMask (255, 255, 255, 255); gl::DepthMask (1); } }); shader_diffuse.with (|shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; glezz::front_face (gl::CW); let inverse_pumpkin = pumpkin_model_mat.inverse (); let object_space_light = make_object_space_vec (&inverse_pumpkin, &light); let object_space_sky = make_object_space_vec (&inverse_pumpkin, &Vec3::from ((0.0, 0.0, 1.0))); glezz::uniform_3fv (unis [&MIN_BRIGHT], &black); glezz::uniform_3fv (unis [&MIN_ALBEDO], &white); glezz::uniform_3fv (unis [&OBJECT_SPACE_SKY], &object_space_sky); let mvp = view_mat * world_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); glezz::uniform_3fv (unis [&ALBEDO], &pitch_colors [grass_index]); unsafe { gl::StencilFunc (gl::NOTEQUAL, 0, 1); gl::StencilOp (gl::KEEP, gl::KEEP, gl::KEEP); } glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &Vec3::from ((0.0, 0.0, 0.0))); mesh_pitch.draw (attrs, grass_index); unsafe { gl::StencilFunc (gl::EQUAL, 0, 1); gl::StencilOp (gl::KEEP, gl::KEEP, gl::KEEP); } glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &object_space_light); mesh_pitch.draw (attrs, grass_index); }); window.gl_swap_window (); std::thread::sleep (Duration::from_millis (15)); } } #[cfg (test)] mod tests { use super::*; #[test] pub fn iqm () { } }