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

421 lines
10 KiB
Rust

use std::{
collections::HashMap,
};
use anyhow::Result;
use maplit::hashmap;
use opengl_rust::{
prelude::*,
gl_state::*,
physics::PhysicsBody,
renderable_model::{
attributes,
RenderableModel,
},
shader_closure::ShaderLookup,
texture::Texture,
};
mod graphics;
use graphics::Graphics;
#[derive (Default)]
pub struct GameState {
player: PhysicsBody,
aabbs: Vec <opengl_rust::physics::Aabb>,
phys_tris: Vec <opengl_rust::physics::Triangle>,
}
impl GameState {
fn reset_level (&mut self, level: &LoadedLevel) {
self.player = Default::default ();
self.player.pos = level.player_spawn;
}
}
#[tokio::main]
async fn main () -> 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 ("3D platformer", 320 * 2, 240 * 2)
.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 level = LoadedLevel::from_path ("gltf/level-00.glb")?;
let scene = level.level.scenes ().next ().unwrap ();
let mut camera = None;
let mut phys_tris: Vec <opengl_rust::physics::Triangle> = Vec::new ();
for node in scene.nodes () {
if let Some (c) = node.camera () {
camera = Some (Camera {
mat: gltf_node_get_mat4 (&node).inverse (),
});
}
let mesh = match node.mesh () {
None => continue,
Some (x) => x,
};
let m = gltf_node_get_mat4 (&node);
for prim in mesh.primitives () {
use gltf::Semantic;
let positions = match prim.get (&Semantic::Positions) {
None => continue,
Some (x) => x,
};
let pos_view = match positions.view () {
None => continue,
Some (x) => x,
};
let indices = match prim.indices () {
None => continue,
Some (x) => x,
};
let indices_view = match indices.view () {
None => continue,
Some (x) => x,
};
let idx_start = indices.offset () + indices_view.offset ();
let idx_slice = &level.buffer [idx_start..idx_start + indices.count () * 2];
let vert_start = positions.offset () + pos_view.offset ();
let vert_slice = &level.buffer [vert_start..vert_start + positions.count () * 4 * 3];
for chunk in idx_slice.chunks_exact (2 * 3) {
let read_idx = |i: usize| u16::from_le_bytes ([chunk [i * 2 + 0], chunk [i * 2 + 1]]);
let idxs = [
read_idx (0),
read_idx (1),
read_idx (2),
];
let read_pos_coord = |i| f32::from_le_bytes ([
vert_slice [i * 4 + 0],
vert_slice [i * 4 + 1],
vert_slice [i * 4 + 2],
vert_slice [i * 4 + 3],
]);
let read_pos = move |i| {
let i = usize::try_from (i).unwrap ();
m.transform_point3 (Vec3::new (
read_pos_coord (i * 3 + 0),
read_pos_coord (i * 3 + 1),
read_pos_coord (i * 3 + 2),
))
};
// glTF triangle winding is backwards from what I expected
// no biggie
let verts = [
read_pos (idxs [0]),
read_pos (idxs [2]),
read_pos (idxs [1]),
];
phys_tris.push (opengl_rust::physics::Triangle {
verts,
});
}
}
}
let camera = match camera {
None => bail! ("Couldn't find camera node in glTF file"),
Some (x) => x,
};
let mut graphics = Graphics::new ();
let mut gl_state = Default::default ();
let mut game_state = GameState::default ();
let phys_params = opengl_rust::physics::Params {
dt: 1.0 / 60.0,
gravity: (0.0, 0.0, -0.5).into (),
margin: 0.00125,
};
let player_acc = 1.0;
let player_acc_air = 0.125;
let player_max_speed = 4.0;
let player_max_speed_air = 8.0;
let player_jump_speed = 12.0;
let kill_z = -3.0;
let mut player_jump_vec: Option <Vec3> = None;
game_state.reset_level (&level);
game_state.phys_tris = phys_tris;
'running: loop {
let _frames_to_do = time_step.step ();
let mut player_wants_to_jump = false;
for event in event_pump.poll_iter () {
match event {
Event::Quit {..} |
Event::KeyDown { keycode: Some (Keycode::Escape), .. } => {
break 'running
},
Event::KeyDown { keycode: Some (Keycode::R), .. } => {
game_state.reset_level (&level);
},
Event::KeyDown { keycode: Some (Keycode::Space), .. } => {
player_wants_to_jump = true;
},
_ => (),
}
}
let kb_state = event_pump.keyboard_state ();
let mut wanted_dir = Vec3::default ();
if kb_state.is_scancode_pressed (Scancode::Left) {
wanted_dir.x -= 1.0;
}
if kb_state.is_scancode_pressed (Scancode::Right) {
wanted_dir.x += 1.0;
}
if kb_state.is_scancode_pressed (Scancode::Up) {
wanted_dir.y += 1.0;
}
if kb_state.is_scancode_pressed (Scancode::Down) {
wanted_dir.y -= 1.0;
}
let wanted_dir = if wanted_dir.length_squared () >= 1.0 {
wanted_dir.normalize ()
}
else {
wanted_dir
};
let old_vel = game_state.player.vel;
let old_vel_2 = old_vel * Vec3::new (1.0, 1.0, 0.0);
let new_vel_2 = match player_jump_vec.as_ref () {
Some (v) => {
// Ground
let acc = player_acc * v.z * v.z;
let wanted_vel = wanted_dir * player_max_speed;
let diff = wanted_vel - old_vel_2;
if diff.length_squared () < acc * acc {
// We're near the wanted velocity, so snap to it
wanted_vel
}
else {
// We're not near the wanted velocity, so accelerate
old_vel_2 + diff.normalize_or_zero () * acc
}
},
// Air
None => {
let proposed_vel = old_vel_2 + wanted_dir * player_acc_air;
if old_vel_2.length_squared () < player_max_speed_air * player_max_speed_air {
// Air control is normal below player_max_speed
proposed_vel
}
else {
// If the player's input would push them beyond player_max_speed,
// apply drag to preserve overall energy
proposed_vel * old_vel_2.length () / proposed_vel.length ()
}
},
};
game_state.player.vel = new_vel_2;
game_state.player.vel.z = old_vel.z;
// dbg! (game_state.player.vel);
if player_wants_to_jump {
if let Some (normal) = player_jump_vec.clone () {
game_state.player.vel.z = 0.0;
game_state.player.vel += normal * player_jump_speed;
}
}
let phys_result = opengl_rust::physics::step (&phys_params, &game_state.phys_tris, &[], 0.5, &game_state.player);
game_state.player = phys_result.body;
if game_state.player.pos.z < kill_z {
game_state.reset_level (&level);
}
// tracing::debug! ("player pos: {}", game_state.player.pos);
player_jump_vec = None;
for normal in &phys_result.normals_hit {
player_jump_vec = Some (match player_jump_vec {
None => *normal,
Some (old) => {
if normal.z > old.z {
*normal
}
else {
old
}
},
});
}
dbg! (game_state.player.pos);
window.gl_make_current (&gl_ctx).unwrap ();
graphics.draw (&game_state, &mut gl_state, &level, &camera);
graphics.frames += 1;
window.gl_swap_window ();
tokio::time::sleep (Duration::from_millis (15)).await;
}
Ok (())
}
struct LoadedLevel {
player_spawn: Vec3,
buffer: Vec <u8>,
level: gltf::Document,
}
impl LoadedLevel {
fn from_path (path: &str) -> Result <Self> {
use gltf::{
Semantic,
scene::Transform,
};
let (level, buffers, _) = gltf::import (path)?;
let buffer = match buffers.get (0) {
None => bail! ("gltf didn't load any buffers"),
Some (x) => x.0.to_vec (),
};
let scene = match level.scenes ().next () {
None => bail! ("No scenes in glTF file"),
Some (x) => x,
};
let mut player_spawn = None;
for node in scene.nodes () {
if node.name () == Some ("Player Spawn") {
let (translation, _, _) = node.transform ().decomposed ();
player_spawn = Some (Vec3::from (translation));
}
else if let Some (camera) = node.camera () {
}
else if let Some (mesh) = node.mesh () {
for (i, prim) in mesh.primitives ().enumerate () {
let positions = match prim.get (&Semantic::Positions) {
None => continue,
Some (x) => x,
};
let normals = match prim.get (&Semantic::Normals) {
None => continue,
Some (x) => x,
};
let indices = match prim.indices () {
None => continue,
Some (x) => x,
};
let pos_view = match positions.view () {
None => continue,
Some (x) => x,
};
let norm_view = match normals.view () {
None => continue,
Some (x) => x,
};
let indices_view = match indices.view () {
None => continue,
Some (x) => x,
};
}
}
}
let player_spawn = match player_spawn {
None => bail! ("glTF file must have `Player Spawn` node"),
Some (x) => x,
};
Ok (Self {
buffer,
level,
player_spawn,
})
}
}
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,
}
struct Camera {
mat: Mat4,
}
fn gltf_node_get_mat4 (node: &gltf::Node) -> Mat4 {
Mat4::from_cols_array_2d (&node.transform ().matrix ())
}