346 lines
14 KiB
C++
346 lines
14 KiB
C++
/* +------------------------------------------------------+
|
|
____/ \____ /| - Open source game framework licensed to freely use, |
|
|
\ / / | copy, modify and sell without restriction |
|
|
+--\ ^__^ /--+ | |
|
|
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
|
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
|
| SPACE ~~~~~ | /
|
|
| ~~~~~~~ BOX |/
|
|
+-------------*/
|
|
|
|
#pragma once
|
|
|
|
/* For logging pre-SPACEBOX messages in sb::file_to_string */
|
|
#if defined(__ANDROID__) || defined(ANDROID)
|
|
#include <android/log.h>
|
|
#endif
|
|
|
|
/* Standard library */
|
|
#include <vector>
|
|
#include <iostream>
|
|
#include <regex>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <iomanip>
|
|
#include <stdexcept>
|
|
#include <map>
|
|
#include <cmath>
|
|
#include <fstream>
|
|
#include <cstdlib>
|
|
#include <initializer_list>
|
|
|
|
/* SDL shared libraries */
|
|
#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"
|
|
#include "glm/gtx/vector_angle.hpp"
|
|
#include "glm/common.hpp"
|
|
#include "glm/gtx/integer.hpp"
|
|
#include "json/json.hpp"
|
|
#include "sdl2-gfx/SDL2_gfxPrimitives.h"
|
|
#include "sdl2-gfx/SDL2_rotozoom.h"
|
|
|
|
/* SPACEBOX */
|
|
#include "Box.hpp"
|
|
#include "Segment.hpp"
|
|
#include "Color.hpp"
|
|
#include "Log.hpp"
|
|
#include "filesystem.hpp"
|
|
|
|
struct Pixels;
|
|
|
|
namespace sb
|
|
{
|
|
|
|
enum scaler {scale2x, xbr};
|
|
|
|
void set_magnitude(glm::vec2&, float);
|
|
Box get_texture_box(SDL_Texture*);
|
|
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);
|
|
void populate_pixel_2d_array(SDL_Renderer*, SDL_Texture*, std::vector<std::vector<SDL_Color>>&);
|
|
void populate_pixel_2d_array(SDL_Renderer*, SDL_Texture*, std::vector<std::vector<SDL_Color>>&, const Box&);
|
|
void apply_array_to_texture(SDL_Renderer*, SDL_Texture*, std::vector<std::vector<SDL_Color>>&);
|
|
void apply_array_to_texture(SDL_Renderer*, SDL_Texture*, std::vector<std::vector<SDL_Color>>&, const Box&);
|
|
std::vector<SDL_Texture*> get_halo_frames(
|
|
SDL_Renderer*, float, int, const std::vector<Color>& = {Color(0, 0, 0), Color(255, 255, 255)},
|
|
float = 4.0f, bool = true);
|
|
std::vector<SDL_Texture*> get_portal_frames(SDL_Renderer*, glm::vec2, float = 60, float = 30, int = 4, int = 6);
|
|
void fill_texture(SDL_Renderer*, SDL_Texture*, const SDL_Color&, const Box&);
|
|
void fill_texture(SDL_Renderer*, SDL_Texture*, const SDL_Color&);
|
|
void fill_texture(SDL_Renderer*, SDL_Texture*, SDL_Texture*, const Box&);
|
|
void fill_texture(SDL_Renderer*, SDL_Texture*, SDL_Texture*);
|
|
SDL_Texture* get_filled_texture(SDL_Renderer*, glm::vec2, const SDL_Color&, Uint32 = SDL_PIXELFORMAT_RGBA32);
|
|
SDL_Texture* get_filled_texture(SDL_Renderer*, glm::vec2, SDL_Texture*, Uint32 = SDL_PIXELFORMAT_RGBA32);
|
|
SDL_Texture* get_hue_shifted_texture(SDL_Renderer*, SDL_Texture*, float);
|
|
SDL_Texture* duplicate_texture(SDL_Renderer*, SDL_Texture*);
|
|
SDL_Texture* duplicate_texture(SDL_Renderer*, SDL_Texture*, const glm::vec2&);
|
|
SDL_Texture* get_remapped_texture(SDL_Renderer*, SDL_Texture*, const std::map<Color, Color>&);
|
|
SDL_Texture* get_remapped_texture(SDL_Renderer*, const std::string&, const std::map<Color, Color>&);
|
|
SDL_Texture* get_pixel_scaled_texture(SDL_Renderer*, SDL_Texture*, int = 1, int = scaler::scale2x);
|
|
SDL_Surface* get_surface_from_pixels(Pixels&);
|
|
std::vector<fs::path> glob(fs::path);
|
|
fs::path get_next_file_name(fs::path, int = 0, std::string = "", std::string = "");
|
|
|
|
/*!
|
|
* Read the file at path into a string and return the string.
|
|
*
|
|
* Android builds use SDL_RWops to extract the file from an APK. Otherwise, the file will be read using C++ STL.
|
|
*
|
|
* @param path Path to file, relative to working directory, or on Android, relative to app/src/main/assets/ in
|
|
* the build directory.
|
|
* @return std::string containing file contents
|
|
*/
|
|
std::string file_to_string(const fs::path& path);
|
|
|
|
/*!
|
|
* Copy a file from one path to another.
|
|
*
|
|
* If the destination is a directory, the file will be copied into the directory with the same filename. If the destination exists, the
|
|
* `overwrite_ok` argument controls whether or not it is overwritten.
|
|
*
|
|
* If the source or destination can't be opened, read, or written, information will be written to the log, and an empty fs::path object
|
|
* will be returned.
|
|
*
|
|
* On Android, `source` can be a file in an APK's `assets/` directory if the destination is a writable path on the Android internal
|
|
* or external filesystem, meaning this can be used to copy assets from an APK onto the Android filesystem.
|
|
*
|
|
* This uses SDL's RWops to support cross platform copying.
|
|
*
|
|
* @param to Path to a file to be copied
|
|
* @param from Path to the destination, either a file or directory
|
|
* @param overwrite_ok Only overwrite an existing destination if this is `true`
|
|
* @return Return the path to copied file if successful, or an empty fs::path otherwise
|
|
*/
|
|
fs::path copy_file(fs::path to, fs::path from, bool overwrite_ok = false);
|
|
|
|
/*!
|
|
* Copy the pixel data in a given rectangular area of an SDL surface into a new SDL surface.
|
|
*
|
|
* @warning The coordinate system of the rectangular area is expected to be in SDL format, meaning y=0 at the top of the surface, and
|
|
* y=height at the bottom. However, the Box class uses GL format by default. To set the Y-axis to SDL format, pass false to
|
|
* Box::invert_y(bool).
|
|
*
|
|
* @param source surface to copy pixels from
|
|
* @param area rectangular area to copy
|
|
* @return shared pointer to a new SDL surface
|
|
*/
|
|
std::shared_ptr<SDL_Surface> extract_area(const std::shared_ptr<SDL_Surface>& surface, const sb::Box& area);
|
|
|
|
/*!
|
|
* Copy pixel data from a given SDL surface into a vector of new SDL surfaces, transforming the original surface into equally sized
|
|
* tiles. The number of tiles and size of each tile is determined by the count parameter. Count is a 2D vector indicating how many
|
|
* tiles per row and how many rows. For example, if count is {4, 3}, 12 tiles total will be created, 4 per row.
|
|
*
|
|
* The tiles will be in order from the top left tile, going row by row until the bottom right tile. Note that y=0 at the top of image,
|
|
* not the bottom, which is common in image manipulation, although is not the coordinate system GL uses, so tile 0 on the Y-axis is
|
|
* the top of the image in this function.
|
|
*
|
|
* If the count of tiles does not divide evenly into the number of pixels in the surface in either dimension, the rightmost and/or
|
|
* bottommost tiles will be smaller than the rest.
|
|
*
|
|
* The pixel data in the passed surface is copied into new shared pointers to new SDL surfaces.
|
|
*
|
|
* @param source surface to split into tiles
|
|
* @param count number of tiles as columns x rows
|
|
* @return vector of shared pointers to new SDL surfaces
|
|
*/
|
|
std::vector<std::shared_ptr<SDL_Surface>> extract_tiles_by_count(const std::shared_ptr<SDL_Surface>& source, const glm::ivec2& count);
|
|
|
|
/*!
|
|
* Return an unsorted vector of keys from the passed map.
|
|
*/
|
|
template<typename Key, typename Value, template <typename...> class Map>
|
|
std::vector<Key> get_keys(const Map<Key, Value>& map)
|
|
{
|
|
std::vector<Key> keys;
|
|
keys.reserve(map.size());
|
|
for (auto& member : map)
|
|
{
|
|
keys.push_back(member.first);
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
/* Returns true if member is found in container, false otherwise */
|
|
template<typename T1, typename T2>
|
|
bool is_in_container(T1& container, T2& member)
|
|
{
|
|
return std::find(container.begin(), container.end(), member) != container.end();
|
|
}
|
|
|
|
/* Front pad passed end string with fill character until it is width characters long and return a new string */
|
|
template<typename T>
|
|
std::string pad(T end, int width, char fill = '0')
|
|
{
|
|
std::stringstream padded;
|
|
padded.fill(fill);
|
|
padded.width(width);
|
|
padded << end;
|
|
return padded.str();
|
|
}
|
|
|
|
/* Return a vector of values in order from start to stop with step between each value, not including the stop value.
|
|
* Returns a vector of the same type as step. */
|
|
template <typename N, typename N2 = N>
|
|
std::vector<N2> range(N start, N stop, N2 step = N(1))
|
|
{
|
|
if (step == N2(0))
|
|
{
|
|
throw std::invalid_argument("step for range must be non-zero");
|
|
}
|
|
std::vector<N2> result;
|
|
while ((step > 0) ? (start < stop) : (start > stop))
|
|
{
|
|
result.push_back(start);
|
|
start += step;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* If only one argument is provided, treat it as the stop value, and use 0 for start and 1 for step */
|
|
template <typename N>
|
|
std::vector<N> range(N stop)
|
|
{
|
|
return range(N(0), stop, N(1));
|
|
}
|
|
|
|
/* Return a vector of exactly count values from start to end, inclusive, with an even amount between each value */
|
|
template <typename N>
|
|
std::vector<float> range_count(N start, N end, int count)
|
|
{
|
|
/* negative or zero count is invalid */
|
|
if (count < 1)
|
|
{
|
|
throw std::invalid_argument("count argument must be greater than zero");
|
|
}
|
|
float step = (end - start) / (count - 1);
|
|
std::vector<float> all;
|
|
all.reserve(count);
|
|
for (int ii = 0; ii < count; ii++)
|
|
{
|
|
all.push_back(start + ii * step);
|
|
}
|
|
return all;
|
|
}
|
|
|
|
/* Return a map where the keys are the percent way through the range from start to end each value is,
|
|
* with step between each value. End is not included in the range. */
|
|
template <typename N, typename N2 = N>
|
|
std::map<float, N2> range_percent(N start, N end, N2 step = N(1))
|
|
{
|
|
std::map<float, N2> range_percent_map;
|
|
std::vector<N2> all = range(start, end, step);
|
|
int ii = 0;
|
|
for (N2& current : all)
|
|
{
|
|
range_percent_map[ii++ / static_cast<float>(all.size() - 1)] = current;
|
|
}
|
|
return range_percent_map;
|
|
}
|
|
|
|
/* Return a map where the keys are the percent way through the range from start to end (inclusive) each value is,
|
|
* with exactly count values in the range, spaced evenly. */
|
|
template <typename N>
|
|
std::map<float, float> range_percent_count(N start, N end, int count)
|
|
{
|
|
/* negative or zero count is invalid */
|
|
if (count < 1)
|
|
{
|
|
throw std::invalid_argument("count argument must be greater than zero");
|
|
}
|
|
std::map<float, float> range_percent_map;
|
|
std::vector<float> all = range_count(start, end, count);
|
|
int ii = 0;
|
|
for (float& current : all)
|
|
{
|
|
range_percent_map[ii++ / static_cast<float>(all.size() - 1)] = current;
|
|
}
|
|
return range_percent_map;
|
|
}
|
|
|
|
/*!
|
|
* Extend a vector's contents in-place by another vector's contents. The vectors must contain identical types.
|
|
*
|
|
* @param vector_to_extend vector that will be edited to include the contents of the second vector
|
|
* @param incoming_vector vector of values to append to existing vector
|
|
*/
|
|
template <typename VectorType>
|
|
void extend(std::vector<VectorType>& vector_to_extend, const std::vector<VectorType>& incoming_vector)
|
|
{
|
|
vector_to_extend.insert(vector_to_extend.end(), incoming_vector.begin(), incoming_vector.end());
|
|
}
|
|
|
|
}
|
|
|
|
int SDL_SetRenderDrawColor(SDL_Renderer*, const Color&);
|
|
int SDL_RenderFillRect(SDL_Renderer*, const Box&);
|
|
int lineColor(SDL_Renderer*, const Segment&, const Color&, std::uint8_t = 1);
|
|
|
|
namespace std
|
|
{
|
|
/* Stream a text representation of a glm::vec of any type or dimension */
|
|
template<glm::length_t dimensions, typename Type, glm::qualifier qualifier>
|
|
std::ostream& operator<<(std::ostream& out, const glm::vec<dimensions, Type, qualifier>& vec)
|
|
{
|
|
out << "{" << vec.x << ", " << vec.y;
|
|
if constexpr (dimensions > 2)
|
|
{
|
|
out << ", " << vec.z;
|
|
if constexpr (dimensions > 3)
|
|
{
|
|
out << ", " << vec.w;
|
|
}
|
|
}
|
|
out << "}";
|
|
return out;
|
|
}
|
|
|
|
/* Add a GLM matrix to the stream, each row on its own line */
|
|
template<glm::length_t columns, glm::length_t rows, typename Type, glm::qualifier qualifier>
|
|
std::ostream& operator<<(std::ostream& out, const glm::mat<columns, rows, Type, qualifier>& mat)
|
|
{
|
|
/* get the transpose so we can iterate over the original matrix by row instead of column */
|
|
glm::mat<rows, columns, Type, qualifier> transpose = glm::transpose(mat);
|
|
out << std::endl << "{";
|
|
/* print each column of the transpose, therefore printing each row of the original */
|
|
for (std::size_t ii = 0; ii < static_cast<std::size_t>(transpose.length()); ii++)
|
|
{
|
|
if (ii > 0)
|
|
{
|
|
out << " ";
|
|
}
|
|
out << transpose[ii];
|
|
if (ii == static_cast<std::size_t>(transpose.length() - 1))
|
|
{
|
|
out << "}";
|
|
}
|
|
else
|
|
{
|
|
out << std::endl;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream&, const SDL_Color&);
|
|
|
|
/* Add the contents of a vector to the output stream */
|
|
template <typename Type>
|
|
std::ostream& operator<<(std::ostream& out, const std::vector<Type>& members)
|
|
{
|
|
out << "{ ";
|
|
for (const Type& member : members)
|
|
{
|
|
out << member << " ";
|
|
}
|
|
out << "}";
|
|
return out;
|
|
}
|
|
}
|