use opengl_rust::{ prelude::*, shader, }; 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, bumps: &[(f32, f32)], ) -> anyhow::Result <()> { 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 * 8; 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 (); let bump_period = 360 * 7; let bump_theta = (state.logic_frames % bump_period) as f32 * 3.1415926535 * 2.0 / bump_period as f32; let ClientArrays { vertexes, indexes, } = make_heightmap_arrays (bumps); upload_vertexes (&vertexes)?; 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 (); Ok (()) } struct ClientArrays { vertexes: Vec , indexes: Vec , } fn make_heightmap_arrays (bumps: &[(f32, f32)]) -> ClientArrays { 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 d2: f32 = x * x + y * y; if d2 > 8.0 * 8.0 { return 0.0; } let d = d2.sqrt (); let t = f32::min (1.0, f32::max (0.0, (8.0 - d) / 6.0)); let z = -2.0 * t * t + 3.0 * t * t; f32::max (0.0, 4.0 * z) }; let height_fn = |(x, y)| { bumps.iter () .map (|(bump_x, bump_y)| bump_at (bump_x, bump_y, x, y)) .sum () }; 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; } } ClientArrays { vertexes, indexes, } } fn upload_vertexes (vertexes: &[f32]) -> anyhow::Result <()> { unsafe { gl::BufferSubData ( gl::ARRAY_BUFFER, 0, (vertexes.len () * 4).try_into ()?, &vertexes [0] as *const f32 as *const c_void ); } Ok (()) } #[tokio::main] async fn main () -> anyhow::Result <()> { use std::sync::Arc; use futures_util::StreamExt; use tokio::sync::Mutex; 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 ClientArrays { vertexes, indexes, } = make_heightmap_arrays (&[]); debug! ("Floats in vertex buffer: {}", vertexes.len ()); let vertex_buffer = gpu_buffers::VertexBuffer::streaming (vertexes.len ()); let index_buffer = gpu_buffers::IndexBuffer::from_slice_u32 (&indexes); upload_vertexes (&vertexes)?; unsafe { gl::BufferSubData ( gl::ARRAY_BUFFER, 0, (vertexes.len () * 4).try_into ()?, &vertexes [0] as *const f32 as *const c_void ); } let graphics_ctx = GraphicsContext { window, gl_ctx, vertex_buffer, index_buffer, shader_program, shader_locations, }; let mut game_state = GameState { logic_frames: 0, }; let server_cert = tokio::fs::read ("quic_server.crt").await?; let server_addr = "127.0.0.1:5000".parse().unwrap(); let endpoint = make_client_endpoint("0.0.0.0:0".parse().unwrap(), &[&server_cert])?; // connect to server let quinn::NewConnection { connection, mut datagrams, .. } = endpoint .connect(&server_addr, "localhost") .unwrap() .await .unwrap(); println!("[client] connected: addr={}", connection.remote_address()); let networked_state = Arc::new (Mutex::new (NetworkedState::default ())); let networked_state_2 = Arc::clone (&networked_state); tokio::spawn (async move { while let Some (Ok (datagram)) = datagrams.next ().await { let state = rmp_serde::from_slice (&datagram)?; let mut guard = networked_state_2.lock ().await; *guard = state; } Ok::<_, anyhow::Error> (()) }); 'running: loop { let frames_to_do = time_step.step (); let _mouse = event_pump.mouse_state (); let mut cmd = NetworkCommand::default (); for event in event_pump.poll_iter() { match event { Event::Quit {..} | Event::KeyDown { keycode: Some (Keycode::Escape), .. } => { break 'running }, Event::KeyDown { keycode: Some (keycode), .. } => { match keycode { Keycode::Left => cmd.left = true, Keycode::Right => cmd.right = true, Keycode::Up => cmd.up = true, Keycode::Down => cmd.right = true, _ => (), } }, _ => (), } } let keyboard_state = event_pump.keyboard_state (); if keyboard_state.is_scancode_pressed (Scancode::Left) { cmd.left = true } if keyboard_state.is_scancode_pressed (Scancode::Right) { cmd.right = true } if keyboard_state.is_scancode_pressed (Scancode::Up) { cmd.up = true } if keyboard_state.is_scancode_pressed (Scancode::Down) { cmd.down = true } let bytes = rmp_serde::to_vec (&cmd)?; connection.send_datagram (bytes.into ())?; for _ in 0..frames_to_do { game_state.logic_frames += 1; } let state = { let guard = networked_state.lock ().await; guard.clone () }; draw_graphics (&graphics_ctx, &game_state, &state.positions)?; graphics_frames += 1; std::thread::sleep (Duration::from_millis (15)); } Ok (()) }