/* +------------------------------------------------------+ ____/ \____ /| - Open source game framework licensed to freely use, | \ / / | copy, modify and sell without restriction | +--\ ^__^ /--+ | | | ~/ \~ | | - created for | | ~~~~~~~~~~~~ | +------------------------------------------------------+ | SPACE ~~~~~ | / | ~~~~~~~ BOX |/ +-------------*/ #pragma once /* For logging pre-SPACEBOX messages in sb::file_to_string */ #if defined(__ANDROID__) || defined(ANDROID) #include #endif /* Standard library */ #include #include #include #include #include #include #include #include #include #include #include #include /* 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> get_blinds_boxes(glm::vec2, float = 0.05f, int = 4); void populate_pixel_2d_array(SDL_Renderer*, SDL_Texture*, std::vector>&); void populate_pixel_2d_array(SDL_Renderer*, SDL_Texture*, std::vector>&, const Box&); void apply_array_to_texture(SDL_Renderer*, SDL_Texture*, std::vector>&); void apply_array_to_texture(SDL_Renderer*, SDL_Texture*, std::vector>&, const Box&); std::vector get_halo_frames( SDL_Renderer*, float, int, const std::vector& = {Color(0, 0, 0), Color(255, 255, 255)}, float = 4.0f, bool = true); std::vector 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&); SDL_Texture* get_remapped_texture(SDL_Renderer*, const std::string&, const std::map&); SDL_Texture* get_pixel_scaled_texture(SDL_Renderer*, SDL_Texture*, int = 1, int = scaler::scale2x); SDL_Surface* get_surface_from_pixels(Pixels&); std::vector 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 extract_area(const std::shared_ptr& 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> extract_tiles_by_count(const std::shared_ptr& source, const glm::ivec2& count); /*! * Return an unsorted vector of keys from the passed map. */ template class Map> std::vector get_keys(const Map& map) { std::vector 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 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 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 std::vector 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 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 std::vector 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 std::vector 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 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 std::map range_percent(N start, N end, N2 step = N(1)) { std::map range_percent_map; std::vector all = range(start, end, step); int ii = 0; for (N2& current : all) { range_percent_map[ii++ / static_cast(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 std::map 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 range_percent_map; std::vector all = range_count(start, end, count); int ii = 0; for (float& current : all) { range_percent_map[ii++ / static_cast(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 void extend(std::vector& vector_to_extend, const std::vector& 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 std::ostream& operator<<(std::ostream& out, const glm::vec& 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 std::ostream& operator<<(std::ostream& out, const glm::mat& mat) { /* get the transpose so we can iterate over the original matrix by row instead of column */ glm::mat 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(transpose.length()); ii++) { if (ii > 0) { out << " "; } out << transpose[ii]; if (ii == static_cast(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 std::ostream& operator<<(std::ostream& out, const std::vector& members) { out << "{ "; for (const Type& member : members) { out << member << " "; } out << "}"; return out; } }