From bb78d60407e3db374bbbd3185622d5bae8810af1 Mon Sep 17 00:00:00 2001 From: bog Date: Thu, 26 Oct 2023 21:03:41 +0200 Subject: [PATCH] :sparkles: draw sprites on the game window. --- assets/images/demo.png | Bin 0 -> 421 bytes assets/manifest.xml | 14 ++++ assets/shaders/default_fragment.glsl | 21 +++++ assets/shaders/default_vertex.glsl | 15 ++++ meson.build | 26 ++++++ src/Game.cpp | 28 ++++++- src/Game.hpp | 1 + src/Services.cpp | 16 ++++ src/Services.hpp | 3 + src/Sprite.cpp | 45 +++++++++++ src/Sprite.hpp | 34 ++++++++ src/commons.hpp | 5 ++ src/conf.in.hpp | 1 + src/gfx/Graphics.cpp | 89 ++++++++++++++++++++ src/gfx/Graphics.hpp | 43 ++++++++++ src/gfx/Material.cpp | 27 +++++++ src/gfx/Material.hpp | 30 +++++++ src/gfx/Mesh.cpp | 78 ++++++++++++++++++ src/gfx/Mesh.hpp | 44 ++++++++++ src/gfx/Shader.cpp | 116 +++++++++++++++++++++++++++ src/gfx/Shader.hpp | 34 ++++++++ src/gfx/Shape.cpp | 97 ++++++++++++++++++++++ src/gfx/Shape.hpp | 49 +++++++++++ src/gfx/Texture.cpp | 48 +++++++++++ src/gfx/Texture.hpp | 29 +++++++ src/main.cpp | 14 +++- src/res/BaseRes.cpp | 12 +++ src/res/BaseRes.hpp | 22 +++++ src/res/ImageRes.cpp | 28 +++++++ src/res/ImageRes.hpp | 25 ++++++ src/res/Resources.cpp | 53 ++++++++++++ src/res/Resources.hpp | 64 +++++++++++++++ src/res/TextRes.cpp | 29 +++++++ src/res/TextRes.hpp | 23 ++++++ 34 files changed, 1159 insertions(+), 4 deletions(-) create mode 100644 assets/images/demo.png create mode 100644 assets/manifest.xml create mode 100644 assets/shaders/default_fragment.glsl create mode 100644 assets/shaders/default_vertex.glsl create mode 100644 src/Sprite.cpp create mode 100644 src/Sprite.hpp create mode 100644 src/gfx/Graphics.cpp create mode 100644 src/gfx/Graphics.hpp create mode 100644 src/gfx/Material.cpp create mode 100644 src/gfx/Material.hpp create mode 100644 src/gfx/Mesh.cpp create mode 100644 src/gfx/Mesh.hpp create mode 100644 src/gfx/Shader.cpp create mode 100644 src/gfx/Shader.hpp create mode 100644 src/gfx/Shape.cpp create mode 100644 src/gfx/Shape.hpp create mode 100644 src/gfx/Texture.cpp create mode 100644 src/gfx/Texture.hpp create mode 100644 src/res/BaseRes.cpp create mode 100644 src/res/BaseRes.hpp create mode 100644 src/res/ImageRes.cpp create mode 100644 src/res/ImageRes.hpp create mode 100644 src/res/Resources.cpp create mode 100644 src/res/Resources.hpp create mode 100644 src/res/TextRes.cpp create mode 100644 src/res/TextRes.hpp diff --git a/assets/images/demo.png b/assets/images/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..c84cfbc52e6d14eb463e220a753d053ba553bebb GIT binary patch literal 421 zcmV;W0b2fvP)v*C!k@4ERi-GU8YFKk(MP`Aq^AIg#j2sspOMoS+YMLDB#`wyZ`-V zy(d|g?c5WO4&P}9*Ya-}Wa78a_aWxLpM4jPCIF@oD?&uBmVjBvdqhYAW+k^oyhz4# zlWRV?fT`6!f`X&Uz%`=Re82Wg3@H}h=i})jyCDJS^sw6GvBh>>IS(mTHX%h*gjj?U zIRH2SsQ&SHv_mmGE1G~?6kx?wWWXvw@9m?*vf&)+65Ima0%&AXVbQUkOd*=PfELeU zyKWLwV0wAsX}iJ90Nq+4#r#Nu`9C_h1yKNxzQ+;AVYSJb<35`L@WhhRH6lW?MW5`V zQV1w2=X^ZnNf-%tEG-Ax(7F)<^3@9gNd17fS`AXGb}mO>a8A0AgcRM^?zb5G{#dHJ zWaL8PopYYA6@}FY8swgnS85t4&Iqer3&K^7{+30=pa>-6-xK^kaX + + + + + + + + + + + diff --git a/assets/shaders/default_fragment.glsl b/assets/shaders/default_fragment.glsl new file mode 100644 index 0000000..a5d871b --- /dev/null +++ b/assets/shaders/default_fragment.glsl @@ -0,0 +1,21 @@ +#version 130 + +in vec4 f_color; +in vec2 f_tex; + +out vec4 out_color; +uniform sampler2D tex_sampler; +uniform int has_texture; + +void main() { + + if (has_texture != 0) + { + vec4 texcolor = f_color * texture(tex_sampler, f_tex); + out_color = texcolor; + } + else + { + out_color = f_color; + } +} diff --git a/assets/shaders/default_vertex.glsl b/assets/shaders/default_vertex.glsl new file mode 100644 index 0000000..59226f6 --- /dev/null +++ b/assets/shaders/default_vertex.glsl @@ -0,0 +1,15 @@ +#version 130 + +in vec3 pos; +in vec4 color; +in vec2 tex; +out vec2 f_tex; +out vec4 f_color; + +uniform mat4 mvp; + +void main() { + gl_Position = mvp * vec4(pos, 1.0); + f_color = color; + f_tex = tex; +} diff --git a/meson.build b/meson.build index d26cd4f..8cf4790 100644 --- a/meson.build +++ b/meson.build @@ -7,8 +7,11 @@ project('mornelune', 'cpp_stdd=c++17' ]) +share_dir = get_option('prefix') / get_option('datadir') / 'mornelune' + conf = configuration_data() conf.set('version', meson.project_version()) +conf.set('share_dir', share_dir) configure_file( input: 'src/conf.in.hpp', @@ -24,6 +27,7 @@ executable( 'src/Game.cpp', 'src/Services.cpp', 'src/Service.cpp', + 'src/Sprite.cpp', # Logs 'src/logs/Logs.cpp', @@ -32,8 +36,30 @@ executable( # Events 'src/events/Events.cpp', 'src/events/Event.cpp', + + # Graphics + 'src/gfx/Graphics.cpp', + 'src/gfx/Shader.cpp', + 'src/gfx/Mesh.cpp', + 'src/gfx/Material.cpp', + 'src/gfx/Texture.cpp', + 'src/gfx/Shape.cpp', + + # Resources + 'src/res/Resources.cpp', + 'src/res/BaseRes.cpp', + 'src/res/TextRes.cpp', + 'src/res/ImageRes.cpp', + ], dependencies: [ + dependency('sdl2'), + dependency('SDL2_image'), + dependency('glew'), + dependency('glm'), + dependency('tinyxml2') ], install: true ) + +install_subdir('assets', install_dir: share_dir) diff --git a/src/Game.cpp b/src/Game.cpp index 9d35b09..6247135 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,7 +1,9 @@ #include "Game.hpp" #include "logs/FileLogs.hpp" -#include "src/events/Events.hpp" -#include "src/logs/Logs.hpp" +#include "events/Events.hpp" +#include "logs/Logs.hpp" +#include "gfx/Graphics.hpp" +#include "Sprite.hpp" namespace ml { @@ -16,6 +18,28 @@ namespace ml void Game::start() { ML_SYS(FileLogs)->log(LEVEL_INFO, "game started"); + + m_running = true; + + SDL_Event event; + + Sprite sprite {256.0f, 256.0f, 128.0f, 128.0f}; + sprite.set_texture("demo"); + + while (m_running) + { + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + m_running = false; + } + } + + ML_SYS(Graphics)->clear(4, 139, 154); + ML_SYS(Graphics)->draw(sprite); + ML_SYS(Graphics)->update(); + } } void Game::stop() diff --git a/src/Game.hpp b/src/Game.hpp index 644d10a..4155215 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -19,6 +19,7 @@ namespace ml void stop(); private: + bool m_running = false; }; } diff --git a/src/Services.cpp b/src/Services.cpp index 242bc2b..562ecea 100644 --- a/src/Services.cpp +++ b/src/Services.cpp @@ -21,4 +21,20 @@ namespace ml /*virtual*/ Services::~Services() { } + + void Services::init() + { + for (auto& entry: m_services) + { + entry.second->init(); + } + } + + void Services::free() + { + for (auto& entry: m_services) + { + entry.second->free(); + } + } } diff --git a/src/Services.hpp b/src/Services.hpp index 5a64692..c62b193 100644 --- a/src/Services.hpp +++ b/src/Services.hpp @@ -16,6 +16,9 @@ namespace ml virtual ~Services(); + void init(); + void free(); + template size_t id(); diff --git a/src/Sprite.cpp b/src/Sprite.cpp new file mode 100644 index 0000000..6717cd9 --- /dev/null +++ b/src/Sprite.cpp @@ -0,0 +1,45 @@ +#include "Sprite.hpp" + +namespace ml +{ + /*explicit*/ Sprite::Sprite(float x, float y, float w, float h) + : Shape(std::make_unique(glm::vec3{x, y, 0.0f}, + glm::vec3{w, h, 0.0f}), + std::make_unique(), + std::make_unique()) + , m_pos { glm::vec3 {x, y, 0.0f} } + , m_size { glm::vec3 {w, h, 0.0f} } + + { + } + + /*virtual*/ Sprite::~Sprite() + { + } + + void Sprite::set_texture(std::string const& name) + { + m_material = std::make_unique(name); + float const tw = m_material->texture().width(); + float const th = m_material->texture().height(); + + set_sub_texture(name, 0, 0, tw, th); + } + + void Sprite::set_sub_texture(std::string const& name, + float x, float y, float w, float h) + { + m_material = std::make_unique(name); + + float const tw = m_material->texture().width(); + float const th = m_material->texture().height(); + + m_mesh = std::make_unique(m_pos, m_size, + glm::vec2 {x / tw, + y / th}, + glm::vec2 {w / tw, + h / th}); + + update(); + } +} diff --git a/src/Sprite.hpp b/src/Sprite.hpp new file mode 100644 index 0000000..30af581 --- /dev/null +++ b/src/Sprite.hpp @@ -0,0 +1,34 @@ +#ifndef ml_SPRITE_HPP +#define ml_SPRITE_HPP + +#include +#include "commons.hpp" +#include "gfx/Mesh.hpp" +#include "gfx/Shader.hpp" +#include "gfx/Material.hpp" +#include "gfx/Shape.hpp" + +namespace ml +{ + /** + * Textured AABB. + * @see Shape + **/ + class Sprite: public Shape + { + public: + explicit Sprite(float x, float y, float w, float h); + virtual ~Sprite(); + + void set_texture(std::string const& name); + + void set_sub_texture(std::string const& name, + float x, float y, float w, float h); + + private: + glm::vec3 m_pos; + glm::vec3 m_size; + }; +} + +#endif diff --git a/src/commons.hpp b/src/commons.hpp index 30c3433..4404084 100644 --- a/src/commons.hpp +++ b/src/commons.hpp @@ -1,6 +1,8 @@ #ifndef ml_COMMONS_HPP #define ml_COMMONS_HPP +#include "conf.hpp" + #include #include #include @@ -12,6 +14,9 @@ #include #include +#define ML_ASSET(NAME) \ + (std::filesystem::path(ML_SHARE_DIR) / "assets" / NAME) + #define ML ml::Services::get() #define ML_SYS(SYSTEM) ML->get() diff --git a/src/conf.in.hpp b/src/conf.in.hpp index 66ee0d9..deea662 100644 --- a/src/conf.in.hpp +++ b/src/conf.in.hpp @@ -2,5 +2,6 @@ #define ml_CONF_HPP #define ML_VERSION std::string("@version@") +#define ML_SHARE_DIR std::string("@share_dir@") #endif diff --git a/src/gfx/Graphics.cpp b/src/gfx/Graphics.cpp new file mode 100644 index 0000000..3c31463 --- /dev/null +++ b/src/gfx/Graphics.cpp @@ -0,0 +1,89 @@ +#include +#include +#include "Graphics.hpp" + +namespace ml +{ + /*explicit*/ Graphics::Graphics(std::string const& title, + unsigned width, + unsigned height) + : m_title { title } + , m_width { width } + , m_height { height } + { + m_projection = glm::ortho(0.0f, + static_cast(m_width), + static_cast(m_height), + -1.0f, + 0.0f, + 1.0f); + } + + /*virtual*/ Graphics::~Graphics() + { + } + + void Graphics::init() /*override*/ + { + SDL_Init(SDL_INIT_VIDEO); + int init_flags = IMG_INIT_PNG; + + if ((IMG_Init(init_flags) & init_flags) != init_flags) + { + throw gfx_error {"cannot initialize SDL2 image"}; + } + + m_window = SDL_CreateWindow(m_title.c_str(), + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + m_width, m_height, + SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL); + + if (!m_window) + { + throw gfx_error {"cannot create window"}; + } + + m_context = SDL_GL_CreateContext(m_window); + + if (!m_context) + { + throw gfx_error {"cannot create GL context"}; + } + + int status = glewInit(); + + if (status != GLEW_OK) + { + throw gfx_error {"cannot create GLEW context"}; + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + void Graphics::free() /*override*/ + { + SDL_GL_DeleteContext(m_context); + SDL_DestroyWindow(m_window); + IMG_Quit(); + SDL_Quit(); + } + + void Graphics::clear(unsigned r, unsigned g, unsigned b) + { + glClearColor(r/255.0f, g/255.0f, b/255.0f, 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + + void Graphics::draw(Shape const& shape) + { + shape.shader().set_matrix("mvp", m_projection); + shape.draw(); + } + + void Graphics::update() + { + SDL_GL_SwapWindow(m_window); + } +} diff --git a/src/gfx/Graphics.hpp b/src/gfx/Graphics.hpp new file mode 100644 index 0000000..da5e5c0 --- /dev/null +++ b/src/gfx/Graphics.hpp @@ -0,0 +1,43 @@ +#ifndef ml_GRAPHICS_HPP +#define ml_GRAPHICS_HPP + +#include +#include +#include +#include +#include +#include "../commons.hpp" +#include "../Service.hpp" +#include "Shape.hpp" + +namespace ml +{ + ML_ERROR(gfx_error); + + class Graphics: public Service + { + public: + explicit Graphics(std::string const& title, + unsigned width, + unsigned height); + virtual ~Graphics(); + + void init() override; + void free() override; + + void clear(unsigned r, unsigned g, unsigned b); + void draw(Shape const& shape); + void update(); + + private: + std::string m_title; + unsigned m_width; + unsigned m_height; + glm::mat4 m_projection; + + SDL_Window* m_window = nullptr; + SDL_GLContext m_context; + }; +} + +#endif diff --git a/src/gfx/Material.cpp b/src/gfx/Material.cpp new file mode 100644 index 0000000..f77262f --- /dev/null +++ b/src/gfx/Material.cpp @@ -0,0 +1,27 @@ +#include "Material.hpp" + +namespace ml +{ + /*explicit*/ Material::Material(glm::vec4 const& color) + : m_color { color } + { + } + + /*explicit*/ Material::Material(std::string const& texture_name) + : m_texture { std::make_unique() } + { + m_texture->load(texture_name); + } + + /*virtual*/ Material::~Material() + { + } + + void Material::use() const + { + if (m_texture) + { + m_texture->use(); + } + } +} diff --git a/src/gfx/Material.hpp b/src/gfx/Material.hpp new file mode 100644 index 0000000..778cc6f --- /dev/null +++ b/src/gfx/Material.hpp @@ -0,0 +1,30 @@ +#ifndef ml_MATERIAL_HPP +#define ml_MATERIAL_HPP + +#include +#include "../commons.hpp" +#include "Texture.hpp" + +namespace ml +{ + class Material + { + public: + explicit Material(glm::vec4 const& color=glm::vec4{1.0f}); + explicit Material(std::string const& texture_name); + + virtual ~Material(); + + glm::vec4 color() const { return m_color; } + bool has_texture() const { return m_texture != nullptr; } + Texture const& texture() const { return *m_texture; } + + void use() const; + + private: + glm::vec4 m_color = {1.0f, 1.0f, 1.0f, 1.0f}; + std::unique_ptr m_texture; + }; +} + +#endif diff --git a/src/gfx/Mesh.cpp b/src/gfx/Mesh.cpp new file mode 100644 index 0000000..7d9f55b --- /dev/null +++ b/src/gfx/Mesh.cpp @@ -0,0 +1,78 @@ +#include "Mesh.hpp" + +namespace ml +{ + /*explicit*/ Mesh::Mesh() + { + } + + /*explicit*/ Mesh::Mesh(glm::vec3 pos, + glm::vec3 size, + glm::vec2 tpos, + glm::vec2 tsize) + { + quad(pos, size, tpos, tsize); + } + + /*virtual*/ Mesh::~Mesh() + { + } + + glm::vec3 Mesh::get(size_t index) const + { + if (index >= count()) + { + throw mesh_error {"cannot access to vertex '" + + std::to_string(index) + + "'"}; + } + + return m_vertices[index]; + } + + Mesh& Mesh::vertex(glm::vec3 v) + { + m_vertices.push_back(v); + return *this; + } + + Mesh& Mesh::texture(glm::vec2 v) + { + m_textures.push_back(v); + return *this; + } + + Mesh& Mesh::quad(glm::vec3 pos, + glm::vec3 size, + glm::vec2 tpos, + glm::vec2 tsize) + { + // a -- b + // | | + // d -- c + + float hw = size.x / 2.0f; + float hh = size.y / 2.0f; + + glm::vec3 a {pos.x - hw, pos.y - hh, pos.z}; + glm::vec3 b {pos.x + hw, pos.y - hh, pos.z}; + glm::vec3 c {pos.x + hw, pos.y + hh, pos.z}; + glm::vec3 d {pos.x - hw, pos.y + hh, pos.z}; + + glm::vec2 ta {tpos.x, tpos.y}; + glm::vec2 tb {tpos.x + tsize.x, tpos.y}; + glm::vec2 tc {tpos.x + tsize.x, tpos.y + tsize.y}; + glm::vec2 td {tpos.x, tpos.y + tsize.y}; + + vertex(a); texture(ta); + vertex(b); texture(tb); + vertex(c); texture(tc); + + vertex(a); texture(ta); + vertex(c); texture(tc); + vertex(d); texture(td); + + return *this; + } + +} diff --git a/src/gfx/Mesh.hpp b/src/gfx/Mesh.hpp new file mode 100644 index 0000000..85e78de --- /dev/null +++ b/src/gfx/Mesh.hpp @@ -0,0 +1,44 @@ +#ifndef ml_MESH_HPP +#define ml_MESH_HPP + +#include + +#include "../commons.hpp" + +namespace ml +{ + ML_ERROR(mesh_error); + + class Mesh + { + public: + explicit Mesh(); + explicit Mesh(glm::vec3 pos, + glm::vec3 size, + glm::vec2 tpos = glm::vec2 {0.0f, 0.0f}, + glm::vec2 tsize = glm::vec2 {1.0f, 1.0f}); + + virtual ~Mesh(); + + Mesh& vertex(glm::vec3 v); + Mesh& texture(glm::vec2 v); + + Mesh& quad(glm::vec3 pos, + glm::vec3 size, + glm::vec2 tpos = glm::vec2 {0.0f, 0.0f}, + glm::vec2 tsize = glm::vec2 {1.0f, 1.0f}); + + std::vector data() const { return m_vertices; } + std::vector tex_data() const { return m_textures; } + + size_t count() const { return m_vertices.size(); } + + glm::vec3 get(size_t index) const; + + private: + std::vector m_vertices; + std::vector m_textures; + }; +} + +#endif diff --git a/src/gfx/Shader.cpp b/src/gfx/Shader.cpp new file mode 100644 index 0000000..9f69820 --- /dev/null +++ b/src/gfx/Shader.cpp @@ -0,0 +1,116 @@ +#include +#include "Shader.hpp" +#include "../Services.hpp" +#include "../res/Resources.hpp" +#include "../res/TextRes.hpp" + +namespace ml +{ + /*explicit*/ Shader::Shader() + : m_program { glCreateProgram() } + { + load_shader(GL_VERTEX_SHADER, "vertex-shader"); + load_shader(GL_FRAGMENT_SHADER, "fragment-shader"); + + for (auto shader: m_shaders) + { + glAttachShader(m_program, shader); + } + + glLinkProgram(m_program); + GLint status = GL_FALSE; + + glGetProgramiv(m_program, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) + { + size_t const SZ = 1024; + char msg [SZ]; + + glGetProgramInfoLog(m_program, SZ, NULL, msg); + throw shader_error {std::string() + "link error: " + msg}; + } + } + + /*virtual*/ Shader::~Shader() + { + for (auto shader: m_shaders) + { + glDeleteShader(shader); + } + + glDeleteProgram(m_program); + } + + int Shader::attrib(std::string const& name) const + { + int res = glGetAttribLocation(m_program, name.c_str()); + + if (res < 0) + { + throw shader_error {"cannot find attribute '" + name + "'"}; + } + + return res; + } + + int Shader::uniform(std::string const& name) const + { + use(); + int res = glGetUniformLocation(m_program, name.c_str()); + + if (res < 0) + { + throw shader_error {"cannot find uniform '" + name + "'"}; + } + + return res; + } + + void Shader::set_matrix(std::string const& name, glm::mat4 mat) const + { + use(); + glUniformMatrix4fv(uniform(name), 1, GL_FALSE, glm::value_ptr(mat)); + } + + void Shader::set_int(std::string const& name, int value) const + { + use(); + glUniform1i(uniform(name), value); + } + + void Shader::use() const + { + glUseProgram(m_program); + } + + GLuint Shader::load_shader(GLenum shader_type, std::string const& name) + { + GLuint shader = glCreateShader(shader_type); + + auto res = ML_SYS(Resources)->get(name); + std::string source = res->data(); + char const* src [] = { source.c_str() }; + + glShaderSource(shader, 1, src, nullptr); + + glCompileShader(shader); + + GLint status = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + + if (status == GL_FALSE) + { + size_t const SZ = 1024; + char msg [SZ]; + + glGetShaderInfoLog(shader, SZ, NULL, msg); + throw shader_error {std::string() + "shader '" + + name + + "' error: " + msg}; + } + + m_shaders.push_back(shader); + return shader; + } +} diff --git a/src/gfx/Shader.hpp b/src/gfx/Shader.hpp new file mode 100644 index 0000000..6a26a02 --- /dev/null +++ b/src/gfx/Shader.hpp @@ -0,0 +1,34 @@ +#ifndef ml_SHADER_HPP +#define ml_SHADER_HPP + +#include +#include +#include "../commons.hpp" + +namespace ml +{ + ML_ERROR(shader_error); + + class Shader + { + public: + explicit Shader(); + virtual ~Shader(); + + int attrib(std::string const& name) const; + int uniform(std::string const& name) const; + + void set_matrix(std::string const& name, glm::mat4 mat) const; + void set_int(std::string const& name, int value) const; + + void use() const; + + private: + GLuint m_program; + std::vector m_shaders; + + GLuint load_shader(GLenum shader_type, std::string const& name); + }; +} + +#endif diff --git a/src/gfx/Shape.cpp b/src/gfx/Shape.cpp new file mode 100644 index 0000000..348c0d5 --- /dev/null +++ b/src/gfx/Shape.cpp @@ -0,0 +1,97 @@ +#include "Shape.hpp" + +namespace ml +{ + /*explicit*/ Shape::Shape(std::unique_ptr mesh, + std::unique_ptr shader, + std::unique_ptr material) + : m_mesh { std::move(mesh) } + , m_shader { std::move(shader) } + , m_material { std::move(material) } + { + update(); + } + + /*virtual*/ Shape::~Shape() + { + glDeleteBuffers(ML_GL_BUFFER_COUNT, m_vbo); + glDeleteVertexArrays(1, &m_vao); + } + + void Shape::draw() const + { + glBindVertexArray(m_vao); + m_shader->use(); + m_material->use(); + + glDrawArrays(GL_TRIANGLES, 0, m_mesh->count()); + } + + void Shape::update() + { + m_shader->use(); + m_shader->set_int("has_texture", m_material->has_texture() ? 1 : 0); + + glGenVertexArrays(1, &m_vao); + glBindVertexArray(m_vao); + + glGenBuffers(ML_GL_BUFFER_COUNT, m_vbo); + + auto vecs = m_mesh->data(); + + // POS + // --- + glBindBuffer(GL_ARRAY_BUFFER, m_vbo[ML_GL_POS_BUFFER]); + + glBufferData(GL_ARRAY_BUFFER, + m_mesh->count() * sizeof(glm::vec3), + vecs.data(), + GL_STATIC_DRAW); + + glEnableVertexAttribArray(m_shader->attrib("pos")); + glVertexAttribPointer(m_shader->attrib("pos"), + 3, + GL_FLOAT, + GL_FALSE, + sizeof(glm::vec3), + nullptr); + + glBindBuffer(GL_ARRAY_BUFFER, m_vbo[ML_GL_POS_BUFFER]); + + // COLOR + // ----- + std::vector colors(vecs.size(), + m_material->color()); + + glBindBuffer(GL_ARRAY_BUFFER, m_vbo[ML_GL_COLOR_BUFFER]); + glBufferData(GL_ARRAY_BUFFER, + colors.size() * sizeof(glm::vec4), + colors.data(), + GL_STATIC_DRAW); + + glEnableVertexAttribArray(m_shader->attrib("color")); + glVertexAttribPointer(m_shader->attrib("color"), + 4, + GL_FLOAT, + GL_FALSE, + sizeof(glm::vec4), + nullptr); + // TEX + // --- + std::vector texpos = m_mesh->tex_data(); + + glBindBuffer(GL_ARRAY_BUFFER, m_vbo[ML_GL_TEX_BUFFER]); + glBufferData(GL_ARRAY_BUFFER, + texpos.size() * sizeof(glm::vec2), + texpos.data(), + GL_STATIC_DRAW); + + glEnableVertexAttribArray(m_shader->attrib("tex")); + glVertexAttribPointer(m_shader->attrib("tex"), + 2, + GL_FLOAT, + GL_FALSE, + sizeof(glm::vec2), + nullptr); + } +} diff --git a/src/gfx/Shape.hpp b/src/gfx/Shape.hpp new file mode 100644 index 0000000..8953e51 --- /dev/null +++ b/src/gfx/Shape.hpp @@ -0,0 +1,49 @@ +#ifndef ml_SHAPE_HPP +#define ml_SHAPE_HPP + +#include + +#include "../commons.hpp" +#include "Mesh.hpp" +#include "Shader.hpp" +#include "Material.hpp" + +namespace ml +{ + enum ML_GL_BUFFER { + ML_GL_POS_BUFFER = 0, + ML_GL_COLOR_BUFFER, + ML_GL_TEX_BUFFER, + ML_GL_BUFFER_COUNT + }; + + /** + * Anything drawable should inherit from this class. + * @see Sprite + * @see Graphics + **/ + class Shape + { + public: + explicit Shape(std::unique_ptr mesh, + std::unique_ptr shader, + std::unique_ptr material); + virtual ~Shape(); + + Shader const& shader() const { return *m_shader; } + void draw() const; + + void update(); + + protected: + std::unique_ptr m_mesh; + std::unique_ptr m_shader; + std::unique_ptr m_material; + + private: + GLuint m_vao; + GLuint m_vbo[ML_GL_BUFFER_COUNT]; + }; +} + +#endif diff --git a/src/gfx/Texture.cpp b/src/gfx/Texture.cpp new file mode 100644 index 0000000..7a36646 --- /dev/null +++ b/src/gfx/Texture.cpp @@ -0,0 +1,48 @@ +#include "Texture.hpp" +#include "../Services.hpp" +#include "../res/Resources.hpp" +#include "../res/ImageRes.hpp" + +namespace ml +{ + /*explicit*/ Texture::Texture() + { + glGenTextures(1, &m_tex); + } + + /*virtual*/ Texture::~Texture() + { + glDeleteTextures(1, &m_tex); + } + + void Texture::use() const + { + glBindTexture(GL_TEXTURE_2D, m_tex); + } + + void Texture::load(std::string const& name) + { + auto res = ML_SYS(Resources)->get(name); + auto surface = res->data(); + m_width = surface->w; + m_height = surface->h; + + use(); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + m_width, + m_height, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + surface->pixels); + } +} diff --git a/src/gfx/Texture.hpp b/src/gfx/Texture.hpp new file mode 100644 index 0000000..de6b7b9 --- /dev/null +++ b/src/gfx/Texture.hpp @@ -0,0 +1,29 @@ +#ifndef ml_TEXTURE_HPP +#define ml_TEXTURE_HPP + +#include +#include +#include "../commons.hpp" + +namespace ml +{ + class Texture + { + public: + explicit Texture(); + virtual ~Texture(); + + unsigned width() const { return m_width; } + unsigned height() const { return m_height; } + + void use() const; + void load(std::string const& name); + + private: + GLuint m_tex; + unsigned m_width = 0; + unsigned m_height = 0; + }; +} + +#endif diff --git a/src/main.cpp b/src/main.cpp index 0699326..8b1f7d4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,9 +3,13 @@ #include "conf.hpp" #include "commons.hpp" +// Services +#include "Services.hpp" #include "logs/FileLogs.hpp" #include "events/Events.hpp" -#include "Services.hpp" +#include "gfx/Graphics.hpp" +#include "res/Resources.hpp" + #include "Game.hpp" @@ -13,12 +17,18 @@ int main(int, char**) { ML->add (std::filesystem::temp_directory_path() / "mornelune.log"); + + ML->add(); + ML->add(); + ML->add("Mornelune", 1024, 768); + ML->init(); ml::Game game; - game.start(); game.stop(); + ML->free(); + return 0; } diff --git a/src/res/BaseRes.cpp b/src/res/BaseRes.cpp new file mode 100644 index 0000000..09a6b96 --- /dev/null +++ b/src/res/BaseRes.cpp @@ -0,0 +1,12 @@ +#include "BaseRes.hpp" + +namespace ml +{ + /*explicit*/ BaseRes::BaseRes() + { + } + + /*virtual*/ BaseRes::~BaseRes() + { + } +} diff --git a/src/res/BaseRes.hpp b/src/res/BaseRes.hpp new file mode 100644 index 0000000..fa51643 --- /dev/null +++ b/src/res/BaseRes.hpp @@ -0,0 +1,22 @@ +#ifndef ml_BASERES_HPP +#define ml_BASERES_HPP + +#include "../commons.hpp" + +namespace ml +{ + ML_ERROR(resource_error); + + class BaseRes + { + public: + explicit BaseRes(); + virtual ~BaseRes(); + + virtual void load(std::filesystem::path const& path) = 0; + + private: + }; +} + +#endif diff --git a/src/res/ImageRes.cpp b/src/res/ImageRes.cpp new file mode 100644 index 0000000..7ce1f99 --- /dev/null +++ b/src/res/ImageRes.cpp @@ -0,0 +1,28 @@ +#include "ImageRes.hpp" +#include "src/res/BaseRes.hpp" + +namespace ml +{ + /*explicit*/ ImageRes::ImageRes() + { + } + + /*virtual*/ ImageRes::~ImageRes() + { + if (m_surface) + { + SDL_FreeSurface(m_surface); + m_surface = nullptr; + } + } + + void ImageRes::load(std::filesystem::path const& path) /*override*/ + { + m_surface = IMG_Load(path.c_str()); + + if (!m_surface) + { + throw resource_error {"cannot load image '" + path.string() + "'"}; + } + } +} diff --git a/src/res/ImageRes.hpp b/src/res/ImageRes.hpp new file mode 100644 index 0000000..a36aeee --- /dev/null +++ b/src/res/ImageRes.hpp @@ -0,0 +1,25 @@ +#ifndef ml_IMAGERES_HPP +#define ml_IMAGERES_HPP + +#include +#include "../commons.hpp" +#include "BaseRes.hpp" + +namespace ml +{ + class ImageRes: public BaseRes + { + public: + explicit ImageRes(); + virtual ~ImageRes(); + + SDL_Surface* data() const { return m_surface; } + + void load(std::filesystem::path const& path) override; + + private: + SDL_Surface* m_surface = nullptr; + }; +} + +#endif diff --git a/src/res/Resources.cpp b/src/res/Resources.cpp new file mode 100644 index 0000000..417fd96 --- /dev/null +++ b/src/res/Resources.cpp @@ -0,0 +1,53 @@ +#include +namespace xml = tinyxml2; + +#include "../Services.hpp" +#include "Resources.hpp" +#include "TextRes.hpp" +#include "ImageRes.hpp" +#include "BaseRes.hpp" + +namespace ml +{ + /*explicit*/ Resources::Resources() + { + } + + /*virtual*/ Resources::~Resources() + { + } + + void Resources::init() /*override*/ + { + xml::XMLDocument doc; + doc.LoadFile(ML_ASSET("manifest.xml").string().c_str()); + + auto* root = doc.RootElement(); + + xml::XMLElement* itr = root->FirstChildElement(); + + while (itr) + { + std::string type = itr->Attribute("type"); + std::filesystem::path path = itr->Attribute("path"); + std::string name = itr->Attribute("name"); + + if (type == "text") + { + ML_SYS(Resources)->load(name, path); + } + else if (type == "image") + { + ML_SYS(Resources)->load(name, path); + } + else + { + throw resource_error {"unknown resource '" + + name + + "' of type '" + type + "'"}; + } + + itr = itr->NextSiblingElement(); + } + } +} diff --git a/src/res/Resources.hpp b/src/res/Resources.hpp new file mode 100644 index 0000000..dff9e3d --- /dev/null +++ b/src/res/Resources.hpp @@ -0,0 +1,64 @@ +#ifndef ml_RESOURCES_HPP +#define ml_RESOURCES_HPP + +#include "../commons.hpp" +#include "../Service.hpp" +#include "BaseRes.hpp" +#include + +namespace ml +{ + class Resources: public Service + { + public: + explicit Resources(); + virtual ~Resources(); + + void init() override; + + template + void load(std::string const& name, + std::filesystem::path const& path); + + + template + std::shared_ptr get(std::string const& name) const; + + private: + std::unordered_map> + m_resources; + }; + + template + void Resources::load(std::string const& name, + std::filesystem::path const& path) + { + if (auto itr = m_resources.find(name); + itr == std::end(m_resources)) + { + auto res = std::make_shared(); + res->load(ML_ASSET(path)); + + m_resources.insert({name, res}); + + return; + } + + throw resource_error {"resource '" + name + "' already exists"}; + } + + template + std::shared_ptr Resources::get(std::string const& name) const + { + if (auto itr = m_resources.find(name); + itr != std::end(m_resources)) + { + return std::static_pointer_cast(itr->second); + } + + throw resource_error {"cannot find resource '" + name + "'"}; + } + +} + +#endif diff --git a/src/res/TextRes.cpp b/src/res/TextRes.cpp new file mode 100644 index 0000000..cd508a1 --- /dev/null +++ b/src/res/TextRes.cpp @@ -0,0 +1,29 @@ +#include "TextRes.hpp" + +namespace ml +{ + /*explicit*/ TextRes::TextRes() + { + } + + /*virtual*/ TextRes::~TextRes() + { + } + + void TextRes::load(std::filesystem::path const& path) /*override*/ + { + std::stringstream ss; + + std::ifstream file { path }; + + if (!file) + { + throw resource_error + {"cannot find resource '" + path.string() + "'"}; + } + + ss << file.rdbuf(); + + m_data = ss.str(); + } +} diff --git a/src/res/TextRes.hpp b/src/res/TextRes.hpp new file mode 100644 index 0000000..895735a --- /dev/null +++ b/src/res/TextRes.hpp @@ -0,0 +1,23 @@ +#ifndef ml_TEXTRES_HPP +#define ml_TEXTRES_HPP + +#include "BaseRes.hpp" + +namespace ml +{ + class TextRes: public BaseRes + { + public: + explicit TextRes(); + virtual ~TextRes(); + + std::string data() const { return m_data; } + + void load(std::filesystem::path const& path) override; + + private: + std::string m_data; + }; +} + +#endif