diff --git a/assets/images/walk_0.png b/assets/images/walk_0.png new file mode 100644 index 0000000..cd3aa56 Binary files /dev/null and b/assets/images/walk_0.png differ diff --git a/assets/shaders/canvas_fragment.glsl b/assets/shaders/canvas_fragment.glsl new file mode 100644 index 0000000..29e9a53 --- /dev/null +++ b/assets/shaders/canvas_fragment.glsl @@ -0,0 +1,19 @@ +#version 130 + +in vec4 _color; +in vec2 _tex_coord; + +out vec4 frag_color; +uniform sampler2D sampler; +uniform int has_texture; + +void main() { + if (has_texture == 1) + { + frag_color = _color * texture(sampler, _tex_coord); + } + else + { + frag_color = _color; + } +} diff --git a/assets/shaders/canvas_vertex.glsl b/assets/shaders/canvas_vertex.glsl new file mode 100644 index 0000000..840af99 --- /dev/null +++ b/assets/shaders/canvas_vertex.glsl @@ -0,0 +1,16 @@ +#version 130 + +in vec3 pos; +in vec4 color; +in vec2 tex_coord; + +out vec4 _color; +out vec2 _tex_coord; + +uniform mat4 proj; + +void main() { + gl_Position = proj * vec4(pos, 1.0); + _color = color; + _tex_coord = tex_coord; +} diff --git a/meson.build b/meson.build index 02df48e..dd78f2d 100644 --- a/meson.build +++ b/meson.build @@ -24,8 +24,25 @@ executable( 'rest-in-dust', sources: [ 'src/main.cpp', + + # game + 'src/Game.cpp', + 'src/BaseScene.cpp', + + # arena + 'src/arena/Arena.cpp', + + # gfx + 'src/gfx/Window.cpp', + 'src/gfx/Shaders.cpp', + 'src/gfx/Canvas.cpp', + 'src/gfx/Texture.cpp', ], dependencies: [ + dependency('glew'), + dependency('sdl2'), + dependency('SDL2_image'), + dependency('glm'), ], install: true ) diff --git a/src/BaseScene.cpp b/src/BaseScene.cpp new file mode 100644 index 0000000..b846ba7 --- /dev/null +++ b/src/BaseScene.cpp @@ -0,0 +1,13 @@ +#include "BaseScene.hpp" + +namespace rid +{ + /*explicit*/ BaseScene::BaseScene(Game& game) + : m_game { game } + { + } + + /*virtual*/ BaseScene::~BaseScene() + { + } +} diff --git a/src/BaseScene.hpp b/src/BaseScene.hpp new file mode 100644 index 0000000..5828a95 --- /dev/null +++ b/src/BaseScene.hpp @@ -0,0 +1,25 @@ +#ifndef rid_BASESCENE_HPP +#define rid_BASESCENE_HPP + +#include "gfx/Window.hpp" +#include "Game.hpp" + +namespace rid +{ + class BaseScene + { + public: + explicit BaseScene(Game& game); + virtual ~BaseScene(); + + virtual void update(float) {}; + virtual void draw(Window const&) {}; + + protected: + Game& m_game; + + private: + }; +} + +#endif diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..246eb5b --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,66 @@ +#include "Game.hpp" +#include "BaseScene.hpp" +#include "arena/Arena.hpp" + +namespace rid +{ + /*explicit*/ Game::Game() + { + } + + /*virtual*/ Game::~Game() + { + } + + int Game::exec() + { + m_window = std::make_unique(640, 480); + SDL_Event event; + float dt = 0.0f; + + set_scene(std::make_unique(*this)); + + while (m_window->is_open()) + { + auto now = std::chrono::steady_clock::now(); + + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + m_window->close(); + } + } + + if (m_current_scene) + { + m_current_scene->update(dt); + } + + m_window->clear(); + + if (m_current_scene) + { + m_current_scene->draw(*m_window); + } + + m_window->update(); + + if (m_next_scene) + { + m_current_scene = std::move(m_next_scene); + m_next_scene.reset(); + } + + auto elapsed = std::chrono::steady_clock::now() - now; + dt = std::chrono::duration_cast(elapsed).count() / 1000000.0f; + } + + return 0; + } + + void Game::set_scene(std::unique_ptr scene) + { + m_next_scene = std::move(scene); + } +} diff --git a/src/Game.hpp b/src/Game.hpp new file mode 100644 index 0000000..f768032 --- /dev/null +++ b/src/Game.hpp @@ -0,0 +1,30 @@ +#ifndef rid_GAME_HPP +#define rid_GAME_HPP + +#include "conf.hpp" +#include "gfx/Window.hpp" + +namespace rid +{ + class BaseScene; + + class Game + { + public: + explicit Game(); + virtual ~Game(); + + int exec(); + + // Scenes management + // ----------------- + void set_scene(std::unique_ptr scene); + + private: + std::unique_ptr m_window; + std::unique_ptr m_current_scene; + std::unique_ptr m_next_scene; + }; +} + +#endif diff --git a/src/arena/Arena.cpp b/src/arena/Arena.cpp new file mode 100644 index 0000000..daf4a73 --- /dev/null +++ b/src/arena/Arena.cpp @@ -0,0 +1,34 @@ +#include "Arena.hpp" +#include "conf.hpp" + +namespace rid +{ + /*explicit*/ Arena::Arena(Game& game) + : BaseScene(game) + , m_canvas { std::make_unique() } + { + auto tex = std::make_unique(); + tex->load(RID_DATADIR / "assets" / "images" / "walk_0.png"); + m_canvas->set_texture(std::move(tex)); + + m_canvas->draw_tex(glm::vec2 {512.0f, 512.0f}, + glm::vec2 {96.0f, 96.0f}, + glm::vec4 {1.0f, 1.0f, 1.0f, 1.0f}, + glm::vec2 {0.f, 0.0f}, + glm::vec2 {32.0f, 32.0f}); + + } + + /*virtual*/ Arena::~Arena() + { + } + + void Arena::update(float) /*override*/ + { + } + + void Arena::draw(Window const& win) /*override*/ + { + win.draw(*m_canvas); + } +} diff --git a/src/arena/Arena.hpp b/src/arena/Arena.hpp new file mode 100644 index 0000000..35d6831 --- /dev/null +++ b/src/arena/Arena.hpp @@ -0,0 +1,24 @@ +#ifndef rid_ARENA_HPP +#define rid_ARENA_HPP + +#include "conf.hpp" +#include "../BaseScene.hpp" +#include "../gfx/Canvas.hpp" + +namespace rid +{ + class Arena: public BaseScene + { + public: + explicit Arena(Game& game); + virtual ~Arena(); + + void update(float dt) override; + void draw(Window const& win) override; + + private: + std::unique_ptr m_canvas; + }; +} + +#endif diff --git a/src/conf.in.hpp b/src/conf.in.hpp index 90ccf22..c58a15f 100644 --- a/src/conf.in.hpp +++ b/src/conf.in.hpp @@ -1,10 +1,26 @@ #ifndef rid_CONF_HPP #define rid_CONF_HPP +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + #define RID_VERSION std::string("@version@") #define RID_DATADIR std::filesystem::path("@datadir@") -#include -#include +#define RID_ERROR(NAME) \ + struct NAME : public std::runtime_error { \ + explicit NAME (std::string const& what) : std::runtime_error { what } {} \ + } + #endif diff --git a/src/gfx/Canvas.cpp b/src/gfx/Canvas.cpp new file mode 100644 index 0000000..b8e1db5 --- /dev/null +++ b/src/gfx/Canvas.cpp @@ -0,0 +1,152 @@ +#include "Canvas.hpp" + +namespace rid +{ + /*explicit*/ Canvas::Canvas() + : m_shaders { std::make_unique("canvas") } + { + glGenVertexArrays(1, &m_vao); + use(); + glGenBuffers(1, &m_vbo); + + m_shaders->use(); + m_shaders->set_int("has_texture", 0); + + update(); + } + + /*virtual*/ Canvas::~Canvas() + { + glDeleteVertexArrays(1, &m_vao); + glDeleteBuffers(1, &m_vbo); + } + + void Canvas::set_texture(std::unique_ptr texture) + { + m_shaders->use(); + m_shaders->set_int("has_texture", 1); + m_texture = std::move(texture); + } + + void Canvas::draw_tex(glm::vec2 pos, + glm::vec2 size, + glm::vec4 color, + glm::vec2 tex_pos, + glm::vec2 tex_size) + { + // A -- B + // | | + // D -- C + + float tx = tex_pos.x; + float ty = tex_pos.y; + float tw = tex_size.x; + float th = tex_size.y; + + if (m_texture) + { + tx /= static_cast(m_texture->width()); + ty /= static_cast(m_texture->height()); + tw /= static_cast(m_texture->width()); + th /= static_cast(m_texture->height()); + } + + Vertex a { + pos.x - size.x/2.0f, pos.y - size.y/2.0f, 0.0f, + color.x, color.y, color.z, color.w, + tx, ty + }; + + Vertex b { + pos.x + size.x/2.0f, pos.y - size.y/2.0f, 0.0f, + color.x, color.y, color.z, color.w, + tx + tw, ty + }; + + Vertex c { + pos.x + size.x/2.0f, pos.y + size.y/2.0f, 0.0f, + color.x, color.y, color.z, color.w, + tx + tw, ty + th + }; + + Vertex d { + pos.x - size.x/2.0f, pos.y + size.y/2.0f, 0.0f, + color.x, color.y, color.z, color.w, + tx, ty + th + }; + + m_vertices.push_back(a); + m_vertices.push_back(b); + m_vertices.push_back(c); + + m_vertices.push_back(a); + m_vertices.push_back(c); + m_vertices.push_back(d); + + update(); + } + + void Canvas::draw(glm::mat4 transform) const + { + use(); + m_shaders->set_mat4("proj", transform); + glDrawArrays(GL_TRIANGLES, 0, m_vertices.size()); + } + + void Canvas::use() const + { + glBindVertexArray(m_vao); + m_shaders->use(); + + if (m_texture) + { + m_texture->use(); + } + } + + void Canvas::update() const + { + use(); + + glBindBuffer(GL_ARRAY_BUFFER, m_vbo); + + glBufferData(GL_ARRAY_BUFFER, + sizeof(Vertex) * m_vertices.size(), + m_vertices.data(), + GL_STATIC_DRAW); + + // Position + // -------- + GLint pos = m_shaders->attrib("pos"); + glEnableVertexAttribArray(pos); + glVertexAttribPointer(pos, + 3, + GL_FLOAT, + GL_FALSE, + sizeof(Vertex), + nullptr); + + // Color + // ----- + GLint color = m_shaders->attrib("color"); + glEnableVertexAttribArray(color); + glVertexAttribPointer(color, + 4, + GL_FLOAT, + GL_FALSE, + sizeof(Vertex), + reinterpret_cast(offsetof(Vertex, r))); + + // Tex coord + // --------- + GLint tex = m_shaders->attrib("tex_coord"); + glEnableVertexAttribArray(tex); + glVertexAttribPointer(tex, + 2, + GL_FLOAT, + GL_FALSE, + sizeof(Vertex), + reinterpret_cast(offsetof(Vertex, u))); + + } +} diff --git a/src/gfx/Canvas.hpp b/src/gfx/Canvas.hpp new file mode 100644 index 0000000..75b02bd --- /dev/null +++ b/src/gfx/Canvas.hpp @@ -0,0 +1,48 @@ +#ifndef rid_CANVAS_HPP +#define rid_CANVAS_HPP + +#include "conf.hpp" +#include "Shaders.hpp" +#include "Texture.hpp" + +namespace rid +{ + struct Vertex + { + float x; float y; float z; + float r; float g; float b; float a; + float u; float v; + }; + + class Canvas + { + public: + explicit Canvas(); + virtual ~Canvas(); + + void set_texture(std::unique_ptr texture); + + // Draw stuff + // ---------- + void draw_tex(glm::vec2 pos, + glm::vec2 size, + glm::vec4 color, + glm::vec2 tex_pos, + glm::vec2 tex_size); + + void draw(glm::mat4 transform) const; + void clear() { m_vertices.clear(); update(); } + + private: + std::unique_ptr m_shaders; + std::vector m_vertices; + std::unique_ptr m_texture; + GLuint m_vao; + GLuint m_vbo; + + void use() const; + void update() const; + }; +} + +#endif diff --git a/src/gfx/Shaders.cpp b/src/gfx/Shaders.cpp new file mode 100644 index 0000000..68ae443 --- /dev/null +++ b/src/gfx/Shaders.cpp @@ -0,0 +1,122 @@ +#include "Shaders.hpp" +#include + +namespace rid +{ + /*explicit*/ Shaders::Shaders(std::string const& name) + : m_program { glCreateProgram() } + { + use(); + + load_shader(GL_VERTEX_SHADER, RID_DATADIR / "assets" / "shaders" / (name + "_vertex.glsl")); + load_shader(GL_FRAGMENT_SHADER, RID_DATADIR / "assets" / "shaders" / (name + "_fragment.glsl")); + + glLinkProgram(m_program); + + GLint status = GL_FALSE; + glGetProgramiv(m_program, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) + { + size_t const SZ = 256; + char msg[SZ]; + + glGetProgramInfoLog(m_program, SZ, nullptr, msg); + + throw shaders_error {std::string() + "Cannot link shader program '" + name + "': " + msg}; + } + + } + + /*virtual*/ Shaders::~Shaders() + { + glDeleteProgram(m_program); + } + + void Shaders::use() const + { + glUseProgram(m_program); + } + + GLint Shaders::attrib(std::string const& name) const + { + GLint value = glGetAttribLocation(m_program, name.c_str()); + + if (value < 0) + { + throw shaders_error {"Cannot find attribute '" + name + "'."}; + } + + return value; + } + + GLint Shaders::uniform(std::string const& name) const + { + GLint value = glGetUniformLocation(m_program, name.c_str()); + + if (value < 0) + { + throw shaders_error {"Cannot find uniform '" + name + "'."}; + } + + return value; + } + + void Shaders::set_mat4(std::string const& name, glm::mat4 mat) + { + GLint loc = uniform(name); + glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(mat)); + } + + void Shaders::set_int(std::string const& name, int value) + { + GLint loc = uniform(name); + glUniform1i(loc, value); + } + + GLuint Shaders::load_shader(GLenum shader_type, + std::filesystem::path const& shader_path) + { + GLuint shader = glCreateShader(shader_type); + + if (!std::filesystem::is_regular_file(shader_path)) + { + throw shaders_error {"Cannot load shader: '" + + shader_path.string() + "' is not a regular file."}; + } + + std::ifstream file {shader_path}; + + if (!file) + { + throw shaders_error {"Cannot load shader: cannot open '" + + shader_path.string() + "'."}; + } + + std::stringstream ss; + ss << file.rdbuf(); + + std::string source = ss.str(); + char const* sources[] = { source.c_str() }; + + glShaderSource(shader, 1, sources, nullptr); + glCompileShader(shader); + + GLint status = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + + if (status == GL_FALSE) + { + size_t const SZ = 256; + char msg[SZ]; + + glGetShaderInfoLog(shader, SZ, nullptr, msg); + + throw shaders_error {"Cannot compile shader '" + shader_path.string() + "': " + msg}; + } + + glAttachShader(m_program, shader); + + return shader; + } +} diff --git a/src/gfx/Shaders.hpp b/src/gfx/Shaders.hpp new file mode 100644 index 0000000..43c6787 --- /dev/null +++ b/src/gfx/Shaders.hpp @@ -0,0 +1,31 @@ +#ifndef rid_SHADERS_HPP +#define rid_SHADERS_HPP + +#include +#include "conf.hpp" + +namespace rid +{ + RID_ERROR(shaders_error); + + class Shaders + { + public: + explicit Shaders(std::string const& name); + virtual ~Shaders(); + + void use() const; + GLint attrib(std::string const& name) const; + GLint uniform(std::string const& name) const; + + void set_mat4(std::string const& name, glm::mat4 mat); + void set_int(std::string const& name, int value); + + private: + GLuint m_program; + + GLuint load_shader(GLenum shader_type, std::filesystem::path const& shader_path); + }; +} + +#endif diff --git a/src/gfx/Texture.cpp b/src/gfx/Texture.cpp new file mode 100644 index 0000000..a35ccb1 --- /dev/null +++ b/src/gfx/Texture.cpp @@ -0,0 +1,53 @@ +#include "Texture.hpp" + +namespace rid +{ + /*explicit*/ Texture::Texture() + { + glGenTextures(1, &m_texture); + } + + /*virtual*/ Texture::~Texture() + { + glDeleteTextures(1, &m_texture); + } + + void Texture::load(std::filesystem::path const& tex_path) + { + SDL_Surface* surface = IMG_Load(tex_path.c_str()); + + surface = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA32, 0); + + if (!surface) + { + throw texture_error {"Cannot load texture '" + tex_path.string() + "'."}; + } + + m_width = surface->w; + m_height = surface->h; + + use(); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + surface->w, + surface->h, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + surface->pixels); + + SDL_FreeSurface(surface); + } + + void Texture::use() const + { + glBindTexture(GL_TEXTURE_2D, m_texture); + } +} diff --git a/src/gfx/Texture.hpp b/src/gfx/Texture.hpp new file mode 100644 index 0000000..b05801e --- /dev/null +++ b/src/gfx/Texture.hpp @@ -0,0 +1,33 @@ +#ifndef rid_TEXTURE_HPP +#define rid_TEXTURE_HPP + +#include +#include +#include + +#include "conf.hpp" + +namespace rid +{ + RID_ERROR(texture_error); + + class Texture + { + public: + explicit Texture(); + virtual ~Texture(); + + int width() const { return m_width; } + int height() const { return m_height; } + + void load(std::filesystem::path const& tex_path); + void use() const; + + private: + GLuint m_texture; + int m_width = 0; + int m_height = 0; + }; +} + +#endif diff --git a/src/gfx/Window.cpp b/src/gfx/Window.cpp new file mode 100644 index 0000000..db61203 --- /dev/null +++ b/src/gfx/Window.cpp @@ -0,0 +1,82 @@ +#include "Window.hpp" +#include "SDL_image.h" + +namespace rid +{ + /*explicit*/ Window::Window(int width, int height) + : m_width { width } + , m_height { height } + { + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + throw display_error {"Cannot initialize SDL 2."}; + } + + // Init SDL2 image + { + int flags = IMG_INIT_PNG; + int status = IMG_Init(flags); + + if ( (status & flags) != flags ) + { + throw display_error {"Cannot initialize sdl2 image."}; + } + } + + std::string const title = "Rest In Dust v" + RID_VERSION; + + m_window = SDL_CreateWindow(title.c_str(), + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + m_width, m_height, + SDL_WINDOW_SHOWN + | SDL_WINDOW_OPENGL + | SDL_WINDOW_MAXIMIZED); + + if (!m_window) + { + throw display_error {"Cannot create SDL2 window."}; + } + + m_context = SDL_GL_CreateContext(m_window); + + if (!m_context) + { + throw display_error {"Cannot create SDL2 GL context."}; + } + + if (glewInit() != GLEW_OK) + { + throw display_error {"Cannot initialize GLEW."}; + } + } + + /*virtual*/ Window::~Window() + { + SDL_GL_DeleteContext(m_context); + IMG_Quit(); + SDL_Quit(); + } + + void Window::draw(Canvas const& canvas) const + { + int w, h; + SDL_GetWindowSize(m_window, &w, &h); + glm::mat4 proj = glm::ortho(0.0f, static_cast(w), + static_cast(h), 0.0f); + + + canvas.draw(proj); + } + + void Window::clear(glm::vec4 const& color) const + { + glClearColor(color.x, color.y, color.z, color.w); + glClear(GL_COLOR_BUFFER_BIT); + } + + void Window::update() const + { + SDL_GL_SwapWindow(m_window); + } +} diff --git a/src/gfx/Window.hpp b/src/gfx/Window.hpp new file mode 100644 index 0000000..b808494 --- /dev/null +++ b/src/gfx/Window.hpp @@ -0,0 +1,48 @@ +#ifndef rid_WINDOW_HPP +#define rid_WINDOW_HPP + +#include +#include +#include +#include "conf.hpp" +#include "Canvas.hpp" + +namespace rid +{ + RID_ERROR(display_error); + + class Window + { + public: + explicit Window(int width, int height); + virtual ~Window(); + + // Drawing + // ------- + void draw(Canvas const& canvas) const; + + // Update + // ------ + void clear(glm::vec4 const& color = {0.0f, 0.0f, 0.0f, 1.0f}) const; + void update() const; + + // State + // ----- + bool is_open() const { return m_is_open; } + void close() { m_is_open = false; } + + // Dimensions + // ---------- + int width() const { return m_width; } + int height() const { return m_height; } + + private: + SDL_Window* m_window = nullptr; + SDL_GLContext m_context = nullptr; + int m_width; + int m_height; + bool m_is_open = true; + }; +} + +#endif diff --git a/src/main.cpp b/src/main.cpp index c2e00bb..431800a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ #include -#include "conf.hpp" +#include "Game.hpp" int main(int, char**) { - std::cout << "Rest In Dust V" << RID_VERSION << std::endl; - return 0; + rid::Game game; + return game.exec(); }