use glam::{Mat4, Vec3, Vec4}; use sdl2::event::Event; use sdl2::keyboard::{Keycode, Scancode}; use std::collections::*; use std::iter::FromIterator; 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 , ALBEDO , MIN_ALBEDO , MIN_BRIGHT , TEXTURE } } 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 (); // I love how with Rust I can throw unwrap ()s everywhere // It's safer than C / C++'s default behavior of unchecked errors // It's more programmer-friendly and explicit than C#'s unchecked // exceptions that can appear almost anywhere at runtime with no // compile-time warning // And I'm still not actually checking errors - Just checkmarking // that I know where they are. let vert_shader = ShaderObject::from_file (gl::VERTEX_SHADER, "shaders/pumpkin-vert.glsl").unwrap (); let frag_shader = ShaderObject::from_file (gl::FRAGMENT_SHADER, "shaders/pumpkin-frag.glsl").unwrap (); let shader_program = ShaderProgram::new (&vert_shader, &frag_shader).unwrap (); let uni_lookup: HashMap <_, &str> = HashMap::from_iter ({ use uniforms::*; vec! [ (MVP, "mvp"), (OBJECT_SPACE_LIGHT, "object_space_light"), (ALBEDO, "albedo"), (MIN_ALBEDO, "min_albedo"), (MIN_BRIGHT, "min_bright"), (TEXTURE, "texture"), ].into_iter () }); let unis = shader_program.get_uniform_locations (uni_lookup.values ().map (|s| *s)); let unis: HashMap = HashMap::from_iter ( uni_lookup.iter () .map (|(key, name)| (*key, *unis.get (*name).unwrap ())) ); let attrs = shader_program.get_attribute_locations (vec! [ "pos", "uv", "normal", ].into_iter ()); let texture = Texture::from_file ("sky.png"); texture.bind (); let model_data = load_small_file ("pumpking.iqm", 1024 * 1024); let model = Model::from_slice (&model_data [..]); let renderable_model = RenderableModel::from_iqm (&model); let sky_data = load_small_file ("sky-sphere.iqm", 1024 * 1024); let sky_model = Model::from_slice (&sky_data [..]); let renderable_sky = RenderableModel::from_iqm (&sky_model); glezz::enable_vertex_attrib_array (attrs ["pos"]); glezz::enable_vertex_attrib_array (attrs ["uv"]); glezz::enable_vertex_attrib_array (attrs ["normal"]); 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 model_mat = Mat4::from_translation (Vec3::from ((0.0, 0.0, -2.7 * 0.5))) ; 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) ; let mvp_mat = view_mat * model_mat; let sky_mvp_mat = view_mat * Mat4::from_scale (Vec3::from ((16.0, 16.0, 16.0))); let light = Vec3::from ((2.0, 0.0, 5.0)).normalize (); let object_space_light = model_mat.inverse () * Vec4::from ((light.x (), light.y (), light.z (), 0.0)); let object_space_light = Vec3::from ((object_space_light.x (), object_space_light.y (), object_space_light.z ())); let orange = color_from_255 ((255.0, 154.0, 0.0)); let green = color_from_255 ((14.0, 127.0, 24.0)); let white = Vec3::from ((1.0, 1.0, 1.0)); let black = Vec3::from ((0.0, 0.0, 0.0)); let orange = orange * orange; let green = green * green; glezz::clear_color (1.0f32, 1.0f32, 1.0f32, 1.0f32); glezz::clear (gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); glezz::disable (gl::CULL_FACE); { use uniforms::*; glezz::uniform_3fv (unis [&MIN_BRIGHT], &black); glezz::uniform_3fv (unis [&MIN_ALBEDO], &white); glezz::uniform_3fv (unis [&ALBEDO], &orange); glezz::uniform_matrix_4fv (unis [&MVP], &mvp_mat); glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &object_space_light); renderable_model.draw (&attrs, 0); glezz::uniform_3fv (unis [&ALBEDO], &green); renderable_model.draw (&attrs, 1); let draw_sky = true; if draw_sky { 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); renderable_sky.draw (&attrs, 0); } } window.gl_swap_window (); std::thread::sleep (Duration::from_millis (15)); } } #[cfg (test)] mod tests { use super::*; #[test] pub fn iqm () { } }