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"