263 lines
7.8 KiB
Rust
263 lines
7.8 KiB
Rust
use std::time::{Duration, Instant};
|
|
|
|
use anymap::AnyMap;
|
|
use cgmath::Matrix3;
|
|
use glium::{
|
|
glutin, index::PrimitiveType, texture::Texture2d, uniform, Display, Frame, IndexBuffer,
|
|
Surface, VertexBuffer,
|
|
};
|
|
use glium_glyph::{glyph_brush::Section, GlyphBrush};
|
|
use glutin::{
|
|
event::{Event, WindowEvent},
|
|
event_loop::ControlFlow,
|
|
};
|
|
use resources::Shaders;
|
|
use simplelog::{TermLogger, TerminalMode};
|
|
use state::GameState;
|
|
|
|
pub mod resources;
|
|
pub mod state;
|
|
|
|
const TIMESTEP: f32 = 1.0 / 20.0;
|
|
const SCREEN_WIDTH: f32 = 320.0;
|
|
const SCREEN_HEIGHT: f32 = 240.0;
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct Vertex {
|
|
position: [f32; 2],
|
|
tex_coords: [f32; 2],
|
|
}
|
|
|
|
glium::implement_vertex!(Vertex, position, tex_coords);
|
|
|
|
// Where overall state is kept.
|
|
struct State {
|
|
current_fps: u32,
|
|
|
|
render_texture: Texture2d,
|
|
render_vertices: VertexBuffer<Vertex>,
|
|
screen_scale: (f32, f32),
|
|
|
|
current_state: Box<dyn GameState>,
|
|
next_state: Option<Box<dyn GameState>>,
|
|
|
|
resources: AnyMap,
|
|
}
|
|
|
|
impl State {
|
|
fn new(display: &Display) -> State {
|
|
// FPS thingies
|
|
let current_fps = 0;
|
|
|
|
let render_texture =
|
|
Texture2d::empty(display, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32).unwrap();
|
|
let render_vertices = VertexBuffer::new(
|
|
display,
|
|
&[
|
|
Vertex {
|
|
position: [-1.0, -1.0],
|
|
tex_coords: [0.0, 0.0],
|
|
},
|
|
Vertex {
|
|
position: [-1.0, 1.0],
|
|
tex_coords: [0.0, 1.0],
|
|
},
|
|
Vertex {
|
|
position: [1.0, 1.0],
|
|
tex_coords: [1.0, 1.0],
|
|
},
|
|
Vertex {
|
|
position: [1.0, -1.0],
|
|
tex_coords: [1.0, 0.0],
|
|
},
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
let screen_scale = {
|
|
let size = display.gl_window().window().inner_size();
|
|
(
|
|
size.height as f32 / SCREEN_HEIGHT * SCREEN_WIDTH / size.width as f32,
|
|
size.width as f32 / SCREEN_WIDTH * SCREEN_HEIGHT / size.height as f32,
|
|
)
|
|
};
|
|
|
|
// Set initial state
|
|
let current_state = Box::new(state::TestState);
|
|
let next_state = None;
|
|
|
|
// The resources map
|
|
let mut resources = AnyMap::new();
|
|
resources::load_resources(display, &mut resources);
|
|
|
|
// Package it all together UwU
|
|
State {
|
|
current_fps,
|
|
render_texture,
|
|
render_vertices,
|
|
screen_scale,
|
|
current_state,
|
|
next_state,
|
|
resources,
|
|
}
|
|
}
|
|
|
|
fn input(&mut self, event: &Event<()>) {
|
|
self.current_state.input(&mut self.resources, event);
|
|
}
|
|
|
|
fn update(&mut self, dt: f32) {
|
|
// Swap state if new one is present
|
|
let mut next_state = None;
|
|
std::mem::swap(&mut next_state, &mut self.next_state);
|
|
if let Some(state) = next_state {
|
|
self.current_state = state;
|
|
}
|
|
|
|
// Run current state's update
|
|
self.next_state = self.current_state.update(&mut self.resources, dt);
|
|
}
|
|
|
|
fn render(&mut self, display: &Display, target: &mut Frame) {
|
|
{
|
|
let mut texture_target = self.render_texture.as_surface();
|
|
texture_target.clear_color_srgb(0.0, 0.0, 0.0, 1.0);
|
|
|
|
self.current_state
|
|
.render(&mut self.resources, display, &mut texture_target);
|
|
}
|
|
|
|
let render_indices =
|
|
IndexBuffer::new(display, PrimitiveType::TriangleStrip, &[1, 2, 0, 3 as u16]).unwrap();
|
|
|
|
let program = self.resources.get::<Shaders>().unwrap().get("crt").unwrap();
|
|
|
|
let mut scale = (1.0, 1.0);
|
|
if self.screen_scale.0 < self.screen_scale.1 {
|
|
scale.0 = self.screen_scale.0;
|
|
} else {
|
|
scale.1 = self.screen_scale.1;
|
|
}
|
|
let scale_matrix = Matrix3::from_nonuniform_scale(scale.0, scale.1);
|
|
|
|
let uniforms = uniform! {
|
|
matrix: cgmath::conv::array3x3(scale_matrix),
|
|
texture: self.render_texture.sampled().magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest),
|
|
screen_resolution: [SCREEN_WIDTH, SCREEN_HEIGHT],
|
|
/*curvature: [3.0, 3.0 as f32],
|
|
scanline_opacity: [0.5, 0.5 as f32],
|
|
brightness: 2.0 as f32,*/
|
|
};
|
|
|
|
target
|
|
.draw(
|
|
&self.render_vertices,
|
|
&render_indices,
|
|
program,
|
|
&uniforms,
|
|
&Default::default(),
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
// It is the entrypoint, duh.
|
|
fn main() {
|
|
// Set up the logger
|
|
TermLogger::init(
|
|
log::LevelFilter::Info,
|
|
simplelog::Config::default(),
|
|
TerminalMode::Mixed,
|
|
)
|
|
.unwrap();
|
|
log::info!("Logger is set up!");
|
|
|
|
// Set up windowing and graphics context
|
|
let event_loop = glutin::event_loop::EventLoop::new();
|
|
let display = {
|
|
let wb = glutin::window::WindowBuilder::new();
|
|
let cb = glutin::ContextBuilder::new().with_vsync(true);
|
|
Display::new(wb, cb, &event_loop).unwrap()
|
|
};
|
|
|
|
// The main state of program or whatever
|
|
let mut state = State::new(&display);
|
|
|
|
// Timing stuff
|
|
let mut last_update = Instant::now();
|
|
let mut update_accumulator = 0.0;
|
|
let mut next_fps_check = Instant::now() + Duration::from_secs(1);
|
|
let mut frame_counter: u32 = 0;
|
|
|
|
// Run the main event loop
|
|
event_loop.run(move |event, _, control_flow| {
|
|
*control_flow = ControlFlow::Poll;
|
|
|
|
match event {
|
|
// Stuff to happen every frame.
|
|
Event::MainEventsCleared => {
|
|
// Game timestep and state update stuff.
|
|
update_accumulator += last_update.elapsed().as_secs_f32();
|
|
last_update = Instant::now();
|
|
while update_accumulator >= TIMESTEP {
|
|
update_accumulator -= TIMESTEP;
|
|
state.update(TIMESTEP);
|
|
}
|
|
|
|
// Measure framerate
|
|
let now = Instant::now();
|
|
frame_counter += 1;
|
|
if now >= next_fps_check {
|
|
state.current_fps = frame_counter;
|
|
frame_counter = 0;
|
|
next_fps_check = now + Duration::from_secs(1);
|
|
}
|
|
|
|
// Let us render some shit!
|
|
let mut target = display.draw();
|
|
target.clear_color_srgb(0.0, 0.0, 0.0, 1.0);
|
|
|
|
state.render(&display, &mut target);
|
|
|
|
// Draw the FPS Indicator
|
|
let glyph_brush = state.resources.get_mut::<GlyphBrush>().unwrap();
|
|
glyph_brush.queue(Section {
|
|
text: &format!("FPS: {:?}", state.current_fps),
|
|
screen_position: (0.0, 0.0),
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
..Default::default()
|
|
});
|
|
glyph_brush.draw_queued(&display, &mut target);
|
|
|
|
target.finish().unwrap();
|
|
}
|
|
|
|
Event::WindowEvent {
|
|
event: WindowEvent::Resized(size),
|
|
..
|
|
} => {
|
|
state.screen_scale = (
|
|
size.height as f32 / SCREEN_HEIGHT * SCREEN_WIDTH / size.width as f32,
|
|
size.width as f32 / SCREEN_WIDTH * SCREEN_HEIGHT / size.height as f32,
|
|
);
|
|
}
|
|
|
|
// Quit if window wants to be closed
|
|
// TODO: Check state to see if allowed to exit.
|
|
Event::WindowEvent {
|
|
event: WindowEvent::CloseRequested,
|
|
..
|
|
} => {
|
|
*control_flow = ControlFlow::Exit;
|
|
}
|
|
|
|
// Cleanup here
|
|
Event::LoopDestroyed => {}
|
|
|
|
event => {
|
|
state.input(&event);
|
|
}
|
|
}
|
|
});
|
|
}
|