use std::{ convert::TryInto, ffi::{ CString, c_void, }, time::Duration, }; use anyhow::{ anyhow, Context, }; use glam::{ Mat4, Vec3, }; use sdl2::{ event::Event, keyboard::{Keycode, Scancode}, }; use tracing::instrument; use opengl_rust::{ glezz, gpu_buffers, shader, timestep::TimeStep, }; struct GameState { logic_frames: u64, } struct GraphicsContext { window: sdl2::video::Window, gl_ctx: sdl2::video::GLContext, vertex_buffer: gpu_buffers::VertexBuffer, index_buffer: gpu_buffers::IndexBuffer, shader_program: shader::ShaderProgram, shader_locations: ShaderLocations, } struct ShaderLocations { attr_pos: u32, attr_normal: u32, attr_color: u32, uni_mvp: i32, } impl ShaderLocations { pub fn new (shader_program: &shader::ShaderProgram) -> anyhow::Result { 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")?, }) } } #[instrument (level = "trace", skip (ctx, state))] fn draw_graphics (ctx: &GraphicsContext, state: &GameState) { let shader_locs = &ctx.shader_locations; let attr_pos = shader_locs.attr_pos; let attr_normal = shader_locs.attr_normal; let attr_color = shader_locs.attr_color; let uni_mvp = shader_locs.uni_mvp; ctx.window.gl_make_current (&ctx.gl_ctx).unwrap (); glezz::enable (gl::DEPTH_TEST); glezz::clear_color (0.392f32, 0.710f32, 0.965f32, 1.0f32); glezz::clear (gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT); let screen_size = (960.0, 540.0); let proj_mat = Mat4::perspective_rh_gl (30.0f32.to_radians (), screen_size.0 / screen_size.1, 0.125, 200.0); let spin_period = 360 * 4; let view_mat = proj_mat * Mat4::from_translation (Vec3::from ((0.0, 0.0, -80.0))) * Mat4::from_rotation_x (-60.0 * 3.14159 / 180.0) * Mat4::from_rotation_z ((state.logic_frames % spin_period) as f32 * 3.1415926535 * 2.0 / spin_period as f32) * Mat4::from_translation (Vec3::from ((-32.0, -32.0, -5.0))); let mvp = view_mat; glezz::uniform_matrix_4fv (uni_mvp, &mvp); ctx.shader_program.use_program (); glezz::enable_vertex_attrib_array (Some (attr_pos)); glezz::enable_vertex_attrib_array (Some (attr_color)); glezz::enable_vertex_attrib_array (Some (attr_normal)); ctx.vertex_buffer.bind (); ctx.index_buffer.bind (); unsafe { let num_quads = 64 * 64; let stride = 4 * 9; gl::VertexAttribPointer (attr_pos, 3, gl::FLOAT, 0, stride, 0 as *const u8 as *const c_void); gl::VertexAttribPointer (attr_normal, 3, gl::FLOAT, 0, stride, (4 * 3) as *const u8 as *const c_void); gl::VertexAttribPointer (attr_color, 3, gl::FLOAT, 0, stride, (4 * 6) as *const u8 as *const c_void); gl::DrawRangeElements (gl::TRIANGLES, 0, num_quads * 4, num_quads as i32 * 6, gl::UNSIGNED_INT, 0 as *const u8 as *const c_void); } ctx.window.gl_swap_window (); } fn main () -> anyhow::Result <()> { tracing_subscriber::fmt::fmt () .with_env_filter (tracing_subscriber::EnvFilter::from_default_env()) .with_span_events (tracing_subscriber::fmt::format::FmtSpan::CLOSE) .init (); 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 ("Heightmap terrain demo", 960, 540) .position_centered () .opengl () .build () ?; 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 ().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 time_step = TimeStep::new (60, 1000); 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 (); let shader_program = shader::shader_from_files ("shaders/terrain-vert.glsl", "shaders/terrain-frag.glsl"); let shader_locations = ShaderLocations::new (&shader_program)?; let mut vertexes = vec![]; let mut indexes = vec![]; let mut start_index = 0; let bump_at = |bump_x, bump_y, x, y| { let x = x - bump_x; let y = y - bump_y; let d = x * x + y * y; f32::max (0.0, 5.0 - d * 0.125) }; let height_fn = |(x, y)| { bump_at (32.0, 32.0, x, y) + bump_at (64.0, 32.0, x, y) + bump_at (32.0, 48.0, x, y) }; for y in 0..64 { for x in 0..64 { let (r, g, b) = if (x + y) % 2 == 0 { (0.4, 0.4, 0.4) } else { (0.5, 0.5, 0.5) }; let x = x as f32; let y = y as f32; let i = start_index; let xys = [ (x + 0.0, y + 0.0), (x + 1.0, y + 0.0), (x + 1.0, y + 1.0), (x + 0.0, y + 1.0), ]; let zs = [ height_fn (xys [0]), height_fn (xys [1]), height_fn (xys [2]), height_fn (xys [3]), ]; let slope_x = (zs [1] + zs [2]) - (zs [0] + zs [3]); let slope_y = (zs [2] + zs [3]) - (zs [0] + zs [1]); let tangent_x = Vec3::from ((1.0, 0.0, slope_x)); let tangent_y = Vec3::from ((0.0, 1.0, slope_y)); let normal = Vec3::cross (tangent_x, tangent_y) .normalize (); let (nx, ny, nz) = normal.into (); for ((x, y), z) in xys.iter ().zip (&zs) { vertexes.extend (&[ *x, *y, *z, nx, ny, nz, r, g, b, ]) } indexes.extend (&[ i + 0, i + 1, i + 2, i + 0, i + 2, i + 3, ]); start_index += 4; } } let vertex_buffer = gpu_buffers::VertexBuffer::from_slice (&vertexes); let index_buffer = gpu_buffers::IndexBuffer::from_slice_u32 (&indexes); let graphics_ctx = GraphicsContext { window, gl_ctx, vertex_buffer, index_buffer, shader_program, shader_locations, }; let mut game_state = GameState { logic_frames: 0, }; '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 }, _ => (), } } for _ in 0..frames_to_do { game_state.logic_frames += 1; } draw_graphics (&graphics_ctx, &game_state); graphics_frames += 1; std::thread::sleep (Duration::from_millis (15)); } Ok (()) }