From f9d171626f18f2f6c0b429b002d34081ca76ead2 Mon Sep 17 00:00:00 2001 From: frank Date: Sun, 23 Jul 2023 17:11:41 -0400 Subject: [PATCH] added more conversion rules for JSON array to GLM vertex, truncate attribute string representation, post reconfig event on configuration reload, added font load function, added exception handling to functions that bind textures --- src/Attributes.cpp | 9 +++++-- src/Configuration.cpp | 1 + src/Configuration.hpp | 41 ++++++++++++++++++++++++----- src/Game.cpp | 36 +++++++++++++++++--------- src/Game.hpp | 15 ++++++++++- src/Model.cpp | 60 ++++++++++++++++++++++++++++++++++++++----- src/Model.hpp | 30 +++++++++++++++++++++- src/Pad.hpp | 2 +- src/Sprite.hpp | 2 +- src/Text.cpp | 41 ++++++++++++++++++++++++----- src/Text.hpp | 22 ++++++++++++++++ src/Texture.cpp | 8 +++++- src/extension.hpp | 7 +++++ 13 files changed, 235 insertions(+), 39 deletions(-) diff --git a/src/Attributes.cpp b/src/Attributes.cpp index 9595f9d..b52e5fb 100644 --- a/src/Attributes.cpp +++ b/src/Attributes.cpp @@ -249,13 +249,18 @@ void sb::Attributes::extend(const Attributes& other, std::size_t count) } } -std::ostream& sb::operator<<(std::ostream& out, const Attributes& attributes) +std::ostream& sb::operator<<(std::ostream& out, const sb::Attributes& attributes) { out << ", std::monostate>) { - out << vector; + std::size_t max = 8; + out << std::vector>(vector.begin(), vector.begin() + std::min(vector.size(), max)); + if (vector.size() > max) + { + out << " + (" << (vector.size() - max) << " more)"; + } } }, attributes.vertices); out << ">"; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 2ca4053..fe2855b 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -210,6 +210,7 @@ void Configuration::refresh() message << "config file modified, reloading " << path; sb::Log::log(message, sb::Log::DEBUG); merge(path); + sb::Delegate::post("reconfig"); } } #else diff --git a/src/Configuration.hpp b/src/Configuration.hpp index 2673185..61c6a24 100644 --- a/src/Configuration.hpp +++ b/src/Configuration.hpp @@ -10,11 +10,16 @@ #pragma once +/* Standard library */ #include #include #include #include + +/* SPACEBOX distributed libraries */ #include "json/json.hpp" + +/* SPACEBOX */ #include "filesystem.hpp" #include "Node.hpp" #include "Animation.hpp" @@ -202,16 +207,36 @@ namespace glm } } + /*! + * Convert JSON value into GLM vertex of 2 - 4 dimensions. The resulting vertex will be the dimensions specified in the template + * argument. If the JSON value is an array with the same number of dimensions, it is converted directly into the vertex. If the + * JSON value is a scalar, the vertex is created with the dimensions all set to the scalar. If the JSON value is an array of smaller + * dimension, the missing dimensions are set to 0. If the JSON value is an array of larger dimension, the extra dimensions are left + * out of the vertex. + * + * @param j JSON object containing a scalar or array to be converted into a 2-4D GLM vertex + * @param v reference to a vertex that will be set to the value contained in the JSON + */ template void from_json(const nlohmann::json& j, vec& v) { - j.at(0).get_to(v.x); - j.at(1).get_to(v.y); - if constexpr (dimensions > 2) { - j.at(2).get_to(v.z); - } - if constexpr (dimensions > 3) { - j.at(3).get_to(v.w); + for (std::size_t ii = 0; ii < dimensions; ii++) + { + if (j.is_array()) + { + if (j.size() < ii + 1) + { + v[ii] = 0; + } + else + { + j.at(ii).get_to(v[ii]); + } + } + else + { + j.get_to(v[ii]); + } } } } @@ -254,3 +279,5 @@ namespace sb { using ::Configuration; } + +#include "Delegate.hpp" diff --git a/src/Game.cpp b/src/Game.cpp index 80fb3a8..8de38a7 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -177,17 +177,7 @@ Game::Game() } /* Try to load the default font path. The font will be freed when the Game object is destroyed. */ - _font = std::shared_ptr( - TTF_OpenFont(configuration()["display"]["default font path"].get().c_str(), - configuration()["display"]["default font size"]), TTF_CloseFont); - if (_font.get() == nullptr) - { - sb::Log::log("Could not load BPmono.ttf", sb::Log::ERROR); - } - else - { - sb::Log::log("Loaded BPmono.ttf"); - } + _font = font(configuration()("display", "default font path"), configuration()("display", "default font size")); if (Mix_Init(MIX_INIT_OGG) == 0) { @@ -551,11 +541,33 @@ Audio& Game::get_audio() return audio; } -const std::shared_ptr& sb::Game::font() const +std::shared_ptr sb::Game::font() const { return _font; } +std::shared_ptr sb::Game::font(const fs::path& path, int size) const +{ + std::shared_ptr font = std::shared_ptr(TTF_OpenFont(path.c_str(), size), TTF_CloseFont); + if (font.get() == nullptr) + { + std::ostringstream message; + message << "Could not load " << path; + sb::Log::log(message, sb::Log::ERROR); + if (path != configuration()("display", "default font path").get()) + { + return this->font(); + } + } + else + { + std::ostringstream message; + message << "Loaded " << path; + sb::Log::log(message); + } + return font; +} + void Game::run() { SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT); diff --git a/src/Game.hpp b/src/Game.hpp index d713f64..e375c27 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -146,7 +146,20 @@ public: const Input& get_input() const; Input& get_input(); Audio& get_audio(); - const std::shared_ptr& font() const; + + /*! + * @return shared pointer to the default font pre-loaded at construction + */ + std::shared_ptr font() const; + + /*! + * Get a new font object with the given font size. If the font cannot be loaded, the default font will be returned. If there was an error, + * the shared pointer will point to `nullptr`. + * + * @return shared pointer to the font object created from the TTF font file at the given path + */ + std::shared_ptr font(const fs::path& path, int size) const; + void run(); void frame(float); void flag_to_end(); diff --git a/src/Model.cpp b/src/Model.cpp index bd096dc..b52aff0 100644 --- a/src/Model.cpp +++ b/src/Model.cpp @@ -154,21 +154,46 @@ void sb::Model::add(sb::VBO& vbo) } } -void sb::Model::bind() +void sb::Model::bind_textures() const { - /* Bind textures */ - for (sb::Texture& texture : textures()) + for (const sb::Texture& texture : textures()) { - texture.bind(); + try + { + texture.bind(); + } + catch (const std::runtime_error& error) + { + std::ostringstream message; + message << "Error binding " << *this << ": " << error.what(); + throw std::runtime_error(message.str()); + } } +} - /* Bind attributes */ +void sb::Model::bind_attributes() const +{ for (auto& [name, attributes] : attributes()) { - attributes->bind(); + try + { + attributes->bind(); + } + catch (const std::runtime_error& error) + { + std::ostringstream message; + message << "Error binding " << *this << ": " << error.what(); + throw std::runtime_error(message.str()); + } } } +void sb::Model::bind() const +{ + bind_textures(); + bind_attributes(); +} + const glm::mat4& sb::Model::transformation() const { return _transformation; @@ -217,6 +242,29 @@ sb::Model::operator glm::mat4() const return _transformation; } +sb::Model::operator std::string() const +{ + std::ostringstream message; + message << " 0) + { + message << " and " << _attributes.size() << " attributes ( "; + for (const auto& [name, attributes] : _attributes) + { + message << name << " "; + } + message << ")"; + } + message << ">"; + return message.str(); +} + +std::ostream& sb::operator<<(std::ostream& out, const sb::Model& model) +{ + out << std::string(model); + return out; +} + sb::PlaneDoubleBuffer::PlaneDoubleBuffer() : Plane() { texture(sb::Texture()); diff --git a/src/Model.hpp b/src/Model.hpp index 7042240..6cfb744 100644 --- a/src/Model.hpp +++ b/src/Model.hpp @@ -211,11 +211,23 @@ namespace sb */ void add(sb::VBO& vbo); + /*! + * Bind all of this model's textures by calling each of their bind methods. Textures must already have GL indices set, for example by + * calling Texture::generate() on each. + */ + virtual void bind_textures() const; + + /*! + * Bind all of this model's attributes by calling each of their bind methods. Attributes must already have GL indices set, for example + * by calling Attributes::index(GLint) on each. + */ + virtual void bind_attributes() const; + /*! * Bind all of this model's attributes and textures by calling each of their bind methods. Textures and attributes all must already * have GL indices set, for example by calling Texture::generate() and Attributes::index(GLint) on each. */ - void bind(); + virtual void bind() const; /*! * @return a constanst reference to the model's transformation matrix @@ -277,6 +289,22 @@ namespace sb */ operator glm::mat4() const; + /*! + * Convert to a string with some debugging information about the model. + */ + operator std::string() const; + + /*! + * Overload the stream operator to support model objects. Add a string representation of the model to the output stream. Since + * this is defined as a friend function and isn't in the global scope, it should prevent it being looked up with arguments other + * than model objects. + * + * @param out output stream + * @param model model object to print + * @return edited output stream + */ + friend std::ostream& operator<<(std::ostream& out, const Model& model); + }; class Plane : public Model diff --git a/src/Pad.hpp b/src/Pad.hpp index 38b5969..efa900a 100644 --- a/src/Pad.hpp +++ b/src/Pad.hpp @@ -209,7 +209,7 @@ namespace sb { glUniform1i(texture_flag_uniform.value(), true); } - plane.texture().bind(); + plane.bind_textures(); } else if (texture_flag_uniform.has_value()) { diff --git a/src/Sprite.hpp b/src/Sprite.hpp index d89aee6..853bd99 100644 --- a/src/Sprite.hpp +++ b/src/Sprite.hpp @@ -296,7 +296,7 @@ namespace sb { glUniform1i(texture_flag_uniform.value(), true); } - plane.texture().bind(); + plane.bind_textures(); } else if (texture_flag_uniform.has_value()) { diff --git a/src/Text.cpp b/src/Text.cpp index 8d9c5b6..0faaa75 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -2,19 +2,33 @@ using namespace sb; +void Text::bind_textures() const +{ + try + { + Model::bind_textures(); + } + catch (const std::runtime_error& error) + { + std::ostringstream message; + message << "Error binding textures for " << *this << ": " << error.what(); + throw std::runtime_error(message.str()); + } +} + void Text::content(const std::string& content) { _content = content; refresh(); } -void Text::foreground(const sb::Color& foreground) +void Text::foreground(const Color& foreground) { _foreground = foreground; refresh(); } -void Text::background(const sb::Color& background) +void Text::background(const Color& background) { _background = background; refresh(); @@ -44,7 +58,7 @@ void Text::refresh() std::shared_ptr blended {TTF_RenderText_Blended(_font.get(), _content.c_str(), _foreground), SDL_FreeSurface}; if (!blended) { - sb::Log::sdl_error("Could not create text"); + Log::sdl_error("Could not create text"); } else { @@ -68,13 +82,13 @@ void Text::refresh() if (!container) { - sb::Log::sdl_error("Could not create container surface for rendered text"); + Log::sdl_error("Could not create container surface for rendered text"); } else { SDL_FillRect(container.get(), nullptr, _background); - sb::Box blended_box {0.0f, 0.0f, float(blended->w), float(blended->h), false}; - sb::Box container_box {0.0f, 0.0f, float(container->w), float(container->h), false}; + Box blended_box {0.0f, 0.0f, float(blended->w), float(blended->h), false}; + Box container_box {0.0f, 0.0f, float(container->w), float(container->h), false}; blended_box.center(container_box.center()); SDL_Rect r = blended_box; SDL_BlitSurface(blended.get(), nullptr, container.get(), &r); @@ -86,7 +100,7 @@ void Text::refresh() if (!flipped) { - sb::Log::sdl_error("Could not flip surface"); + Log::sdl_error("Could not flip surface"); } else { @@ -97,3 +111,16 @@ void Text::refresh() } } } + +Text::operator std::string() const +{ + std::ostringstream message; + message << ""; + return message.str(); +} + +std::ostream& sb::operator<<(std::ostream& out, const Text& text) +{ + out << std::string(text); + return out; +} diff --git a/src/Text.hpp b/src/Text.hpp index 40a14e2..19845c3 100644 --- a/src/Text.hpp +++ b/src/Text.hpp @@ -1,4 +1,5 @@ #include +#include #include "SDL.h" #include "SDL_ttf.h" @@ -54,6 +55,11 @@ namespace sb texture(sb::Texture()); } + /*! + * Bind the texture rendered from the text object's text content and any other textures attached. + */ + void bind_textures() const; + /*! * @param content text to be displayed */ @@ -85,6 +91,22 @@ namespace sb * @param quality quality of texture scaling passed to `glTexParameter` */ void scaling_quality(GLint quality); + + /*! + * Convert the text object to a string with some debugging information. + */ + operator std::string() const; + + /*! + * Overload the stream operator to support text objects. Add a string representation of the text object to the output stream. Since + * this is defined as a friend function and isn't in the global scope, it should prevent it being looked up with arguments other + * than text objects. + * + * @param out output stream + * @param text text object to print + * @return edited output stream + */ + friend std::ostream& operator<<(std::ostream& out, const Text& text); }; } diff --git a/src/Texture.cpp b/src/Texture.cpp index a066573..e9e7cbc 100644 --- a/src/Texture.cpp +++ b/src/Texture.cpp @@ -145,7 +145,13 @@ void Texture::bind() const } else { - throw std::runtime_error("Cannot bind texture that has not been generated yet."); + std::ostringstream message; + message << "Cannot bind texture that has not been generated yet."; + if (!path.empty()) + { + message << " Texture path is \"" << path << "\"."; + } + throw std::runtime_error(message.str()); } } diff --git a/src/extension.hpp b/src/extension.hpp index 77fdf6b..b0a93e3 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -15,6 +15,7 @@ #include #endif +/* Standard library */ #include #include #include @@ -26,9 +27,13 @@ #include #include #include + +/* SDL */ #include "SDL.h" #include "SDL_image.h" #include "SDL_pixels.h" + +/* SPACEBOX packaged libraries */ #define GLM_ENABLE_EXPERIMENTAL #include "glm/trigonometric.hpp" #include "glm/vec2.hpp" @@ -37,6 +42,8 @@ #include "glm/gtx/integer.hpp" #include "json/json.hpp" #include "sdl2-gfx/SDL2_gfxPrimitives.h" + +/* SPACEBOX */ #include "Box.hpp" #include "Segment.hpp" #include "Color.hpp"