diff --git a/src/bin/pumpkin.rs b/src/bin/pumpkin.rs index d7bec29..3fd7dff 100644 --- a/src/bin/pumpkin.rs +++ b/src/bin/pumpkin.rs @@ -32,6 +32,7 @@ where V: Into )) } +// TODO: Use iota macro const KEY_LEFT: usize = 0; const KEY_RIGHT: usize = KEY_LEFT + 1; const KEY_UP: usize = KEY_RIGHT + 1; @@ -60,41 +61,79 @@ impl ControllerState { } } +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 * std::f32::consts::PI / 180.0; + let azi = self.azimuth * std::f32::consts::PI / 180.0; + + 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 () + } +} + struct WorldState { - azimuth: f32, - altitude: f32, + camera: EulerAngles, + wind: EulerAngles, spin_speed: i32, } impl WorldState { pub fn new () -> Self { Self { - azimuth: 0.0, - altitude: 0.0, + camera: Default::default (), + wind: Default::default (), spin_speed: 0, } } - pub fn step (&mut self, controller: &ControllerState) { + pub fn step ( + &mut self, + controller: &ControllerState, + user_control: &UserControl + ) { const SPIN_RAMP_TIME: i32 = 30; let spin_f = 4.0 * self.spin_speed as f32 / SPIN_RAMP_TIME as f32; + let controlled_angle = match user_control { + UserControl::Camera => &mut self.camera, + UserControl::Wind => &mut self.wind, + }; + if controller.is_pressed (KEY_LEFT) { self.spin_speed = std::cmp::min (self.spin_speed + 1, SPIN_RAMP_TIME); - self.azimuth += spin_f; + controlled_angle.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; + controlled_angle.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); + controlled_angle.altitude = f32::min (90.0, controlled_angle.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); + controlled_angle.altitude = f32::max (-90.0, controlled_angle.altitude - spin_f); } else { self.spin_speed = 0; @@ -145,11 +184,13 @@ where P: AsRef struct Arrow { origin: Vec3, direction: Vec3, + color: Vec3, } struct RenderableArrow { model_mat: Mat4, inv_model_mat: Mat4, + color: Vec3, } #[derive (Copy, Clone, PartialEq, Eq)] @@ -233,6 +274,37 @@ impl From for u32 { } } +#[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, @@ -257,9 +329,12 @@ pub struct StencilState { // 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 , @@ -269,9 +344,11 @@ pub struct IsoGlState { 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, @@ -282,13 +359,11 @@ impl std::default::Default for IsoGlState { // These are POD IDs or hashes of non-POD data pub struct NonIsoGlState { - shader_id: Option , } impl std::default::Default for NonIsoGlState { fn default () -> Self { Self { - shader_id: None, } } } @@ -307,17 +382,13 @@ impl std::default::Default for GlState { } } -pub struct Pass <'a> { +pub struct Pass { // In the context of a Pass, "None" means "Don't care" iso: IsoGlState, - - // Non-iso state must have its sources here - - shader: Option <&'a ShaderClosure>, } -impl Pass <'_> { +impl Pass { pub fn apply_diff (&self, old_state: &mut IsoGlState) { let state = &self.iso; @@ -381,6 +452,16 @@ impl Pass <'_> { } } + 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); @@ -425,15 +506,20 @@ impl Pass <'_> { callback (); } - pub fn with_shader (&self, gl_state: &mut GlState, callback: F) - where F: Fn (BorrowedShaderVars) + 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.shader { + if let Some (s) = self.iso.shader_id { self.apply_diff (&mut gl_state.iso); - s.with (gl_state.non_iso.shader_id, |shader_vars| { + shader_component.lookup (s).with (gl_state.iso.shader_id, |shader_vars| { callback (shader_vars); }); - gl_state.non_iso.shader_id = Some (s.get_id ()); + gl_state.iso.shader_id = Some (s); } else { panic! ("Called with_shader on a pass with no shader"); @@ -441,9 +527,15 @@ impl Pass <'_> { } } +pub trait ShaderLookup { + fn lookup <'a> (&'a self, id: u32) -> &'a ShaderClosure; +} + struct GameGraphics { - shader_diffuse: ShaderClosure, - shader_shadow: ShaderClosure, + passes: Vec , + + shaders: Vec , + shader_lookup: HashMap , mesh_pumpkin: RenderableModel, mesh_sky: RenderableModel, @@ -456,6 +548,12 @@ struct GameGraphics { 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 = { @@ -480,10 +578,21 @@ impl GameGraphics { ] }; - 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); + 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 (); - shader_diffuse.with (None, |shader_vars| { + 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]); @@ -538,9 +647,183 @@ impl GameGraphics { (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::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 { - shader_diffuse, - shader_shadow, + passes, + shaders, + shader_lookup, mesh_pumpkin, mesh_sky, @@ -574,8 +857,8 @@ impl GameGraphics { green, ]; - let longitude = state.azimuth.to_radians (); - let latitude = (state.altitude - 90.0).to_radians (); + let longitude = state.camera.azimuth.to_radians (); + let latitude = (state.camera.altitude - 90.0).to_radians (); let proj_mat = Mat4::perspective_rh_gl (30.0f32.to_radians (), 1280.0 / 720.0, 0.5, 500.0); @@ -598,122 +881,9 @@ impl GameGraphics { //println! ("Started frame"); - let passes = vec! [ - // Clear everything - Pass { - shader: None, - iso: IsoGlState { - flags: hashmap! {}, - front_face: None, - stencil: None, - color_mask: Some ((1, 1, 1, 1)), - depth_mask: Some (1), - stencil_mask: Some (255), - }, - }, - // Draw world - Pass { - shader: Some (&self.shader_diffuse), - iso: IsoGlState { - flags: hashmap! { - gl::CULL_FACE => true, - gl::DEPTH_TEST => true, - gl::TEXTURE_2D => true, - gl::STENCIL_TEST => false, - }, - front_face: Some (FrontFace::Cw), - stencil: None, - color_mask: Some ((1, 1, 1, 1)), - depth_mask: Some (1), - stencil_mask: Some (0), - }, - }, - // Write shadows into stencil buffer - Pass { - shader: Some (&self.shader_shadow), - iso: IsoGlState { - flags: hashmap! { - gl::CULL_FACE => true, - gl::DEPTH_TEST => true, - 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, - }, - }), - color_mask: Some ((0, 0, 0, 0)), - depth_mask: Some (0), - stencil_mask: Some (255), - }, - }, - // Draw lit ground - Pass { - shader: Some (&self.shader_diffuse), - iso: IsoGlState { - 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, - }, - }), - color_mask: Some ((1, 1, 1, 1)), - depth_mask: Some (1), - stencil_mask: Some (0), - }, - }, - // Draw unlit ground - Pass { - shader: Some (&self.shader_diffuse), - iso: IsoGlState { - 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, - }, - }), - color_mask: Some ((1, 1, 1, 1)), - depth_mask: Some (1), - stencil_mask: Some (0), - }, - }, - ]; + let mut passes = self.passes.iter (); - passes [0].with (gl_state, || { + 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); }); @@ -730,7 +900,8 @@ impl GameGraphics { use uniforms::*; // Draw the world except the ground plane - passes [1].with_shader (gl_state, |shader_vars| { + passes.next ().unwrap ().with_shader (gl_state, self, + |shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; @@ -759,19 +930,6 @@ impl GameGraphics { i != self.grass_index }); - glezz::uniform_3fv (unis [&ALBEDO], &magenta); - 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); - - self.mesh_arrow.draw_all (attrs, |_| true); - } - let draw_sky = true; if draw_sky { self.texture.bind (); @@ -786,7 +944,8 @@ impl GameGraphics { }); // Draw shadows into stencil buffer - passes [2].with_shader (gl_state, |shader_vars| { + passes.next ().unwrap ().with_shader (gl_state, self, + |shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; @@ -813,7 +972,8 @@ impl GameGraphics { let object_space_light = make_object_space_vec (&inverse_pumpkin, &light); // Draw unlit ground - passes [3].with_shader (gl_state, |shader_vars| { + passes.next ().unwrap ().with_shader (gl_state, self, + |shader_vars| { let unis = shader_vars.unis; let attrs = shader_vars.attrs; @@ -835,16 +995,51 @@ impl GameGraphics { }); // Draw lit ground - passes [4].with_shader (gl_state, |shader_vars| { + 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], &object_space_light); 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); + } + }); } } +#[derive (Copy, Clone, PartialEq, Eq)] +enum UserControl { + Camera, + Wind, +} + fn main () { let sdl_context = sdl2::init ().unwrap (); let video_subsystem = sdl_context.video ().unwrap (); @@ -871,16 +1066,13 @@ fn main () { let graphics = GameGraphics::new (); let mut gl_state = Default::default (); + let mut user_control = UserControl::Camera; + let mut graphics_frames = 0; + 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() { @@ -889,27 +1081,55 @@ fn main () { Event::KeyDown { keycode: Some (Keycode::Escape), .. } => { break 'running }, + Event::KeyDown { keycode: Some (Keycode::C), .. } => { + user_control = UserControl::Camera; + }, + Event::KeyDown { keycode: Some (Keycode::W), .. } => { + user_control = UserControl::Wind; + }, _ => (), } } + let controller = ControllerState::from_sdl_keyboard (&event_pump.keyboard_state ()); + + for _ in 0..frames_to_do { + state.step (&controller, &user_control); + } + + let gravity = (0.0, 0.0, -1.0).into (); + let wind = state.wind.to_vec3 () * -1.0; + let origin: Vec3 = (0.0, 0.0, 1.35).into (); + + 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 = vec![ Arrow { - origin: (0.0, 0.0, 1.0).into (), - direction: (0.0, 0.0, 1.0).into (), + origin: (0.0, 0.0, 1.35).into (), + direction: gravity, + color: (1.0, 0.5, 0.5).into (), }, Arrow { - origin: (1.0, 0.0, 1.0).into (), - direction: (1.0, 0.0, 0.0).into (), - }, - Arrow { - origin: (0.0, 1.0, 1.0).into (), - direction: (0.0, 1.0, 0.0).into (), + origin: origin + wind * -2.0, + direction: wind, + color: if user_control == UserControl::Wind { + control_flash + } + else { + (1.0, 0.5, 1.0).into () + }, }, ]; let renderable_arrows: Vec <_> = arrows.iter ().map (|arrow| { - let d = arrow.direction; + 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) @@ -932,9 +1152,11 @@ fn main () { 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 ((0.125, 0.125, 0.125).into ()) * + Mat4::from_scale ((s, s, s).into ()) * dir_mat; let inv_model_mat = model_mat.inverse (); @@ -942,6 +1164,7 @@ fn main () { RenderableArrow { model_mat, inv_model_mat, + color: arrow.color, } }).collect (); @@ -950,6 +1173,7 @@ fn main () { graphics.draw (&state, &mut gl_state, &renderable_arrows); window.gl_swap_window (); + graphics_frames += 1; std::thread::sleep (Duration::from_millis (15)); } diff --git a/src/glezz.rs b/src/glezz.rs index 04cee65..786ab78 100644 --- a/src/glezz.rs +++ b/src/glezz.rs @@ -97,6 +97,12 @@ pub fn stencil_mask (v: u32) { } } +pub fn depth_func (v: u32) { + unsafe { + gl::DepthFunc (v); + } +} + // More abstract things below here