#[macro_use] extern crate maplit; use float_ord::FloatOrd; use glam::{Mat4, Quat, 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 shader_closure::*; 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 )) } struct EulerAngles { pub azimuth: f32, pub altitude: f32, } impl Default for EulerAngles { fn default () -> Self { Self { azimuth: 0.0, altitude: 0.0, } } } impl EulerAngles { pub fn to_vec3 (&self) -> Vec3 { let alt = self.altitude.to_radians (); let azi = self.azimuth.to_radians (); let z = alt.sin (); let xy_len = alt.cos (); let x = xy_len * -azi.sin (); let y = xy_len * azi.cos (); (x, y, z).into () } } // TODO: Use iota macro 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] } pub fn control_eulers ( &self, controlled_angle: &mut EulerAngles, spin_speed: i32 ) -> i32 { const SPIN_RAMP_TIME: i32 = 30; let spin_f = 4.0 * spin_speed as f32 / SPIN_RAMP_TIME as f32; if self.is_pressed (KEY_LEFT) { controlled_angle.azimuth += spin_f; } else if self.is_pressed (KEY_RIGHT) { controlled_angle.azimuth -= spin_f; } else if self.is_pressed (KEY_UP) { controlled_angle.altitude = f32::min (90.0, controlled_angle.altitude + spin_f); } else if self.is_pressed (KEY_DOWN) { controlled_angle.altitude = f32::max (-90.0, controlled_angle.altitude - spin_f); } else { return 0; } std::cmp::min (spin_speed + 1, SPIN_RAMP_TIME) } pub fn control_quat ( &self, controlled_quat: &mut Quat, spin_speed: i32 ) -> i32 { const SPIN_RAMP_TIME: i32 = 30; let spin_f = 2.0 * (spin_speed + 1) as f32 / SPIN_RAMP_TIME as f32; let spin_f = spin_f.to_radians (); let mut delta = Quat::default (); if self.is_pressed (KEY_LEFT) { delta = delta.mul_quat (Quat::from_rotation_y (-spin_f)); } if self.is_pressed (KEY_RIGHT) { delta = delta.mul_quat (Quat::from_rotation_y (spin_f)); } if self.is_pressed (KEY_UP) { delta = delta.mul_quat (Quat::from_rotation_x (-0.5 * spin_f)); } if self.is_pressed (KEY_DOWN) { delta = delta.mul_quat (Quat::from_rotation_x (spin_f)); } //println! ("spin_f {}, Quat {:?}", spin_f, delta); if delta == Quat::default () { 0 } else { *controlled_quat = (controlled_quat.mul_quat (delta)).normalize (); std::cmp::min (spin_speed + 1, SPIN_RAMP_TIME) } } } enum PlayMode { WindTunnel, FreeFlight, } #[derive (Copy, Clone, PartialEq, Eq)] enum UserControl { Camera, Wind, Airplane, Sunlight, } struct WindTunnelState { user_control: UserControl, camera: EulerAngles, wind: EulerAngles, airplane: EulerAngles, sunlight: EulerAngles, spin_speed: i32, } struct Airplane { vel: Vec3, pos: Vec3, ori: Quat, } struct FlightState { airplane: Airplane, spin_speed: i32, } impl FlightState { pub fn handle_event (&mut self, event: &sdl2::event::Event) { } pub fn step (&mut self, controller: &ControllerState) { self.spin_speed = controller.control_quat (&mut self.airplane.ori, self.spin_speed); let airplane = &mut self.airplane; // Info let nose = airplane.ori.mul_vec3 ((0.0, 1.0, 0.0).into ()); let speed = airplane.vel.length (); // Different from nose since planes are always drifting let direction = match speed { 0.0 => Vec3::from ((0.0, 0.0, 0.0)), _ => airplane.vel * (1.0 / speed), }; let object_space_dir = airplane.ori.conjugate ().mul_vec3 (direction); // Forces let gravity = Vec3::from ((0.0, 0.0, -0.25)); let thrust = nose; let laminar_drag = 0.5 * speed * -object_space_dir.y () * nose; let turbulent_dir = Vec3::from ((0.5 * object_space_dir.x (), 0.0, object_space_dir.z ())); let turbulent_drag = -speed * speed * airplane.ori.mul_vec3 (turbulent_dir); let air_drag = laminar_drag + turbulent_drag; // Accumulate forces and run an Euler integration step let dt = 1.0 / 60.0; airplane.vel += dt * (thrust + gravity + air_drag); airplane.pos += dt * airplane.vel; if airplane.pos.z () < 0.0 { airplane.vel.set_z (0.0); airplane.pos.set_z (0.0); } // Gauges let alti = airplane.pos.z () * 100.0; let airspeed = speed * 100.0; println! ("Alti: {}, Airspeed: {}", alti as i32, airspeed as i32 ); } } struct WorldState { play_mode: PlayMode, wind_tunnel: WindTunnelState, flight: FlightState, } impl WindTunnelState { pub fn handle_event (&mut self, event: &sdl2::event::Event) { match event { Event::KeyDown { keycode: Some (Keycode::C), .. } => { self.user_control = UserControl::Camera; }, Event::KeyDown { keycode: Some (Keycode::W), .. } => { self.user_control = UserControl::Wind; }, Event::KeyDown { keycode: Some (Keycode::P), .. } => { self.user_control = UserControl::Airplane; }, Event::KeyDown { keycode: Some (Keycode::L), .. } => { self.user_control = UserControl::Sunlight; }, _ => (), } } pub fn step ( &mut self, controller: &ControllerState ) { let controlled_angle = match self.user_control { UserControl::Camera => &mut self.camera, UserControl::Wind => &mut self.wind, UserControl::Airplane => &mut self.airplane, UserControl::Sunlight => &mut self.sunlight, }; self.spin_speed = controller.control_eulers (controlled_angle, self.spin_speed); } } impl WorldState { pub fn new () -> Self { Self { play_mode: PlayMode::FreeFlight, wind_tunnel: WindTunnelState { user_control: UserControl::Camera, camera: Default::default (), wind: Default::default (), airplane: Default::default (), sunlight: EulerAngles { altitude: 90.0, azimuth: 0.0, }, spin_speed: 0, }, flight: FlightState { airplane: Airplane { pos: (0.0, -0.5, 0.125).into (), vel: (0.0, 0.0, 0.0).into (), ori: Default::default (), }, spin_speed: 0, }, } } pub fn handle_event (&mut self, event: &sdl2::event::Event) { match event { Event::KeyDown { keycode: Some (Keycode::T), .. } => { self.play_mode = PlayMode::WindTunnel; }, Event::KeyDown { keycode: Some (Keycode::F), .. } => { self.play_mode = PlayMode::FreeFlight; }, _ => match self.play_mode { PlayMode::WindTunnel => self.wind_tunnel.handle_event (event), PlayMode::FreeFlight => self.flight.handle_event (event), }, } } pub fn step ( &mut self, controller: &ControllerState ) { match self.play_mode { PlayMode::WindTunnel => self.wind_tunnel.step (controller), PlayMode::FreeFlight => self.flight.step (controller), } } } 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 Arrow { origin: Vec3, direction: Vec3, color: Vec3, } struct RenderableArrow { model_mat: Mat4, inv_model_mat: Mat4, color: Vec3, } #[derive (Copy, Clone, PartialEq, Eq)] pub enum FrontFace { Cw, Ccw, } impl From for u32 { fn from (v: FrontFace) -> Self { use FrontFace::*; { use gl::*; match v { Cw => CW, Ccw => CCW, } } } } #[derive (Copy, Clone, PartialEq, Eq)] pub enum StencilOp { Keep, Zero, Replace, Incr, Decr, Invert, IncrWrap, DecrWrap, } impl From for u32 { fn from (v: StencilOp) -> Self { use StencilOp::*; { use gl::*; match v { Keep => KEEP, Zero => ZERO, Replace => REPLACE, Incr => INCR, Decr => DECR, Invert => INVERT, IncrWrap => INCR_WRAP, DecrWrap => DECR_WRAP, } } } } #[derive (Copy, Clone, PartialEq, Eq)] pub enum StencilFunc { Never, Always, Less, LessEqual, Equal, Greater, GreaterEqual, NotEqual, } impl From for u32 { fn from (v: StencilFunc) -> Self { use StencilFunc::*; { use gl::*; match v { Never => NEVER, Always => ALWAYS, Less => LESS, LessEqual => LEQUAL, Equal => EQUAL, Greater => GREATER, GreaterEqual => GEQUAL, NotEqual => NOTEQUAL, } } } } #[derive (Copy, Clone, PartialEq, Eq)] pub enum DepthFunc { Never, Always, Less, LessEqual, Equal, Greater, GreaterEqual, NotEqual, } impl From for u32 { fn from (v: DepthFunc) -> Self { use DepthFunc::*; { use gl::*; match v { Never => NEVER, Always => ALWAYS, Less => LESS, LessEqual => LEQUAL, Equal => EQUAL, Greater => GREATER, GreaterEqual => GEQUAL, NotEqual => NOTEQUAL, } } } } #[derive (Copy, Clone, PartialEq, Eq)] pub struct StencilOpState { sfail: StencilOp, dpfail: StencilOp, dppass: StencilOp, } #[derive (Copy, Clone, PartialEq, Eq)] pub struct StencilFuncState { func: StencilFunc, reference: i32, mask: u32, } #[derive (Copy, Clone, PartialEq, Eq)] pub struct StencilState { op: StencilOpState, func: StencilFuncState, } // These are POD where no extra data is needed // to safely use the flags / numbers pub struct IsoGlState { shader_id: Option , flags: HashMap , front_face: Option , stencil: Option , depth_func: Option , color_mask: Option <(u8, u8, u8, u8)>, depth_mask: Option , stencil_mask: Option , } impl std::default::Default for IsoGlState { fn default () -> Self { Self { shader_id: None, flags: hashmap! {}, front_face: None, stencil: None, depth_func: None, color_mask: None, depth_mask: None, stencil_mask: None, } } } // These are POD IDs or hashes of non-POD data pub struct NonIsoGlState { } impl std::default::Default for NonIsoGlState { fn default () -> Self { Self { } } } pub struct GlState { iso: IsoGlState, non_iso: NonIsoGlState, } impl std::default::Default for GlState { fn default () -> Self { Self { iso: Default::default (), non_iso: Default::default (), } } } pub struct Pass { // In the context of a Pass, "None" means "Don't care" iso: IsoGlState, } impl Pass { pub fn apply_diff (&self, old_state: &mut IsoGlState) { let state = &self.iso; let mut flag_elision_count = 0; for (flag, value) in state.flags.iter () { let old_entry = old_state.flags.entry (*flag); if match &old_entry { hash_map::Entry::Vacant (_) => true, hash_map::Entry::Occupied (o) => o.get () != value, } { if *value { glezz::enable (*flag); } else { glezz::disable (*flag); } old_entry.or_insert (*value); } else { flag_elision_count += 1; } } if flag_elision_count > 0 { //println! ("Elided {} flags", flag_elision_count); } if let Some (v) = &state.front_face { if old_state.front_face != state.front_face { glezz::front_face ((*v).into ()); old_state.front_face = state.front_face; } else { //println! ("Elided front_face ()"); } } if let Some (v) = &state.stencil { if old_state.stencil != state.stencil { let func = &v.func; unsafe { gl::StencilFunc ( func.func.into (), func.reference.into (), func.mask.into () ); } let op = &v.op; unsafe { gl::StencilOp ( op.sfail.into (), op.dpfail.into (), op.dppass.into () ); } } else { //println! ("Elided stencil state"); } } if let Some (v) = &state.depth_func { if old_state.depth_func != state.depth_func { glezz::depth_func ((*v).into ()); old_state.depth_func = state.depth_func; } else { //println! ("Elided depth_func ()"); } } if let Some ((r, g, b, a)) = &state.color_mask { if old_state.color_mask != state.color_mask { glezz::color_mask (*r, *g, *b, *a); old_state.color_mask = state.color_mask; } else { //println! ("Elided color_mask ()"); } } if let Some (v) = &state.depth_mask { if old_state.depth_mask != state.depth_mask { glezz::depth_mask (*v); old_state.depth_mask = state.depth_mask; } else { //println! ("Elided depth_mask ()"); } } if let Some (v) = &state.stencil_mask { if old_state.stencil_mask != state.stencil_mask { glezz::stencil_mask (*v); old_state.stencil_mask = state.stencil_mask; } else { //println! ("Elided stencil_mask ()"); } } } pub fn apply_slow (&self) { let mut iso = IsoGlState::default (); self.apply_diff (&mut iso); } pub fn with (&self, gl_state: &mut GlState, callback: F) where F: Fn () { self.apply_diff (&mut gl_state.iso); callback (); } pub fn with_shader ( &self, gl_state: &mut GlState, shader_component: &S, callback: F ) where F: Fn (BorrowedShaderVars), S: ShaderLookup { if let Some (s) = self.iso.shader_id { self.apply_diff (&mut gl_state.iso); shader_component.lookup (s).with (gl_state.iso.shader_id, |shader_vars| { callback (shader_vars); }); gl_state.iso.shader_id = Some (s); } else { panic! ("Called with_shader on a pass with no shader"); } } } pub trait ShaderLookup { fn lookup <'a> (&'a self, id: u32) -> &'a ShaderClosure; } struct GameGraphics { passes: Vec , shaders: Vec , shader_lookup: HashMap , mesh_airplane: RenderableModel, mesh_sky: RenderableModel, mesh_pitch: RenderableModel, mesh_arrow: RenderableModel, texture_sky: Texture, texture_grass: Texture, pitch_colors: Vec , grass_index: usize, } impl ShaderLookup for GameGraphics { fn lookup <'a> (&'a self, id: u32) -> &'a ShaderClosure { &self.shaders [self.shader_lookup [&id]] } } impl GameGraphics { pub fn new () -> Self { 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 shaders: Vec <_> = [ ("shaders/pumpkin-vert.glsl", "shaders/pumpkin-frag.glsl"), ("shaders/shadow-vert.glsl", "shaders/shadow-frag.glsl"), ].iter () .map (|(v, f)| { ShaderClosure::new (shader_from_files (v, f), &uniform_names, &attr_names) }) .collect (); let shader_lookup = HashMap::from_iter (shaders.iter ().enumerate () .map (|(i, s)| { (s.get_id (), i) })); shaders [0].with (None, |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 mesh_airplane = renderable_from_iqm_file ("airplane.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 texture_sky = Texture::from_file ("sky.png"); let texture_grass = Texture::from_file ("grass.png"); 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 ()) }; let passes = vec! [ // Clear everything Pass { iso: IsoGlState { shader_id: None, flags: hashmap! {}, front_face: None, stencil: None, depth_func: None, color_mask: Some ((1, 1, 1, 1)), depth_mask: Some (1), stencil_mask: Some (255), }, }, // Draw world Pass { iso: IsoGlState { shader_id: Some (shaders [0].get_id ()), flags: hashmap! { gl::CULL_FACE => true, gl::DEPTH_TEST => true, gl::TEXTURE_2D => true, gl::STENCIL_TEST => false, }, front_face: Some (FrontFace::Cw), stencil: Some (StencilState { func: StencilFuncState { func: StencilFunc::Always, reference: 0, mask: 0, }, op: StencilOpState { sfail: StencilOp::Keep, dpfail: StencilOp::Keep, dppass: StencilOp::Keep, }, }), depth_func: Some (DepthFunc::Less), color_mask: Some ((1, 1, 1, 1)), depth_mask: Some (1), stencil_mask: Some (0), }, }, // Write shadows into stencil buffer Pass { iso: IsoGlState { shader_id: Some (shaders [1].get_id ()), flags: hashmap! { gl::CULL_FACE => true, gl::DEPTH_TEST => true, gl::TEXTURE_2D => false, gl::STENCIL_TEST => true, }, front_face: Some (FrontFace::Ccw), stencil: Some (StencilState { func: StencilFuncState { func: StencilFunc::Always, reference: 1, mask: 1, }, op: StencilOpState { sfail: StencilOp::Keep, dpfail: StencilOp::Keep, dppass: StencilOp::Replace, }, }), depth_func: Some (DepthFunc::Less), color_mask: Some ((0, 0, 0, 0)), depth_mask: Some (0), stencil_mask: Some (255), }, }, // Draw lit ground Pass { iso: IsoGlState { shader_id: Some (shaders [0].get_id ()), flags: hashmap! { gl::CULL_FACE => true, gl::DEPTH_TEST => true, gl::TEXTURE_2D => true, gl::STENCIL_TEST => true, }, front_face: Some (FrontFace::Cw), stencil: Some (StencilState { func: StencilFuncState { func: StencilFunc::NotEqual, reference: 0, mask: 1, }, op: StencilOpState { sfail: StencilOp::Keep, dpfail: StencilOp::Keep, dppass: StencilOp::Keep, }, }), depth_func: Some (DepthFunc::Less), color_mask: Some ((1, 1, 1, 1)), depth_mask: Some (1), stencil_mask: Some (0), }, }, // Draw unlit ground Pass { iso: IsoGlState { shader_id: Some (shaders [0].get_id ()), flags: hashmap! { gl::CULL_FACE => true, gl::DEPTH_TEST => true, gl::TEXTURE_2D => true, gl::STENCIL_TEST => true, }, front_face: Some (FrontFace::Cw), stencil: Some (StencilState { func: StencilFuncState { func: StencilFunc::Equal, reference: 0, mask: 1, }, op: StencilOpState { sfail: StencilOp::Keep, dpfail: StencilOp::Keep, dppass: StencilOp::Keep, }, }), depth_func: Some (DepthFunc::Less), color_mask: Some ((1, 1, 1, 1)), depth_mask: Some (1), stencil_mask: Some (0), }, }, // Clear depth Pass { iso: IsoGlState { shader_id: None, flags: hashmap! {}, front_face: None, stencil: None, depth_func: None, color_mask: None, depth_mask: Some (1), stencil_mask: None, }, }, // Draw arrows Pass { iso: IsoGlState { shader_id: Some (shaders [0].get_id ()), flags: hashmap! { gl::CULL_FACE => true, gl::DEPTH_TEST => true, gl::TEXTURE_2D => true, gl::STENCIL_TEST => false, }, front_face: Some (FrontFace::Cw), stencil: Some (StencilState { func: StencilFuncState { func: StencilFunc::Always, reference: 0, mask: 0, }, op: StencilOpState { sfail: StencilOp::Keep, dpfail: StencilOp::Keep, dppass: StencilOp::Keep, }, }), depth_func: Some (DepthFunc::Less), color_mask: Some ((1, 1, 1, 1)), depth_mask: Some (1), stencil_mask: Some (0), }, }, ]; Self { passes, shaders, shader_lookup, mesh_airplane, mesh_sky, mesh_pitch, mesh_arrow, texture_sky, texture_grass, pitch_colors, grass_index, } } pub fn draw ( &self, state: &WorldState, gl_state: &mut GlState, arrows: &[RenderableArrow] ) { let magenta = color_from_255 ((255.0, 0.0, 255.0)); 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 light = state.wind_tunnel.sunlight.to_vec3 (); let shadow_mat = { let mut mat = Mat4::identity (); mat.set_z_axis ((-light.x () / light.z (), -light.y () / light.z (), 0.0, 0.0).into ()); mat }; let mut passes = self.passes.iter (); //println! ("Started frame"); passes.next ().unwrap ().with (gl_state, || { 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); }); let airplane_model_mat = match state.play_mode { PlayMode::WindTunnel => { let euler = &state.wind_tunnel.airplane; Mat4::from_translation ((0.0, 0.0, 2.7 * 0.5).into ()) * Mat4::from_rotation_z (euler.azimuth.to_radians ()) * Mat4::from_rotation_x (euler.altitude.to_radians ()) }, PlayMode::FreeFlight => { let airplane = &state.flight.airplane; Mat4::from_translation (airplane.pos) * Mat4::from_quat (airplane.ori) }, }; let inverse_airplane = airplane_model_mat.inverse (); let proj_mat = Mat4::perspective_rh_gl (30.0f32.to_radians (), 1280.0 / 720.0, 0.125, 200.0); let airplane_scale = 1.0 / 128.0; let view_mat = match state.play_mode { PlayMode::WindTunnel => { let state = &state.wind_tunnel; let longitude = state.camera.azimuth.to_radians (); let latitude = (state.camera.altitude - 90.0).to_radians (); 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 ()) }, PlayMode::FreeFlight => { proj_mat * Mat4::from_translation (Vec3::from ((0.0, -4.8, -32.0)) * airplane_scale) * Mat4::from_rotation_x (-90.0f32.to_radians ()) * inverse_airplane }, }; let airplane_model_mat = airplane_model_mat * Mat4::from_scale ((airplane_scale, airplane_scale, airplane_scale).into ()); let world_model_mat = Mat4::identity (); use uniforms::*; // Draw the world except the ground plane passes.next ().unwrap ().with_shader (gl_state, self, |shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; let mvp = view_mat * airplane_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); let object_space_light = make_object_space_vec (&inverse_airplane, &light); let object_space_sky = make_object_space_vec (&inverse_airplane, &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); let gunmetal_grey = color_from_255 ((133.0, 149.0, 161.0)); glezz::uniform_3fv (unis [&ALBEDO], &gunmetal_grey); self.mesh_airplane.draw_all (attrs, |i| { true }); let mvp = view_mat * world_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &light); glezz::uniform_3fv (unis [&OBJECT_SPACE_SKY], &Vec3::from ((0.0, 0.0, 1.0))); self.mesh_pitch.draw_all (attrs, |i| { glezz::uniform_3fv (unis [&ALBEDO], &self.pitch_colors [i]); i != self.grass_index }); let draw_sky = true; if draw_sky { let sky_mvp_mat = view_mat * Mat4::from_scale ((64.0, 64.0, 64.0).into ()); self.texture_sky.bind (); 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); self.mesh_sky.draw_all (attrs, |_| true); } }); // Draw shadows into stencil buffer passes.next ().unwrap ().with_shader (gl_state, self, |shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; let view_mat = view_mat * shadow_mat; let mvp = view_mat * airplane_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); self.mesh_airplane.draw_all (attrs, |_| true); let mvp = view_mat * world_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); self.mesh_pitch.draw_all (attrs, |i| i != self.grass_index); for arrow in arrows.iter () { let mvp = view_mat * arrow.model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); self.mesh_arrow.draw_all (attrs, |_| true); } }); // Draw unlit ground passes.next ().unwrap ().with_shader (gl_state, self, |shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; glezz::uniform_3fv (unis [&MIN_BRIGHT], &black); glezz::uniform_3fv (unis [&MIN_ALBEDO], &black); glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &light); glezz::uniform_3fv (unis [&OBJECT_SPACE_SKY], &Vec3::from ((0.0, 0.0, 1.0))); let mvp = view_mat * world_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); glezz::uniform_3fv (unis [&ALBEDO], &self.pitch_colors [self.grass_index]); glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &Vec3::from ((0.0, 0.0, 0.0))); self.mesh_pitch.draw (attrs, self.grass_index); }); self.texture_grass.bind (); // Draw lit ground passes.next ().unwrap ().with_shader (gl_state, self, |shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &light); glezz::uniform_3fv (unis [&OBJECT_SPACE_SKY], &Vec3::from ((0.0, 0.0, 1.0))); self.mesh_pitch.draw (attrs, self.grass_index); }); // Clear depth passes.next ().unwrap ().with (gl_state, || { glezz::clear (gl::DEPTH_BUFFER_BIT); }); // Draw arrows passes.next ().unwrap ().with_shader (gl_state, self, |shader_vars| { let unis = &shader_vars.unis; glezz::uniform_3fv (unis [&MIN_BRIGHT], &black); glezz::uniform_3fv (unis [&MIN_ALBEDO], &black); for arrow in arrows.iter () { let mvp = view_mat * arrow.model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); let object_space_light = make_object_space_vec (&arrow.inv_model_mat, &light); let object_space_sky = make_object_space_vec (&arrow.inv_model_mat, &Vec3::from ((0.0, 0.0, 1.0))); glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &object_space_light); glezz::uniform_3fv (unis [&OBJECT_SPACE_SKY], &object_space_sky); glezz::uniform_3fv (unis [&ALBEDO], &arrow.color); self.mesh_arrow.draw_all (shader_vars.attrs, |_| true); } }); } } 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 mut time_step = TimeStep::new (60, 1000); let mut state = WorldState::new (); let graphics = GameGraphics::new (); let mut gl_state = Default::default (); let mut graphics_frames = 0; let mut event_pump = sdl_context.event_pump ().unwrap (); 'running: loop { let frames_to_do = time_step.step (); 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 }, _ => state.handle_event (&event), } } let controller = ControllerState::from_sdl_keyboard (&event_pump.keyboard_state ()); for _ in 0..frames_to_do { state.step (&controller); } let control_flash = if graphics_frames % 16 >= 8 { (1.0, 1.0, 1.0).into () } else { (1.0, 0.0, 0.0).into () }; let arrows = match state.play_mode { PlayMode::WindTunnel => { let state = &state.wind_tunnel; let purple = (1.0, 0.5, 1.0).into (); let origin: Vec3 = (0.0, 0.0, 1.35).into (); let gravity = (0.0, 0.0, -1.0).into (); let wind = state.wind.to_vec3 () * -1.0; let wind_force = (wind.x (), 0.125 * wind.y (), wind.z ()).into (); let get_flash = |control_type, default_color| { if state.user_control == control_type { control_flash } else { default_color } }; vec![ Arrow { origin: (0.0, 0.0, 1.35).into (), direction: gravity, color: (1.0, 0.5, 0.5).into (), }, Arrow { origin: origin + wind * -2.0, direction: wind, color: get_flash (UserControl::Wind, purple), }, Arrow { origin: origin, direction: wind_force, color: purple, }, Arrow { origin: origin, direction: state.airplane.to_vec3 () * 0.5, color: get_flash (UserControl::Airplane, (0.0, 0.0, 0.0).into ()), } ] }, _ => vec![], }; let renderable_arrows: Vec <_> = arrows.iter ().map (|arrow| { let dir_len = arrow.direction.length (); let d = arrow.direction / dir_len; let up: Vec3 = if d.z () > 0.5 { (-1.0, 0.0, 0.0) } else if d.z () < -0.5 { (-1.0, 0.0, 0.0) } else { (0.0, 0.0, 1.0) }.into (); // These are probably all fucked let left = d.cross (up); let up = d.cross (left); let mut dir_mat = Mat4::identity (); dir_mat.set_x_axis ((left.x (), left.y (), left.z (), 0.0).into ()); dir_mat.set_y_axis ((up.x (), up.y (), up.z (), 0.0).into ()); dir_mat.set_z_axis ((d.x (), d.y (), d.z (), 0.0).into ()); let s = dir_len * 0.0625; let model_mat = Mat4::from_translation (arrow.origin) * Mat4::from_scale ((s, s, s).into ()) * dir_mat; let inv_model_mat = model_mat.inverse (); RenderableArrow { model_mat, inv_model_mat, color: arrow.color, } }).collect (); window.gl_make_current (&gl_ctx).unwrap (); graphics.draw (&state, &mut gl_state, &renderable_arrows); window.gl_swap_window (); graphics_frames += 1; std::thread::sleep (Duration::from_millis (15)); } } #[cfg (test)] mod tests { use super::*; #[test] pub fn sizes () { use std::mem; assert_eq! (8, mem::size_of::