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

275 lines
6.6 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::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 <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)
{
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 (())
}