2021-32-bit-holiday-jam/src/bin/platformer.rs

297 lines
7.1 KiB
Rust

use std::{
collections::HashMap,
};
use anyhow::Result;
use maplit::hashmap;
use opengl_rust::{
prelude::*,
gl_state::*,
renderable_model::{
attributes,
RenderableModel,
},
shader_closure::ShaderLookup,
texture::Texture,
};
mod uniforms {
use iota::iota;
iota! {
pub const
MVP: u32 = iota;
, OBJECT_SPACE_LIGHT
, OBJECT_SPACE_SKY
, ALBEDO
, MIN_ALBEDO
, MIN_BRIGHT
, TEXTURE
}
}
struct GameGraphics {
passes: Vec <Pass>,
shaders: Vec <ShaderClosure>,
shader_lookup: HashMap <u32, usize>,
mesh_cube: RenderableModel,
mesh_sky: RenderableModel,
text_stream: TriangleStream,
texture_sky: Texture,
}
impl ShaderLookup for GameGraphics {
fn lookup <'a> (&'a self, id: u32) -> &'a ShaderClosure {
&self.shaders [self.shader_lookup [&id]]
}
}
impl GameGraphics {
fn draw (
&self,
gl_state: &mut GlState,
) {
use uniforms as u;
let white = color_from_255 ((255.0, 255.0, 255.0));
let black = color_from_255 ((0.0, 0.0, 0.0));
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 view_mat = proj_mat *
Mat4::from_translation ((0.0, 0.0, -20.0).into ()) *
Mat4::from_rotation_x ((45.0 - 90.0f32).to_radians ())
;
let world_model_mat = Mat4::IDENTITY;
self.passes [0].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);
});
self.passes [1].with_shader (gl_state, self, |shader_vars| {
let attrs = shader_vars.attrs;
let unis = shader_vars.unis;
{
let mvp = view_mat * Mat4::IDENTITY;
glezz::uniform_matrix_4fv (unis [&u::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_cube.draw_all (attrs, |_| {
true
});
}
{
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 [&u::MVP], &sky_mvp_mat);
glezz::uniform_3fv (unis [&u::ALBEDO], &white);
glezz::uniform_3fv (unis [&u::MIN_BRIGHT], &white);
glezz::uniform_3fv (unis [&u::MIN_ALBEDO], &black);
glezz::uniform_1i (unis [&u::TEXTURE], 0);
self.mesh_sky.draw_all (attrs, |_| true);
}
});
}
}
#[tokio::main]
async fn main () -> Result <()> {
let sdl_context = sdl2::init ().map_err (|e| anyhow! ("Can't init SDL: {}", e))?;
let video_subsystem = sdl_context.video ().map_err (|e| anyhow! ("Can't get SDL video subsystem: {}", e))?;
let window = video_subsystem.window ("3D platformer", 1280, 720)
.position_centered ()
.opengl ()
.build ()
?;
gl::load_with (|s| {
video_subsystem.gl_get_proc_address (s) as *const _
});
if ! gl::ClearColor::is_loaded () {
bail! ("gl::ClearColor didn't load, something is wrong with OpenGL");
}
let gl_ctx = window.gl_create_context ().map_err (|e| anyhow! ("Can't create OpenGL context: {}", e))?;
window.gl_make_current (&gl_ctx).map_err (|e| anyhow! ("Can't make OpenGL context current: {}", e))?;
let mut event_pump = sdl_context.event_pump ().unwrap ();
let mut time_step = TimeStep::new (60, 1000);
let mut graphics_frames = 0;
let uniform_names = {
vec! [
(uniforms::MVP, "uni_mvp"),
(uniforms::OBJECT_SPACE_LIGHT, "uni_object_space_light"),
(uniforms::OBJECT_SPACE_SKY, "uni_object_space_sky"),
(uniforms::ALBEDO, "uni_albedo"),
(uniforms::MIN_ALBEDO, "uni_min_albedo"),
(uniforms::MIN_BRIGHT, "uni_min_bright"),
(uniforms::TEXTURE, "uni_texture"),
]
};
let attr_names = {
vec! [
(attributes::POS, "attr_pos"),
(attributes::UV, "attr_uv"),
(attributes::NORMAL, "attr_normal"),
]
};
let shaders: Vec <_> = [
("shaders/pumpkin-vert.glsl", "shaders/pumpkin-frag.glsl"),
("shaders/shadow-vert.glsl", "shaders/shadow-frag.glsl"),
("shaders/terrain-vert.glsl", "shaders/terrain-frag.glsl"),
].into_iter ()
.map (|(v, f)| {
ShaderClosure::new (shader_from_files (v, f), &uniform_names, &attr_names)
})
.collect ();
let mesh_cube = renderable_from_iqm_file ("cube.iqm");
let mesh_sky = renderable_from_iqm_file ("sky-sphere.iqm");
let passes = vec![
// Clear everything
Pass::default ()
.color_mask ([1, 1, 1, 1])
.depth_mask (1)
.clone (),
// Draw world
Pass::default ()
.shader (&shaders [0])
.flags ([
(gl::CULL_FACE, true),
(gl::DEPTH_TEST, true),
(gl::TEXTURE_2D, true),
(gl::STENCIL_TEST, false),
].into_iter ())
.front_face (FrontFace::Cw)
.color_mask ([1, 1, 1, 1])
.depth_mask (1)
.clone (),
];
let text_stream = TriangleStream {
verts: gpu_buffers::VertexBuffer::streaming (4 * 5 * 6 * 1024),
indices: {
let quad = [
0, 1, 2,
0, 2, 3,
];
let v: Vec <u32> = (0u32..1024).map (|i| {
quad.iter ().map (move |j| 4 * i + j)
}).flatten ().collect ();
gpu_buffers::IndexBuffer::from_slice_u32 (&v)
}
};
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;
glezz::enable_vertex_attrib_array (attrs [attributes::POS]);
glezz::enable_vertex_attrib_array (attrs [attributes::UV]);
glezz::enable_vertex_attrib_array (attrs [attributes::NORMAL]);
});
let graphics = GameGraphics {
mesh_cube,
mesh_sky,
passes,
shader_lookup,
shaders,
text_stream,
texture_sky: Texture::from_file ("sky.png"),
};
let mut gl_state = Default::default ();
'running: loop {
let _frames_to_do = time_step.step ();
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 ();
graphics.draw (&mut gl_state);
window.gl_swap_window ();
graphics_frames += 1;
tokio::time::sleep (Duration::from_millis (15)).await;
}
Ok (())
}
struct ShaderLocations {
attr_pos: u32,
attr_normal: u32,
attr_color: u32,
uni_mvp: i32,
}
impl ShaderLocations {
pub fn new (shader_program: &opengl_rust::shader::ShaderProgram) -> anyhow::Result <Self>
{
let attr = |name: &str| shader_program.get_attribute_location (&CString::new (name.as_bytes ())?).try_into ().context ("Attribute location negative");
let uni = |name: &str| shader_program.get_uniform_location (&CString::new (name.as_bytes ())?).try_into ().context ("Uniform location bad");
Ok (Self {
attr_pos: attr ("attr_pos")?,
attr_normal: attr ("attr_normal")?,
attr_color: attr ("attr_color")?,
uni_mvp: uni ("uni_mvp")?,
})
}
}
struct TriangleStream {
pub verts: gpu_buffers::VertexBuffer,
pub indices: gpu_buffers::IndexBuffer,
}
fn color_from_255 <V> (rgb: V) -> Vec3
where V: Into <Vec3>
{
let rgb: Vec3 = rgb.into ();
Vec3::from ((
rgb.x / 255.0,
rgb.y / 255.0,
rgb.z / 255.0
))
}