restored gl screen capture; get points on circle utility function

This commit is contained in:
frank 2021-08-02 20:18:56 -04:00
parent 14759a1c79
commit 2831f2fc60
8 changed files with 135 additions and 72 deletions

View File

@ -33,8 +33,7 @@ void Configuration::set_defaults()
}; };
sys_config["input"] = { sys_config["input"] = {
{"suppress-any-key-on-mods", true}, {"suppress-any-key-on-mods", true},
{"system-any-key-ignore-commands", {"system-any-key-ignore-commands", {"fullscreen", "screenshot", "toggle-framerate", "record", "quit"}},
{"fullscreen", "screenshot", "toggle-framerate", "record", "quit"}},
{"any-key-ignore-commands", {}}, {"any-key-ignore-commands", {}},
{"default-unsuppress-delay", 700}, {"default-unsuppress-delay", 700},
{"ignore-repeat-keypress", true} {"ignore-repeat-keypress", true}

View File

@ -1,11 +1,13 @@
#include "Display.hpp" #include "Display.hpp"
#include "Game.hpp" #include "Game.hpp"
/* Create a Display instance and subscribe to commands */
Display::Display(Node* parent) : Node(parent) Display::Display(Node* parent) : Node(parent)
{ {
get_delegate().subscribe(&Display::respond, this); get_delegate().subscribe(&Display::respond, this);
} }
/* Return the (x, y) size in pixels of the window as an integer vector */
glm::ivec2 Display::get_window_size() const glm::ivec2 Display::get_window_size() const
{ {
glm::ivec2 size; glm::ivec2 size;
@ -13,11 +15,13 @@ glm::ivec2 Display::get_window_size() const
return size; return size;
} }
/* Return the window dimensions as a Box object */
Box Display::get_window_box() const Box Display::get_window_box() const
{ {
return Box(glm::vec2(0, 0), get_window_size()); return Box(glm::vec2(0, 0), get_window_size());
} }
/* Get the pixel format of display at specified index (defaults to index 0) */
Uint32 Display::get_pixel_format(int display_index) const Uint32 Display::get_pixel_format(int display_index) const
{ {
SDL_DisplayMode display_mode; SDL_DisplayMode display_mode;
@ -32,30 +36,32 @@ Uint32 Display::get_pixel_format(int display_index) const
} }
} }
/* Fill the supplied, pre-allocated buffer with 32-bit pixels (8 bits per component) from the GL
* read buffer if in GL context or from the SDL renderer if in SDL context */
void Display::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y) const void Display::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y) const
{ {
if (get_root()->is_gl_context) if (get_root()->is_gl_context)
{ {
// GLenum format; GLenum format;
// #if SDL_BYTEORDER == SDL_BIG_ENDIAN #if SDL_BYTEORDER == SDL_BIG_ENDIAN
// format = GL_BGRA; format = GL_BGRA;
// #else #else
// format = GL_RGBA; format = GL_RGBA;
// #endif #endif
// glReadBuffer(GL_FRONT); glReadBuffer(GL_FRONT);
// glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels); glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels);
// // SDL_Log("(%i, %i, %i) (%i, %i, %i, %i)", // SDL_Log("(%i, %i, %i) (%i, %i, %i, %i)",
// // glCheckFramebufferStatus(GL_FRAMEBUFFER), // glCheckFramebufferStatus(GL_FRAMEBUFFER),
// // glCheckFramebufferStatus(GL_READ_FRAMEBUFFER), // glCheckFramebufferStatus(GL_READ_FRAMEBUFFER),
// // glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER), // glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER),
// // pixels[0], pixels[1], pixels[2], pixels[3]); // pixels[0], pixels[1], pixels[2], pixels[3]);
} }
else else
{ {
SDL_Renderer* renderer = const_cast<SDL_Renderer*>(get_renderer()); SDL_Renderer* renderer = const_cast<SDL_Renderer*>(get_renderer());
SDL_SetRenderTarget(renderer, nullptr); SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderPresent(renderer); SDL_RenderPresent(renderer);
SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_RGBA32, pixels, bpp / 8 * w); SDL_RenderReadPixels(renderer, nullptr, SDL_PIXELFORMAT_RGBA32, pixels, bpp / 8 * w);
} }
} }
@ -64,8 +70,7 @@ SDL_Surface* Display::get_screen_surface() const
glm::ivec2 size = get_window_size(); glm::ivec2 size = get_window_size();
unsigned char* pixels = new unsigned char[bpp / 8 * size.x * size.y]; unsigned char* pixels = new unsigned char[bpp / 8 * size.x * size.y];
get_screen_pixels(pixels, size.x, size.y); get_screen_pixels(pixels, size.x, size.y);
SDL_Surface* surface = get_screen_surface_from_pixels( SDL_Surface* surface = get_screen_surface_from_pixels(pixels, get_root()->is_gl_context);
pixels, get_root()->is_gl_context);
delete[] pixels; delete[] pixels;
return surface; return surface;
} }
@ -101,6 +106,7 @@ SDL_Surface* Display::get_screen_surface_from_pixels(unsigned char* pixels, bool
return surface; return surface;
} }
/* Handle fullscreen request */
void Display::respond(SDL_Event& event) void Display::respond(SDL_Event& event)
{ {
if (get_delegate().compare(event, "fullscreen")) if (get_delegate().compare(event, "fullscreen"))
@ -109,17 +115,19 @@ void Display::respond(SDL_Event& event)
} }
} }
/* Use SDL window flags to determine if fullscreen is on or off and use SDL fullscreen
* function to toggle the fullscreen state */
void Display::toggle_fullscreen() const void Display::toggle_fullscreen() const
{ {
SDL_Window* window = const_cast<SDL_Window*>(get_window()); SDL_Window* window = const_cast<SDL_Window*>(get_window());
if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN)
{ {
SDL_Log("fullscreen requested"); log("fullscreen requested");
SDL_SetWindowFullscreen(window, 0); SDL_SetWindowFullscreen(window, 0);
} }
else else
{ {
SDL_Log("exit fullscreen requested"); log("exit fullscreen requested");
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
} }
} }

View File

@ -629,6 +629,8 @@ Audio& Game::get_audio()
return audio; return audio;
} }
/* Applies delta timing to a vector: returns the passed vector as weighted by the amount of time passed since the
* last frame update, allowing for vector values to change the same amount over time independent of the frame rate */
glm::vec2 Game::weight(glm::vec2 motion) glm::vec2 Game::weight(glm::vec2 motion)
{ {
return {weight(motion.x), weight(motion.y)}; return {weight(motion.x), weight(motion.y)};
@ -669,10 +671,10 @@ void Game::frame(float ticks)
// std::cout << ", last_frame_length: " << last_frame_length << " [rendering frame]"; // std::cout << ", last_frame_length: " << last_frame_length << " [rendering frame]";
if (last_frame_length < 1000) if (last_frame_length < 1000)
{ {
if (!is_gl_context) // if (!is_gl_context)
{ // {
recorder.update(); recorder.update();
} // }
delegate.dispatch(); delegate.dispatch();
audio.update(); audio.update();
input.unsuppress_animation.update(); input.unsuppress_animation.update();
@ -719,16 +721,23 @@ void Game::flag_to_end()
done = true; done = true;
} }
void Game::set_framerate(int f) /* Set the length of a frame in seconds by passing the framerate, the amount of frames to display
* per second */
void Game::set_framerate(int framerate)
{ {
if (f < 1) if (framerate < 1)
{ {
f = 1; framerate = 1;
} }
framerate = f;
frame_length = 1000.0 / framerate; frame_length = 1000.0 / framerate;
} }
/* Return the length of a frame in seconds */
float Game::get_frame_length() const
{
return frame_length;
}
void Game::handle_quit_event(SDL_Event &event) void Game::handle_quit_event(SDL_Event &event)
{ {
if (event.type == SDL_QUIT) if (event.type == SDL_QUIT)

View File

@ -50,10 +50,14 @@ class Game : public Node
private: private:
int ticks;
float frame_length = 1000.0 / 60.0;
static void sdl_log_override(void*, int, SDL_LogPriority, const char*); static void sdl_log_override(void*, int, SDL_LogPriority, const char*);
public: public:
/* Prevent an instance of this class from being copied or moved */
Game(const Game&) = delete; Game(const Game&) = delete;
Game& operator=(const Game&) = delete; Game& operator=(const Game&) = delete;
Game(Game&&) = delete; Game(Game&&) = delete;
@ -63,9 +67,8 @@ public:
SDL_Window* window; SDL_Window* window;
SDL_Renderer* renderer = nullptr; SDL_Renderer* renderer = nullptr;
SDL_GLContext glcontext = nullptr; SDL_GLContext glcontext = nullptr;
int frame_count_this_second = 0, framerate, ticks, last_frame_length; int frame_count_this_second = 0, last_frame_length;
float frame_length = 1000.0 / 60.0, frame_time_overflow = 0, last_frame_timestamp, float frame_time_overflow = 0, last_frame_timestamp, last_frame_count_timestamp;
last_frame_count_timestamp, emscripten_previous_time;
bool done = false, show_framerate = true, is_gl_context = true; bool done = false, show_framerate = true, is_gl_context = true;
Configuration configuration = Configuration(this); Configuration configuration = Configuration(this);
Delegate delegate = Delegate(this); Delegate delegate = Delegate(this);
@ -106,11 +109,14 @@ public:
void flag_to_end(); void flag_to_end();
virtual void update() {}; virtual void update() {};
void set_framerate(int); void set_framerate(int);
float get_frame_length() const;
void handle_quit_event(SDL_Event&); void handle_quit_event(SDL_Event&);
void quit(); void quit();
virtual std::string get_class_name() const { return "Game"; } virtual std::string get_class_name() const { return "Game"; }
~Game(); ~Game();
/* Applies delta timing to a value: returns the value as weighted by the amount of time passed since the
* last frame update, allowing for values to change the same amount over time independent of the frame rate */
template<typename T> template<typename T>
float weight(T amount) float weight(T amount)
{ {

View File

@ -3,23 +3,28 @@
#include "extension.hpp" #include "extension.hpp"
#include "Recorder.hpp" #include "Recorder.hpp"
/* Create a Recorder instance. Subscribe to command input and set audio callback.
* Only will be active if enabled by the configuration, in which case frames will
* automatically begin to be stashed */
Recorder::Recorder(Node* parent) : Node(parent) Recorder::Recorder(Node* parent) : Node(parent)
{ {
get_delegate().subscribe(&Recorder::respond, this); get_delegate().subscribe(&Recorder::respond, this);
animation.play(); animation.play();
Mix_SetPostMix(&process_audio, this); Mix_SetPostMix(Recorder::process_audio, this);
if (!get_configuration()["recording"]["enabled"]) if (!get_configuration()["recording"]["enabled"])
{ {
deactivate(); deactivate();
} }
} }
float Recorder::get_frame_length() /* Returns length of a recorded video frame in seconds. Defaults to the frame length of the game if this hasn't
* been configured by the user. */
float Recorder::frame_length()
{ {
return get_configuration()["recording"].value( return get_configuration()["recording"].value("video-frame-length", get_root()->get_frame_length());
"video-frame-length", get_root()->frame_length);
} }
/* Handle commands for screenshot, record video and save video */
void Recorder::respond(SDL_Event& event) void Recorder::respond(SDL_Event& event)
{ {
if (get_delegate().compare(event, "screenshot")) if (get_delegate().compare(event, "screenshot"))
@ -51,40 +56,48 @@ void Recorder::respond(SDL_Event& event)
} }
} }
/* Save the current screen pixels as a PNG in the path specified by the configuration. Parent
* directories in the path will be created if necessary. The file name will be auto generated
* based on the configuration. An index will be included in the file name based on previous
* screenshots found in the output directory. */
void Recorder::capture_screen() void Recorder::capture_screen()
{ {
nlohmann::json config = get_configuration(); nlohmann::json config = get_configuration();
SDL_Surface* surface = get_display().get_screen_surface(); SDL_Surface* surface = get_display().get_screen_surface();
fs::path directory = config["recording"]["screenshot-directory"]; fs::path directory = config["recording"]["screenshot-directory"];
fs::create_directories(directory); fs::create_directories(directory);
std::string prefix = config["recording"]["screenshot-prefix"]. std::string prefix = config["recording"]["screenshot-prefix"].get<std::string>();
get<std::string>(); std::string extension = config["recording"]["screenshot-extension"].get<std::string>();
std::string extension = config["recording"]["screenshot-extension"].
get<std::string>();
int zfill = config["recording"]["screenshot-zfill"]; int zfill = config["recording"]["screenshot-zfill"];
fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension); fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension);
IMG_SavePNG(surface, path.c_str()); IMG_SavePNG(surface, path.c_str());
SDL_FreeSurface(surface); SDL_FreeSurface(surface);
SDL_Log("Saved screenshot to %s", path.c_str()); std::ostringstream message;
message << "saved screenshot to " << path;
log(message.str());
} }
/* Writes a video of what was just displayed on the screen up until the function was called. The length
* of the video is determined by the stash length written in the configuration. This is accomplished by
* writing the contents of the most recent Stash object. */
void Recorder::grab_stash() void Recorder::grab_stash()
{ {
if (!is_recording and !writing_recording) if (!is_recording and !writing_recording)
{ {
int length = get_configuration()["recording"]["max-stash-length"]; int length = get_configuration()["recording"]["max-stash-length"];
SDL_Log("Stashing most recent %i seconds of video...", length / 1000); std::ostringstream message;
message << "stashing most recent " << length / 1000.0f << " seconds of video";
log(message.str());
most_recent_stash = current_stash; most_recent_stash = current_stash;
current_stash = Stash(); current_stash = Stash();
writing_recording = true; writing_recording = true;
std::function<void()> f = std::function<void()> f = std::bind(&Recorder::write_most_recent_frames, this);
std::bind(&Recorder::write_most_recent_frames, this);
std::thread writing(f); std::thread writing(f);
writing.detach(); writing.detach();
} }
else else
{ {
SDL_Log("Recording in progress, cannot grab most recent frames"); log("recording in progress, cannot grab most recent frames");
} }
} }
@ -95,19 +108,16 @@ void Recorder::write_most_recent_frames()
open_audio_file(); open_audio_file();
while (!most_recent_stash.audio_buffers.empty()) while (!most_recent_stash.audio_buffers.empty())
{ {
write_audio(most_recent_stash.audio_buffers.front(), write_audio(most_recent_stash.audio_buffers.front(), most_recent_stash.audio_buffer_lengths.front());
most_recent_stash.audio_buffer_lengths.front()); most_recent_stash.audio_buffers.erase(most_recent_stash.audio_buffers.begin());
most_recent_stash.audio_buffers.erase( most_recent_stash.audio_buffer_lengths.erase(most_recent_stash.audio_buffer_lengths.begin());
most_recent_stash.audio_buffers.begin());
most_recent_stash.audio_buffer_lengths.erase(
most_recent_stash.audio_buffer_lengths.begin());
} }
audio_file.close(); audio_file.close();
if (get_configuration()["recording"]["write-mp4"]) if (get_configuration()["recording"]["write-mp4"])
{ {
write_mp4(); write_mp4();
} }
SDL_Log("Wrote video frames to %s", current_video_directory.c_str()); SDL_Log("wrote video frames to %s", current_video_directory.c_str());
writing_recording = false; writing_recording = false;
} }
@ -115,7 +125,7 @@ void Recorder::start_recording()
{ {
if (!writing_recording) if (!writing_recording)
{ {
SDL_Log("Starting recording..."); log("starting recording");
is_recording = true; is_recording = true;
video_stashes.push_back(Stash()); video_stashes.push_back(Stash());
make_directory(); make_directory();
@ -123,7 +133,7 @@ void Recorder::start_recording()
} }
else else
{ {
SDL_Log("Writing in progress, cannot start recording"); log("writing in progress, cannot start recording");
} }
} }
@ -142,7 +152,7 @@ void Recorder::add_frame()
unsigned char* pixels = new unsigned char[bytes]; unsigned char* pixels = new unsigned char[bytes];
get_display().get_screen_pixels(pixels, size.x, size.y); get_display().get_screen_pixels(pixels, size.x, size.y);
int max_length = get_configuration()["recording"]["max-stash-length"]; int max_length = get_configuration()["recording"]["max-stash-length"];
float length = get_frame_length() * current_stash.pixel_buffers.size(); float length = frame_length() * current_stash.pixel_buffers.size();
if (length > max_length) if (length > max_length)
{ {
delete[] current_stash.pixel_buffers.front(); delete[] current_stash.pixel_buffers.front();
@ -157,10 +167,9 @@ void Recorder::add_frame()
memcpy(vid_pixels, pixels, bytes); memcpy(vid_pixels, pixels, bytes);
video_stashes.back().pixel_buffers.push_back(vid_pixels); video_stashes.back().pixel_buffers.push_back(vid_pixels);
video_stashes.back().flipped.push_back(get_root()->is_gl_context); video_stashes.back().flipped.push_back(get_root()->is_gl_context);
if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length) if (video_stashes.back().pixel_buffers.size() * frame_length() > max_length)
{ {
std::function<void(Stash*)> f = std::function<void(Stash*)> f = std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1);
std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1);
std::thread writing(f, &video_stashes.back()); std::thread writing(f, &video_stashes.back());
writing.detach(); writing.detach();
// int frame_offset = video_stashes.back().frame_offset; // int frame_offset = video_stashes.back().frame_offset;
@ -182,9 +191,8 @@ void Recorder::add_frame()
// write_stash_frames(video_stashes.back().pixel_buffers, // write_stash_frames(video_stashes.back().pixel_buffers,
// video_stashes.back().flipped, // video_stashes.back().flipped,
// video_stashes.back().frame_offset); // video_stashes.back().frame_offset);
video_stashes.push_back( video_stashes.push_back(Stash(video_stashes.back().frame_offset +
Stash(video_stashes.back().frame_offset + video_stashes.back().pixel_buffers.size()));
video_stashes.back().pixel_buffers.size()));
} }
} }
} }
@ -264,7 +272,7 @@ void Recorder::write_stash_frames(Stash* stash)
GifWriteFrame(&gif_writer, (const uint8_t*) converted->pixels, GifWriteFrame(&gif_writer, (const uint8_t*) converted->pixels,
frame->w, frame->h, gif_frame_length / 10); frame->w, frame->h, gif_frame_length / 10);
} }
elapsed += get_frame_length(); elapsed += frame_length();
delete[] stash->pixel_buffers.front(); delete[] stash->pixel_buffers.front();
stash->pixel_buffers.erase(stash->pixel_buffers.begin()); stash->pixel_buffers.erase(stash->pixel_buffers.begin());
stash->flipped.erase(stash->flipped.begin()); stash->flipped.erase(stash->flipped.begin());
@ -336,7 +344,7 @@ void Recorder::write_mp4()
std::string pixel_format = get_configuration()["recording"]["mp4-pixel-format"].get<std::string>(); std::string pixel_format = get_configuration()["recording"]["mp4-pixel-format"].get<std::string>();
fs::path images_match = current_video_directory / "%05d.png"; fs::path images_match = current_video_directory / "%05d.png";
mp4_command << "ffmpeg -f s16le -ac 2 -ar 22050 -i " << current_audio_path.string() << mp4_command << "ffmpeg -f s16le -ac 2 -ar 22050 -i " << current_audio_path.string() <<
" -f image2 -framerate " << (1000 / get_frame_length()) << " -f image2 -framerate " << (1000 / frame_length()) <<
" -i " << images_match.string() << " -s " << size.x << "x" << size.y << " -i " << images_match.string() << " -s " << size.x << "x" << size.y <<
" -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " << " -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " <<
current_video_directory.string() << ".mp4"; current_video_directory.string() << ".mp4";
@ -352,22 +360,21 @@ void Recorder::write_audio(Uint8* stream, int len)
void Recorder::update() void Recorder::update()
{ {
if (is_recording and get_memory_size() > if (is_recording and get_memory_size() > get_configuration()["recording"]["max-video-memory"])
get_configuration()["recording"]["max-video-memory"])
{ {
end_recording(); end_recording();
} }
animation.set_frame_length(get_frame_length()); animation.set_frame_length(frame_length());
animation.update(); animation.update();
} }
void process_audio(void* context, Uint8* stream, int len) void Recorder::process_audio(void* context, Uint8* stream, int len)
{ {
Recorder* recorder = static_cast<Recorder*>(context); Recorder* recorder = static_cast<Recorder*>(context);
if (recorder->is_active()) if (recorder->is_active())
{ {
int max_length = recorder->get_configuration()["recording"]["max-stash-length"]; int max_length = recorder->get_configuration()["recording"]["max-stash-length"];
float length = recorder->get_frame_length() * recorder->current_stash.pixel_buffers.size(); float length = recorder->frame_length() * recorder->current_stash.pixel_buffers.size();
if (length > max_length) if (length > max_length)
{ {
delete[] recorder->current_stash.audio_buffers.front(); delete[] recorder->current_stash.audio_buffers.front();

View File

@ -32,9 +32,11 @@ struct Stash
Stash(int frame_offset = 0) : frame_offset(frame_offset) {} Stash(int frame_offset = 0) : frame_offset(frame_offset) {}
}; };
struct Recorder : Node class Recorder : public Node
{ {
private:
Stash current_stash = Stash(); Stash current_stash = Stash();
Stash most_recent_stash; Stash most_recent_stash;
std::list<Stash> in_game_stashes; std::list<Stash> in_game_stashes;
@ -44,8 +46,12 @@ struct Recorder : Node
bool is_recording = false, writing_recording = false, writing_most_recent = false; bool is_recording = false, writing_recording = false, writing_most_recent = false;
std::ofstream audio_file; std::ofstream audio_file;
float frame_length();
static void process_audio(void*, Uint8*, int);
public:
Recorder(Node*); Recorder(Node*);
float get_frame_length();
void respond(SDL_Event&); void respond(SDL_Event&);
void capture_screen(); void capture_screen();
void grab_stash(); void grab_stash();
@ -66,6 +72,4 @@ struct Recorder : Node
}; };
void process_audio(void*, Uint8*, int);
#endif #endif

View File

@ -6,11 +6,32 @@ void sfw::set_magnitude(glm::vec2& vector, float magnitude)
vector = glm::normalize(vector) * magnitude; vector = glm::normalize(vector) * magnitude;
} }
/* Return coordinates of a point x, y at specified angle on a circle described by center and radius */
glm::vec2 sfw::get_point_on_circle(const glm::vec2& center, float radius, float angle) glm::vec2 sfw::get_point_on_circle(const glm::vec2& center, float radius, float angle)
{ {
return {center.x + std::sin(angle) * radius, center.y - std::cos(angle) * radius}; return {center.x + std::sin(angle) * radius, center.y - std::cos(angle) * radius};
} }
/* Return a point x, y at specified angle on a circle at the origin with specified radius (default 1) */
glm::vec2 sfw::get_point_on_circle(float angle, float radius)
{
return get_point_on_circle({0, 0}, radius, angle);
}
/* Return a vector of count number of points evenly spaced around a circle starting at the angle offset
* (defaults to 0) */
std::vector<glm::vec2> sfw::get_points_on_circle(int count, float radius, const glm::vec2& center, float offset)
{
std::vector<glm::vec2> points;
points.reserve(count);
float step = glm::two_pi<float>() / count;
for (int ii = 0; ii < count; ii++)
{
points.push_back(get_point_on_circle(center, radius, ii * step + offset));
}
return points;
}
Box sfw::get_texture_box(SDL_Texture* texture) Box sfw::get_texture_box(SDL_Texture* texture)
{ {
int width, height; int width, height;
@ -637,6 +658,12 @@ std::ostream& operator<<(std::ostream& out, const glm::vec2& vector)
return out; return out;
} }
std::ostream& operator<<(std::ostream& out, const glm::vec3& vector)
{
out << "{" << vector.x << ", " << vector.y << ", " << vector.z << "}";
return out;
}
std::ostream& operator<<(std::ostream& out, const SDL_Color& color) std::ostream& operator<<(std::ostream& out, const SDL_Color& color)
{ {
out << "{" << static_cast<int>(color.r) << ", " << static_cast<int>(color.g) << ", " << out << "{" << static_cast<int>(color.r) << ", " << static_cast<int>(color.g) << ", " <<

View File

@ -37,6 +37,8 @@ namespace sfw
void set_magnitude(glm::vec2&, float); void set_magnitude(glm::vec2&, float);
glm::vec2 get_point_on_circle(const glm::vec2&, float, float); glm::vec2 get_point_on_circle(const glm::vec2&, float, float);
glm::vec2 get_point_on_circle(float, float = 1.0f);
std::vector<glm::vec2> get_points_on_circle(int, float = 1.0f, const glm::vec2& = {0, 0}, float = 0.0f);
Box get_texture_box(SDL_Texture*); Box get_texture_box(SDL_Texture*);
glm::vec2 fit_and_preserve_aspect(const glm::vec2&, const glm::vec2&); glm::vec2 fit_and_preserve_aspect(const glm::vec2&, const glm::vec2&);
std::vector<std::vector<Box>> get_blinds_boxes(glm::vec2, float = 0.05f, int = 4); std::vector<std::vector<Box>> get_blinds_boxes(glm::vec2, float = 0.05f, int = 4);
@ -187,6 +189,7 @@ std::ostream& operator<<(std::ostream& out, const std::vector<T>& members)
} }
std::ostream& operator<<(std::ostream&, const glm::vec2&); std::ostream& operator<<(std::ostream&, const glm::vec2&);
std::ostream& operator<<(std::ostream&, const glm::vec3&);
std::ostream& operator<<(std::ostream&, const SDL_Color&); std::ostream& operator<<(std::ostream&, const SDL_Color&);
#endif #endif