#[macro_use] extern crate maplit; 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 gl_state::*; use renderable_model::*; use shader::*; 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 () } } mod keys { use iota::iota; iota! { pub const KEY_LEFT: usize = iota; , KEY_RIGHT , KEY_UP , KEY_DOWN , YAW_LEFT , YAW_RIGHT } } struct ControllerState { keys: Vec , analog_left_x: i16, analog_left_y: i16, analog_right_x: i16, analog_right_y: i16, trigger_left: i16, trigger_right: i16, } impl ControllerState { pub fn new ( k: &sdl2::keyboard::KeyboardState, c: &Option ) -> Self { use sdl2::controller::*; let f = |code| k.is_scancode_pressed (code); let b = |code| match c { None => false, Some (c) => c.button (code), }; let key_or_gamepad = |key, button| f (key) || b (button); let axis_or_zero = |a| c.as_ref ().map_or (0, |c| c.axis (a)); Self { keys: vec! [ key_or_gamepad (Scancode::Left, Button::DPadLeft), key_or_gamepad (Scancode::Right, Button::DPadRight), key_or_gamepad (Scancode::Up, Button::DPadUp), key_or_gamepad (Scancode::Down, Button::DPadDown), b (Button::LeftShoulder), b (Button::RightShoulder), ], analog_left_x: axis_or_zero (Axis::LeftX), analog_left_y: axis_or_zero (Axis::LeftY), analog_right_x: axis_or_zero (Axis::RightX), analog_right_y: axis_or_zero (Axis::RightY), trigger_left: axis_or_zero (Axis::TriggerLeft), trigger_right: axis_or_zero (Axis::TriggerRight), } } pub fn is_pressed (&self, code: usize) -> bool { self.keys [code] } pub fn control_eulers ( &self, controlled_angle: &mut EulerAngles, spin_speed: i32 ) -> i32 { use keys::*; 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 { use keys::*; 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)); } if self.is_pressed (YAW_LEFT) { delta = delta.mul_quat (Quat::from_rotation_z (spin_f)); } if self.is_pressed (YAW_RIGHT) { delta = delta.mul_quat (Quat::from_rotation_z (-spin_f)); } //println! ("spin_f {}, Quat {:?}", spin_f, delta); if delta == Quat::default () { let analog_scale = 1.0f32.to_radians () / 32768.0; delta = delta.mul_quat (Quat::from_rotation_y (self.analog_left_x as f32 * analog_scale)); delta = delta.mul_quat (Quat::from_rotation_x (self.analog_left_y as f32 * analog_scale)); *controlled_quat = (controlled_quat.mul_quat (delta)).normalize (); 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 { frames: u64, airplane: Airplane, spin_speed: i32, arrows: Vec , lookaround: EulerAngles, } impl Default for FlightState { fn default () -> Self { Self { frames: 0, airplane: Airplane { pos: (0.0, -20.0, 20.0).into (), vel: (0.0, 0.0, 0.0).into (), ori: Default::default (), }, spin_speed: 0, arrows: vec![], lookaround: Default::default (), } } } 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); self.lookaround.altitude = controller.analog_right_y as f32 * -90.0 / 32768.0; self.lookaround.azimuth = controller.analog_right_x as f32 * 180.0 / 32768.0; let throttle = 1.0 + (controller.trigger_right as f32 - controller.trigger_left as f32) / 32768.0; let airplane = &mut self.airplane; let microsteps = 4; for microstep in 0..microsteps { // 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 = if speed == 0.0 { Vec3::from ((0.0, 0.0, 0.0)) } else { airplane.vel * (1.0 / speed) }; let inverse_ori = airplane.ori.conjugate (); let object_space_dir = inverse_ori.mul_vec3 (direction); // Forces let gravity = Vec3::from ((0.0, 0.0, -0.25)); let thrust = 0.125 * nose * throttle; let linear_drag = 0.0 * 0.25 * speed * -object_space_dir.y () * nose; let turbulent_dir = Vec3::from ((-1.0 * object_space_dir.x (), -0.03125 * object_space_dir.y (), -16.0 * object_space_dir.z ())); let quadratic_drag = speed * speed * airplane.ori.mul_vec3 (turbulent_dir); let air_drag = linear_drag + quadratic_drag; // Accumulate forces and run an Euler integration step let dt = 1.0 / 60.0 / microsteps as f32; 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); } if microstep == microsteps - 1 { let make_arrow = |direction, color| Arrow { origin: airplane.pos.clone (), direction: direction * 0.125, color, }; self.arrows = vec! [ make_arrow (gravity, color_from_255 ((128.0, 128.0, 128.0))), make_arrow (thrust, color_from_255 ((255.0, 128.0, 0.0))), make_arrow (linear_drag, color_from_255 ((128.0, 128.0, 128.0))), make_arrow (quadratic_drag, color_from_255 ((0.0, 255.0, 255.0))), ]; // Gauges let alti = airplane.pos.z () * 100.0; let sink_rate = -airplane.vel.z () * 100.0; let air_speed = speed * 100.0; let ground_vel = Vec3::from ((airplane.vel.x (), airplane.vel.y (), 0.0)); let ground_speed = ground_vel.length () * 100.0; let glide_ratio = if sink_rate > 1.0 && throttle == 0.0 { Some (ground_speed / sink_rate) } else { None }; println! ("Alti: {}, Airspeed: {}, Groundspeed: {}, Throttle: {}, Sink Rate: {}, Glide Ratio: {:?}\nLaminar: {}, Turbulent: {}", alti as i32, air_speed as i32, ground_speed as i32, (throttle * 100.0) as i32, sink_rate as i32, glide_ratio, (linear_drag.length () * 100.0) as i32, (quadratic_drag.length () * 100.0) as i32 ); } } self.frames += 1; } } 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: Default::default (), } } 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 ())) } #[derive (Clone)] struct Arrow { origin: Vec3, direction: Vec3, color: Vec3, } struct RenderableArrow { model_mat: Mat4, inv_model_mat: Mat4, color: Vec3, } struct GameGraphics { passes: Vec , shaders: Vec , shader_lookup: HashMap , mesh_airplane: RenderableModel, mesh_sky: RenderableModel, mesh_pitch: RenderableModel, mesh_arrow: RenderableModel, mesh_truck: RenderableModel, texture_sky: Texture, texture_grass: Texture, texture_font: 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 mesh_truck = renderable_from_iqm_file ("truk.iqm"); let texture_sky = Texture::from_file ("sky.png"); let texture_grass = Texture::from_file ("grass.png"); let texture_font = Texture::from_file ("font.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 => false, 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), }, }, // 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 UI Pass { iso: IsoGlState { shader_id: Some (shaders [0].get_id ()), flags: hashmap! { gl::CULL_FACE => false, gl::DEPTH_TEST => false, gl::TEXTURE_2D => true, gl::STENCIL_TEST => false, }, front_face: None, stencil: None, 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, mesh_truck, texture_sky, texture_grass, texture_font, 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 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 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 screen_size = (1280.0, 720.0); let proj_mat = Mat4::perspective_rh_gl (30.0f32.to_radians (), screen_size.0 / screen_size.1, 0.125, 200.0); let airplane_scale = 1.0 / 8.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, -20.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, -1.2, -8.0)) * airplane_scale) * Mat4::from_rotation_x (state.flight.lookaround.altitude.to_radians ()) * Mat4::from_rotation_y (state.flight.lookaround.azimuth.to_radians ()) * 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 truck_model_mat = Mat4::from_rotation_z ((state.flight.frames as f32).to_radians ()) * Mat4::from_translation (Vec3::from ((1.0, 0.0, 0.0))) ; let inverse_truck = truck_model_mat.inverse (); let truck_model_mat = truck_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 gunmetal_grey = color_from_255 ((133.0, 149.0, 161.0)); glezz::uniform_3fv (unis [&ALBEDO], &gunmetal_grey); glezz::uniform_3fv (unis [&MIN_BRIGHT], &black); glezz::uniform_3fv (unis [&MIN_ALBEDO], &white); { 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 [&OBJECT_SPACE_LIGHT], &object_space_light); glezz::uniform_3fv (unis [&OBJECT_SPACE_SKY], &object_space_sky); self.mesh_airplane.draw_all (attrs, |_i| { true }); } { let mvp = view_mat * truck_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); let object_space_light = make_object_space_vec (&inverse_truck, &light); let object_space_sky = make_object_space_vec (&inverse_truck, &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); self.mesh_truck.draw_all (attrs, |_| { 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 * truck_model_mat; glezz::uniform_matrix_4fv (unis [&MVP], &mvp); self.mesh_truck.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); } }); self.texture_grass.bind (); // 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], &white); glezz::uniform_3fv (unis [&OBJECT_SPACE_LIGHT], &Vec3::from ((0.0, 0.0, 0.0))); self.mesh_pitch.draw (attrs, self.grass_index); }); // 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 [&ALBEDO], &white); 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], &white); 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); } }); // Clear depth passes.next ().unwrap ().with (gl_state, || { glezz::clear (gl::DEPTH_BUFFER_BIT); }); // Draw UI self.texture_font.bind (); if true { passes.next ().unwrap ().with_shader (gl_state, self, |shader_vars| { let attrs = &shader_vars.attrs; let unis = &shader_vars.unis; glezz::uniform_3fv (unis [&MIN_BRIGHT], &white); glezz::uniform_3fv (unis [&MIN_ALBEDO], &black); glezz::uniform_3fv (unis [&ALBEDO], &white); let font_size = (8.0, 18.0); let mvp = Mat4::from_scale ((2.0 * 256.0 / screen_size.0, 2.0 * 72.0 / screen_size.1, 1.0).into ()); glezz::uniform_matrix_4fv (unis [&MVP], &mvp); let pos: Vec = vec! [ 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, ]; use std::convert::TryInto; let uv: Vec = vec! [ 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, ]; let indices: Vec = vec! [ 0, 1, 2, 0, 2, 3, ]; unsafe { use renderable_model::attributes::*; use std::ffi::c_void; gl::BindBuffer (gl::ARRAY_BUFFER, 0); gl::BindBuffer (gl::ELEMENT_ARRAY_BUFFER, 0); gl::VertexAttribPointer (attrs [POS].unwrap (), 3, gl::FLOAT, 0u8, 4 * 3, &pos [0] as *const f32 as *const c_void); gl::VertexAttribPointer (attrs [UV].unwrap (), 2, gl::FLOAT, 0u8, 4 * 2, &uv [0] as *const f32 as *const c_void); gl::DrawRangeElements (gl::TRIANGLES, 0, 6, 6, gl::UNSIGNED_INT, &indices [0] as *const u32 as *const c_void); } }); } } } 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 controller_subsystem = sdl_context.game_controller ().unwrap (); let controller = controller_subsystem.open (0).ok (); 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::new ( &event_pump.keyboard_state (), &controller ); 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 ()), } ] }, PlayMode::FreeFlight => state.flight.arrows.clone (), }; 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::