413 lines
9.7 KiB
Rust
413 lines
9.7 KiB
Rust
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::{
|
|
debug,
|
|
instrument,
|
|
};
|
|
|
|
use opengl_rust::{
|
|
glezz,
|
|
gpu_buffers,
|
|
network_protocol::*,
|
|
quinn_common::make_client_endpoint,
|
|
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 <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")?,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[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 <f32>,
|
|
indexes: Vec <u32>,
|
|
}
|
|
|
|
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 (())
|
|
}
|