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

This commit is contained in:
ohsqueezy 2023-07-23 17:11:41 -04:00
parent b93aae19e0
commit f9d171626f
13 changed files with 235 additions and 39 deletions

View File

@ -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 << "<Attributes " << attributes.dimensions() << "D, " << attributes.size() << " bytes, ";
std::visit([&] (const auto& vector) {
if constexpr (!std::is_same_v<std::decay_t<decltype(vector)>, std::monostate>)
{
out << vector;
std::size_t max = 8;
out << std::vector<std::decay_t<decltype(*vector.begin())>>(vector.begin(), vector.begin() + std::min(vector.size(), max));
if (vector.size() > max)
{
out << " + (" << (vector.size() - max) << " more)";
}
}
}, attributes.vertices);
out << ">";

View File

@ -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

View File

@ -10,11 +10,16 @@
#pragma once
/* Standard library */
#include <fstream>
#include <ostream>
#include <iomanip>
#include <functional>
/* 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 <length_t dimensions, typename VectorType, qualifier qualifier>
void from_json(const nlohmann::json& j, vec<dimensions, VectorType, qualifier>& 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"

View File

@ -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_Font>(
TTF_OpenFont(configuration()["display"]["default font path"].get<std::string>().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<TTF_Font>& sb::Game::font() const
std::shared_ptr<TTF_Font> sb::Game::font() const
{
return _font;
}
std::shared_ptr<TTF_Font> sb::Game::font(const fs::path& path, int size) const
{
std::shared_ptr<TTF_Font> font = std::shared_ptr<TTF_Font>(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<fs::path>())
{
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);

View File

@ -146,7 +146,20 @@ public:
const Input& get_input() const;
Input& get_input();
Audio& get_audio();
const std::shared_ptr<TTF_Font>& font() const;
/*!
* @return shared pointer to the default font pre-loaded at construction
*/
std::shared_ptr<TTF_Font> 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<TTF_Font> font(const fs::path& path, int size) const;
void run();
void frame(float);
void flag_to_end();

View File

@ -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 << "<sb::Model with " << _textures.size() << " textures";
if (_attributes.size() > 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());

View File

@ -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

View File

@ -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())
{

View File

@ -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())
{

View File

@ -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<SDL_Surface> 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 << "<sb::Text \"" << _content << "\">";
return message.str();
}
std::ostream& sb::operator<<(std::ostream& out, const Text& text)
{
out << std::string(text);
return out;
}

View File

@ -1,4 +1,5 @@
#include <optional>
#include <stdexcept>
#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);
};
}

View File

@ -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());
}
}

View File

@ -15,6 +15,7 @@
#include <android/log.h>
#endif
/* Standard library */
#include <vector>
#include <iostream>
#include <regex>
@ -26,9 +27,13 @@
#include <cmath>
#include <fstream>
#include <cstdlib>
/* 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"