diff --git a/assets/shaders/crt/frag.glsl b/assets/shaders/crt/frag.glsl index a320c80..621c9c9 100644 --- a/assets/shaders/crt/frag.glsl +++ b/assets/shaders/crt/frag.glsl @@ -8,8 +8,8 @@ uniform sampler2D texture; uniform vec2 screen_resolution; uniform vec2 curvature = vec2(3.0, 3.0); -uniform vec2 scanline_opacity = vec2(1.0, 1.0); -uniform float brightness = 3.0; +uniform vec2 scanline_opacity = vec2(0.5, 0.5); +uniform float brightness = 2.0; out vec4 f_color; diff --git a/assets/textures/backgrounds/grass-hills.png b/assets/textures/backgrounds/grass-hills.png new file mode 100644 index 0000000..23a60c3 Binary files /dev/null and b/assets/textures/backgrounds/grass-hills.png differ diff --git a/assets/textures/tiles/atlas b/assets/textures/tiles/atlas new file mode 100644 index 0000000..e69de29 diff --git a/src/assets.rs b/src/assets.rs new file mode 100644 index 0000000..5ff2d2e --- /dev/null +++ b/src/assets.rs @@ -0,0 +1,153 @@ +use std::collections::HashMap; + +use glium::{ + texture::{RawImage2d, SrgbTexture2d}, + Display, Program, +}; +use glob::glob; + +pub type Textures = HashMap; + +#[derive(Debug)] +pub struct TextureAtlas { + pub texture: SrgbTexture2d, + rects: HashMap, + width: u32, + height: u32, +} + +pub struct AtlasUVs { + pub bottom_left: [f32; 2], + pub bottom_right: [f32; 2], + pub top_left: [f32; 2], + pub top_right: [f32; 2], +} + +impl TextureAtlas { + fn new(display: &Display) -> TextureAtlas { + log::info!("Loading textures"); + let paths = glob("assets/textures/**/*.png").unwrap(); + let items = paths.map(|entry| { + let path = entry.unwrap(); + + let image = image::open(&path).unwrap().to_rgba8(); + let image_dimensions = image.dimensions(); + let image = image.into_raw(); + + let name_path = path + .with_extension("") + .strip_prefix("assets/textures/") + .unwrap() + .to_owned(); + let mut name = String::new(); + for part in name_path.iter() { + name.push_str(part.to_str().unwrap()); + name.push(':'); + } + name.pop(); + + crunch::Item::new( + (name, image), + image_dimensions.0 as usize, + image_dimensions.1 as usize, + crunch::Rotation::None, + ) + }); + + log::info!("Packing textures into atlas"); + let (width, height, items) = crunch::pack_into_po2(4096, items).unwrap(); + + let items = items.into_iter().map(|entry| { + let rect = glium::Rect { + left: entry.0.x as u32, + bottom: entry.0.y as u32, + width: entry.0.w as u32, + height: entry.0.h as u32, + }; + let name = entry.1 .0; + let image = entry.1 .1; + (name, image, rect) + }); + let mut atlas = TextureAtlas { + texture: SrgbTexture2d::empty(display, width as u32, height as u32).unwrap(), + rects: HashMap::new(), + width: width as u32, + height: height as u32, + }; + for (name, image, rect) in items { + atlas.texture.write( + rect, + RawImage2d::from_raw_rgba_reversed(&image, (rect.width, rect.height)), + ); + atlas.rects.insert(name, rect); + } + atlas + } + + pub fn get_uv(&self, name: String) -> AtlasUVs { + let rect = self.rects.get(&name).unwrap(); + AtlasUVs { + bottom_left: Self::coord_to_uv( + rect.left, + rect.bottom + rect.height, + self.width, + self.height, + ), + bottom_right: Self::coord_to_uv( + rect.left + rect.width, + rect.bottom + rect.height, + self.width, + self.height, + ), + top_left: Self::coord_to_uv(rect.left, rect.bottom, self.width, self.height), + top_right: Self::coord_to_uv( + rect.left + rect.width, + rect.bottom, + self.width, + self.height, + ), + } + } + + fn coord_to_uv(x: u32, y: u32, width: u32, height: u32) -> [f32; 2] { + [x as f32 / width as f32, (y as f32 / height as f32)] + } +} + +pub type Shaders = HashMap; + +fn load_shaders(display: &Display) -> Shaders { + log::info!("Loading shaders."); + let mut shaders: Shaders = Shaders::new(); + for entry in glob("assets/shaders/*").unwrap() { + if let Ok(path) = entry { + // Operate only on directories + if path.is_file() { + continue; + } + + let vert_source = std::fs::read_to_string(path.join("vert.glsl")).unwrap(); + let frag_source = std::fs::read_to_string(path.join("frag.glsl")).unwrap(); + let program = Program::from_source(display, &vert_source, &frag_source, None).unwrap(); + + let name = path.file_name().unwrap().to_str().unwrap().to_owned(); + shaders.insert(name.to_string(), program); + log::info!("Loaded shader {:?} from {:?}.", name, path); + } + } + shaders +} + +pub struct Assets { + pub texture_atlas: TextureAtlas, + pub shaders: Shaders, +} + +impl Assets { + pub fn new(display: &Display) -> Assets { + Assets { + texture_atlas: TextureAtlas::new(display), + shaders: load_shaders(display), + } + } +} diff --git a/src/main.rs b/src/main.rs index fcdb071..953c19a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,24 @@ use std::time::{Duration, Instant}; use anymap::AnyMap; +use assets::{Assets, Shaders}; use cgmath::Matrix3; use glium::{ glutin, index::PrimitiveType, texture::Texture2d, uniform, Display, Frame, IndexBuffer, Surface, VertexBuffer, }; -use glium_glyph::{glyph_brush::Section, GlyphBrush}; +use glium_glyph::{ + glyph_brush::{rusttype::Font, 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 assets; pub mod state; const TIMESTEP: f32 = 1.0 / 20.0; @@ -41,7 +44,7 @@ struct State { current_state: Box, next_state: Option>, - resources: AnyMap, + resources: Assets, } impl State { @@ -83,12 +86,11 @@ impl State { }; // Set initial state - let current_state = Box::new(state::TestState); + let current_state = Box::new(state::TestState { time: 0.0 }); let next_state = None; // The resources map - let mut resources = AnyMap::new(); - resources::load_resources(display, &mut resources); + let resources = Assets::new(display); // Package it all together UwU State { @@ -130,7 +132,7 @@ impl State { let render_indices = IndexBuffer::new(display, PrimitiveType::TriangleStrip, &[1, 2, 0, 3 as u16]).unwrap(); - let program = self.resources.get::().unwrap().get("crt").unwrap(); + let program = self.resources.shaders.get("crt").unwrap(); let mut scale = (1.0, 1.0); if self.screen_scale.0 < self.screen_scale.1 { @@ -143,7 +145,7 @@ impl State { 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], + screen_resolution: [SCREEN_HEIGHT, SCREEN_HEIGHT], /*curvature: [3.0, 3.0 as f32], scanline_opacity: [0.5, 0.5 as f32], brightness: 2.0 as f32,*/ @@ -180,6 +182,11 @@ fn main() { Display::new(wb, cb, &event_loop).unwrap() }; + // For font rendering + let dejavu = std::fs::read("assets/fonts/DejaVuSans.ttf").unwrap(); + let fonts = vec![Font::from_bytes(dejavu).unwrap()]; + let mut glyph_brush = GlyphBrush::new(&display, fonts); + // The main state of program or whatever let mut state = State::new(&display); @@ -220,7 +227,6 @@ fn main() { state.render(&display, &mut target); // Draw the FPS Indicator - let glyph_brush = state.resources.get_mut::().unwrap(); glyph_brush.queue(Section { text: &format!("FPS: {:?}", state.current_fps), screen_position: (0.0, 0.0), diff --git a/src/resources.rs b/src/resources.rs deleted file mode 100644 index 6b39354..0000000 --- a/src/resources.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::collections::HashMap; - -use anymap::AnyMap; -use glium::{texture::SrgbTexture2d, Display, Program}; -use glium_glyph::{glyph_brush::rusttype::Font, GlyphBrush}; -use glob::glob; - -pub type Textures = HashMap; -pub type Shaders = HashMap; - -// Set up and load all global resources. -pub fn load_resources(display: &Display, resources: &mut AnyMap) { - // Load images - log::info!("Loading textures"); - let mut textures: Textures = HashMap::new(); - for entry in glob("assets/textures/**/*.png").unwrap() { - use image::io::Reader; - - if let Ok(path) = entry { - let image = Reader::open(&path).unwrap().decode().unwrap().to_rgba8(); - let image_dimensions = image.dimensions(); - - let image = glium::texture::RawImage2d::from_raw_rgba_reversed( - &image.into_raw(), - image_dimensions, - ); - let texture = SrgbTexture2d::new(display, image).unwrap(); - - let name_path = path - .with_extension("") - .strip_prefix("assets/textures/") - .unwrap() - .to_owned(); - let mut name = String::new(); - for part in name_path.iter() { - name.push_str(part.to_str().unwrap()); - name.push(':'); - } - name.pop(); - - textures.insert(name.to_string(), texture); - log::info!("Loaded texture {:?} from {:?}.", name, path); - } - } - resources.insert(textures); - - // Load shaders - log::info!("Loading shaders."); - let mut shaders: HashMap = HashMap::new(); - for entry in glob("assets/shaders/*").unwrap() { - if let Ok(path) = entry { - // Operate only on directories - if path.is_file() { - continue; - } - - let vert_source = std::fs::read_to_string(path.join("vert.glsl")).unwrap(); - let frag_source = std::fs::read_to_string(path.join("frag.glsl")).unwrap(); - let program = Program::from_source(display, &vert_source, &frag_source, None).unwrap(); - - let name = path.file_name().unwrap().to_str().unwrap().to_owned(); - shaders.insert(name.to_string(), program); - log::info!("Loaded shader {:?} from {:?}.", name, path); - } - } - resources.insert(shaders); - - // Set up font stuff - let dejavu = std::fs::read("assets/fonts/DejaVuSans.ttf").unwrap(); - let fonts = vec![Font::from_bytes(dejavu).unwrap()]; - let glyph_brush = GlyphBrush::new(display, fonts); - log::info!("Set up font."); - resources.insert(glyph_brush); -} diff --git a/src/state.rs b/src/state.rs index 0354e4f..2c56313 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,26 +4,22 @@ use glium::{framebuffer::SimpleFrameBuffer, glutin::event::Event, Display, Frame mod test_state; pub use test_state::TestState; +use crate::assets::Assets; + pub trait GameState { - fn input(&mut self, resources: &mut AnyMap, event: &Event<()>); - fn update(&mut self, resources: &mut AnyMap, dt: f32) -> Option>; - fn render(&mut self, resources: &mut AnyMap, display: &Display, target: &mut SimpleFrameBuffer); + fn input(&mut self, assets: &Assets, event: &Event<()>); + fn update(&mut self, assets: &Assets, dt: f32) -> Option>; + fn render(&mut self, assets: &Assets, display: &Display, target: &mut SimpleFrameBuffer); } pub struct DummyState; impl GameState for DummyState { - fn update(&mut self, _resourcess: &mut AnyMap, _dt: f32) -> Option> { + fn update(&mut self, _assets: &Assets, _dt: f32) -> Option> { None } - fn input(&mut self, _resources: &mut AnyMap, _event: &Event<()>) {} + fn input(&mut self, _assets: &Assets, _event: &Event<()>) {} - fn render( - &mut self, - _resources: &mut AnyMap, - _display: &Display, - _target: &mut SimpleFrameBuffer, - ) { - } + fn render(&mut self, _assets: &Assets, _display: &Display, _target: &mut SimpleFrameBuffer) {} } diff --git a/src/state/test_state.rs b/src/state/test_state.rs index b215a9f..ed02ca2 100644 --- a/src/state/test_state.rs +++ b/src/state/test_state.rs @@ -2,24 +2,26 @@ use glium::{ framebuffer::SimpleFrameBuffer, implement_vertex, index::PrimitiveType, uniform, uniforms::Sampler, IndexBuffer, Surface, VertexBuffer, }; -use resources::Shaders; -use crate::resources::{self, Textures}; +use crate::assets::Assets; use super::GameState; -pub struct TestState; +pub struct TestState { + pub time: f32, +} impl GameState for TestState { - fn input(&mut self, resources: &mut anymap::AnyMap, event: &glium::glutin::event::Event<()>) {} + fn input(&mut self, assets: &Assets, event: &glium::glutin::event::Event<()>) {} - fn update(&mut self, resources: &mut anymap::AnyMap, dt: f32) -> Option> { + fn update(&mut self, assets: &Assets, dt: f32) -> Option> { + self.time += 0.0; None } fn render( &mut self, - resources: &mut anymap::AnyMap, + assets: &Assets, display: &glium::Display, target: &mut SimpleFrameBuffer, ) { @@ -33,24 +35,46 @@ impl GameState for TestState { implement_vertex!(Vertex, position, tex_coords); + let tex_coords = crate::assets::AtlasUVs { + top_left: [0.0, 0.0], + top_right: [1.0, 0.0], + bottom_left: [0.0, 1.0], + bottom_right: [1.0, 1.0], + }; + let tex_coords = assets + .texture_atlas + .get_uv("backgrounds:grass-hills".to_string()); + VertexBuffer::new( display, &[ Vertex { position: [-1.0, -1.0], - tex_coords: [0.0, 0.0], + tex_coords: [ + (tex_coords.top_left[0] - self.time.sin() / 8.0), + tex_coords.top_left[1], + ], }, Vertex { position: [-1.0, 1.0], - tex_coords: [0.0, 1.0], + tex_coords: [ + (tex_coords.bottom_left[0] - self.time.sin() / 8.0), + tex_coords.bottom_left[1], + ], }, Vertex { position: [1.0, 1.0], - tex_coords: [1.0, 1.0], + tex_coords: [ + (tex_coords.bottom_right[0] / 2.0 - self.time.sin() / 8.0), + tex_coords.bottom_right[1], + ], }, Vertex { position: [1.0, -1.0], - tex_coords: [1.0, 0.0], + tex_coords: [ + (tex_coords.top_right[0] / 2.0 - self.time.sin() / 8.0), + tex_coords.top_right[1], + ], }, ], ) @@ -60,23 +84,13 @@ impl GameState for TestState { let index_buffer = IndexBuffer::new(display, PrimitiveType::TriangleStrip, &[1 as u16, 2, 0, 3]).unwrap(); - let program = resources.get::().unwrap().get("basic").unwrap(); - - let texture = resources - .get::() - .unwrap() - .get("tiles:dirt") - .unwrap(); + let program = assets.shaders.get("basic").unwrap(); let uniforms = uniform! { - /*matrix: [ - [1.0,0.0,0.0], - [0.0,1.0,0.0], - [0.0,0.0,1.0 as f32], - ],*/ - matrix: cgmath::conv::array3x3(cgmath::Matrix3::from_nonuniform_scale(1.0/crate::SCREEN_WIDTH*16.0, 1.0/crate::SCREEN_HEIGHT*16.0)), - texture: texture.sampled().magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest), - + matrix: cgmath::conv::array3x3( + cgmath::Matrix3::from_nonuniform_scale(1.0, 1.0 as f32) + ), + texture: assets.texture_atlas.texture.sampled().magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest), }; target