301 lines
7.2 KiB
Rust
301 lines
7.2 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;
|
|
|
|
pub struct GameState {
|
|
player: PhysicsBody,
|
|
aabbs: Vec <opengl_rust::physics::Aabb>,
|
|
}
|
|
|
|
impl Default for GameState {
|
|
fn default () -> Self {
|
|
let player = Default::default ();
|
|
let aabbs = [
|
|
((-4.0, -4.0, -3.0), (4.0, 4.0, -1.0)),
|
|
((-1.5, 1.0, -1.0), (-0.5, 2.0, 0.0)),
|
|
((-0.5, 1.0, 0.0), (0.5, 2.0, 1.0)),
|
|
((0.5, 1.0, 1.0), (1.5, 2.0, 2.0)),
|
|
].into_iter ()
|
|
.map (|(min, max)| {
|
|
opengl_rust::physics::Aabb {
|
|
min: min.into (),
|
|
max: max.into (),
|
|
}
|
|
})
|
|
.collect ();
|
|
|
|
Self {
|
|
player,
|
|
aabbs,
|
|
}
|
|
}
|
|
}
|
|
|
|
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, 240)
|
|
.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 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.25).into (),
|
|
margin: 0.00125,
|
|
};
|
|
|
|
let player_speed = 2.0;
|
|
let player_jump_speed = 8.0;
|
|
let mut player_jump_vec: Option <Vec3> = None;
|
|
|
|
game_state.reset_level (&level);
|
|
|
|
'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 player_speed = if player_jump_vec.is_some () {
|
|
game_state.player.vel.x = 0.0;
|
|
game_state.player.vel.y = 0.0;
|
|
player_speed
|
|
}
|
|
else {
|
|
0.125
|
|
};
|
|
|
|
if kb_state.is_scancode_pressed (Scancode::Left) {
|
|
game_state.player.vel.x -= player_speed;
|
|
}
|
|
if kb_state.is_scancode_pressed (Scancode::Right) {
|
|
game_state.player.vel.x += player_speed;
|
|
}
|
|
if kb_state.is_scancode_pressed (Scancode::Up) {
|
|
game_state.player.vel.y += player_speed;
|
|
}
|
|
if kb_state.is_scancode_pressed (Scancode::Down) {
|
|
game_state.player.vel.y -= player_speed;
|
|
}
|
|
|
|
if player_wants_to_jump {
|
|
if let Some (normal) = player_jump_vec.clone () {
|
|
game_state.player.vel += normal * player_jump_speed;
|
|
}
|
|
}
|
|
|
|
let phys_result = opengl_rust::physics::step (&phys_params, &[], &game_state.aabbs, 0.5, &game_state.player);
|
|
|
|
game_state.player = phys_result.body;
|
|
// tracing::debug! ("player pos: {}", game_state.player.pos);
|
|
// dbg! (player_jump_vec);
|
|
|
|
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
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
window.gl_make_current (&gl_ctx).unwrap ();
|
|
|
|
graphics.draw (&game_state, &mut gl_state, &level);
|
|
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 () {
|
|
println! ("Node {:?} is a mesh", node.name ());
|
|
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,
|
|
};
|
|
dbg! (positions.offset (), pos_view.offset (), pos_view.stride ());
|
|
dbg! (normals.offset (), norm_view.offset (), norm_view.stride ());
|
|
dbg! (indices.offset (), indices.data_type (), indices_view.offset (), indices_view.stride ());
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|